From e7f374b33919379f81e7759606f2f56d4db72898 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 22 Sep 2023 21:23:46 +0530 Subject: [PATCH 1/6] 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/6] 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/6] 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}