From e7f374b33919379f81e7759606f2f56d4db72898 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 22 Sep 2023 21:23:46 +0530 Subject: [PATCH 1/9] chore: project issues topbar --- .../core/filters/issues-view-filter.tsx | 6 +- web/components/core/views/issues-view.tsx | 1 + .../core/views/list-view/single-issue.tsx | 2 - web/components/headers/index.ts | 1 + web/components/headers/project-issues.tsx | 46 ++++ .../display-filters/display-properties.tsx | 57 ++--- .../display-filters/extra-options.tsx | 40 ++-- .../display-filters/group-by.tsx | 43 ++-- .../issue-layouts/display-filters/index.tsx | 61 ++---- .../display-filters/issue-type.tsx | 40 ++-- .../display-filters/order-by.tsx | 39 ++-- .../issue-layouts/filters/assignees.tsx | 81 +++---- .../issue-layouts/filters/created-by.tsx | 69 +++--- .../issue-layouts/filters/index.tsx | 88 ++++---- .../issue-layouts/filters/labels.tsx | 64 +++--- .../issue-layouts/filters/priority.tsx | 86 +++----- .../issue-layouts/filters/state-group.tsx | 115 +++------- .../issue-layouts/filters/state.tsx | 80 +++---- .../issue-layouts/helpers/dropdown.tsx | 8 +- .../issue-layouts/helpers/filter-option.tsx | 47 ++-- .../issue-layouts/layout-selection.tsx | 119 +++------- .../my-issues/my-issues-view-options.tsx | 197 ++++++++--------- .../profile/profile-issues-view-options.tsx | 204 ++++++++---------- web/constants/issue.ts | 63 ++++-- .../projects/[projectId]/issues/index.tsx | 113 ++++++---- web/store/issue_filters.ts | 129 +++++++++-- web/store/project.ts | 6 +- web/types/issues.d.ts | 6 - web/types/projects.d.ts | 13 +- web/types/view-props.d.ts | 23 +- web/types/workspace.d.ts | 11 +- 31 files changed, 897 insertions(+), 961 deletions(-) create mode 100644 web/components/headers/index.ts create mode 100644 web/components/headers/project-issues.tsx diff --git a/web/components/core/filters/issues-view-filter.tsx b/web/components/core/filters/issues-view-filter.tsx index 2a8f8c1c65a..06f78a79233 100644 --- a/web/components/core/filters/issues-view-filter.tsx +++ b/web/components/core/filters/issues-view-filter.tsx @@ -25,11 +25,11 @@ import { import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; import { checkIfArraysHaveSameElements } from "helpers/array.helper"; // types -import { Properties, TIssueViewOptions } from "types"; +import { Properties, TIssueLayouts } from "types"; // constants import { ISSUE_GROUP_BY_OPTIONS, ISSUE_ORDER_BY_OPTIONS, ISSUE_FILTER_OPTIONS } from "constants/issue"; -const issueViewOptions: { type: TIssueViewOptions; Icon: any }[] = [ +const issueViewOptions: { type: TIssueLayouts; Icon: any }[] = [ { type: "list", Icon: FormatListBulletedOutlined, @@ -52,7 +52,7 @@ const issueViewOptions: { type: TIssueViewOptions; Icon: any }[] = [ }, ]; -const issueViewForDraftIssues: { type: TIssueViewOptions; Icon: any }[] = [ +const issueViewForDraftIssues: { type: TIssueLayouts; Icon: any }[] = [ { type: "list", Icon: FormatListBulletedOutlined, diff --git a/web/components/core/views/issues-view.tsx b/web/components/core/views/issues-view.tsx index 521318e40b7..2ba81cc25c3 100644 --- a/web/components/core/views/issues-view.tsx +++ b/web/components/core/views/issues-view.tsx @@ -490,6 +490,7 @@ export const IssuesView: React.FC = ({ openIssuesListModal, disableUserAc labels: null, priority: null, state: null, + state_group: null, start_date: null, target_date: null, }) diff --git a/web/components/core/views/list-view/single-issue.tsx b/web/components/core/views/list-view/single-issue.tsx index 54ef1a15baf..b1eedce6975 100644 --- a/web/components/core/views/list-view/single-issue.tsx +++ b/web/components/core/views/list-view/single-issue.tsx @@ -255,8 +255,6 @@ export const SingleListIssue: React.FC = ({ const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disableUserActions || isArchivedIssues; - console.log("properties", properties); - return ( <> { + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { issueFilter } = useMobxStore(); + const { updateUserFilters } = issueFilter; + + const handleLayoutChange = (layout: TIssueLayouts) => { + if (!workspaceSlug || !projectId) return; + + updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + display_filters: { + layout, + }, + }); + }; + + return ( +
+ handleLayoutChange(layout)} + selectedLayout={issueFilter.userDisplayFilters.layout ?? "list"} + /> + + + + + + +
+ ); +}); diff --git a/web/components/issue-layouts/display-filters/display-properties.tsx b/web/components/issue-layouts/display-filters/display-properties.tsx index 402c5064f95..17324750cf8 100644 --- a/web/components/issue-layouts/display-filters/display-properties.tsx +++ b/web/components/issue-layouts/display-filters/display-properties.tsx @@ -1,49 +1,58 @@ import React from "react"; -// components -import { FilterHeader } from "../helpers/filter-header"; -// mobx react lite + +import { useRouter } from "next/router"; + +// mobx import { observer } from "mobx-react-lite"; -// mobx store import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; +// components +import { FilterHeader } from "../helpers/filter-header"; +// types +import { IIssueDisplayProperties } from "types"; +// constants import { ISSUE_DISPLAY_PROPERTIES } from "constants/issue"; export const FilterDisplayProperties = observer(() => { - const store: RootStore = useMobxStore(); + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const store = useMobxStore(); const { issueFilter: issueFilterStore } = store; const [previewEnabled, setPreviewEnabled] = React.useState(true); - const handleDisplayProperties = (key: string, value: boolean) => { - // issueFilterStore.handleUserFilter("display_properties", key, !value); + const handleDisplayProperties = (property: Partial) => { + if (!workspaceSlug || !projectId) return; + + issueFilterStore.updateDisplayProperties(workspaceSlug.toString(), projectId.toString(), property); }; return (
setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
+
{ISSUE_DISPLAY_PROPERTIES.map((displayProperty) => ( -
{ - handleDisplayProperties( - displayProperty?.key, - issueFilterStore?.userDisplayProperties?.[displayProperty?.key] - ); - }} + onClick={() => + handleDisplayProperties({ + [displayProperty.key]: !issueFilterStore?.userDisplayProperties?.[displayProperty.key], + }) + } > - {displayProperty?.title} -
+ {displayProperty.title} + ))}
)} diff --git a/web/components/issue-layouts/display-filters/extra-options.tsx b/web/components/issue-layouts/display-filters/extra-options.tsx index d05703c9ba6..fdc5a98248c 100644 --- a/web/components/issue-layouts/display-filters/extra-options.tsx +++ b/web/components/issue-layouts/display-filters/extra-options.tsx @@ -1,51 +1,35 @@ import React from "react"; + +// mobx +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; + // components import { FilterHeader } from "../helpers/filter-header"; import { FilterOption } from "../helpers/filter-option"; -// mobx react lite -import { observer } from "mobx-react-lite"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; +// constants import { ISSUE_EXTRA_PROPERTIES } from "constants/issue"; -// default data -// import { issueFilterVisibilityData } from "helpers/issue.helper"; export const FilterExtraOptions = observer(() => { - const store: RootStore = useMobxStore(); + const store = useMobxStore(); const { issueFilter: issueFilterStore } = store; const [previewEnabled, setPreviewEnabled] = React.useState(true); - const handleExtraOptions = (key: string, value: boolean) => { - // issueFilterStore.handleUserFilter("display_filters", key, !value); - }; - - const handleExtraOptionsSectionVisibility = (key: string) => { - // issueFilterStore?.issueView && - // issueFilterStore?.issueLayout && - // issueFilterVisibilityData[issueFilterStore?.issueView === "my_issues" ? "my_issues" : "issues"]?.extra_options?.[ - // issueFilterStore?.issueLayout - // ].values?.includes(key); - }; - return (
setPreviewEnabled(!previewEnabled)} /> {previewEnabled && (
- {ISSUE_EXTRA_PROPERTIES.map((_extraProperties) => ( + {ISSUE_EXTRA_PROPERTIES.map((extraProperties) => ( - handleExtraOptions(_extraProperties?.key, issueFilterStore?.userDisplayFilters?.[_extraProperties?.key]) - } - title={_extraProperties.title} + key={extraProperties.key} + isChecked={issueFilterStore?.userDisplayFilters?.[extraProperties.key] ? true : false} + title={extraProperties.title} /> ))}
diff --git a/web/components/issue-layouts/display-filters/group-by.tsx b/web/components/issue-layouts/display-filters/group-by.tsx index 676717adc6b..5b95c61b166 100644 --- a/web/components/issue-layouts/display-filters/group-by.tsx +++ b/web/components/issue-layouts/display-filters/group-by.tsx @@ -1,39 +1,52 @@ import React from "react"; + +import { useRouter } from "next/router"; + +// mobx +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; // components import { FilterHeader } from "../helpers/filter-header"; import { FilterOption } from "../helpers/filter-option"; -// mobx react lite -import { observer } from "mobx-react-lite"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; +// types +import { TIssueGroupByOptions } from "types"; +// constants import { ISSUE_GROUP_BY_OPTIONS } from "constants/issue"; export const FilterGroupBy = observer(() => { - const store: RootStore = useMobxStore(); + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const store = useMobxStore(); const { issueFilter: issueFilterStore } = store; const [previewEnabled, setPreviewEnabled] = React.useState(true); - const handleGroupBy = (key: string, value: string) => { - // issueFilterStore.handleUserFilter("display_filters", key, value); + const handleGroupBy = (value: TIssueGroupByOptions) => { + if (!workspaceSlug || !projectId) return; + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + display_filters: { + group_by: value, + }, + }); }; return (
setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
- {ISSUE_GROUP_BY_OPTIONS.map((_groupBy) => ( +
+ {ISSUE_GROUP_BY_OPTIONS.map((groupBy) => ( handleGroupBy("group_by", _groupBy?.key)} - title={_groupBy.title} + key={groupBy?.key} + isChecked={issueFilterStore?.userDisplayFilters?.group_by === groupBy?.key ? true : false} + onClick={() => handleGroupBy(groupBy.key)} + title={groupBy.title} multiple={false} /> ))} diff --git a/web/components/issue-layouts/display-filters/index.tsx b/web/components/issue-layouts/display-filters/index.tsx index 708d52241aa..cd300947096 100644 --- a/web/components/issue-layouts/display-filters/index.tsx +++ b/web/components/issue-layouts/display-filters/index.tsx @@ -17,65 +17,34 @@ export const DisplayFiltersSelection = observer(() => { const store: RootStore = useMobxStore(); const { issueFilter: issueFilterStore } = store; - // const handleDisplayPropertiesSectionVisibility = - // issueFilterStore?.issueView && - // issueFilterStore?.issueLayout && - // issueFilterVisibilityData[issueFilterStore?.issueView === "my_issues" ? "my_issues" : "issues"] - // ?.display_properties?.[issueFilterStore?.issueLayout]; - - const handleDisplayFilterSectionVisibility = (section_key: string) => { - // issueFilterStore?.issueView && - // issueFilterStore?.issueLayout && - // issueFilterVisibilityData[ - // issueFilterStore?.issueView === "my_issues" ? "my_issues" : "issues" - // ]?.display_filters?.[issueFilterStore?.issueLayout].includes(section_key); - }; - - // const handleExtraOptionsSectionVisibility = - // issueFilterStore?.issueView && - // issueFilterStore?.issueLayout && - // issueFilterVisibilityData[issueFilterStore?.issueView === "my_issues" ? "my_issues" : "issues"]?.extra_options?.[ - // issueFilterStore?.issueLayout - // ].access; - return (
Search container
{/* display properties */} - {/* {handleDisplayPropertiesSectionVisibility && ( -
- -
- )} */} +
+ +
{/* group by */} - {/* {handleDisplayFilterSectionVisibility("group_by") && ( -
- -
- )} */} +
+ +
{/* order by */} - {/* {handleDisplayFilterSectionVisibility("order_by") && ( -
- -
- )} */} +
+ +
{/* issue type */} - {/* {handleDisplayFilterSectionVisibility("issue_type") && ( -
- -
- )} */} +
+ +
{/* Options */} - {/* {handleExtraOptionsSectionVisibility && ( -
- -
- )} */} +
+ +
); diff --git a/web/components/issue-layouts/display-filters/issue-type.tsx b/web/components/issue-layouts/display-filters/issue-type.tsx index b9c3ace847e..6af37c8d5fb 100644 --- a/web/components/issue-layouts/display-filters/issue-type.tsx +++ b/web/components/issue-layouts/display-filters/issue-type.tsx @@ -1,38 +1,52 @@ import React from "react"; + +import { useRouter } from "next/router"; + +// mobx import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; // components import { FilterHeader } from "../helpers/filter-header"; import { FilterOption } from "../helpers/filter-option"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; +// types +import { TIssueTypeFilters } from "types"; +// constants import { ISSUE_FILTER_OPTIONS } from "constants/issue"; export const FilterIssueType = observer(() => { - const store: RootStore = useMobxStore(); + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const store = useMobxStore(); const { issueFilter: issueFilterStore } = store; const [previewEnabled, setPreviewEnabled] = React.useState(true); - const handleIssueType = (key: string, value: string) => { - // issueFilterStore.handleUserFilter("display_filters", key, value); + const handleIssueType = (value: TIssueTypeFilters) => { + if (!workspaceSlug || !projectId) return; + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + display_filters: { + type: value, + }, + }); }; return (
setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
- {ISSUE_FILTER_OPTIONS.map((_issueType) => ( +
+ {ISSUE_FILTER_OPTIONS.map((issueType) => ( handleIssueType("type", _issueType?.key)} - title={_issueType.title} + key={issueType?.key} + isChecked={issueFilterStore?.userDisplayFilters?.type === issueType?.key ? true : false} + onClick={() => handleIssueType(issueType?.key)} + title={issueType.title} multiple={false} /> ))} diff --git a/web/components/issue-layouts/display-filters/order-by.tsx b/web/components/issue-layouts/display-filters/order-by.tsx index 8159dc64140..59993456c3a 100644 --- a/web/components/issue-layouts/display-filters/order-by.tsx +++ b/web/components/issue-layouts/display-filters/order-by.tsx @@ -1,22 +1,35 @@ import React from "react"; + +import { useRouter } from "next/router"; + +// mobx +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; // components import { FilterHeader } from "../helpers/filter-header"; import { FilterOption } from "../helpers/filter-option"; -// mobx react lite -import { observer } from "mobx-react-lite"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; +// types +import { TIssueOrderByOptions } from "types"; +// constants import { ISSUE_ORDER_BY_OPTIONS } from "constants/issue"; export const FilterOrderBy = observer(() => { - const store: RootStore = useMobxStore(); + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const store = useMobxStore(); const { issueFilter: issueFilterStore } = store; const [previewEnabled, setPreviewEnabled] = React.useState(true); - const handleOrderBy = (key: string, value: string) => { - // issueFilterStore.handleUserFilter("display_filters", key, value); + const handleOrderBy = (value: TIssueOrderByOptions) => { + if (!workspaceSlug || !projectId) return; + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + display_filters: { + order_by: value, + }, + }); }; return ( @@ -28,12 +41,12 @@ export const FilterOrderBy = observer(() => { /> {previewEnabled && (
- {ISSUE_ORDER_BY_OPTIONS.map((_orderBy) => ( + {ISSUE_ORDER_BY_OPTIONS.map((orderBy) => ( handleOrderBy("order_by", _orderBy?.key)} - title={_orderBy.title} + key={orderBy?.key} + isChecked={issueFilterStore?.userDisplayFilters?.order_by === orderBy?.key ? true : false} + onClick={() => handleOrderBy(orderBy.key)} + title={orderBy.title} multiple={false} /> ))} diff --git a/web/components/issue-layouts/filters/assignees.tsx b/web/components/issue-layouts/filters/assignees.tsx index 7c2d449932b..4b357f56703 100644 --- a/web/components/issue-layouts/filters/assignees.tsx +++ b/web/components/issue-layouts/filters/assignees.tsx @@ -1,67 +1,52 @@ -import React from "react"; +import React, { useState } from "react"; + +import { useRouter } from "next/router"; + +// mobx +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; // components import { FilterHeader } from "../helpers/filter-header"; import { FilterOption } from "../helpers/filter-option"; -// mobx react lite -import { observer } from "mobx-react-lite"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; +// ui +import { Avatar } from "components/ui"; + +type Props = { onClick: (stateId: string) => void }; -export const MemberIcons = ({ display_name, avatar }: { display_name: string; avatar: string | null }) => ( -
- {avatar ? ( - {display_name - ) : ( -
- {(display_name ?? "U")[0]} -
- )} -
-); +export const FilterAssignees: React.FC = observer((props) => { + const { onClick } = props; -export const FilterAssignees = observer(() => { - const store: RootStore = useMobxStore(); - const { issueFilter: issueFilterStore } = store; + const [previewEnabled, setPreviewEnabled] = useState(true); - const [previewEnabled, setPreviewEnabled] = React.useState(true); + const router = useRouter(); + const { projectId } = router.query; - const handleFilter = (key: string, value: string) => { - // let _value = - // issueFilterStore?.userFilters?.filters?.[key] != null - // ? issueFilterStore?.userFilters?.filters?.[key].includes(value) - // ? issueFilterStore?.userFilters?.filters?.[key].filter((p: string) => p != value) - // : [...issueFilterStore?.userFilters?.filters?.[key], value] - // : [value]; - // _value = _value && _value.length > 0 ? _value : null; - // issueFilterStore.handleUserFilter("filters", key, _value); - }; + const store = useMobxStore(); + const { issueFilter: issueFilterStore, project: projectStore } = store; return (
setPreviewEnabled(!previewEnabled)} /> {previewEnabled && (
- {issueFilterStore?.projectMembers && - issueFilterStore?.projectMembers.length > 0 && - issueFilterStore?.projectMembers.map((_member) => ( - handleFilter("assignees", _member?.member?.id)} - icon={} - title={`${_member?.member?.display_name} (${_member?.member?.first_name} ${_member?.member?.last_name})`} - /> - ))} + {projectStore.members?.[projectId?.toString() ?? ""]?.map((member) => ( + onClick(member.member?.id)} + icon={} + title={member.member?.display_name} + /> + ))}
)}
diff --git a/web/components/issue-layouts/filters/created-by.tsx b/web/components/issue-layouts/filters/created-by.tsx index 35693de2f0e..fcdc862c6ac 100644 --- a/web/components/issue-layouts/filters/created-by.tsx +++ b/web/components/issue-layouts/filters/created-by.tsx @@ -1,56 +1,47 @@ -import React from "react"; +import React, { useState } from "react"; + +import { useRouter } from "next/router"; + +// mobx +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; // components -import { MemberIcons } from "./assignees"; import { FilterHeader } from "../helpers/filter-header"; import { FilterOption } from "../helpers/filter-option"; -// mobx react lite -import { observer } from "mobx-react-lite"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; +// ui +import { Avatar } from "components/ui"; + +type Props = { onClick: (stateId: string) => void }; + +export const FilterCreatedBy: React.FC = observer((props) => { + const { onClick } = props; -export const FilterCreatedBy = observer(() => { - const store: RootStore = useMobxStore(); - const { issueFilters: issueFilterStore } = store; + const [previewEnabled, setPreviewEnabled] = useState(true); - const [previewEnabled, setPreviewEnabled] = React.useState(true); + const router = useRouter(); + const { projectId } = router.query; - const handleFilter = (key: string, value: string) => { - let _value = - issueFilterStore?.userFilters?.filters?.[key] != null - ? issueFilterStore?.userFilters?.filters?.[key].includes(value) - ? issueFilterStore?.userFilters?.filters?.[key].filter((p: string) => p != value) - : [...issueFilterStore?.userFilters?.filters?.[key], value] - : [value]; - _value = _value && _value.length > 0 ? _value : null; - issueFilterStore.handleUserFilter("filters", key, _value); - }; + const store = useMobxStore(); + const { issueFilter: issueFilterStore, project: projectStore } = store; return (
setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
- {issueFilterStore?.projectMembers && - issueFilterStore?.projectMembers.length > 0 && - issueFilterStore?.projectMembers.map((_member) => ( - handleFilter("created_by", _member?.member?.id)} - icon={} - title={`${_member?.member?.display_name} (${_member?.member?.first_name} ${_member?.member?.last_name})`} - /> - ))} +
+ {projectStore.members?.[projectId?.toString() ?? ""]?.map((member) => ( + onClick(member.member?.id)} + icon={} + title={member.member?.display_name} + /> + ))}
)}
diff --git a/web/components/issue-layouts/filters/index.tsx b/web/components/issue-layouts/filters/index.tsx index c52cf5be167..5f16b2e282e 100644 --- a/web/components/issue-layouts/filters/index.tsx +++ b/web/components/issue-layouts/filters/index.tsx @@ -13,79 +13,79 @@ import { observer } from "mobx-react-lite"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; -// default data -import { issueFilterVisibilityData } from "store/helpers/issue-data"; +import { useRouter } from "next/router"; +import { IIssueFilterOptions } from "types"; export const FilterSelection = observer(() => { + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + const store: RootStore = useMobxStore(); - const { issueFilters: issueFilterStore } = store; + const { issueFilter: issueFilterStore } = store; + + const handleFilterUpdate = (filter) => { + if (!workspaceSlug || !projectId) return; + + const newValues = issueFilterStore.userFilters?.[filter.key] ?? []; + + if (issueFilterStore.userFilters?.[filter.key]?.includes(filter.value)) + newValues.splice(newValues.indexOf(filter.value), 1); + else newValues.push(filter.value); - const handleFilterSectionVisibility = (section_key: string) => - issueFilterStore?.issueView && - issueFilterStore?.issueLayout && - issueFilterVisibilityData[issueFilterStore?.issueView === "my_issues" ? "my_issues" : "issues"]?.filters?.[ - issueFilterStore?.issueLayout - ]?.includes(section_key); + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + filters: { + [filter.key]: newValues, + }, + }); + }; return (
-
Search container
+
Search container
{/* priority */} - {handleFilterSectionVisibility("priority") && ( -
- -
- )} +
+ handleFilterUpdate({ key: "priority", value: priority })} /> +
{/* state group */} - {handleFilterSectionVisibility("state_group") && ( -
- -
- )} +
+ handleFilterUpdate({ key: "state_group", value: stateGroup })} /> +
{/* state */} - {handleFilterSectionVisibility("state") && ( -
- -
- )} +
+ handleFilterUpdate({ key: "state", value: stateId })} /> +
{/* assignees */} - {handleFilterSectionVisibility("assignees") && ( -
- -
- )} +
+ handleFilterUpdate({ key: "assignees", value: memberId })} /> +
{/* created_by */} - {handleFilterSectionVisibility("created_by") && ( -
- -
- )} +
+ handleFilterUpdate({ key: "created_by", value: memberId })} /> +
{/* labels */} - {handleFilterSectionVisibility("labels") && ( -
- -
- )} +
+ handleFilterUpdate({ key: "labels", value: labelId })} /> +
{/* start_date */} - {handleFilterSectionVisibility("start_date") && ( + {/* {handleFilterSectionVisibility("start_date") && (
- )} + )} */} {/* due_date */} - {handleFilterSectionVisibility("due_date") && ( + {/* {handleFilterSectionVisibility("due_date") && (
- )} + )} */}
); diff --git a/web/components/issue-layouts/filters/labels.tsx b/web/components/issue-layouts/filters/labels.tsx index 4c42e8af9f9..39fcc6d1fb7 100644 --- a/web/components/issue-layouts/filters/labels.tsx +++ b/web/components/issue-layouts/filters/labels.tsx @@ -1,4 +1,7 @@ -import React from "react"; +import React, { useState } from "react"; + +import { useRouter } from "next/router"; + // components import { FilterHeader } from "../helpers/filter-header"; import { FilterOption } from "../helpers/filter-option"; @@ -6,61 +9,42 @@ import { FilterOption } from "../helpers/filter-option"; import { observer } from "mobx-react-lite"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; const LabelIcons = ({ color }: { color: string }) => ( -
-
-
+ ); -export const FilterLabels = observer(() => { - const store: RootStore = useMobxStore(); - const { issueFilters: issueFilterStore } = store; +type Props = { onClick: (stateId: string) => void }; + +export const FilterLabels: React.FC = observer((props) => { + const { onClick } = props; - const [previewEnabled, setPreviewEnabled] = React.useState(true); + const [previewEnabled, setPreviewEnabled] = useState(true); - const handleFilter = (key: string, value: string) => { - let _value = - issueFilterStore?.userFilters?.filters?.[key] != null - ? issueFilterStore?.userFilters?.filters?.[key].includes(value) - ? issueFilterStore?.userFilters?.filters?.[key].filter((p: string) => p != value) - : [...issueFilterStore?.userFilters?.filters?.[key], value] - : [value]; - _value = _value && _value.length > 0 ? _value : null; - issueFilterStore.handleUserFilter("filters", key, _value); - }; + const router = useRouter(); + const { projectId } = router.query; - const handleLabels = - issueFilterStore.issueView && issueFilterStore.issueView === "my_issues" - ? issueFilterStore?.workspaceLabels - : issueFilterStore?.projectLabels; + const store = useMobxStore(); + const { issueFilter: issueFilterStore, project: projectStore } = store; return (
setPreviewEnabled(!previewEnabled)} /> {previewEnabled && (
- {handleLabels && - handleLabels.length > 0 && - handleLabels.map((_label) => ( - handleFilter("labels", _label?.id)} - icon={} - title={_label.name} - /> - ))} + {projectStore.labels?.[projectId?.toString() ?? ""]?.map((label) => ( + onClick(label?.id)} + icon={} + title={label.name} + /> + ))}
)}
diff --git a/web/components/issue-layouts/filters/priority.tsx b/web/components/issue-layouts/filters/priority.tsx index 2bbcce8f704..1c2a92f6084 100644 --- a/web/components/issue-layouts/filters/priority.tsx +++ b/web/components/issue-layouts/filters/priority.tsx @@ -1,19 +1,20 @@ -import React from "react"; -// lucide icons -import { AlertCircle, SignalHigh, SignalMedium, SignalLow, Ban } from "lucide-react"; +import React, { useState } from "react"; + +// mobx +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; // components import { FilterHeader } from "../helpers/filter-header"; import { FilterOption } from "../helpers/filter-option"; -// mobx react lite -import { observer } from "mobx-react-lite"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; +// icons +import { AlertCircle, SignalHigh, SignalMedium, SignalLow, Ban } from "lucide-react"; +// constants +import { ISSUE_PRIORITIES } from "constants/issue"; const PriorityIcons = ({ priority, - size = 14, - strokeWidth = 2, + size = 12, + strokeWidth = 1.5, }: { priority: string; size?: number; @@ -21,82 +22,63 @@ const PriorityIcons = ({ }) => { if (priority === "urgent") return ( -
+
); if (priority === "high") return ( -
+
); if (priority === "medium") return ( -
+
); if (priority === "low") return ( -
+
); return ( -
+
); }; -export const FilterPriority = observer(() => { - const store: RootStore = useMobxStore(); - const { issueFilters: issueFilterStore } = store; +type Props = { onClick: (stateId: string) => void }; + +export const FilterPriority: React.FC = observer((props) => { + const { onClick } = props; - const [previewEnabled, setPreviewEnabled] = React.useState(true); + const [previewEnabled, setPreviewEnabled] = useState(true); - const handleFilter = (key: string, value: string) => { - let _value = - issueFilterStore?.userFilters?.filters?.[key] != null - ? issueFilterStore?.userFilters?.filters?.[key].includes(value) - ? issueFilterStore?.userFilters?.filters?.[key].filter((p: string) => p != value) - : [...issueFilterStore?.userFilters?.filters?.[key], value] - : [value]; - _value = _value && _value.length > 0 ? _value : null; - issueFilterStore.handleUserFilter("filters", key, _value); - }; + const store = useMobxStore(); + const { issueFilter: issueFilterStore } = store; return (
setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
- {issueFilterStore?.issueRenderFilters?.priority && - issueFilterStore?.issueRenderFilters?.priority.length > 0 && - issueFilterStore?.issueRenderFilters?.priority.map((_priority) => ( - handleFilter("priority", _priority?.key)} - icon={} - title={_priority.title} - /> - ))} -
-
View less
-
View more
- {/* TODO:
View all
*/} -
+
+ {ISSUE_PRIORITIES.map((priority) => ( + onClick(priority.key)} + icon={} + title={priority.title} + /> + ))}
)}
diff --git a/web/components/issue-layouts/filters/state-group.tsx b/web/components/issue-layouts/filters/state-group.tsx index dcfd71cbfbb..55c5239de40 100644 --- a/web/components/issue-layouts/filters/state-group.tsx +++ b/web/components/issue-layouts/filters/state-group.tsx @@ -1,108 +1,43 @@ -import React from "react"; -import { - StateGroupBacklogIcon, - StateGroupCancelledIcon, - StateGroupCompletedIcon, - StateGroupStartedIcon, - StateGroupUnstartedIcon, -} from "components/icons"; +import React, { useState } from "react"; + +// mobx +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; // components import { FilterHeader } from "../helpers/filter-header"; import { FilterOption } from "../helpers/filter-option"; -// mobx react lite -import { observer } from "mobx-react-lite"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; -// constants -import { STATE_GROUP_COLORS } from "constants/state"; +// icons +import { StateGroupIcon } from "components/icons"; +import { ISSUE_STATE_GROUPS } from "constants/issue"; -export const StateGroupIcons = ({ - stateGroup, - width = "14px", - height = "14px", - color = null, -}: { - stateGroup: string; - width?: string | undefined; - height?: string | undefined; - color?: string | null; -}) => { - if (stateGroup === "cancelled") - return ( -
- -
- ); - if (stateGroup === "completed") - return ( -
- -
- ); - if (stateGroup === "started") - return ( -
- -
- ); - if (stateGroup === "unstarted") - return ( -
- -
- ); - if (stateGroup === "backlog") - return ( -
- -
- ); - return <>; -}; +type Props = { onClick: (stateId: string) => void }; -export const FilterStateGroup = observer(() => { - const store: RootStore = useMobxStore(); - const { issueFilters: issueFilterStore } = store; +export const FilterStateGroup: React.FC = observer((props) => { + const { onClick } = props; - const [previewEnabled, setPreviewEnabled] = React.useState(true); + const [previewEnabled, setPreviewEnabled] = useState(true); - const handleFilter = (key: string, value: string) => { - let _value = - issueFilterStore?.userFilters?.filters?.[key] != null - ? issueFilterStore?.userFilters?.filters?.[key].includes(value) - ? issueFilterStore?.userFilters?.filters?.[key].filter((p: string) => p != value) - : [...issueFilterStore?.userFilters?.filters?.[key], value] - : [value]; - _value = _value && _value.length > 0 ? _value : null; - issueFilterStore.handleUserFilter("filters", key, _value); - }; + const store = useMobxStore(); + const { issueFilter: issueFilterStore } = store; return (
setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
- {issueFilterStore?.issueRenderFilters?.state_group && - issueFilterStore?.issueRenderFilters?.state_group.length > 0 && - issueFilterStore?.issueRenderFilters?.state_group.map((_stateGroup) => ( - handleFilter("state_group", _stateGroup?.key)} - icon={} - title={_stateGroup.title} - /> - ))} +
+ {ISSUE_STATE_GROUPS.map((stateGroup) => ( + onClick(stateGroup.key)} + icon={} + title={stateGroup.title} + /> + ))}
)}
diff --git a/web/components/issue-layouts/filters/state.tsx b/web/components/issue-layouts/filters/state.tsx index ef59fa0ca72..eb9dfd92b01 100644 --- a/web/components/issue-layouts/filters/state.tsx +++ b/web/components/issue-layouts/filters/state.tsx @@ -1,68 +1,52 @@ import React from "react"; + +import { useRouter } from "next/router"; + +// mobx +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; // components -import { StateGroupIcons } from "./state-group"; import { FilterHeader } from "../helpers/filter-header"; import { FilterOption } from "../helpers/filter-option"; -// mobx react lite -import { observer } from "mobx-react-lite"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; -// store default data -import { stateGroups } from "store/helpers/issue-data"; +// icons +import { StateGroupIcon } from "components/icons"; +// helpers +import { getStatesList } from "helpers/state.helper"; -export const FilterState = observer(() => { - const store: RootStore = useMobxStore(); - const { issueFilters: issueFilterStore } = store; +type Props = { onClick: (stateId: string) => void }; - const [previewEnabled, setPreviewEnabled] = React.useState(true); +export const FilterState: React.FC = observer((props) => { + const { onClick } = props; - const handleFilter = (key: string, value: string) => { - let _value = - issueFilterStore?.userFilters?.filters?.[key] != null - ? issueFilterStore?.userFilters?.filters?.[key].includes(value) - ? issueFilterStore?.userFilters?.filters?.[key].filter((p: string) => p != value) - : [...issueFilterStore?.userFilters?.filters?.[key], value] - : [value]; - _value = _value && _value.length > 0 ? _value : null; - issueFilterStore.handleUserFilter("filters", key, _value); - }; + const router = useRouter(); + const { projectId } = router.query; - const countAllState = stateGroups - .map((_stateGroup) => issueFilterStore?.projectStates?.[_stateGroup?.key].length || 0) - .reduce((sum: number, currentValue: number) => sum + currentValue, 0); + const store = useMobxStore(); + const { issueFilter: issueFilterStore, project: projectStore } = store; + + const [previewEnabled, setPreviewEnabled] = React.useState(true); - console.log("countAllState", countAllState); + const statesByGroups = projectStore.states?.[projectId?.toString() ?? ""]; + const statesList = getStatesList(statesByGroups); return (
setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
- {stateGroups.map( - (_stateGroup) => - issueFilterStore?.projectStates && - issueFilterStore?.projectStates[_stateGroup?.key] && - issueFilterStore?.projectStates[_stateGroup?.key].length > 0 && - issueFilterStore?.projectStates[_stateGroup?.key].map((_state: any) => ( - handleFilter("state", _state?.id)} - icon={} - title={_state?.name} - /> - )) - )} +
+ {statesList?.map((state) => ( + onClick(state?.id)} + icon={} + title={state?.name} + /> + ))}
)}
diff --git a/web/components/issue-layouts/helpers/dropdown.tsx b/web/components/issue-layouts/helpers/dropdown.tsx index 4e12c2bc0d3..ffcee94338f 100644 --- a/web/components/issue-layouts/helpers/dropdown.tsx +++ b/web/components/issue-layouts/helpers/dropdown.tsx @@ -17,7 +17,7 @@ export const IssueDropdown = ({ children, title = "Dropdown" }: IIssueDropdown) return ( <>
{title}
@@ -33,10 +33,8 @@ export const IssueDropdown = ({ children, title = "Dropdown" }: IIssueDropdown) leaveFrom="opacity-100 translate-y-0" leaveTo="opacity-0 translate-y-1" > - -
- {children} -
+ + {children} diff --git a/web/components/issue-layouts/helpers/filter-option.tsx b/web/components/issue-layouts/helpers/filter-option.tsx index 63abda72f2b..2e0a32cb905 100644 --- a/web/components/issue-layouts/helpers/filter-option.tsx +++ b/web/components/issue-layouts/helpers/filter-option.tsx @@ -2,27 +2,34 @@ import React from "react"; // lucide icons import { Check } from "lucide-react"; -interface IFilterOption { - isChecked: boolean; +type Props = { icon?: React.ReactNode; - title: string; - multiple?: boolean; + isChecked: boolean; + title: React.ReactNode; onClick?: () => void; -} + multiple?: boolean; +}; + +export const FilterOption: React.FC = (props) => { + const { icon, isChecked, multiple = true, onClick, title } = props; -export const FilterOption = ({ isChecked, icon, title, multiple = true, onClick }: IFilterOption) => ( -
-
- {isChecked && } -
- {icon} -
{title}
-
-); +
+ {isChecked && } +
+
+
{icon}
+
{title}
+
+ + ); +}; diff --git a/web/components/issue-layouts/layout-selection.tsx b/web/components/issue-layouts/layout-selection.tsx index a6629a2aa41..aeff4867797 100644 --- a/web/components/issue-layouts/layout-selection.tsx +++ b/web/components/issue-layouts/layout-selection.tsx @@ -1,97 +1,40 @@ import React from "react"; -// lucide icons -import { Columns, Grid3x3, Calendar, GanttChart, List } from "lucide-react"; -// mobx react lite -import { observer } from "mobx-react-lite"; -// mobx store -import { RootStore } from "store/root"; -import { useMobxStore } from "lib/mobx/store-provider"; -// types and default data -import { TIssueLayouts } from "store/issue_filters.legacy"; -import { issueFilterVisibilityData } from "store/helpers/issue-data"; -export const LayoutSelection = observer(() => { - const store: RootStore = useMobxStore(); - const { issueFilters: issueFilterStore, issueView: issueStore } = store; +// types +import { TIssueLayouts } from "types"; +// constants +import { ISSUE_LAYOUTS } from "constants/issue"; - const layoutSelectionFilters: { key: TIssueLayouts; title: string; icon: any }[] = [ - { - key: "list", - title: "List", - icon: List, - }, - { - key: "kanban", - title: "Kanban", - icon: Grid3x3, - }, - { - key: "calendar", - title: "Calendar", - icon: Calendar, - }, - { - key: "spreadsheet", - title: "Spreadsheet", - icon: Columns, - }, - { - key: "gantt_chart", - title: "Gantt", - icon: GanttChart, - }, - ]; +type Props = { + layouts: TIssueLayouts[]; + onChange: (layout: TIssueLayouts) => void; + selectedLayout: TIssueLayouts; +}; - const handleLayoutSectionVisibility = (layout_key: string) => - issueFilterStore?.issueView && - issueFilterVisibilityData[issueFilterStore?.issueView === "my_issues" ? "my_issues" : "issues"].layout.includes( - layout_key - ); - - const handleLayoutSelection = (_layoutKey: string) => { - issueFilterStore.handleUserFilter("display_filters", "layout", _layoutKey); - }; - - // console.log("----"); - // console.log("my_user_id", issueFilterStore.myUserId); - // console.log("workspace_id", issueFilterStore.workspaceId); - // console.log("project_id", issueFilterStore.projectId); - // console.log("module_id", issueFilterStore.moduleId); - // console.log("cycle_id", issueFilterStore.cycleId); - // console.log("view_id", issueFilterStore.viewId); - - // console.log("issue_view", issueFilterStore.issueView); - // console.log("issue_layout", issueFilterStore.issueLayout); - - // console.log("user_filters", issueFilterStore.userFilters); - // console.log("issues", issueStore.issues); - // console.log("issues", issueStore.getIssues); - // console.log("----"); +export const LayoutSelection: React.FC = (props) => { + const { layouts, onChange, selectedLayout } = props; return (
- {layoutSelectionFilters.map( - (_layout) => - handleLayoutSectionVisibility(_layout?.key) && ( -
handleLayoutSelection(_layout?.key)} - > - <_layout.icon - size={14} - strokeWidth={2} - className={`${ - issueFilterStore?.issueLayout == _layout?.key - ? `text-custom-text-100` - : `text-custom-text-100 group-hover:text-custom-text-200` - }`} - /> -
- ) - )} + {ISSUE_LAYOUTS.filter((l) => layouts.includes(l.key)).map((layout) => ( +
onChange(layout.key)} + > + +
+ ))}
); -}); +}; diff --git a/web/components/issues/my-issues/my-issues-view-options.tsx b/web/components/issues/my-issues/my-issues-view-options.tsx index 90d9b8971a9..f9aa62c2fe0 100644 --- a/web/components/issues/my-issues/my-issues-view-options.tsx +++ b/web/components/issues/my-issues/my-issues-view-options.tsx @@ -18,11 +18,11 @@ import { FormatListBulletedOutlined, GridViewOutlined } from "@mui/icons-materia import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; import { checkIfArraysHaveSameElements } from "helpers/array.helper"; // types -import { Properties, TIssueViewOptions } from "types"; +import { Properties, TIssueLayouts } from "types"; // constants import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue"; -const issueViewOptions: { type: TIssueViewOptions; Icon: any }[] = [ +const issueViewOptions: { type: TIssueLayouts; Icon: any }[] = [ { type: "list", Icon: FormatListBulletedOutlined, @@ -37,8 +37,9 @@ export const MyIssuesViewOptions: React.FC = () => { const router = useRouter(); const { workspaceSlug } = router.query; - const { displayFilters, setDisplayFilters, properties, setProperty, filters, setFilters } = - useMyIssuesFilters(workspaceSlug?.toString()); + const { displayFilters, setDisplayFilters, properties, setProperty, filters, setFilters } = useMyIssuesFilters( + workspaceSlug?.toString() + ); const { isEstimateActive } = useEstimateOption(); @@ -48,9 +49,7 @@ export const MyIssuesViewOptions: React.FC = () => { {issueViewOptions.map((option) => ( {replaceUnderscoreIfSnakeCase(option.type)} View - } + tooltipContent={{replaceUnderscoreIfSnakeCase(option.type)} View} position="bottom" >
+
); diff --git a/web/components/issue-layouts/helpers/filter-option.tsx b/web/components/issue-layouts/helpers/filter-option.tsx index 2e0a32cb905..a67c1142ff1 100644 --- a/web/components/issue-layouts/helpers/filter-option.tsx +++ b/web/components/issue-layouts/helpers/filter-option.tsx @@ -16,7 +16,7 @@ export const FilterOption: React.FC = (props) => { return ( ))}
); From 4acb93d2c6e64c3c4be4aa102a16651fffefe4b4 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Mon, 25 Sep 2023 12:41:07 +0530 Subject: [PATCH 3/9] refactor: file structure --- .../core/views/board-view/all-boards.tsx | 21 ++-- web/components/headers/project-issues.tsx | 12 +-- .../display-filters-selection.tsx | 42 ++++++++ .../display-filters/display-properties.tsx | 2 +- .../issue-layouts/display-filters/index.ts | 6 ++ .../issue-layouts/display-filters/index.tsx | 51 --------- .../issue-layouts/filters/assignees.tsx | 30 ++++-- .../issue-layouts/filters/created-by.tsx | 30 ++++-- .../filters/filter-selection.tsx | 71 ++++++++++++ web/components/issue-layouts/filters/index.ts | 9 ++ .../issue-layouts/filters/index.tsx | 92 ---------------- .../issue-layouts/filters/labels.tsx | 30 ++++-- .../issue-layouts/filters/priority.tsx | 22 +++- .../issue-layouts/filters/start-date.tsx | 37 +++---- .../issue-layouts/filters/state-group.tsx | 26 ++++- .../issue-layouts/filters/state.tsx | 30 ++++-- .../issue-layouts/filters/target-date.tsx | 34 ++---- .../issue-layouts/helpers/dropdown.tsx | 5 +- web/components/issue-layouts/helpers/index.ts | 3 + web/components/issue-layouts/index.ts | 4 + web/components/issue-layouts/root.tsx | 2 +- web/constants/issue.ts | 9 +- web/helpers/issue.helper.ts | 101 ++++++++++++++---- .../projects/[projectId]/issues/index.tsx | 10 +- web/store/issue_filters.ts | 18 +++- web/types/view-props.d.ts | 17 +++ 26 files changed, 419 insertions(+), 295 deletions(-) create mode 100644 web/components/issue-layouts/display-filters/display-filters-selection.tsx create mode 100644 web/components/issue-layouts/display-filters/index.ts delete mode 100644 web/components/issue-layouts/display-filters/index.tsx create mode 100644 web/components/issue-layouts/filters/filter-selection.tsx create mode 100644 web/components/issue-layouts/filters/index.ts delete mode 100644 web/components/issue-layouts/filters/index.tsx create mode 100644 web/components/issue-layouts/helpers/index.ts create mode 100644 web/components/issue-layouts/index.ts diff --git a/web/components/core/views/board-view/all-boards.tsx b/web/components/core/views/board-view/all-boards.tsx index 058eafdf806..4c42ae166b3 100644 --- a/web/components/core/views/board-view/all-boards.tsx +++ b/web/components/core/views/board-view/all-boards.tsx @@ -65,28 +65,21 @@ export const AllBoards: React.FC = ({ const { displayFilters, groupedIssues } = viewProps; - console.log("viewProps", viewProps); - return ( <> - isMyIssue ? mutateMyIssues() : isProfileIssue ? mutateProfileIssues() : mutateIssues() - } + handleMutation={() => (isMyIssue ? mutateMyIssues() : isProfileIssue ? mutateProfileIssues() : mutateIssues())} projectId={myIssueProjectId ? myIssueProjectId : projectId?.toString() ?? ""} workspaceSlug={workspaceSlug?.toString() ?? ""} readOnly={disableUserActions} /> {groupedIssues ? ( -
+
{Object.keys(groupedIssues).map((singleGroup, index) => { const currentState = - displayFilters?.group_by === "state" - ? states?.find((s) => s.id === singleGroup) - : null; + displayFilters?.group_by === "state" ? states?.find((s) => s.id === singleGroup) : null; - if (!displayFilters?.show_empty_groups && groupedIssues[singleGroup].length === 0) - return null; + if (!displayFilters?.show_empty_groups && groupedIssues[singleGroup].length === 0) return null; return ( = ({
{Object.keys(groupedIssues).map((singleGroup, index) => { const currentState = - displayFilters?.group_by === "state" - ? states?.find((s) => s.id === singleGroup) - : null; + displayFilters?.group_by === "state" ? states?.find((s) => s.id === singleGroup) : null; if (groupedIssues[singleGroup].length === 0) return (
{currentState && ( diff --git a/web/components/headers/project-issues.tsx b/web/components/headers/project-issues.tsx index fee58b01277..11d46846b1a 100644 --- a/web/components/headers/project-issues.tsx +++ b/web/components/headers/project-issues.tsx @@ -4,24 +4,20 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; // components -import { LayoutSelection } from "components/issue-layouts/layout-selection"; +import { DisplayFiltersSelection, FilterSelection, IssueDropdown, LayoutSelection } from "components/issue-layouts"; // types import { TIssueLayouts } from "types"; -import { IssueDropdown } from "components/issue-layouts/helpers/dropdown"; -import { FilterSelection } from "components/issue-layouts/filters"; -import { DisplayFiltersSelection } from "components/issue-layouts/display-filters"; export const ProjectIssuesHeader = observer(() => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; - const { issueFilter } = useMobxStore(); - const { updateUserFilters } = issueFilter; + const { issueFilter: issueFilterStore } = useMobxStore(); const handleLayoutChange = (layout: TIssueLayouts) => { if (!workspaceSlug || !projectId) return; - updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { display_filters: { layout, }, @@ -33,7 +29,7 @@ export const ProjectIssuesHeader = observer(() => { handleLayoutChange(layout)} - selectedLayout={issueFilter.userDisplayFilters.layout ?? "list"} + selectedLayout={issueFilterStore.userDisplayFilters.layout ?? "list"} /> diff --git a/web/components/issue-layouts/display-filters/display-filters-selection.tsx b/web/components/issue-layouts/display-filters/display-filters-selection.tsx new file mode 100644 index 00000000000..05cd4ff9237 --- /dev/null +++ b/web/components/issue-layouts/display-filters/display-filters-selection.tsx @@ -0,0 +1,42 @@ +import React from "react"; + +// components +import { + FilterDisplayProperties, + FilterExtraOptions, + FilterGroupBy, + FilterIssueType, + FilterOrderBy, +} from "components/issue-layouts"; + +export const DisplayFiltersSelection = () => ( +
+
Search container
+
+ {/* display properties */} +
+ +
+ + {/* group by */} +
+ +
+ + {/* order by */} +
+ +
+ + {/* issue type */} +
+ +
+ + {/* Options */} +
+ +
+
+
+); diff --git a/web/components/issue-layouts/display-filters/display-properties.tsx b/web/components/issue-layouts/display-filters/display-properties.tsx index 17324750cf8..f7fb87d2c90 100644 --- a/web/components/issue-layouts/display-filters/display-properties.tsx +++ b/web/components/issue-layouts/display-filters/display-properties.tsx @@ -43,7 +43,7 @@ export const FilterDisplayProperties = observer(() => { className={`rounded transition-all text-xs border px-2 py-0.5 ${ issueFilterStore?.userDisplayProperties?.[displayProperty.key] ? "bg-custom-primary-100 border-custom-primary-100 text-white" - : "border-custom-border-200" + : "border-custom-border-200 hover:bg-custom-background-80" }`} onClick={() => handleDisplayProperties({ diff --git a/web/components/issue-layouts/display-filters/index.ts b/web/components/issue-layouts/display-filters/index.ts new file mode 100644 index 00000000000..27ed9943b2c --- /dev/null +++ b/web/components/issue-layouts/display-filters/index.ts @@ -0,0 +1,6 @@ +export * from "./display-filters-selection"; +export * from "./display-properties"; +export * from "./extra-options"; +export * from "./group-by"; +export * from "./issue-type"; +export * from "./order-by"; diff --git a/web/components/issue-layouts/display-filters/index.tsx b/web/components/issue-layouts/display-filters/index.tsx deleted file mode 100644 index cd300947096..00000000000 --- a/web/components/issue-layouts/display-filters/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from "react"; -// components -import { FilterDisplayProperties } from "./display-properties"; -import { FilterGroupBy } from "./group-by"; -import { FilterOrderBy } from "./order-by"; -import { FilterIssueType } from "./issue-type"; -import { FilterExtraOptions } from "./extra-options"; -// mobx react lite -import { observer } from "mobx-react-lite"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; -// default data -// import { issueFilterVisibilityData } from "store/helpers/issue-data"; - -export const DisplayFiltersSelection = observer(() => { - const store: RootStore = useMobxStore(); - const { issueFilter: issueFilterStore } = store; - - return ( -
-
Search container
-
- {/* display properties */} -
- -
- - {/* group by */} -
- -
- - {/* order by */} -
- -
- - {/* issue type */} -
- -
- - {/* Options */} -
- -
-
-
- ); -}); diff --git a/web/components/issue-layouts/filters/assignees.tsx b/web/components/issue-layouts/filters/assignees.tsx index 4b357f56703..b60686ea3d9 100644 --- a/web/components/issue-layouts/filters/assignees.tsx +++ b/web/components/issue-layouts/filters/assignees.tsx @@ -1,29 +1,39 @@ import React, { useState } from "react"; -import { useRouter } from "next/router"; - // mobx import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; // components -import { FilterHeader } from "../helpers/filter-header"; -import { FilterOption } from "../helpers/filter-option"; +import { FilterHeader, FilterOption } from "components/issue-layouts"; // ui import { Avatar } from "components/ui"; -type Props = { onClick: (stateId: string) => void }; +type Props = { + workspaceSlug: string; + projectId: string; +}; export const FilterAssignees: React.FC = observer((props) => { - const { onClick } = props; + const { workspaceSlug, projectId } = props; const [previewEnabled, setPreviewEnabled] = useState(true); - const router = useRouter(); - const { projectId } = router.query; - const store = useMobxStore(); const { issueFilter: issueFilterStore, project: projectStore } = store; + const handleUpdateAssignees = (value: string) => { + const newValues = issueFilterStore.userFilters?.assignees ?? []; + + if (issueFilterStore.userFilters?.assignees?.includes(value)) newValues.splice(newValues.indexOf(value), 1); + else newValues.push(value); + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + filters: { + assignees: newValues, + }, + }); + }; + return (
= observer((props) => { ? true : false } - onClick={() => onClick(member.member?.id)} + onClick={() => handleUpdateAssignees(member.member?.id)} icon={} title={member.member?.display_name} /> diff --git a/web/components/issue-layouts/filters/created-by.tsx b/web/components/issue-layouts/filters/created-by.tsx index fcdc862c6ac..42fb8082eb2 100644 --- a/web/components/issue-layouts/filters/created-by.tsx +++ b/web/components/issue-layouts/filters/created-by.tsx @@ -1,29 +1,39 @@ import React, { useState } from "react"; -import { useRouter } from "next/router"; - // mobx import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; // components -import { FilterHeader } from "../helpers/filter-header"; -import { FilterOption } from "../helpers/filter-option"; +import { FilterHeader, FilterOption } from "components/issue-layouts"; // ui import { Avatar } from "components/ui"; -type Props = { onClick: (stateId: string) => void }; +type Props = { + workspaceSlug: string; + projectId: string; +}; export const FilterCreatedBy: React.FC = observer((props) => { - const { onClick } = props; + const { workspaceSlug, projectId } = props; const [previewEnabled, setPreviewEnabled] = useState(true); - const router = useRouter(); - const { projectId } = router.query; - const store = useMobxStore(); const { issueFilter: issueFilterStore, project: projectStore } = store; + const handleUpdateCreatedBy = (value: string) => { + const newValues = issueFilterStore.userFilters?.created_by ?? []; + + if (issueFilterStore.userFilters?.created_by?.includes(value)) newValues.splice(newValues.indexOf(value), 1); + else newValues.push(value); + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + filters: { + created_by: newValues, + }, + }); + }; + return (
= observer((props) => { onClick(member.member?.id)} + onClick={() => handleUpdateCreatedBy(member.member?.id)} icon={} title={member.member?.display_name} /> diff --git a/web/components/issue-layouts/filters/filter-selection.tsx b/web/components/issue-layouts/filters/filter-selection.tsx new file mode 100644 index 00000000000..0d4c54e6622 --- /dev/null +++ b/web/components/issue-layouts/filters/filter-selection.tsx @@ -0,0 +1,71 @@ +import React from "react"; + +// components +import { + FilterAssignees, + FilterCreatedBy, + FilterLabels, + FilterPriority, + FilterState, + FilterStateGroup, +} from "components/issue-layouts"; + +type Props = { + workspaceSlug: string; + projectId: string; +}; + +export const FilterSelection: React.FC = (props) => { + const { workspaceSlug, projectId } = props; + + return ( +
+
Search container
+
+ {/* priority */} +
+ +
+ + {/* state group */} +
+ +
+ + {/* state */} +
+ +
+ + {/* assignees */} +
+ +
+ + {/* created_by */} +
+ +
+ + {/* labels */} +
+ +
+ + {/* start_date */} + {/* {handleFilterSectionVisibility("start_date") && ( +
+ +
+ )} */} + + {/* due_date */} + {/* {handleFilterSectionVisibility("due_date") && ( +
+ +
+ )} */} +
+
+ ); +}; diff --git a/web/components/issue-layouts/filters/index.ts b/web/components/issue-layouts/filters/index.ts new file mode 100644 index 00000000000..e804899d2ed --- /dev/null +++ b/web/components/issue-layouts/filters/index.ts @@ -0,0 +1,9 @@ +export * from "./assignees"; +export * from "./created-by"; +export * from "./filter-selection"; +export * from "./labels"; +export * from "./priority"; +export * from "./start-date"; +export * from "./state-group"; +export * from "./state"; +export * from "./target-date"; diff --git a/web/components/issue-layouts/filters/index.tsx b/web/components/issue-layouts/filters/index.tsx deleted file mode 100644 index 5f16b2e282e..00000000000 --- a/web/components/issue-layouts/filters/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React from "react"; -// components -import { FilterPriority } from "./priority"; -import { FilterState } from "./state"; -import { FilterStateGroup } from "./state-group"; -import { FilterAssignees } from "./assignees"; -import { FilterCreatedBy } from "./created-by"; -import { FilterLabels } from "./labels"; -import { FilterStartDate } from "./start-date"; -import { FilterTargetDate } from "./target-date"; -// mobx react lite -import { observer } from "mobx-react-lite"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; -import { useRouter } from "next/router"; -import { IIssueFilterOptions } from "types"; - -export const FilterSelection = observer(() => { - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - - const store: RootStore = useMobxStore(); - const { issueFilter: issueFilterStore } = store; - - const handleFilterUpdate = (filter) => { - if (!workspaceSlug || !projectId) return; - - const newValues = issueFilterStore.userFilters?.[filter.key] ?? []; - - if (issueFilterStore.userFilters?.[filter.key]?.includes(filter.value)) - newValues.splice(newValues.indexOf(filter.value), 1); - else newValues.push(filter.value); - - issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { - filters: { - [filter.key]: newValues, - }, - }); - }; - - return ( -
-
Search container
-
- {/* priority */} -
- handleFilterUpdate({ key: "priority", value: priority })} /> -
- - {/* state group */} -
- handleFilterUpdate({ key: "state_group", value: stateGroup })} /> -
- - {/* state */} -
- handleFilterUpdate({ key: "state", value: stateId })} /> -
- - {/* assignees */} -
- handleFilterUpdate({ key: "assignees", value: memberId })} /> -
- - {/* created_by */} -
- handleFilterUpdate({ key: "created_by", value: memberId })} /> -
- - {/* labels */} -
- handleFilterUpdate({ key: "labels", value: labelId })} /> -
- - {/* start_date */} - {/* {handleFilterSectionVisibility("start_date") && ( -
- -
- )} */} - - {/* due_date */} - {/* {handleFilterSectionVisibility("due_date") && ( -
- -
- )} */} -
-
- ); -}); diff --git a/web/components/issue-layouts/filters/labels.tsx b/web/components/issue-layouts/filters/labels.tsx index 39fcc6d1fb7..52191b1bfb5 100644 --- a/web/components/issue-layouts/filters/labels.tsx +++ b/web/components/issue-layouts/filters/labels.tsx @@ -1,10 +1,7 @@ import React, { useState } from "react"; -import { useRouter } from "next/router"; - // components -import { FilterHeader } from "../helpers/filter-header"; -import { FilterOption } from "../helpers/filter-option"; +import { FilterHeader, FilterOption } from "components/issue-layouts"; // mobx react lite import { observer } from "mobx-react-lite"; // mobx store @@ -14,19 +11,32 @@ const LabelIcons = ({ color }: { color: string }) => ( ); -type Props = { onClick: (stateId: string) => void }; +type Props = { + workspaceSlug: string; + projectId: string; +}; export const FilterLabels: React.FC = observer((props) => { - const { onClick } = props; + const { workspaceSlug, projectId } = props; const [previewEnabled, setPreviewEnabled] = useState(true); - const router = useRouter(); - const { projectId } = router.query; - const store = useMobxStore(); const { issueFilter: issueFilterStore, project: projectStore } = store; + const handleUpdateLabels = (value: string) => { + const newValues = issueFilterStore.userFilters?.labels ?? []; + + if (issueFilterStore.userFilters?.labels?.includes(value)) newValues.splice(newValues.indexOf(value), 1); + else newValues.push(value); + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + filters: { + labels: newValues, + }, + }); + }; + return (
= observer((props) => { onClick(label?.id)} + onClick={() => handleUpdateLabels(label?.id)} icon={} title={label.name} /> diff --git a/web/components/issue-layouts/filters/priority.tsx b/web/components/issue-layouts/filters/priority.tsx index 1c2a92f6084..1ad11b22535 100644 --- a/web/components/issue-layouts/filters/priority.tsx +++ b/web/components/issue-layouts/filters/priority.tsx @@ -4,8 +4,7 @@ import React, { useState } from "react"; import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; // components -import { FilterHeader } from "../helpers/filter-header"; -import { FilterOption } from "../helpers/filter-option"; +import { FilterHeader, FilterOption } from "components/issue-layouts"; // icons import { AlertCircle, SignalHigh, SignalMedium, SignalLow, Ban } from "lucide-react"; // constants @@ -51,16 +50,29 @@ const PriorityIcons = ({ ); }; -type Props = { onClick: (stateId: string) => void }; +type Props = { workspaceSlug: string; projectId: string }; export const FilterPriority: React.FC = observer((props) => { - const { onClick } = props; + const { workspaceSlug, projectId } = props; const [previewEnabled, setPreviewEnabled] = useState(true); const store = useMobxStore(); const { issueFilter: issueFilterStore } = store; + const handleUpdatePriority = (value: string) => { + const newValues = issueFilterStore.userFilters?.priority ?? []; + + if (issueFilterStore.userFilters?.priority?.includes(value)) newValues.splice(newValues.indexOf(value), 1); + else newValues.push(value); + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + filters: { + priority: newValues, + }, + }); + }; + return (
= observer((props) => { onClick(priority.key)} + onClick={() => handleUpdatePriority(priority.key)} icon={} title={priority.title} /> diff --git a/web/components/issue-layouts/filters/start-date.tsx b/web/components/issue-layouts/filters/start-date.tsx index 8c92518af66..c2e03810068 100644 --- a/web/components/issue-layouts/filters/start-date.tsx +++ b/web/components/issue-layouts/filters/start-date.tsx @@ -1,45 +1,32 @@ import React from "react"; -// components -import { FilterHeader } from "../helpers/filter-header"; -import { FilterOption } from "../helpers/filter-option"; -// mobx react lite + +// mobx import { observer } from "mobx-react-lite"; -// mobx store import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; +// components +import { FilterHeader, FilterOption } from "components/issue-layouts"; export const FilterStartDate = observer(() => { - const store: RootStore = useMobxStore(); - const { issueFilters: issueFilterStore } = store; + const store = useMobxStore(); + const { issueFilter: issueFilterStore } = store; const [previewEnabled, setPreviewEnabled] = React.useState(true); - const handleFilter = (key: string, value: string) => { - const _value = [value]; - issueFilterStore.handleUserFilter("filters", key, _value); - }; - return (
setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
- {issueFilterStore?.issueRenderFilters?.start_date && - issueFilterStore?.issueRenderFilters?.start_date.length > 0 && - issueFilterStore?.issueRenderFilters?.start_date.map((_startDate) => ( +
+ {issueFilterStore?.userFilters?.start_date && + issueFilterStore?.userFilters?.start_date.length > 0 && + issueFilterStore?.userFilters?.start_date.map((_startDate) => ( handleFilter("start_date", _startDate?.key)} + isChecked={issueFilterStore?.userFilters?.start_date?.includes(_startDate?.key) ? true : false} title={_startDate.title} multiple={false} /> diff --git a/web/components/issue-layouts/filters/state-group.tsx b/web/components/issue-layouts/filters/state-group.tsx index 55c5239de40..7b16138964c 100644 --- a/web/components/issue-layouts/filters/state-group.tsx +++ b/web/components/issue-layouts/filters/state-group.tsx @@ -4,22 +4,38 @@ import React, { useState } from "react"; import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; // components -import { FilterHeader } from "../helpers/filter-header"; -import { FilterOption } from "../helpers/filter-option"; +import { FilterHeader, FilterOption } from "components/issue-layouts"; // icons import { StateGroupIcon } from "components/icons"; +// constants import { ISSUE_STATE_GROUPS } from "constants/issue"; -type Props = { onClick: (stateId: string) => void }; +type Props = { + workspaceSlug: string; + projectId: string; +}; export const FilterStateGroup: React.FC = observer((props) => { - const { onClick } = props; + const { workspaceSlug, projectId } = props; const [previewEnabled, setPreviewEnabled] = useState(true); const store = useMobxStore(); const { issueFilter: issueFilterStore } = store; + const handleUpdateStateGroup = (value: string) => { + const newValues = issueFilterStore.userFilters?.state_group ?? []; + + if (issueFilterStore.userFilters?.state_group?.includes(value)) newValues.splice(newValues.indexOf(value), 1); + else newValues.push(value); + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + filters: { + state_group: newValues, + }, + }); + }; + return (
= observer((props) => { onClick(stateGroup.key)} + onClick={() => handleUpdateStateGroup(stateGroup.key)} icon={} title={stateGroup.title} /> diff --git a/web/components/issue-layouts/filters/state.tsx b/web/components/issue-layouts/filters/state.tsx index eb9dfd92b01..410541806af 100644 --- a/web/components/issue-layouts/filters/state.tsx +++ b/web/components/issue-layouts/filters/state.tsx @@ -1,25 +1,22 @@ import React from "react"; -import { useRouter } from "next/router"; - // mobx import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; // components -import { FilterHeader } from "../helpers/filter-header"; -import { FilterOption } from "../helpers/filter-option"; +import { FilterHeader, FilterOption } from "components/issue-layouts"; // icons import { StateGroupIcon } from "components/icons"; // helpers import { getStatesList } from "helpers/state.helper"; -type Props = { onClick: (stateId: string) => void }; +type Props = { + workspaceSlug: string; + projectId: string; +}; export const FilterState: React.FC = observer((props) => { - const { onClick } = props; - - const router = useRouter(); - const { projectId } = router.query; + const { workspaceSlug, projectId } = props; const store = useMobxStore(); const { issueFilter: issueFilterStore, project: projectStore } = store; @@ -29,6 +26,19 @@ export const FilterState: React.FC = observer((props) => { const statesByGroups = projectStore.states?.[projectId?.toString() ?? ""]; const statesList = getStatesList(statesByGroups); + const handleUpdateState = (value: string) => { + const newValues = issueFilterStore.userFilters?.state ?? []; + + if (issueFilterStore.userFilters?.state?.includes(value)) newValues.splice(newValues.indexOf(value), 1); + else newValues.push(value); + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + filters: { + state: newValues, + }, + }); + }; + return (
= observer((props) => { onClick(state?.id)} + onClick={() => handleUpdateState(state?.id)} icon={} title={state?.name} /> diff --git a/web/components/issue-layouts/filters/target-date.tsx b/web/components/issue-layouts/filters/target-date.tsx index cec765b057c..44a54f7aac6 100644 --- a/web/components/issue-layouts/filters/target-date.tsx +++ b/web/components/issue-layouts/filters/target-date.tsx @@ -1,46 +1,32 @@ import React from "react"; -// components -import { FilterHeader } from "../helpers/filter-header"; -import { FilterOption } from "../helpers/filter-option"; -// mobx react lite +// mobx import { observer } from "mobx-react-lite"; -// mobx store import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; +// components +import { FilterHeader, FilterOption } from "components/issue-layouts"; export const FilterTargetDate = observer(() => { - const store: RootStore = useMobxStore(); - const { issueFilters: issueFilterStore } = store; + const store = useMobxStore(); + const { issueFilter: issueFilterStore } = store; const [previewEnabled, setPreviewEnabled] = React.useState(true); - const handleFilter = (key: string, value: string) => { - const _value = [value]; - issueFilterStore.handleUserFilter("filters", key, _value); - }; - return (
setPreviewEnabled(!previewEnabled)} /> {previewEnabled && (
- {issueFilterStore?.issueRenderFilters?.due_date && - issueFilterStore?.issueRenderFilters?.due_date.length > 0 && - issueFilterStore?.issueRenderFilters?.due_date.map((_targetDate) => ( + {issueFilterStore?.userFilters?.target_date && + issueFilterStore?.userFilters?.target_date.length > 0 && + issueFilterStore?.userFilters?.target_date.map((_targetDate) => ( handleFilter("target_date", _targetDate?.key)} + isChecked={issueFilterStore?.userFilters?.target_date?.includes(_targetDate?.key) ? true : false} title={_targetDate.title} multiple={false} /> diff --git a/web/components/issue-layouts/helpers/dropdown.tsx b/web/components/issue-layouts/helpers/dropdown.tsx index e5bf02e6667..1358b787c94 100644 --- a/web/components/issue-layouts/helpers/dropdown.tsx +++ b/web/components/issue-layouts/helpers/dropdown.tsx @@ -1,8 +1,9 @@ import { Fragment } from "react"; + // headless ui import { Popover, Transition } from "@headlessui/react"; -// lucide icons -import { ChevronDown, ChevronUp } from "lucide-react"; +// icons +import { ChevronUp } from "lucide-react"; interface IIssueDropdown { children: React.ReactNode; diff --git a/web/components/issue-layouts/helpers/index.ts b/web/components/issue-layouts/helpers/index.ts new file mode 100644 index 00000000000..ef38d9884d5 --- /dev/null +++ b/web/components/issue-layouts/helpers/index.ts @@ -0,0 +1,3 @@ +export * from "./dropdown"; +export * from "./filter-header"; +export * from "./filter-option"; diff --git a/web/components/issue-layouts/index.ts b/web/components/issue-layouts/index.ts new file mode 100644 index 00000000000..61375f0ffab --- /dev/null +++ b/web/components/issue-layouts/index.ts @@ -0,0 +1,4 @@ +export * from "./display-filters"; +export * from "./filters"; +export * from "./helpers"; +export * from "./layout-selection"; diff --git a/web/components/issue-layouts/root.tsx b/web/components/issue-layouts/root.tsx index 1db144bd982..1c5752af008 100644 --- a/web/components/issue-layouts/root.tsx +++ b/web/components/issue-layouts/root.tsx @@ -2,7 +2,7 @@ import React from "react"; // components import { LayoutSelection } from "./layout-selection"; import { IssueDropdown } from "./helpers/dropdown"; -import { FilterSelection } from "./filters"; +import { FilterSelection } from "./filters/filter-selection"; import { DisplayFiltersSelection } from "./display-filters"; import { FilterPreview } from "./filters-preview"; diff --git a/web/constants/issue.ts b/web/constants/issue.ts index a3a7b8c25df..d9cd715b0ee 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -1,15 +1,21 @@ +// icons import { Calendar, GanttChart, Kanban, List, Sheet } from "lucide-react"; +// types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, TIssueGroupByOptions, TIssueLayouts, TIssueOrderByOptions, + TIssuePriorities, TIssueTypeFilters, TStateGroups, } from "types"; -export const ISSUE_PRIORITIES = [ +export const ISSUE_PRIORITIES: { + key: TIssuePriorities; + title: string; +}[] = [ { key: "urgent", title: "Urgent" }, { key: "high", title: "High" }, { key: "medium", title: "Medium" }, @@ -55,6 +61,7 @@ export const ISSUE_GROUP_BY_OPTIONS: { { key: "labels", title: "Labels" }, { key: "assignees", title: "Assignees" }, { key: "created_by", title: "Created By" }, + { key: null, title: "None" }, ]; export const ISSUE_ORDER_BY_OPTIONS: { diff --git a/web/helpers/issue.helper.ts b/web/helpers/issue.helper.ts index 203e8fb2893..57a0e6eded4 100644 --- a/web/helpers/issue.helper.ts +++ b/web/helpers/issue.helper.ts @@ -1,7 +1,7 @@ import { orderArrayBy } from "helpers/array.helper"; import { renderDateFormat } from "helpers/date-time.helper"; // types -import { IIssue, TIssueGroupByOptions, TIssueOrderByOptions } from "types"; +import { IIssue, TIssueGroupByOptions, TIssueLayouts, TIssueOrderByOptions, TIssueParams } from "types"; type THandleIssuesMutation = ( formData: Partial, @@ -79,24 +79,6 @@ export const handleIssuesMutation: THandleIssuesMutation = ( } }; -export type TIssueLayouts = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt_chart"; -export type TIssueParams = - | "priority" - | "state_group" - | "state" - | "assignees" - | "created_by" - | "labels" - | "start_date" - | "target_date" - | "group_by" - | "order_by" - | "type" - | "sub_issue" - | "show_empty_groups" - | "calendar_date_range" - | "start_target_date"; - export const handleIssueQueryParamsByLayout = (_layout: TIssueLayouts | undefined): TIssueParams[] | null => { if (_layout === "list") return [ @@ -197,3 +179,84 @@ export const handleIssueParamsDateFormat = (key: string, start_date: any | null, if (key === "custom" && start_date && target_date) return `${renderDateFormat(start_date)};after,${renderDateFormat(target_date)};before`; }; + +// export const issueFilterVisibilityData: { +// [key: string]: { +// layout: TIssueLayouts[]; +// filters: { +// [key in TIssueLayouts]: string[]; +// }; +// }; +// } = { +// my_issues: { +// layout: ["list", "kanban"], +// filters: { +// list: ["priority", "state_group", "labels", "start_date", "due_date"], +// kanban: ["priority", "state_group", "labels", "start_date", "due_date"], +// }, +// display_properties: { +// list: true, +// kanban: true, +// }, +// display_filters: { +// list: ["group_by", "order_by", "issue_type"], +// kanban: ["group_by", "order_by", "issue_type"], +// }, +// extra_options: { +// list: { +// access: true, +// values: ["show_empty_groups"], +// }, +// kanban: { +// access: true, +// values: ["show_empty_groups"], +// }, +// }, +// }, +// issues: { +// layout: ["list", "kanban", "calendar", "spreadsheet", "gantt_chart"], +// filters: { +// list: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], +// kanban: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], +// calendar: ["priority", "state", "assignees", "created_by", "labels"], +// spreadsheet: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], +// gantt_chart: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], +// }, +// display_properties: { +// list: true, +// kanban: true, +// calendar: true, +// spreadsheet: true, +// gantt_chart: false, +// }, +// display_filters: { +// list: ["group_by", "order_by", "issue_type", "sub_issue", "show_empty_groups"], +// kanban: ["group_by", "order_by", "issue_type", "sub_issue", "show_empty_groups"], +// calendar: ["issue_type"], +// spreadsheet: ["issue_type"], +// gantt_chart: ["order_by", "issue_type", "sub_issue"], +// }, +// extra_options: { +// list: { +// access: true, +// values: ["show_empty_groups", "sub_issue"], +// }, +// kanban: { +// access: true, +// values: ["show_empty_groups", "sub_issue"], +// }, +// calendar: { +// access: false, +// values: [], +// }, +// spreadsheet: { +// access: false, +// values: [], +// }, +// gantt_chart: { +// access: true, +// values: ["sub_issue"], +// }, +// }, +// }, +// }; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx index 180e3f95f60..49b0da10613 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx @@ -15,7 +15,7 @@ import { IssueViewContextProvider } from "contexts/issue-view.context"; // helper import { truncateText } from "helpers/string.helper"; // components -import { FiltersLayout, IssuesFilterView, IssuesView } from "components/core"; +import { IssuesFilterView, IssuesView } from "components/core"; import { AnalyticsProjectModal } from "components/analytics"; import { ProjectIssuesHeader } from "components/headers"; // ui @@ -48,24 +48,24 @@ const ProjectIssues: NextPage = () => { ); useSWR( - "REVALIDATE_ALL", + workspaceSlug && projectId ? "REVALIDATE_ALL" : null, workspaceSlug && projectId ? () => issueFilter.fetchUserFilters(workspaceSlug.toString(), projectId.toString()) : null ); useSWR( - "PROJECT_STATES", + workspaceSlug && projectId ? "PROJECT_STATES" : null, workspaceSlug && projectId ? () => project.fetchProjectStates(workspaceSlug.toString(), projectId.toString()) : null ); useSWR( - "PROJECT_LABELS", + workspaceSlug && projectId ? "PROJECT_LABELS" : null, workspaceSlug && projectId ? () => project.fetchProjectLabels(workspaceSlug.toString(), projectId.toString()) : null ); useSWR( - "PROJECT_MEMBERS", + workspaceSlug && projectId ? "PROJECT_MEMBERS" : null, workspaceSlug && projectId ? () => project.fetchProjectMembers(workspaceSlug.toString(), projectId.toString()) : null diff --git a/web/store/issue_filters.ts b/web/store/issue_filters.ts index 0706816c6b6..988dbd2e712 100644 --- a/web/store/issue_filters.ts +++ b/web/store/issue_filters.ts @@ -4,7 +4,14 @@ import { ProjectService } from "services/project.service"; import { IssueService } from "services/issue.service"; // types import { RootStore } from "./root"; -import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, IProjectViewProps } from "types"; +import { + IIssueDisplayFilterOptions, + IIssueDisplayProperties, + IIssueFilterOptions, + IProjectViewProps, + TIssueParams, +} from "types"; +import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; export interface IIssueFilterStore { loader: boolean; @@ -15,6 +22,7 @@ export interface IIssueFilterStore { defaultDisplayFilters: IIssueDisplayFilterOptions; defaultFilters: IIssueFilterOptions; + // action fetchUserFilters: (workspaceSlug: string, projectSlug: string) => Promise; updateUserFilters: ( workspaceSlug: string, @@ -26,6 +34,9 @@ export interface IIssueFilterStore { projectSlug: string, properties: Partial ) => Promise; + + // computed + appliedFilters: TIssueParams[] | null; } class IssueFilterStore implements IIssueFilterStore { @@ -79,6 +90,7 @@ class IssueFilterStore implements IIssueFilterStore { updateDisplayProperties: action, // computed + appliedFilters: computed, }); this.rootStore = _rootStore; @@ -87,6 +99,10 @@ class IssueFilterStore implements IIssueFilterStore { this.issueService = new IssueService(); } + get appliedFilters(): TIssueParams[] | null { + return handleIssueQueryParamsByLayout(this.userDisplayFilters.layout); + } + fetchUserFilters = async (workspaceSlug: string, projectId: string) => { try { const memberResponse = await this.projectService.projectMemberMe(workspaceSlug, projectId); diff --git a/web/types/view-props.d.ts b/web/types/view-props.d.ts index 72e2e93bb92..dbdb72955cb 100644 --- a/web/types/view-props.d.ts +++ b/web/types/view-props.d.ts @@ -30,6 +30,23 @@ export type TIssueOrderByOptions = export type TIssueTypeFilters = "active" | "backlog" | null; +export type TIssueParams = + | "priority" + | "state_group" + | "state" + | "assignees" + | "created_by" + | "labels" + | "start_date" + | "target_date" + | "group_by" + | "order_by" + | "type" + | "sub_issue" + | "show_empty_groups" + | "calendar_date_range" + | "start_target_date"; + export interface IIssueFilterOptions { assignees?: string[] | null; created_by?: string[] | null; From 5bc7452f6ec268bc163b2ce8db2bcf26ba7afd5e Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Mon, 25 Sep 2023 13:02:48 +0530 Subject: [PATCH 4/9] chore: layout wise authorization added --- web/components/headers/project-issues.tsx | 2 +- .../display-filters-selection.tsx | 75 ++++--- .../display-filters/extra-options.tsx | 34 +-- .../issue-layouts/filters/priority.tsx | 4 +- .../issue-layouts/helpers/filter-header.tsx | 2 +- web/constants/issue.ts | 2 +- web/helpers/issue.helper.ts | 193 ++++++++++-------- 7 files changed, 190 insertions(+), 122 deletions(-) diff --git a/web/components/headers/project-issues.tsx b/web/components/headers/project-issues.tsx index 11d46846b1a..086c58cd4d2 100644 --- a/web/components/headers/project-issues.tsx +++ b/web/components/headers/project-issues.tsx @@ -32,7 +32,7 @@ export const ProjectIssuesHeader = observer(() => { selectedLayout={issueFilterStore.userDisplayFilters.layout ?? "list"} /> - + diff --git a/web/components/issue-layouts/display-filters/display-filters-selection.tsx b/web/components/issue-layouts/display-filters/display-filters-selection.tsx index 05cd4ff9237..d323f2ddb48 100644 --- a/web/components/issue-layouts/display-filters/display-filters-selection.tsx +++ b/web/components/issue-layouts/display-filters/display-filters-selection.tsx @@ -1,5 +1,8 @@ import React from "react"; +// mobx +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; // components import { FilterDisplayProperties, @@ -8,35 +11,57 @@ import { FilterIssueType, FilterOrderBy, } from "components/issue-layouts"; +// helpers +import { issueFilterVisibilityData } from "helpers/issue.helper"; -export const DisplayFiltersSelection = () => ( -
-
Search container
-
- {/* display properties */} -
- -
+export const DisplayFiltersSelection = observer(() => { + const { issueFilter: issueFilterStore } = useMobxStore(); - {/* group by */} -
- -
+ const isDisplayFilterEnabled = (displayFilter: string) => + issueFilterVisibilityData.issues.display_filters[issueFilterStore.userDisplayFilters.layout ?? "list"].includes( + displayFilter + ); - {/* order by */} -
- -
+ return ( +
+
Search container
+
+ {/* display properties */} + {issueFilterVisibilityData.issues.display_properties[issueFilterStore.userDisplayFilters.layout ?? "list"] && ( +
+ +
+ )} - {/* issue type */} -
- -
+ {/* group by */} + {isDisplayFilterEnabled("group_by") && ( +
+ +
+ )} + + {/* order by */} + {isDisplayFilterEnabled("order_by") && ( +
+ +
+ )} + + {/* issue type */} + {isDisplayFilterEnabled("issue_type") && ( +
+ +
+ )} - {/* Options */} -
- + {/* Options */} + {issueFilterVisibilityData.issues.extra_options[issueFilterStore.userDisplayFilters.layout ?? "list"] + .access && ( +
+ +
+ )}
-
-); + ); +}); diff --git a/web/components/issue-layouts/display-filters/extra-options.tsx b/web/components/issue-layouts/display-filters/extra-options.tsx index fdc5a98248c..a321c6793f8 100644 --- a/web/components/issue-layouts/display-filters/extra-options.tsx +++ b/web/components/issue-layouts/display-filters/extra-options.tsx @@ -1,20 +1,26 @@ -import React from "react"; +import React, { useState } from "react"; // mobx import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; // components -import { FilterHeader } from "../helpers/filter-header"; -import { FilterOption } from "../helpers/filter-option"; +import { FilterHeader, FilterOption } from "components/issue-layouts"; +// helpers +import { issueFilterVisibilityData } from "helpers/issue.helper"; // constants -import { ISSUE_EXTRA_PROPERTIES } from "constants/issue"; +import { ISSUE_EXTRA_OPTIONS } from "constants/issue"; export const FilterExtraOptions = observer(() => { + const [previewEnabled, setPreviewEnabled] = useState(true); + const store = useMobxStore(); const { issueFilter: issueFilterStore } = store; - const [previewEnabled, setPreviewEnabled] = React.useState(true); + const isExtraOptionEnabled = (option: string) => + issueFilterVisibilityData.issues.extra_options[ + issueFilterStore.userDisplayFilters.layout ?? "list" + ].values.includes(option); return (
@@ -25,13 +31,17 @@ export const FilterExtraOptions = observer(() => { /> {previewEnabled && (
- {ISSUE_EXTRA_PROPERTIES.map((extraProperties) => ( - - ))} + {ISSUE_EXTRA_OPTIONS.map((option) => { + if (!isExtraOptionEnabled(option.key)) return null; + + return ( + + ); + })}
)}
diff --git a/web/components/issue-layouts/filters/priority.tsx b/web/components/issue-layouts/filters/priority.tsx index 1ad11b22535..8260154e9f3 100644 --- a/web/components/issue-layouts/filters/priority.tsx +++ b/web/components/issue-layouts/filters/priority.tsx @@ -74,7 +74,7 @@ export const FilterPriority: React.FC = observer((props) => { }; return ( -
+ <> = observer((props) => { ))}
)} -
+ ); }); diff --git a/web/components/issue-layouts/helpers/filter-header.tsx b/web/components/issue-layouts/helpers/filter-header.tsx index 9dcc0f80356..909e4026c8e 100644 --- a/web/components/issue-layouts/helpers/filter-header.tsx +++ b/web/components/issue-layouts/helpers/filter-header.tsx @@ -9,7 +9,7 @@ interface IFilterHeader { } export const FilterHeader = ({ title, isPreviewEnabled, handleIsPreviewEnabled }: IFilterHeader) => ( -
+
{title}
)} -
+ ); }); diff --git a/web/components/issue-layouts/display-filters/extra-options.tsx b/web/components/issue-layouts/display-filters/extra-options.tsx index a321c6793f8..0588d230939 100644 --- a/web/components/issue-layouts/display-filters/extra-options.tsx +++ b/web/components/issue-layouts/display-filters/extra-options.tsx @@ -23,14 +23,14 @@ export const FilterExtraOptions = observer(() => { ].values.includes(option); return ( -
+ <> setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
+
{ISSUE_EXTRA_OPTIONS.map((option) => { if (!isExtraOptionEnabled(option.key)) return null; @@ -44,6 +44,6 @@ export const FilterExtraOptions = observer(() => { })}
)} -
+ ); }); diff --git a/web/components/issue-layouts/display-filters/group-by.tsx b/web/components/issue-layouts/display-filters/group-by.tsx index 5b95c61b166..8aace595e29 100644 --- a/web/components/issue-layouts/display-filters/group-by.tsx +++ b/web/components/issue-layouts/display-filters/group-by.tsx @@ -33,14 +33,14 @@ export const FilterGroupBy = observer(() => { }; return ( -
+ <> setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
+
{ISSUE_GROUP_BY_OPTIONS.map((groupBy) => ( { ))}
)} -
+ ); }); diff --git a/web/components/issue-layouts/display-filters/issue-type.tsx b/web/components/issue-layouts/display-filters/issue-type.tsx index 6af37c8d5fb..f2142d335d9 100644 --- a/web/components/issue-layouts/display-filters/issue-type.tsx +++ b/web/components/issue-layouts/display-filters/issue-type.tsx @@ -33,14 +33,14 @@ export const FilterIssueType = observer(() => { }; return ( -
+ <> setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
+
{ISSUE_FILTER_OPTIONS.map((issueType) => ( { ))}
)} -
+ ); }); diff --git a/web/components/issue-layouts/display-filters/order-by.tsx b/web/components/issue-layouts/display-filters/order-by.tsx index 59993456c3a..a36778c5525 100644 --- a/web/components/issue-layouts/display-filters/order-by.tsx +++ b/web/components/issue-layouts/display-filters/order-by.tsx @@ -33,14 +33,14 @@ export const FilterOrderBy = observer(() => { }; return ( -
+ <> setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
+
{ISSUE_ORDER_BY_OPTIONS.map((orderBy) => ( { ))}
)} -
+ ); }); diff --git a/web/components/issue-layouts/filters/assignees.tsx b/web/components/issue-layouts/filters/assignees.tsx index b60686ea3d9..f79db0e33f9 100644 --- a/web/components/issue-layouts/filters/assignees.tsx +++ b/web/components/issue-layouts/filters/assignees.tsx @@ -6,15 +6,12 @@ import { useMobxStore } from "lib/mobx/store-provider"; // components import { FilterHeader, FilterOption } from "components/issue-layouts"; // ui -import { Avatar } from "components/ui"; +import { Avatar, Loader } from "components/ui"; -type Props = { - workspaceSlug: string; - projectId: string; -}; +type Props = { workspaceSlug: string; projectId: string; itemsToRender: number }; export const FilterAssignees: React.FC = observer((props) => { - const { workspaceSlug, projectId } = props; + const { workspaceSlug, projectId, itemsToRender } = props; const [previewEnabled, setPreviewEnabled] = useState(true); @@ -34,31 +31,51 @@ export const FilterAssignees: React.FC = observer((props) => { }); }; + const appliedFiltersCount = issueFilterStore.userFilters?.assignees?.length ?? 0; + + const filteredOptions = projectStore.members?.[projectId?.toString() ?? ""]?.filter((member) => + member.member.display_name.toLowerCase().includes(issueFilterStore.filtersSearchQuery.toLowerCase()) + ); + return ( -
+ <> 0 ? ` (${appliedFiltersCount})` : ""}`} isPreviewEnabled={previewEnabled} handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
- {projectStore.members?.[projectId?.toString() ?? ""]?.map((member) => ( - handleUpdateAssignees(member.member?.id)} - icon={} - title={member.member?.display_name} - /> - ))} +
+ {filteredOptions ? ( + filteredOptions.length > 0 ? ( + filteredOptions + .slice(0, itemsToRender) + .map((member) => ( + handleUpdateAssignees(member.member?.id)} + icon={} + title={member.member?.display_name} + /> + )) + ) : ( +

No matches found

+ ) + ) : ( + + + + + + )}
)} -
+ ); }); diff --git a/web/components/issue-layouts/filters/created-by.tsx b/web/components/issue-layouts/filters/created-by.tsx index 42fb8082eb2..c34b461b14d 100644 --- a/web/components/issue-layouts/filters/created-by.tsx +++ b/web/components/issue-layouts/filters/created-by.tsx @@ -6,15 +6,12 @@ import { useMobxStore } from "lib/mobx/store-provider"; // components import { FilterHeader, FilterOption } from "components/issue-layouts"; // ui -import { Avatar } from "components/ui"; +import { Avatar, Loader } from "components/ui"; -type Props = { - workspaceSlug: string; - projectId: string; -}; +type Props = { workspaceSlug: string; projectId: string; itemsToRender: number }; export const FilterCreatedBy: React.FC = observer((props) => { - const { workspaceSlug, projectId } = props; + const { workspaceSlug, projectId, itemsToRender } = props; const [previewEnabled, setPreviewEnabled] = useState(true); @@ -34,26 +31,46 @@ export const FilterCreatedBy: React.FC = observer((props) => { }); }; + const appliedFiltersCount = issueFilterStore.userFilters?.created_by?.length ?? 0; + + const filteredOptions = projectStore.members?.[projectId?.toString() ?? ""]?.filter((member) => + member.member.display_name.toLowerCase().includes(issueFilterStore.filtersSearchQuery.toLowerCase()) + ); + return ( -
+ <> 0 ? ` (${appliedFiltersCount})` : ""}`} isPreviewEnabled={previewEnabled} handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
- {projectStore.members?.[projectId?.toString() ?? ""]?.map((member) => ( - handleUpdateCreatedBy(member.member?.id)} - icon={} - title={member.member?.display_name} - /> - ))} +
+ {filteredOptions ? ( + filteredOptions.length > 0 ? ( + filteredOptions + .slice(0, itemsToRender) + .map((member) => ( + handleUpdateCreatedBy(member.member?.id)} + icon={} + title={member.member?.display_name} + /> + )) + ) : ( +

No matches found

+ ) + ) : ( + + + + + + )}
)} -
+ ); }); diff --git a/web/components/issue-layouts/filters/filter-selection.tsx b/web/components/issue-layouts/filters/filter-selection.tsx deleted file mode 100644 index 5e20d384aec..00000000000 --- a/web/components/issue-layouts/filters/filter-selection.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from "react"; - -// components -import { - FilterAssignees, - FilterCreatedBy, - FilterLabels, - FilterPriority, - FilterState, - FilterStateGroup, -} from "components/issue-layouts"; - -type Props = { - workspaceSlug: string; - projectId: string; -}; - -export const FilterSelection: React.FC = (props) => { - const { workspaceSlug, projectId } = props; - - return ( -
- {/*
Search container
*/} -
- {/* priority */} -
- -
- - {/* state group */} -
- -
- - {/* state */} -
- -
- - {/* assignees */} -
- -
- - {/* created_by */} -
- -
- - {/* labels */} -
- -
- - {/* start_date */} - {/* {handleFilterSectionVisibility("start_date") && ( -
- -
- )} */} - - {/* due_date */} - {/* {handleFilterSectionVisibility("due_date") && ( -
- -
- )} */} -
-
- ); -}; diff --git a/web/components/issue-layouts/filters/filters-selection.tsx b/web/components/issue-layouts/filters/filters-selection.tsx new file mode 100644 index 00000000000..69a6c6ca4fb --- /dev/null +++ b/web/components/issue-layouts/filters/filters-selection.tsx @@ -0,0 +1,230 @@ +import React, { useState } from "react"; + +// mobx +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; +// components +import { + FilterAssignees, + FilterCreatedBy, + FilterLabels, + FilterPriority, + FilterState, + FilterStateGroup, +} from "components/issue-layouts"; +// icons +import { Search, X } from "lucide-react"; +// helpers +import { getStatesList } from "helpers/state.helper"; +// types +import { IIssueFilterOptions } from "types"; +// constants +import { ISSUE_PRIORITIES, ISSUE_STATE_GROUPS } from "constants/issue"; + +type Props = { + workspaceSlug: string; + projectId: string; +}; + +export const FilterSelection: React.FC = observer((props) => { + const { workspaceSlug, projectId } = props; + + const { issueFilter: issueFilterStore, project: projectStore } = useMobxStore(); + + const statesList = getStatesList(projectStore.states?.[projectId?.toString() ?? ""]); + + const [filtersToRender, setFiltersToRender] = useState<{ + [key in keyof IIssueFilterOptions]: { + currentLength: number; + totalLength: number; + }; + }>({ + assignees: { + currentLength: 5, + totalLength: projectStore.members?.[projectId]?.length ?? 0, + }, + created_by: { + currentLength: 5, + totalLength: projectStore.members?.[projectId]?.length ?? 0, + }, + labels: { + currentLength: 5, + totalLength: projectStore.labels?.[projectId]?.length ?? 0, + }, + priority: { + currentLength: 5, + totalLength: ISSUE_PRIORITIES.length, + }, + state_group: { + currentLength: 5, + totalLength: ISSUE_STATE_GROUPS.length, + }, + state: { + currentLength: 5, + totalLength: statesList?.length ?? 0, + }, + }); + + const handleViewMore = (filterName: keyof IIssueFilterOptions) => { + const filterDetails = filtersToRender[filterName]; + + if (!filterDetails) return; + + if (filterDetails.currentLength <= filterDetails.totalLength) + setFiltersToRender((prev) => ({ + ...prev, + [filterName]: { + ...prev[filterName], + currentLength: filterDetails.currentLength + 5, + }, + })); + }; + + const isViewMoreVisible = (filterName: keyof IIssueFilterOptions): boolean => { + const filterDetails = filtersToRender[filterName]; + + if (!filterDetails) return false; + + return filterDetails.currentLength < filterDetails.totalLength; + }; + + return ( +
+
+
+ + issueFilterStore.updateFiltersSearchQuery(e.target.value)} + autoFocus + /> + {issueFilterStore.filtersSearchQuery !== "" && ( + + )} +
+
+
+ {/* priority */} +
+ + {isViewMoreVisible("priority") && ( + + )} +
+ + {/* state group */} +
+ + {isViewMoreVisible("state_group") && ( + + )} +
+ + {/* state */} +
+ + {isViewMoreVisible("state") && ( + + )} +
+ + {/* assignees */} +
+ + {isViewMoreVisible("assignees") && ( + + )} +
+ + {/* created_by */} +
+ + {isViewMoreVisible("created_by") && ( + + )} +
+ + {/* labels */} +
+ + {isViewMoreVisible("labels") && ( + + )} +
+ + {/* start_date */} + {/*
+ +
*/} + + {/* due_date */} + {/*
+ +
*/} +
+
+ ); +}); diff --git a/web/components/issue-layouts/filters/index.ts b/web/components/issue-layouts/filters/index.ts index e804899d2ed..40190a10e3e 100644 --- a/web/components/issue-layouts/filters/index.ts +++ b/web/components/issue-layouts/filters/index.ts @@ -1,6 +1,6 @@ export * from "./assignees"; export * from "./created-by"; -export * from "./filter-selection"; +export * from "./filters-selection"; export * from "./labels"; export * from "./priority"; export * from "./start-date"; diff --git a/web/components/issue-layouts/filters/labels.tsx b/web/components/issue-layouts/filters/labels.tsx index 52191b1bfb5..b71c82dc215 100644 --- a/web/components/issue-layouts/filters/labels.tsx +++ b/web/components/issue-layouts/filters/labels.tsx @@ -1,23 +1,21 @@ import React, { useState } from "react"; -// components -import { FilterHeader, FilterOption } from "components/issue-layouts"; -// mobx react lite +// mobx import { observer } from "mobx-react-lite"; -// mobx store import { useMobxStore } from "lib/mobx/store-provider"; +// components +import { FilterHeader, FilterOption } from "components/issue-layouts"; +// ui +import { Loader } from "components/ui"; const LabelIcons = ({ color }: { color: string }) => ( ); -type Props = { - workspaceSlug: string; - projectId: string; -}; +type Props = { workspaceSlug: string; projectId: string; itemsToRender: number }; export const FilterLabels: React.FC = observer((props) => { - const { workspaceSlug, projectId } = props; + const { workspaceSlug, projectId, itemsToRender } = props; const [previewEnabled, setPreviewEnabled] = useState(true); @@ -37,26 +35,46 @@ export const FilterLabels: React.FC = observer((props) => { }); }; + const appliedFiltersCount = issueFilterStore.userFilters?.labels?.length ?? 0; + + const filteredOptions = projectStore.labels?.[projectId?.toString() ?? ""]?.filter((label) => + label.name.toLowerCase().includes(issueFilterStore.filtersSearchQuery.toLowerCase()) + ); + return ( -
+ <> 0 ? ` (${appliedFiltersCount})` : ""}`} isPreviewEnabled={previewEnabled} handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
- {projectStore.labels?.[projectId?.toString() ?? ""]?.map((label) => ( - handleUpdateLabels(label?.id)} - icon={} - title={label.name} - /> - ))} +
+ {filteredOptions ? ( + filteredOptions.length > 0 ? ( + filteredOptions + .slice(0, itemsToRender) + .map((label) => ( + handleUpdateLabels(label?.id)} + icon={} + title={label.name} + /> + )) + ) : ( +

No matches found

+ ) + ) : ( + + + + + + )}
)} -
+ ); }); diff --git a/web/components/issue-layouts/filters/priority.tsx b/web/components/issue-layouts/filters/priority.tsx index 8260154e9f3..659f76f3277 100644 --- a/web/components/issue-layouts/filters/priority.tsx +++ b/web/components/issue-layouts/filters/priority.tsx @@ -50,10 +50,10 @@ const PriorityIcons = ({ ); }; -type Props = { workspaceSlug: string; projectId: string }; +type Props = { workspaceSlug: string; projectId: string; itemsToRender: number }; export const FilterPriority: React.FC = observer((props) => { - const { workspaceSlug, projectId } = props; + const { workspaceSlug, projectId, itemsToRender } = props; const [previewEnabled, setPreviewEnabled] = useState(true); @@ -73,24 +73,36 @@ export const FilterPriority: React.FC = observer((props) => { }); }; + const appliedFiltersCount = issueFilterStore.userFilters?.priority?.length ?? 0; + + const filteredOptions = ISSUE_PRIORITIES.filter((p) => + p.key.includes(issueFilterStore.filtersSearchQuery.toLowerCase()) + ); + return ( <> 0 ? ` (${appliedFiltersCount})` : ""}`} isPreviewEnabled={previewEnabled} handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
- {ISSUE_PRIORITIES.map((priority) => ( - handleUpdatePriority(priority.key)} - icon={} - title={priority.title} - /> - ))} +
+ {filteredOptions.length > 0 ? ( + filteredOptions + .slice(0, itemsToRender) + .map((priority) => ( + handleUpdatePriority(priority.key)} + icon={} + title={priority.title} + /> + )) + ) : ( +

No matches found

+ )}
)} diff --git a/web/components/issue-layouts/filters/start-date.tsx b/web/components/issue-layouts/filters/start-date.tsx index c2e03810068..c93091a112d 100644 --- a/web/components/issue-layouts/filters/start-date.tsx +++ b/web/components/issue-layouts/filters/start-date.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; // mobx import { observer } from "mobx-react-lite"; @@ -7,20 +7,22 @@ import { useMobxStore } from "lib/mobx/store-provider"; import { FilterHeader, FilterOption } from "components/issue-layouts"; export const FilterStartDate = observer(() => { + const [previewEnabled, setPreviewEnabled] = useState(true); + const store = useMobxStore(); const { issueFilter: issueFilterStore } = store; - const [previewEnabled, setPreviewEnabled] = React.useState(true); + const appliedFiltersCount = issueFilterStore.userFilters?.start_date?.length ?? 0; return ( -
+ <> 0 ? ` (${appliedFiltersCount})` : ""}`} isPreviewEnabled={previewEnabled} handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
+
{issueFilterStore?.userFilters?.start_date && issueFilterStore?.userFilters?.start_date.length > 0 && issueFilterStore?.userFilters?.start_date.map((_startDate) => ( @@ -33,6 +35,6 @@ export const FilterStartDate = observer(() => { ))}
)} -
+ ); }); diff --git a/web/components/issue-layouts/filters/state-group.tsx b/web/components/issue-layouts/filters/state-group.tsx index 7b16138964c..2601eb308d1 100644 --- a/web/components/issue-layouts/filters/state-group.tsx +++ b/web/components/issue-layouts/filters/state-group.tsx @@ -10,13 +10,10 @@ import { StateGroupIcon } from "components/icons"; // constants import { ISSUE_STATE_GROUPS } from "constants/issue"; -type Props = { - workspaceSlug: string; - projectId: string; -}; +type Props = { workspaceSlug: string; projectId: string; itemsToRender: number }; export const FilterStateGroup: React.FC = observer((props) => { - const { workspaceSlug, projectId } = props; + const { workspaceSlug, projectId, itemsToRender } = props; const [previewEnabled, setPreviewEnabled] = useState(true); @@ -36,26 +33,38 @@ export const FilterStateGroup: React.FC = observer((props) => { }); }; + const appliedFiltersCount = issueFilterStore.userFilters?.state_group?.length ?? 0; + + const filteredOptions = ISSUE_STATE_GROUPS.filter((s) => + s.key.includes(issueFilterStore.filtersSearchQuery.toLowerCase()) + ); + return ( -
+ <> 0 ? ` (${appliedFiltersCount})` : ""}`} isPreviewEnabled={previewEnabled} handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
- {ISSUE_STATE_GROUPS.map((stateGroup) => ( - handleUpdateStateGroup(stateGroup.key)} - icon={} - title={stateGroup.title} - /> - ))} +
+ {filteredOptions.length > 0 ? ( + filteredOptions + .slice(0, itemsToRender) + .map((stateGroup) => ( + handleUpdateStateGroup(stateGroup.key)} + icon={} + title={stateGroup.title} + /> + )) + ) : ( +

No matches found

+ )}
)} -
+ ); }); diff --git a/web/components/issue-layouts/filters/state.tsx b/web/components/issue-layouts/filters/state.tsx index 410541806af..a9bace64859 100644 --- a/web/components/issue-layouts/filters/state.tsx +++ b/web/components/issue-layouts/filters/state.tsx @@ -1,28 +1,27 @@ -import React from "react"; +import React, { useState } from "react"; // mobx import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; // components import { FilterHeader, FilterOption } from "components/issue-layouts"; +// ui +import { Loader } from "components/ui"; // icons import { StateGroupIcon } from "components/icons"; // helpers import { getStatesList } from "helpers/state.helper"; -type Props = { - workspaceSlug: string; - projectId: string; -}; +type Props = { workspaceSlug: string; projectId: string; itemsToRender: number }; export const FilterState: React.FC = observer((props) => { - const { workspaceSlug, projectId } = props; + const { workspaceSlug, projectId, itemsToRender } = props; + + const [previewEnabled, setPreviewEnabled] = useState(true); const store = useMobxStore(); const { issueFilter: issueFilterStore, project: projectStore } = store; - const [previewEnabled, setPreviewEnabled] = React.useState(true); - const statesByGroups = projectStore.states?.[projectId?.toString() ?? ""]; const statesList = getStatesList(statesByGroups); @@ -39,26 +38,46 @@ export const FilterState: React.FC = observer((props) => { }); }; + const appliedFiltersCount = issueFilterStore.userFilters?.state?.length ?? 0; + + const filteredOptions = statesList?.filter((s) => + s.name.toLowerCase().includes(issueFilterStore.filtersSearchQuery.toLowerCase()) + ); + return ( -
+ <> 0 ? ` (${appliedFiltersCount})` : ""}`} isPreviewEnabled={previewEnabled} handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
- {statesList?.map((state) => ( - handleUpdateState(state?.id)} - icon={} - title={state?.name} - /> - ))} +
+ {filteredOptions ? ( + filteredOptions.length > 0 ? ( + <> + {filteredOptions.slice(0, itemsToRender).map((state) => ( + handleUpdateState(state?.id)} + icon={} + title={state?.name} + /> + ))} + + ) : ( +

No matches found

+ ) + ) : ( + + + + + + )}
)} -
+ ); }); diff --git a/web/components/issue-layouts/filters/target-date.tsx b/web/components/issue-layouts/filters/target-date.tsx index 44a54f7aac6..7a00b12a4a0 100644 --- a/web/components/issue-layouts/filters/target-date.tsx +++ b/web/components/issue-layouts/filters/target-date.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; // mobx import { observer } from "mobx-react-lite"; @@ -7,15 +7,17 @@ import { useMobxStore } from "lib/mobx/store-provider"; import { FilterHeader, FilterOption } from "components/issue-layouts"; export const FilterTargetDate = observer(() => { + const [previewEnabled, setPreviewEnabled] = useState(true); + const store = useMobxStore(); const { issueFilter: issueFilterStore } = store; - const [previewEnabled, setPreviewEnabled] = React.useState(true); + const appliedFiltersCount = issueFilterStore.userFilters?.target_date?.length ?? 0; return ( -
+ <> 0 ? ` (${appliedFiltersCount})` : ""}`} isPreviewEnabled={previewEnabled} handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)} /> @@ -33,6 +35,6 @@ export const FilterTargetDate = observer(() => { ))}
)} -
+ ); }); diff --git a/web/components/issue-layouts/helpers/filter-header.tsx b/web/components/issue-layouts/helpers/filter-header.tsx index 909e4026c8e..6dd0c7a02ff 100644 --- a/web/components/issue-layouts/helpers/filter-header.tsx +++ b/web/components/issue-layouts/helpers/filter-header.tsx @@ -9,7 +9,7 @@ interface IFilterHeader { } export const FilterHeader = ({ title, isPreviewEnabled, handleIsPreviewEnabled }: IFilterHeader) => ( -
+
{title}
+ + + ))}
); diff --git a/web/components/issue-layouts/root.tsx b/web/components/issue-layouts/root.tsx index 1c5752af008..e53cde0bddf 100644 --- a/web/components/issue-layouts/root.tsx +++ b/web/components/issue-layouts/root.tsx @@ -2,7 +2,7 @@ import React from "react"; // components import { LayoutSelection } from "./layout-selection"; import { IssueDropdown } from "./helpers/dropdown"; -import { FilterSelection } from "./filters/filter-selection"; +import { FilterSelection } from "./filters/filters-selection"; import { DisplayFiltersSelection } from "./display-filters"; import { FilterPreview } from "./filters-preview"; diff --git a/web/constants/fetch-keys.ts b/web/constants/fetch-keys.ts index 0f0643c6642..fa85be713ab 100644 --- a/web/constants/fetch-keys.ts +++ b/web/constants/fetch-keys.ts @@ -2,17 +2,8 @@ import { objToQueryParams } from "helpers/string.helper"; import { IAnalyticsParams, IJiraMetadata, INotificationParams } from "types"; const paramsToKey = (params: any) => { - const { - state, - priority, - assignees, - created_by, - labels, - start_date, - target_date, - sub_issue, - start_target_date, - } = params; + const { state, priority, assignees, created_by, labels, start_date, target_date, sub_issue, start_target_date } = + params; let stateKey = state ? state.split(",") : []; let priorityKey = priority ? priority.split(",") : []; @@ -50,16 +41,7 @@ const inboxParamsToKey = (params: any) => { }; const myIssuesParamsToKey = (params: any) => { - const { - assignees, - created_by, - labels, - priority, - state_group, - subscriber, - start_date, - target_date, - } = params; + const { assignees, created_by, labels, priority, state_group, subscriber, start_date, target_date } = params; let assigneesKey = assignees ? assignees.split(",") : []; let createdByKey = created_by ? created_by.split(",") : []; @@ -88,15 +70,12 @@ export const CURRENT_USER = "CURRENT_USER"; export const USER_WORKSPACE_INVITATIONS = "USER_WORKSPACE_INVITATIONS"; export const USER_WORKSPACES = "USER_WORKSPACES"; -export const WORKSPACE_DETAILS = (workspaceSlug: string) => - `WORKSPACE_DETAILS_${workspaceSlug.toUpperCase()}`; +export const WORKSPACE_DETAILS = (workspaceSlug: string) => `WORKSPACE_DETAILS_${workspaceSlug.toUpperCase()}`; -export const WORKSPACE_MEMBERS = (workspaceSlug: string) => - `WORKSPACE_MEMBERS_${workspaceSlug.toUpperCase()}`; +export const WORKSPACE_MEMBERS = (workspaceSlug: string) => `WORKSPACE_MEMBERS_${workspaceSlug.toUpperCase()}`; export const WORKSPACE_MEMBERS_WITH_EMAIL = (workspaceSlug: string) => `WORKSPACE_MEMBERS_WITH_EMAIL_${workspaceSlug.toUpperCase()}`; -export const WORKSPACE_MEMBERS_ME = (workspaceSlug: string) => - `WORKSPACE_MEMBERS_ME${workspaceSlug.toUpperCase()}`; +export const WORKSPACE_MEMBERS_ME = (workspaceSlug: string) => `WORKSPACE_MEMBERS_ME${workspaceSlug.toUpperCase()}`; export const WORKSPACE_INVITATIONS = "WORKSPACE_INVITATIONS"; export const WORKSPACE_INVITATION_WITH_EMAIL = (workspaceSlug: string) => `WORKSPACE_INVITATION_WITH_EMAIL_${workspaceSlug.toUpperCase()}`; @@ -111,9 +90,7 @@ export const PROJECTS_LIST = ( ) => { if (!params) return `PROJECTS_LIST_${workspaceSlug.toUpperCase()}`; - return `PROJECTS_LIST_${workspaceSlug.toUpperCase()}_${params.is_favorite - .toString() - .toUpperCase()}`; + return `PROJECTS_LIST_${workspaceSlug.toUpperCase()}_${params.is_favorite.toString().toUpperCase()}`; }; export const PROJECT_DETAILS = (projectId: string) => `PROJECT_DETAILS_${projectId.toUpperCase()}`; @@ -149,35 +126,22 @@ export const PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS = (projectId: string, params? return `PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS${projectId.toUpperCase()}_${paramsKey}`; }; -export const PROJECT_ISSUES_DETAILS = (issueId: string) => - `PROJECT_ISSUES_DETAILS_${issueId.toUpperCase()}`; -export const PROJECT_ISSUES_PROPERTIES = (projectId: string) => - `PROJECT_ISSUES_PROPERTIES_${projectId.toUpperCase()}`; -export const PROJECT_ISSUES_COMMENTS = (issueId: string) => - `PROJECT_ISSUES_COMMENTS_${issueId.toUpperCase()}`; -export const PROJECT_ISSUES_ACTIVITY = (issueId: string) => - `PROJECT_ISSUES_ACTIVITY_${issueId.toUpperCase()}`; -export const PROJECT_ISSUE_BY_STATE = (projectId: string) => - `PROJECT_ISSUE_BY_STATE_${projectId.toUpperCase()}`; -export const PROJECT_ISSUE_LABELS = (projectId: string) => - `PROJECT_ISSUE_LABELS_${projectId.toUpperCase()}`; -export const WORKSPACE_LABELS = (workspaceSlug: string) => - `WORKSPACE_LABELS_${workspaceSlug.toUpperCase()}`; -export const PROJECT_GITHUB_REPOSITORY = (projectId: string) => - `PROJECT_GITHUB_REPOSITORY_${projectId.toUpperCase()}`; +export const PROJECT_ISSUES_DETAILS = (issueId: string) => `PROJECT_ISSUES_DETAILS_${issueId.toUpperCase()}`; +export const PROJECT_ISSUES_PROPERTIES = (projectId: string) => `PROJECT_ISSUES_PROPERTIES_${projectId.toUpperCase()}`; +export const PROJECT_ISSUES_COMMENTS = (issueId: string) => `PROJECT_ISSUES_COMMENTS_${issueId.toUpperCase()}`; +export const PROJECT_ISSUES_ACTIVITY = (issueId: string) => `PROJECT_ISSUES_ACTIVITY_${issueId.toUpperCase()}`; +export const PROJECT_ISSUE_BY_STATE = (projectId: string) => `PROJECT_ISSUE_BY_STATE_${projectId.toUpperCase()}`; +export const PROJECT_ISSUE_LABELS = (projectId: string) => `PROJECT_ISSUE_LABELS_${projectId.toUpperCase()}`; +export const WORKSPACE_LABELS = (workspaceSlug: string) => `WORKSPACE_LABELS_${workspaceSlug.toUpperCase()}`; +export const PROJECT_GITHUB_REPOSITORY = (projectId: string) => `PROJECT_GITHUB_REPOSITORY_${projectId.toUpperCase()}`; // cycles export const CYCLES_LIST = (projectId: string) => `CYCLE_LIST_${projectId.toUpperCase()}`; -export const INCOMPLETE_CYCLES_LIST = (projectId: string) => - `INCOMPLETE_CYCLES_LIST_${projectId.toUpperCase()}`; -export const CURRENT_CYCLE_LIST = (projectId: string) => - `CURRENT_CYCLE_LIST_${projectId.toUpperCase()}`; -export const UPCOMING_CYCLES_LIST = (projectId: string) => - `UPCOMING_CYCLES_LIST_${projectId.toUpperCase()}`; -export const DRAFT_CYCLES_LIST = (projectId: string) => - `DRAFT_CYCLES_LIST_${projectId.toUpperCase()}`; -export const COMPLETED_CYCLES_LIST = (projectId: string) => - `COMPLETED_CYCLES_LIST_${projectId.toUpperCase()}`; +export const INCOMPLETE_CYCLES_LIST = (projectId: string) => `INCOMPLETE_CYCLES_LIST_${projectId.toUpperCase()}`; +export const CURRENT_CYCLE_LIST = (projectId: string) => `CURRENT_CYCLE_LIST_${projectId.toUpperCase()}`; +export const UPCOMING_CYCLES_LIST = (projectId: string) => `UPCOMING_CYCLES_LIST_${projectId.toUpperCase()}`; +export const DRAFT_CYCLES_LIST = (projectId: string) => `DRAFT_CYCLES_LIST_${projectId.toUpperCase()}`; +export const COMPLETED_CYCLES_LIST = (projectId: string) => `COMPLETED_CYCLES_LIST_${projectId.toUpperCase()}`; export const CYCLE_ISSUES = (cycleId: string) => `CYCLE_ISSUES_${cycleId.toUpperCase()}`; export const CYCLE_ISSUES_WITH_PARAMS = (cycleId: string, params?: any) => { if (!params) return `CYCLE_ISSUES_WITH_PARAMS_${cycleId.toUpperCase()}`; @@ -199,8 +163,7 @@ export const USER_ISSUES = (workspaceSlug: string, params: any) => { export const USER_ACTIVITY = "USER_ACTIVITY"; export const USER_WORKSPACE_DASHBOARD = (workspaceSlug: string) => `USER_WORKSPACE_DASHBOARD_${workspaceSlug.toUpperCase()}`; -export const USER_PROJECT_VIEW = (projectId: string) => - `USER_PROJECT_VIEW_${projectId.toUpperCase()}`; +export const USER_PROJECT_VIEW = (projectId: string) => `USER_PROJECT_VIEW_${projectId.toUpperCase()}`; export const MODULE_LIST = (projectId: string) => `MODULE_LIST_${projectId.toUpperCase()}`; export const MODULE_ISSUES = (moduleId: string) => `MODULE_ISSUES_${moduleId.toUpperCase()}`; @@ -240,8 +203,7 @@ export const INBOX_ISSUE_DETAILS = (inboxId: string, issueId: string) => export const ISSUE_DETAILS = (issueId: string) => `ISSUE_DETAILS_${issueId.toUpperCase()}`; export const SUB_ISSUES = (issueId: string) => `SUB_ISSUES_${issueId.toUpperCase()}`; export const ISSUE_ATTACHMENTS = (issueId: string) => `ISSUE_ATTACHMENTS_${issueId.toUpperCase()}`; -export const ARCHIVED_ISSUE_DETAILS = (issueId: string) => - `ARCHIVED_ISSUE_DETAILS_${issueId.toUpperCase()}`; +export const ARCHIVED_ISSUE_DETAILS = (issueId: string) => `ARCHIVED_ISSUE_DETAILS_${issueId.toUpperCase()}`; // integrations export const APP_INTEGRATIONS = "APP_INTEGRATIONS"; @@ -271,22 +233,18 @@ export const SLACK_CHANNEL_INFO = (workspaceSlug: string, projectId: string) => `SLACK_CHANNEL_INFO_${workspaceSlug.toString().toUpperCase()}_${projectId.toUpperCase()}`; // Pages -export const RECENT_PAGES_LIST = (projectId: string) => - `RECENT_PAGES_LIST_${projectId.toUpperCase()}`; +export const RECENT_PAGES_LIST = (projectId: string) => `RECENT_PAGES_LIST_${projectId.toUpperCase()}`; export const ALL_PAGES_LIST = (projectId: string) => `ALL_PAGES_LIST_${projectId.toUpperCase()}`; -export const FAVORITE_PAGES_LIST = (projectId: string) => - `FAVORITE_PAGES_LIST_${projectId.toUpperCase()}`; +export const FAVORITE_PAGES_LIST = (projectId: string) => `FAVORITE_PAGES_LIST_${projectId.toUpperCase()}`; export const MY_PAGES_LIST = (projectId: string) => `MY_PAGES_LIST_${projectId.toUpperCase()}`; -export const OTHER_PAGES_LIST = (projectId: string) => - `OTHER_PAGES_LIST_${projectId.toUpperCase()}`; +export const OTHER_PAGES_LIST = (projectId: string) => `OTHER_PAGES_LIST_${projectId.toUpperCase()}`; export const PAGE_DETAILS = (pageId: string) => `PAGE_DETAILS_${pageId.toUpperCase()}`; export const PAGE_BLOCKS_LIST = (pageId: string) => `PAGE_BLOCK_LIST_${pageId.toUpperCase()}`; export const PAGE_BLOCK_DETAILS = (pageId: string) => `PAGE_BLOCK_DETAILS_${pageId.toUpperCase()}`; // estimates export const ESTIMATES_LIST = (projectId: string) => `ESTIMATES_LIST_${projectId.toUpperCase()}`; -export const ESTIMATE_DETAILS = (estimateId: string) => - `ESTIMATE_DETAILS_${estimateId.toUpperCase()}`; +export const ESTIMATE_DETAILS = (estimateId: string) => `ESTIMATE_DETAILS_${estimateId.toUpperCase()}`; // analytics export const ANALYTICS = (workspaceSlug: string, params: IAnalyticsParams) => @@ -294,15 +252,10 @@ export const ANALYTICS = (workspaceSlug: string, params: IAnalyticsParams) => params.segment }_${params.project?.toString()}`; export const DEFAULT_ANALYTICS = (workspaceSlug: string, params?: Partial) => - `DEFAULT_ANALYTICS_${workspaceSlug.toUpperCase()}_${params?.project?.toString()}_${ - params?.cycle - }_${params?.module}`; + `DEFAULT_ANALYTICS_${workspaceSlug.toUpperCase()}_${params?.project?.toString()}_${params?.cycle}_${params?.module}`; // notifications -export const USER_WORKSPACE_NOTIFICATIONS = ( - workspaceSlug: string, - params: INotificationParams -) => { +export const USER_WORKSPACE_NOTIFICATIONS = (workspaceSlug: string, params: INotificationParams) => { const { type, snoozed, archived, read } = params; return `USER_WORKSPACE_NOTIFICATIONS_${workspaceSlug?.toUpperCase()}_TYPE_${( @@ -310,21 +263,13 @@ export const USER_WORKSPACE_NOTIFICATIONS = ( )?.toUpperCase()}_SNOOZED_${snoozed}_ARCHIVED_${archived}_READ_${read}`; }; -export const USER_WORKSPACE_NOTIFICATIONS_DETAILS = ( - workspaceSlug: string, - notificationId: string -) => +export const USER_WORKSPACE_NOTIFICATIONS_DETAILS = (workspaceSlug: string, notificationId: string) => `USER_WORKSPACE_NOTIFICATIONS_DETAILS_${workspaceSlug?.toUpperCase()}_${notificationId?.toUpperCase()}`; export const UNREAD_NOTIFICATIONS_COUNT = (workspaceSlug: string) => `UNREAD_NOTIFICATIONS_COUNT_${workspaceSlug?.toUpperCase()}`; -export const getPaginatedNotificationKey = ( - index: number, - prevData: any, - workspaceSlug: string, - params: any -) => { +export const getPaginatedNotificationKey = (index: number, prevData: any, workspaceSlug: string, params: any) => { if (prevData && !prevData?.results?.length) return null; if (index === 0) @@ -360,9 +305,5 @@ export const USER_PROFILE_ISSUES = (workspaceSlug: string, userId: string, param // reactions export const ISSUE_REACTION_LIST = (workspaceSlug: string, projectId: string, issueId: string) => `ISSUE_REACTION_LIST_${workspaceSlug.toUpperCase()}_${projectId.toUpperCase()}_${issueId.toUpperCase()}`; -export const COMMENT_REACTION_LIST = ( - workspaceSlug: string, - projectId: string, - commendId: string -) => +export const COMMENT_REACTION_LIST = (workspaceSlug: string, projectId: string, commendId: string) => `COMMENT_REACTION_LIST_${workspaceSlug.toUpperCase()}_${projectId.toUpperCase()}_${commendId.toUpperCase()}`; diff --git a/web/constants/issue.ts b/web/constants/issue.ts index 3e8206f2153..0e3b3028a11 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -117,11 +117,11 @@ export const ISSUE_LAYOUTS: { title: string; icon: any; }[] = [ - { key: "list", title: "List View", icon: List }, - { key: "kanban", title: "Kanban View", icon: Kanban }, - { key: "calendar", title: "Calendar View", icon: Calendar }, - { key: "spreadsheet", title: "Spreadsheet View", icon: Sheet }, - { key: "gantt_chart", title: "Gantt Chart View", icon: GanttChart }, + { key: "list", title: "List Layout", icon: List }, + { key: "kanban", title: "Kanban Layout", icon: Kanban }, + { key: "calendar", title: "Calendar Layout", icon: Calendar }, + { key: "spreadsheet", title: "Spreadsheet Layout", icon: Sheet }, + { key: "gantt_chart", title: "Gantt Chart Layout", icon: GanttChart }, ]; export const ISSUE_LIST_FILTERS = [ diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx index 86b80f983da..0461559f964 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx @@ -27,14 +27,7 @@ import { PlusIcon } from "@heroicons/react/24/outline"; // types import type { NextPage } from "next"; // fetch-keys -import { - PROJECT_DETAILS, - INBOX_LIST, - STATES_LIST, - PROJECT_ISSUE_LABELS, - PROJECT_MEMBERS, - USER_PROJECT_VIEW, -} from "constants/fetch-keys"; +import { PROJECT_DETAILS, INBOX_LIST } from "constants/fetch-keys"; const ProjectIssues: NextPage = () => { const [analyticsModal, setAnalyticsModal] = useState(false); @@ -54,29 +47,30 @@ const ProjectIssues: NextPage = () => { workspaceSlug && projectId ? () => inboxService.getInboxes(workspaceSlug as string, projectId as string) : null ); + // TODO: update the fetch keys useSWR( - workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId.toString()) : null, + workspaceSlug && projectId ? "REVALIDATE_USER_PROJECT_FILTERS" : null, workspaceSlug && projectId ? () => issueFilterStore.fetchUserProjectFilters(workspaceSlug.toString(), projectId.toString()) : null ); useSWR( - workspaceSlug && projectId ? STATES_LIST(projectId.toString()) : null, + workspaceSlug && projectId ? "REVALIDATE_PROJECT_STATES_LIST" : null, workspaceSlug && projectId ? () => projectStore.fetchProjectStates(workspaceSlug.toString(), projectId.toString()) : null ); useSWR( - workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId.toString()) : null, + workspaceSlug && projectId ? "REVALIDATE_PROJECT_LABELS_LIST" : null, workspaceSlug && projectId ? () => projectStore.fetchProjectLabels(workspaceSlug.toString(), projectId.toString()) : null ); useSWR( - workspaceSlug && projectId ? PROJECT_MEMBERS(projectId.toString()) : null, + workspaceSlug && projectId ? "REVALIDATE_PROJECT_MEMBERS_LIST" : null, workspaceSlug && projectId ? () => projectStore.fetchProjectMembers(workspaceSlug.toString(), projectId.toString()) : null diff --git a/web/store/issue_filters.ts b/web/store/issue_filters.ts index f52018d8cf5..7d95f88136e 100644 --- a/web/store/issue_filters.ts +++ b/web/store/issue_filters.ts @@ -2,6 +2,8 @@ import { observable, action, computed, makeObservable, runInAction } from "mobx" // services import { ProjectService } from "services/project.service"; import { IssueService } from "services/issue.service"; +// helpers +import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; // types import { RootStore } from "./root"; import { @@ -11,7 +13,6 @@ import { IProjectViewProps, TIssueParams, } from "types"; -import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; export interface IIssueFilterStore { loader: boolean; @@ -21,6 +22,7 @@ export interface IIssueFilterStore { userFilters: IIssueFilterOptions; defaultDisplayFilters: IIssueDisplayFilterOptions; defaultFilters: IIssueFilterOptions; + filtersSearchQuery: string; // action fetchUserProjectFilters: (workspaceSlug: string, projectSlug: string) => Promise; @@ -34,6 +36,7 @@ export interface IIssueFilterStore { projectSlug: string, properties: Partial ) => Promise; + updateFiltersSearchQuery: (query: string) => void; // computed appliedFilters: TIssueParams[] | null; @@ -64,6 +67,7 @@ class IssueFilterStore implements IIssueFilterStore { created_on: true, updated_on: true, }; + filtersSearchQuery: string = ""; // root store rootStore; @@ -83,11 +87,13 @@ class IssueFilterStore implements IIssueFilterStore { userDisplayProperties: observable.ref, userDisplayFilters: observable.ref, userFilters: observable.ref, + filtersSearchQuery: observable.ref, // actions fetchUserProjectFilters: action, updateUserFilters: action, updateDisplayProperties: action, + updateFiltersSearchQuery: action, // computed appliedFilters: computed, @@ -183,6 +189,12 @@ class IssueFilterStore implements IIssueFilterStore { console.log("Failed to update user filters in issue filter store", error); } }; + + updateFiltersSearchQuery: (query: string) => void = (query) => { + runInAction(() => { + this.filtersSearchQuery = query; + }); + }; } export default IssueFilterStore; From 2f4d39d88adb6b1340623c64e860397dc4ed2a56 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Mon, 25 Sep 2023 17:25:51 +0530 Subject: [PATCH 8/9] fix: sticky headers --- web/components/issue-layouts/filters/filters-selection.tsx | 4 ++-- web/components/issue-layouts/helpers/dropdown.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/components/issue-layouts/filters/filters-selection.tsx b/web/components/issue-layouts/filters/filters-selection.tsx index 69a6c6ca4fb..c0416e356e3 100644 --- a/web/components/issue-layouts/filters/filters-selection.tsx +++ b/web/components/issue-layouts/filters/filters-selection.tsx @@ -89,7 +89,7 @@ export const FilterSelection: React.FC = observer((props) => { }; return ( -
+
@@ -112,7 +112,7 @@ export const FilterSelection: React.FC = observer((props) => { )}
-
+
{/* priority */}
- - {children} + +
{children}
From ccdd12e2b026526b81c1bfb9016efaf2a6ed24d3 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Mon, 25 Sep 2023 17:41:59 +0530 Subject: [PATCH 9/9] chore: sub_group_by section added --- .../display-filters-selection.tsx | 23 +++- .../display-filters/extra-options.tsx | 6 +- .../display-filters/group-by.tsx | 5 +- .../issue-layouts/display-filters/index.ts | 1 + .../display-filters/issue-type.tsx | 3 +- .../display-filters/order-by.tsx | 5 +- .../display-filters/sub-group-by.tsx | 64 +++++++++ web/constants/issue.ts | 130 +++++++++++++++--- web/helpers/issue.helper.ts | 116 +--------------- web/types/view-props.d.ts | 2 + 10 files changed, 203 insertions(+), 152 deletions(-) create mode 100644 web/components/issue-layouts/display-filters/sub-group-by.tsx diff --git a/web/components/issue-layouts/display-filters/display-filters-selection.tsx b/web/components/issue-layouts/display-filters/display-filters-selection.tsx index de3a2d716cb..01394548db3 100644 --- a/web/components/issue-layouts/display-filters/display-filters-selection.tsx +++ b/web/components/issue-layouts/display-filters/display-filters-selection.tsx @@ -10,22 +10,25 @@ import { FilterGroupBy, FilterIssueType, FilterOrderBy, + FilterSubGroupBy, } from "components/issue-layouts"; // helpers -import { issueFilterVisibilityData } from "helpers/issue.helper"; +import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; export const DisplayFiltersSelection = observer(() => { const { issueFilter: issueFilterStore } = useMobxStore(); const isDisplayFilterEnabled = (displayFilter: string) => - issueFilterVisibilityData.issues.display_filters[issueFilterStore.userDisplayFilters.layout ?? "list"].includes( - displayFilter - ); + ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues.display_filters[ + issueFilterStore.userDisplayFilters.layout ?? "list" + ].includes(displayFilter); return (
{/* display properties */} - {issueFilterVisibilityData.issues.display_properties[issueFilterStore.userDisplayFilters.layout ?? "list"] && ( + {ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues.display_properties[ + issueFilterStore.userDisplayFilters.layout ?? "list" + ] && (
@@ -38,6 +41,13 @@ export const DisplayFiltersSelection = observer(() => {
)} + {/* sub-group by */} + {isDisplayFilterEnabled("sub_group_by") && ( +
+ +
+ )} + {/* order by */} {isDisplayFilterEnabled("order_by") && (
@@ -53,7 +63,8 @@ export const DisplayFiltersSelection = observer(() => { )} {/* Options */} - {issueFilterVisibilityData.issues.extra_options[issueFilterStore.userDisplayFilters.layout ?? "list"].access && ( + {ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues.extra_options[issueFilterStore.userDisplayFilters.layout ?? "list"] + .access && (
diff --git a/web/components/issue-layouts/display-filters/extra-options.tsx b/web/components/issue-layouts/display-filters/extra-options.tsx index 0588d230939..8b235443562 100644 --- a/web/components/issue-layouts/display-filters/extra-options.tsx +++ b/web/components/issue-layouts/display-filters/extra-options.tsx @@ -6,10 +6,8 @@ import { useMobxStore } from "lib/mobx/store-provider"; // components import { FilterHeader, FilterOption } from "components/issue-layouts"; -// helpers -import { issueFilterVisibilityData } from "helpers/issue.helper"; // constants -import { ISSUE_EXTRA_OPTIONS } from "constants/issue"; +import { ISSUE_EXTRA_OPTIONS, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; export const FilterExtraOptions = observer(() => { const [previewEnabled, setPreviewEnabled] = useState(true); @@ -18,7 +16,7 @@ export const FilterExtraOptions = observer(() => { const { issueFilter: issueFilterStore } = store; const isExtraOptionEnabled = (option: string) => - issueFilterVisibilityData.issues.extra_options[ + ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues.extra_options[ issueFilterStore.userDisplayFilters.layout ?? "list" ].values.includes(option); diff --git a/web/components/issue-layouts/display-filters/group-by.tsx b/web/components/issue-layouts/display-filters/group-by.tsx index 8aace595e29..5ad584b584a 100644 --- a/web/components/issue-layouts/display-filters/group-by.tsx +++ b/web/components/issue-layouts/display-filters/group-by.tsx @@ -6,8 +6,7 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; // components -import { FilterHeader } from "../helpers/filter-header"; -import { FilterOption } from "../helpers/filter-option"; +import { FilterHeader, FilterOption } from "components/issue-layouts"; // types import { TIssueGroupByOptions } from "types"; // constants @@ -35,7 +34,7 @@ export const FilterGroupBy = observer(() => { return ( <> setPreviewEnabled(!previewEnabled)} /> diff --git a/web/components/issue-layouts/display-filters/index.ts b/web/components/issue-layouts/display-filters/index.ts index 27ed9943b2c..741d0a22a8e 100644 --- a/web/components/issue-layouts/display-filters/index.ts +++ b/web/components/issue-layouts/display-filters/index.ts @@ -4,3 +4,4 @@ export * from "./extra-options"; export * from "./group-by"; export * from "./issue-type"; export * from "./order-by"; +export * from "./sub-group-by"; diff --git a/web/components/issue-layouts/display-filters/issue-type.tsx b/web/components/issue-layouts/display-filters/issue-type.tsx index f2142d335d9..c2fe34857c4 100644 --- a/web/components/issue-layouts/display-filters/issue-type.tsx +++ b/web/components/issue-layouts/display-filters/issue-type.tsx @@ -6,8 +6,7 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; // components -import { FilterHeader } from "../helpers/filter-header"; -import { FilterOption } from "../helpers/filter-option"; +import { FilterHeader, FilterOption } from "components/issue-layouts"; // types import { TIssueTypeFilters } from "types"; // constants diff --git a/web/components/issue-layouts/display-filters/order-by.tsx b/web/components/issue-layouts/display-filters/order-by.tsx index a36778c5525..b355dbd1bac 100644 --- a/web/components/issue-layouts/display-filters/order-by.tsx +++ b/web/components/issue-layouts/display-filters/order-by.tsx @@ -6,8 +6,7 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; // components -import { FilterHeader } from "../helpers/filter-header"; -import { FilterOption } from "../helpers/filter-option"; +import { FilterHeader, FilterOption } from "components/issue-layouts"; // types import { TIssueOrderByOptions } from "types"; // constants @@ -35,7 +34,7 @@ export const FilterOrderBy = observer(() => { return ( <> setPreviewEnabled(!previewEnabled)} /> diff --git a/web/components/issue-layouts/display-filters/sub-group-by.tsx b/web/components/issue-layouts/display-filters/sub-group-by.tsx new file mode 100644 index 00000000000..752804f043a --- /dev/null +++ b/web/components/issue-layouts/display-filters/sub-group-by.tsx @@ -0,0 +1,64 @@ +import React, { useState } from "react"; + +import { useRouter } from "next/router"; + +// mobx +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; +// components +import { FilterHeader, FilterOption } from "components/issue-layouts"; +// types +import { TIssueGroupByOptions } from "types"; +// constants +import { ISSUE_GROUP_BY_OPTIONS } from "constants/issue"; + +export const FilterSubGroupBy = observer(() => { + const [previewEnabled, setPreviewEnabled] = useState(true); + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const store = useMobxStore(); + const { issueFilter: issueFilterStore } = store; + + const handleSubGroupBy = (value: TIssueGroupByOptions) => { + if (!workspaceSlug || !projectId) return; + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + display_filters: { + sub_group_by: value, + }, + }); + }; + + return ( + <> + setPreviewEnabled(!previewEnabled)} + /> + {previewEnabled && ( +
+ {ISSUE_GROUP_BY_OPTIONS.map((subGroupBy) => { + if ( + issueFilterStore.userDisplayFilters.group_by !== null && + issueFilterStore.userDisplayFilters.sub_group_by === issueFilterStore.userDisplayFilters.group_by + ) + return null; + + return ( + handleSubGroupBy(subGroupBy.key)} + title={subGroupBy.title} + multiple={false} + /> + ); + })} +
+ )} + + ); +}); diff --git a/web/constants/issue.ts b/web/constants/issue.ts index 0e3b3028a11..f7209ad496e 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -198,26 +198,116 @@ export const ISSUE_GANTT_DISPLAY_FILTERS = [ { key: "sub_issue", title: "Sub Issue" }, ]; -// TODO: update this later -export const ISSUE_EXTRA_DISPLAY_PROPERTIES = { - list: { - access: true, - values: ["show_empty_groups", "sub_issue"], +export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { + [key: string]: { + layout: TIssueLayouts[]; + filters: { + [key in TIssueLayouts]: string[]; + }; + display_properties: { + [key in TIssueLayouts]: boolean; + }; + display_filters: { + [key in TIssueLayouts]: string[]; + }; + extra_options: { + [key in TIssueLayouts]: { + access: boolean; + values: string[]; + }; + }; + }; +} = { + my_issues: { + layout: ["list", "kanban"], + filters: { + list: ["priority", "state_group", "labels", "start_date", "due_date"], + kanban: ["priority", "state_group", "labels", "start_date", "due_date"], + calendar: [], + spreadsheet: [], + gantt_chart: [], + }, + display_properties: { + list: true, + kanban: true, + calendar: true, + spreadsheet: true, + gantt_chart: false, + }, + display_filters: { + list: ["group_by", "sub_group_by", "order_by", "issue_type"], + kanban: ["group_by", "sub_group_by", "order_by", "issue_type"], + calendar: ["issue_type"], + spreadsheet: ["issue_type"], + gantt_chart: ["order_by", "issue_type"], + }, + extra_options: { + list: { + access: true, + values: ["show_empty_groups", "sub_issue"], + }, + kanban: { + access: true, + values: ["show_empty_groups", "sub_issue"], + }, + calendar: { + access: false, + values: [], + }, + spreadsheet: { + access: false, + values: [], + }, + gantt_chart: { + access: false, + values: [], + }, + }, }, - kanban: { - access: true, - values: ["show_empty_groups", "sub_issue"], - }, - calendar: { - access: false, - values: [], - }, - spreadsheet: { - access: false, - values: [], - }, - gantt_chart: { - access: true, - values: ["sub_issue"], + issues: { + layout: ["list", "kanban", "calendar", "spreadsheet", "gantt_chart"], + filters: { + list: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], + kanban: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], + calendar: ["priority", "state", "assignees", "created_by", "labels"], + spreadsheet: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], + gantt_chart: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], + }, + display_properties: { + list: true, + kanban: true, + calendar: true, + spreadsheet: true, + gantt_chart: false, + }, + display_filters: { + list: ["group_by", "sub_group_by", "order_by", "issue_type"], + kanban: ["group_by", "sub_group_by", "order_by", "issue_type"], + calendar: ["issue_type"], + spreadsheet: ["issue_type"], + gantt_chart: ["order_by", "issue_type"], + }, + extra_options: { + list: { + access: true, + values: ["show_empty_groups", "sub_issue"], + }, + kanban: { + access: true, + values: ["show_empty_groups", "sub_issue"], + }, + calendar: { + access: false, + values: [], + }, + spreadsheet: { + access: false, + values: [], + }, + gantt_chart: { + access: true, + values: ["sub_issue"], + }, + }, }, }; diff --git a/web/helpers/issue.helper.ts b/web/helpers/issue.helper.ts index cf6998b8ec3..9bd952f8139 100644 --- a/web/helpers/issue.helper.ts +++ b/web/helpers/issue.helper.ts @@ -91,6 +91,7 @@ export const handleIssueQueryParamsByLayout = (_layout: TIssueLayouts | undefine "start_date", "target_date", "group_by", + "sub_group_by", "order_by", "type", "sub_issue", @@ -107,6 +108,7 @@ export const handleIssueQueryParamsByLayout = (_layout: TIssueLayouts | undefine "start_date", "target_date", "group_by", + "sub_group_by", "order_by", "type", "sub_issue", @@ -179,117 +181,3 @@ export const handleIssueParamsDateFormat = (key: string, start_date: any | null, if (key === "custom" && start_date && target_date) return `${renderDateFormat(start_date)};after,${renderDateFormat(target_date)};before`; }; - -export const issueFilterVisibilityData: { - [key: string]: { - layout: TIssueLayouts[]; - filters: { - [key in TIssueLayouts]: string[]; - }; - display_properties: { - [key in TIssueLayouts]: boolean; - }; - display_filters: { - [key in TIssueLayouts]: string[]; - }; - extra_options: { - [key in TIssueLayouts]: { - access: boolean; - values: string[]; - }; - }; - }; -} = { - my_issues: { - layout: ["list", "kanban"], - filters: { - list: ["priority", "state_group", "labels", "start_date", "due_date"], - kanban: ["priority", "state_group", "labels", "start_date", "due_date"], - calendar: [], - spreadsheet: [], - gantt_chart: [], - }, - display_properties: { - list: true, - kanban: true, - calendar: true, - spreadsheet: true, - gantt_chart: false, - }, - display_filters: { - list: ["group_by", "order_by", "issue_type"], - kanban: ["group_by", "order_by", "issue_type"], - calendar: ["issue_type"], - spreadsheet: ["issue_type"], - gantt_chart: ["order_by", "issue_type"], - }, - extra_options: { - list: { - access: true, - values: ["show_empty_groups", "sub_issue"], - }, - kanban: { - access: true, - values: ["show_empty_groups", "sub_issue"], - }, - calendar: { - access: false, - values: [], - }, - spreadsheet: { - access: false, - values: [], - }, - gantt_chart: { - access: false, - values: [], - }, - }, - }, - issues: { - layout: ["list", "kanban", "calendar", "spreadsheet", "gantt_chart"], - filters: { - list: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], - kanban: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], - calendar: ["priority", "state", "assignees", "created_by", "labels"], - spreadsheet: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], - gantt_chart: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], - }, - display_properties: { - list: true, - kanban: true, - calendar: true, - spreadsheet: true, - gantt_chart: false, - }, - display_filters: { - list: ["group_by", "order_by", "issue_type"], - kanban: ["group_by", "order_by", "issue_type"], - calendar: ["issue_type"], - spreadsheet: ["issue_type"], - gantt_chart: ["order_by", "issue_type"], - }, - extra_options: { - list: { - access: true, - values: ["show_empty_groups", "sub_issue"], - }, - kanban: { - access: true, - values: ["show_empty_groups", "sub_issue"], - }, - calendar: { - access: false, - values: [], - }, - spreadsheet: { - access: false, - values: [], - }, - gantt_chart: { - access: true, - values: ["sub_issue"], - }, - }, - }, -}; diff --git a/web/types/view-props.d.ts b/web/types/view-props.d.ts index dbdb72955cb..6630d527e6a 100644 --- a/web/types/view-props.d.ts +++ b/web/types/view-props.d.ts @@ -40,6 +40,7 @@ export type TIssueParams = | "start_date" | "target_date" | "group_by" + | "sub_group_by" | "order_by" | "type" | "sub_issue" @@ -62,6 +63,7 @@ export interface IIssueFilterOptions { export interface IIssueDisplayFilterOptions { calendar_date_range?: string; group_by?: TIssueGroupByOptions; + sub_group_by?: TIssueGroupByOptions; layout?: TIssueLayouts; order_by?: TIssueOrderByOptions; show_empty_groups?: boolean;