From 4ac437cb26c50f1621b675b2f4f229f1e52c5f42 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 28 Sep 2023 17:04:57 +0530 Subject: [PATCH 01/10] refactor: filters and display filters to accept handlers as props --- web/components/headers/project-issues.tsx | 52 ++++++++++-- .../display-filters-selection.tsx | 80 ++++++++++++++----- .../display-filters/extra-options.tsx | 31 ++++--- .../display-filters/group-by.tsx | 35 +++----- .../display-filters/issue-type.tsx | 31 +++---- .../display-filters/order-by.tsx | 35 +++----- .../display-filters/sub-group-by.tsx | 40 +++------- .../issue-layouts/filters/assignees.tsx | 42 ++++------ .../issue-layouts/filters/created-by.tsx | 33 +++----- .../filters/filters-selection.tsx | 58 ++++++++------ .../issue-layouts/filters/labels.tsx | 37 ++++----- .../issue-layouts/filters/priority.tsx | 39 +++------ .../issue-layouts/filters/state-group.tsx | 39 +++------ .../issue-layouts/filters/state.tsx | 43 ++++------ web/constants/issue.ts | 41 +++++----- web/store/issue_filters.ts | 15 +--- web/types/view-props.d.ts | 2 + 17 files changed, 310 insertions(+), 343 deletions(-) diff --git a/web/components/headers/project-issues.tsx b/web/components/headers/project-issues.tsx index 086c58cd4d2..183b2999c6b 100644 --- a/web/components/headers/project-issues.tsx +++ b/web/components/headers/project-issues.tsx @@ -1,14 +1,16 @@ import { useRouter } from "next/router"; - -// mobx import { observer } from "mobx-react-lite"; + +// mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components import { DisplayFiltersSelection, FilterSelection, IssueDropdown, LayoutSelection } from "components/issue-layouts"; // types -import { TIssueLayouts } from "types"; +import { IIssueDisplayFilterOptions, IIssueFilterOptions, TIssueLayouts } from "types"; +// constants +import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; -export const ProjectIssuesHeader = observer(() => { +export const ProjectIssuesHeader: React.FC = observer(() => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -24,18 +26,54 @@ export const ProjectIssuesHeader = observer(() => { }); }; + const handleFiltersUpdate = (key: keyof IIssueFilterOptions, value: string) => { + if (!workspaceSlug || !projectId) return; + + const newValues = issueFilterStore.userFilters?.[key] ?? []; + + if (issueFilterStore.userFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); + else newValues.push(value); + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + filters: { + [key]: newValues, + }, + }); + }; + + const handleDisplayFiltersUpdate = (updatedDisplayFilter: Partial) => { + if (!workspaceSlug || !projectId) return; + + console.log("Triggered"); + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + display_filters: { + ...updatedDisplayFilter, + }, + }); + }; + return (
handleLayoutChange(layout)} 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 01394548db3..26fbf9d7f74 100644 --- a/web/components/issue-layouts/display-filters/display-filters-selection.tsx +++ b/web/components/issue-layouts/display-filters/display-filters-selection.tsx @@ -1,8 +1,6 @@ import React from "react"; - -// mobx import { observer } from "mobx-react-lite"; -import { useMobxStore } from "lib/mobx/store-provider"; + // components import { FilterDisplayProperties, @@ -12,23 +10,26 @@ import { FilterOrderBy, FilterSubGroupBy, } from "components/issue-layouts"; -// helpers -import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; +// types +import { IIssueDisplayFilterOptions } from "types"; +import { ILayoutDisplayFiltersOptions } from "constants/issue"; + +type Props = { + displayFilters: IIssueDisplayFilterOptions; + handleDisplayFiltersUpdate: (updatedDisplayFilter: Partial) => void; + layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions; +}; -export const DisplayFiltersSelection = observer(() => { - const { issueFilter: issueFilterStore } = useMobxStore(); +export const DisplayFiltersSelection: React.FC = observer((props) => { + const { displayFilters, handleDisplayFiltersUpdate, layoutDisplayFiltersOptions } = props; const isDisplayFilterEnabled = (displayFilter: string) => - ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues.display_filters[ - issueFilterStore.userDisplayFilters.layout ?? "list" - ].includes(displayFilter); + layoutDisplayFiltersOptions.display_filters[displayFilters.layout ?? "list"].includes(displayFilter); return (
{/* display properties */} - {ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues.display_properties[ - issueFilterStore.userDisplayFilters.layout ?? "list" - ] && ( + {layoutDisplayFiltersOptions.display_properties[displayFilters.layout ?? "list"] && (
@@ -37,36 +38,75 @@ export const DisplayFiltersSelection = observer(() => { {/* group by */} {isDisplayFilterEnabled("group_by") && (
- + + handleDisplayFiltersUpdate({ + group_by: val, + }) + } + />
)} {/* sub-group by */} {isDisplayFilterEnabled("sub_group_by") && (
- + + handleDisplayFiltersUpdate({ + sub_group_by: val, + }) + } + />
)} {/* order by */} {isDisplayFilterEnabled("order_by") && (
- + + handleDisplayFiltersUpdate({ + order_by: val, + }) + } + />
)} {/* issue type */} {isDisplayFilterEnabled("issue_type") && (
- + + handleDisplayFiltersUpdate({ + type: val, + }) + } + />
)} {/* Options */} - {ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues.extra_options[issueFilterStore.userDisplayFilters.layout ?? "list"] - .access && ( + {layoutDisplayFiltersOptions.extra_options[displayFilters.layout ?? "list"].access && (
- + + handleDisplayFiltersUpdate({ + [key]: val, + }) + } + enabledExtraOptions={layoutDisplayFiltersOptions.extra_options[displayFilters.layout ?? "list"].values} + />
)}
diff --git a/web/components/issue-layouts/display-filters/extra-options.tsx b/web/components/issue-layouts/display-filters/extra-options.tsx index 8b235443562..ba9fe2f8303 100644 --- a/web/components/issue-layouts/display-filters/extra-options.tsx +++ b/web/components/issue-layouts/display-filters/extra-options.tsx @@ -1,24 +1,28 @@ 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"; +// types +import { IIssueDisplayFilterOptions, TIssueExtraOptions } from "types"; // constants -import { ISSUE_EXTRA_OPTIONS, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; +import { ISSUE_EXTRA_OPTIONS } from "constants/issue"; -export const FilterExtraOptions = observer(() => { - const [previewEnabled, setPreviewEnabled] = useState(true); +type Props = { + selectedExtraOptions: { + sub_issue: boolean; + show_empty_groups: boolean; + }; + handleUpdate: (key: keyof IIssueDisplayFilterOptions, val: boolean) => void; + enabledExtraOptions: TIssueExtraOptions[]; +}; - const store = useMobxStore(); - const { issueFilter: issueFilterStore } = store; +export const FilterExtraOptions: React.FC = observer((props) => { + const { selectedExtraOptions, handleUpdate, enabledExtraOptions } = props; + + const [previewEnabled, setPreviewEnabled] = useState(true); - const isExtraOptionEnabled = (option: string) => - ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues.extra_options[ - issueFilterStore.userDisplayFilters.layout ?? "list" - ].values.includes(option); + const isExtraOptionEnabled = (option: TIssueExtraOptions) => enabledExtraOptions.includes(option); return ( <> @@ -35,7 +39,8 @@ export const FilterExtraOptions = observer(() => { return ( handleUpdate(option.key, !selectedExtraOptions?.[option.key])} title={option.title} /> ); diff --git a/web/components/issue-layouts/display-filters/group-by.tsx b/web/components/issue-layouts/display-filters/group-by.tsx index 5ad584b584a..e810ea2433c 100644 --- a/web/components/issue-layouts/display-filters/group-by.tsx +++ b/web/components/issue-layouts/display-filters/group-by.tsx @@ -1,10 +1,6 @@ -import React from "react"; - -import { useRouter } from "next/router"; - -// mobx +import React, { useState } from "react"; import { observer } from "mobx-react-lite"; -import { useMobxStore } from "lib/mobx/store-provider"; + // components import { FilterHeader, FilterOption } from "components/issue-layouts"; // types @@ -12,24 +8,15 @@ import { TIssueGroupByOptions } from "types"; // constants import { ISSUE_GROUP_BY_OPTIONS } from "constants/issue"; -export const FilterGroupBy = observer(() => { - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - - const store = useMobxStore(); - const { issueFilter: issueFilterStore } = store; - - const [previewEnabled, setPreviewEnabled] = React.useState(true); +type Props = { + selectedGroupBy: TIssueGroupByOptions | undefined; + handleUpdate: (val: TIssueGroupByOptions) => void; +}; - const handleGroupBy = (value: TIssueGroupByOptions) => { - if (!workspaceSlug || !projectId) return; +export const FilterGroupBy: React.FC = observer((props) => { + const { selectedGroupBy, handleUpdate } = props; - issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { - display_filters: { - group_by: value, - }, - }); - }; + const [previewEnabled, setPreviewEnabled] = useState(true); return ( <> @@ -43,8 +30,8 @@ export const FilterGroupBy = observer(() => { {ISSUE_GROUP_BY_OPTIONS.map((groupBy) => ( handleGroupBy(groupBy.key)} + isChecked={selectedGroupBy === groupBy?.key ? true : false} + onClick={() => handleUpdate(groupBy.key)} title={groupBy.title} multiple={false} /> diff --git a/web/components/issue-layouts/display-filters/issue-type.tsx b/web/components/issue-layouts/display-filters/issue-type.tsx index c2fe34857c4..8d0532a0927 100644 --- a/web/components/issue-layouts/display-filters/issue-type.tsx +++ b/web/components/issue-layouts/display-filters/issue-type.tsx @@ -1,10 +1,6 @@ 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, FilterOption } from "components/issue-layouts"; // types @@ -12,25 +8,16 @@ import { TIssueTypeFilters } from "types"; // constants import { ISSUE_FILTER_OPTIONS } from "constants/issue"; -export const FilterIssueType = observer(() => { - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; +type Props = { + selectedIssueType: TIssueTypeFilters | undefined; + handleUpdate: (val: TIssueTypeFilters) => void; +}; - const store = useMobxStore(); - const { issueFilter: issueFilterStore } = store; +export const FilterIssueType: React.FC = observer((props) => { + const { selectedIssueType, handleUpdate } = props; const [previewEnabled, setPreviewEnabled] = React.useState(true); - const handleIssueType = (value: TIssueTypeFilters) => { - if (!workspaceSlug || !projectId) return; - - issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { - display_filters: { - type: value, - }, - }); - }; - return ( <> { {ISSUE_FILTER_OPTIONS.map((issueType) => ( handleIssueType(issueType?.key)} + isChecked={selectedIssueType === issueType?.key ? true : false} + onClick={() => handleUpdate(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 b355dbd1bac..9f3471a8f2f 100644 --- a/web/components/issue-layouts/display-filters/order-by.tsx +++ b/web/components/issue-layouts/display-filters/order-by.tsx @@ -1,10 +1,6 @@ -import React from "react"; - -import { useRouter } from "next/router"; - -// mobx +import React, { useState } from "react"; import { observer } from "mobx-react-lite"; -import { useMobxStore } from "lib/mobx/store-provider"; + // components import { FilterHeader, FilterOption } from "components/issue-layouts"; // types @@ -12,24 +8,15 @@ import { TIssueOrderByOptions } from "types"; // constants import { ISSUE_ORDER_BY_OPTIONS } from "constants/issue"; -export const FilterOrderBy = observer(() => { - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - - const store = useMobxStore(); - const { issueFilter: issueFilterStore } = store; - - const [previewEnabled, setPreviewEnabled] = React.useState(true); +type Props = { + selectedOrderBy: TIssueOrderByOptions | undefined; + handleUpdate: (val: TIssueOrderByOptions) => void; +}; - const handleOrderBy = (value: TIssueOrderByOptions) => { - if (!workspaceSlug || !projectId) return; +export const FilterOrderBy: React.FC = observer((props) => { + const { selectedOrderBy, handleUpdate } = props; - issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { - display_filters: { - order_by: value, - }, - }); - }; + const [previewEnabled, setPreviewEnabled] = useState(true); return ( <> @@ -43,8 +30,8 @@ export const FilterOrderBy = observer(() => { {ISSUE_ORDER_BY_OPTIONS.map((orderBy) => ( handleOrderBy(orderBy.key)} + isChecked={selectedOrderBy === orderBy?.key ? true : false} + onClick={() => handleUpdate(orderBy.key)} title={orderBy.title} multiple={false} /> diff --git a/web/components/issue-layouts/display-filters/sub-group-by.tsx b/web/components/issue-layouts/display-filters/sub-group-by.tsx index ad83067ec16..199b178bef2 100644 --- a/web/components/issue-layouts/display-filters/sub-group-by.tsx +++ b/web/components/issue-layouts/display-filters/sub-group-by.tsx @@ -1,10 +1,6 @@ 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 @@ -12,24 +8,16 @@ import { TIssueGroupByOptions } from "types"; // constants import { ISSUE_GROUP_BY_OPTIONS } from "constants/issue"; -export const FilterSubGroupBy = observer(() => { - const [previewEnabled, setPreviewEnabled] = useState(true); +type Props = { + selectedGroupBy: TIssueGroupByOptions | undefined; + selectedSubGroupBy: TIssueGroupByOptions | undefined; + handleUpdate: (val: TIssueGroupByOptions) => void; +}; - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; +export const FilterSubGroupBy: React.FC = observer((props) => { + const { selectedGroupBy, selectedSubGroupBy, handleUpdate } = props; - 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, - }, - }); - }; + const [previewEnabled, setPreviewEnabled] = useState(true); return ( <> @@ -41,17 +29,13 @@ export const FilterSubGroupBy = observer(() => { {previewEnabled && (
{ISSUE_GROUP_BY_OPTIONS.map((subGroupBy) => { - if ( - issueFilterStore.userDisplayFilters.group_by !== null && - subGroupBy.key === issueFilterStore.userDisplayFilters.group_by - ) - return null; + if (selectedGroupBy !== null && subGroupBy.key === selectedGroupBy) return null; return ( handleSubGroupBy(subGroupBy.key)} + isChecked={selectedSubGroupBy === subGroupBy?.key ? true : false} + onClick={() => handleUpdate(subGroupBy.key)} title={subGroupBy.title} multiple={false} /> diff --git a/web/components/issue-layouts/filters/assignees.tsx b/web/components/issue-layouts/filters/assignees.tsx index f79db0e33f9..869861e93fb 100644 --- a/web/components/issue-layouts/filters/assignees.tsx +++ b/web/components/issue-layouts/filters/assignees.tsx @@ -1,40 +1,33 @@ import React, { useState } from "react"; - -// 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 { Avatar, Loader } from "components/ui"; -type Props = { workspaceSlug: string; projectId: string; itemsToRender: number }; +type Props = { + appliedFilters: string[] | null; + handleUpdate: (val: string) => void; + itemsToRender: number; + projectId: string; + searchQuery: string; +}; export const FilterAssignees: React.FC = observer((props) => { - const { workspaceSlug, projectId, itemsToRender } = props; + const { appliedFilters, handleUpdate, itemsToRender, projectId, searchQuery } = props; const [previewEnabled, setPreviewEnabled] = useState(true); 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, - }, - }); - }; + const { project: projectStore } = store; - const appliedFiltersCount = issueFilterStore.userFilters?.assignees?.length ?? 0; + const appliedFiltersCount = appliedFilters?.length ?? 0; const filteredOptions = projectStore.members?.[projectId?.toString() ?? ""]?.filter((member) => - member.member.display_name.toLowerCase().includes(issueFilterStore.filtersSearchQuery.toLowerCase()) + member.member.display_name.toLowerCase().includes(searchQuery.toLowerCase()) ); return ( @@ -53,13 +46,8 @@ export const FilterAssignees: React.FC = observer((props) => { .map((member) => ( handleUpdateAssignees(member.member?.id)} + isChecked={appliedFilters?.includes(member.member?.id) ? true : false} + onClick={() => handleUpdate(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 c34b461b14d..70d7ac7b1c1 100644 --- a/web/components/issue-layouts/filters/created-by.tsx +++ b/web/components/issue-layouts/filters/created-by.tsx @@ -8,33 +8,26 @@ import { FilterHeader, FilterOption } from "components/issue-layouts"; // ui import { Avatar, Loader } from "components/ui"; -type Props = { workspaceSlug: string; projectId: string; itemsToRender: number }; +type Props = { + appliedFilters: string[] | null; + handleUpdate: (val: string) => void; + itemsToRender: number; + projectId: string; + searchQuery: string; +}; export const FilterCreatedBy: React.FC = observer((props) => { - const { workspaceSlug, projectId, itemsToRender } = props; + const { appliedFilters, handleUpdate, itemsToRender, projectId, searchQuery } = props; const [previewEnabled, setPreviewEnabled] = useState(true); const store = useMobxStore(); - const { issueFilter: issueFilterStore, project: projectStore } = store; + const { 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, - }, - }); - }; - - const appliedFiltersCount = issueFilterStore.userFilters?.created_by?.length ?? 0; + const appliedFiltersCount = appliedFilters?.length ?? 0; const filteredOptions = projectStore.members?.[projectId?.toString() ?? ""]?.filter((member) => - member.member.display_name.toLowerCase().includes(issueFilterStore.filtersSearchQuery.toLowerCase()) + member.member.display_name.toLowerCase().includes(searchQuery.toLowerCase()) ); return ( @@ -53,8 +46,8 @@ export const FilterCreatedBy: React.FC = observer((props) => { .map((member) => ( handleUpdateCreatedBy(member.member?.id)} + isChecked={appliedFilters?.includes(member.member?.id) ? true : false} + onClick={() => handleUpdate(member.member?.id)} icon={} title={member.member?.display_name} /> diff --git a/web/components/issue-layouts/filters/filters-selection.tsx b/web/components/issue-layouts/filters/filters-selection.tsx index 71aa144bf7b..01974e8d3b4 100644 --- a/web/components/issue-layouts/filters/filters-selection.tsx +++ b/web/components/issue-layouts/filters/filters-selection.tsx @@ -19,17 +19,21 @@ import { getStatesList } from "helpers/state.helper"; // types import { IIssueFilterOptions } from "types"; // constants -import { ISSUE_PRIORITIES, ISSUE_STATE_GROUPS } from "constants/issue"; +import { ILayoutDisplayFiltersOptions, ISSUE_PRIORITIES, ISSUE_STATE_GROUPS } from "constants/issue"; type Props = { - workspaceSlug: string; + filters: IIssueFilterOptions; + handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string) => void; + layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions; projectId: string; }; export const FilterSelection: React.FC = observer((props) => { - const { workspaceSlug, projectId } = props; + const { filters, handleFiltersUpdate, layoutDisplayFiltersOptions, projectId } = props; - const { issueFilter: issueFilterStore, project: projectStore } = useMobxStore(); + const [filtersSearchQuery, setFiltersSearchQuery] = useState(""); + + const { project: projectStore } = useMobxStore(); const statesList = getStatesList(projectStore.states?.[projectId?.toString() ?? ""]); @@ -97,16 +101,12 @@ export const FilterSelection: React.FC = observer((props) => { type="text" className="bg-custom-background-90 placeholder:text-custom-text-400 w-full outline-none" placeholder="Search" - value={issueFilterStore.filtersSearchQuery} - onChange={(e) => issueFilterStore.updateFiltersSearchQuery(e.target.value)} + value={filtersSearchQuery} + onChange={(e) => setFiltersSearchQuery(e.target.value)} autoFocus /> - {issueFilterStore.filtersSearchQuery !== "" && ( - )} @@ -116,9 +116,10 @@ export const FilterSelection: React.FC = observer((props) => { {/* priority */}
handleFiltersUpdate("priority", val)} itemsToRender={filtersToRender.priority?.currentLength ?? 0} + searchQuery={filtersSearchQuery} /> {isViewMoreVisible("priority") && (
); }); diff --git a/web/components/issue-layouts/helpers/dropdown.tsx b/web/components/issue-layouts/helpers/dropdown.tsx deleted file mode 100644 index 33e813d99c5..00000000000 --- a/web/components/issue-layouts/helpers/dropdown.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Fragment } from "react"; - -// headless ui -import { Popover, Transition } from "@headlessui/react"; -// icons -import { ChevronUp } from "lucide-react"; - -interface IIssueDropdown { - children: React.ReactNode; - title?: string; -} - -export const IssueDropdown = ({ children, title = "Dropdown" }: IIssueDropdown) => ( - - {({ open }) => { - if (open) { - } - return ( - <> - -
{title}
-
- -
-
- - -
{children}
-
-
- - ); - }} -
-); diff --git a/web/components/issue-layouts/index.ts b/web/components/issue-layouts/index.ts index 61375f0ffab..e69de29bb2d 100644 --- a/web/components/issue-layouts/index.ts +++ b/web/components/issue-layouts/index.ts @@ -1,4 +0,0 @@ -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 250f3204a18..38029c63335 100644 --- a/web/components/issue-layouts/root.tsx +++ b/web/components/issue-layouts/root.tsx @@ -1,9 +1,9 @@ import React from "react"; // components -import { LayoutSelection } from "./layout-selection"; -import { IssueDropdown } from "./helpers/dropdown"; -import { FilterSelection } from "./filters/filters-selection"; -import { DisplayFiltersSelection } from "./display-filters"; +import { LayoutSelection } from "../issues/issue-layouts/header/layout-selection"; +import { IssueDropdown } from "../issues/issue-layouts/header/helpers/dropdown"; +import { FilterSelection } from "../issues/issue-layouts/header/filters/filters-selection"; +import { DisplayFiltersSelection } from "../issues/issue-layouts/header/display-filters"; import { FilterPreview } from "./filters-preview"; diff --git a/web/components/issue-layouts/display-filters/display-filters-selection.tsx b/web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx similarity index 98% rename from web/components/issue-layouts/display-filters/display-filters-selection.tsx rename to web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx index 26fbf9d7f74..510df434492 100644 --- a/web/components/issue-layouts/display-filters/display-filters-selection.tsx +++ b/web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx @@ -9,7 +9,7 @@ import { FilterIssueType, FilterOrderBy, FilterSubGroupBy, -} from "components/issue-layouts"; +} from "components/issues"; // types import { IIssueDisplayFilterOptions } from "types"; import { ILayoutDisplayFiltersOptions } from "constants/issue"; diff --git a/web/components/issue-layouts/display-filters/display-properties.tsx b/web/components/issues/issue-layouts/header/display-filters/display-properties.tsx similarity index 100% rename from web/components/issue-layouts/display-filters/display-properties.tsx rename to web/components/issues/issue-layouts/header/display-filters/display-properties.tsx diff --git a/web/components/issue-layouts/display-filters/extra-options.tsx b/web/components/issues/issue-layouts/header/display-filters/extra-options.tsx similarity index 95% rename from web/components/issue-layouts/display-filters/extra-options.tsx rename to web/components/issues/issue-layouts/header/display-filters/extra-options.tsx index ba9fe2f8303..111d25772e7 100644 --- a/web/components/issue-layouts/display-filters/extra-options.tsx +++ b/web/components/issues/issue-layouts/header/display-filters/extra-options.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { observer } from "mobx-react-lite"; // components -import { FilterHeader, FilterOption } from "components/issue-layouts"; +import { FilterHeader, FilterOption } from "components/issues"; // types import { IIssueDisplayFilterOptions, TIssueExtraOptions } from "types"; // constants diff --git a/web/components/issue-layouts/display-filters/group-by.tsx b/web/components/issues/issue-layouts/header/display-filters/group-by.tsx similarity index 94% rename from web/components/issue-layouts/display-filters/group-by.tsx rename to web/components/issues/issue-layouts/header/display-filters/group-by.tsx index e810ea2433c..d0a65a94f4b 100644 --- a/web/components/issue-layouts/display-filters/group-by.tsx +++ b/web/components/issues/issue-layouts/header/display-filters/group-by.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { observer } from "mobx-react-lite"; // components -import { FilterHeader, FilterOption } from "components/issue-layouts"; +import { FilterHeader, FilterOption } from "components/issues"; // types import { TIssueGroupByOptions } from "types"; // constants diff --git a/web/components/issue-layouts/display-filters/index.ts b/web/components/issues/issue-layouts/header/display-filters/index.ts similarity index 100% rename from web/components/issue-layouts/display-filters/index.ts rename to web/components/issues/issue-layouts/header/display-filters/index.ts diff --git a/web/components/issue-layouts/display-filters/issue-type.tsx b/web/components/issues/issue-layouts/header/display-filters/issue-type.tsx similarity index 94% rename from web/components/issue-layouts/display-filters/issue-type.tsx rename to web/components/issues/issue-layouts/header/display-filters/issue-type.tsx index 8d0532a0927..8732bc739bd 100644 --- a/web/components/issue-layouts/display-filters/issue-type.tsx +++ b/web/components/issues/issue-layouts/header/display-filters/issue-type.tsx @@ -2,7 +2,7 @@ import React from "react"; import { observer } from "mobx-react-lite"; // components -import { FilterHeader, FilterOption } from "components/issue-layouts"; +import { FilterHeader, FilterOption } from "components/issues"; // types import { TIssueTypeFilters } from "types"; // constants diff --git a/web/components/issue-layouts/display-filters/order-by.tsx b/web/components/issues/issue-layouts/header/display-filters/order-by.tsx similarity index 94% rename from web/components/issue-layouts/display-filters/order-by.tsx rename to web/components/issues/issue-layouts/header/display-filters/order-by.tsx index 9f3471a8f2f..0387e9abb59 100644 --- a/web/components/issue-layouts/display-filters/order-by.tsx +++ b/web/components/issues/issue-layouts/header/display-filters/order-by.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { observer } from "mobx-react-lite"; // components -import { FilterHeader, FilterOption } from "components/issue-layouts"; +import { FilterHeader, FilterOption } from "components/issues"; // types import { TIssueOrderByOptions } from "types"; // constants diff --git a/web/components/issue-layouts/display-filters/sub-group-by.tsx b/web/components/issues/issue-layouts/header/display-filters/sub-group-by.tsx similarity index 95% rename from web/components/issue-layouts/display-filters/sub-group-by.tsx rename to web/components/issues/issue-layouts/header/display-filters/sub-group-by.tsx index 199b178bef2..3a4a06a11f1 100644 --- a/web/components/issue-layouts/display-filters/sub-group-by.tsx +++ b/web/components/issues/issue-layouts/header/display-filters/sub-group-by.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { observer } from "mobx-react-lite"; // components -import { FilterHeader, FilterOption } from "components/issue-layouts"; +import { FilterHeader, FilterOption } from "components/issues"; // types import { TIssueGroupByOptions } from "types"; // constants diff --git a/web/components/issue-layouts/filters/assignees.tsx b/web/components/issues/issue-layouts/header/filters/assignees.tsx similarity index 96% rename from web/components/issue-layouts/filters/assignees.tsx rename to web/components/issues/issue-layouts/header/filters/assignees.tsx index 869861e93fb..fb803d1d8c5 100644 --- a/web/components/issue-layouts/filters/assignees.tsx +++ b/web/components/issues/issue-layouts/header/filters/assignees.tsx @@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { FilterHeader, FilterOption } from "components/issue-layouts"; +import { FilterHeader, FilterOption } from "components/issues"; // ui import { Avatar, Loader } from "components/ui"; diff --git a/web/components/issue-layouts/filters/created-by.tsx b/web/components/issues/issue-layouts/header/filters/created-by.tsx similarity index 96% rename from web/components/issue-layouts/filters/created-by.tsx rename to web/components/issues/issue-layouts/header/filters/created-by.tsx index 70d7ac7b1c1..2cefd106eb3 100644 --- a/web/components/issue-layouts/filters/created-by.tsx +++ b/web/components/issues/issue-layouts/header/filters/created-by.tsx @@ -1,10 +1,10 @@ import React, { useState } from "react"; - -// mobx import { observer } from "mobx-react-lite"; + +// mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { FilterHeader, FilterOption } from "components/issue-layouts"; +import { FilterHeader, FilterOption } from "components/issues"; // ui import { Avatar, Loader } from "components/ui"; diff --git a/web/components/issue-layouts/filters/filters-selection.tsx b/web/components/issues/issue-layouts/header/filters/filters-selection.tsx similarity index 99% rename from web/components/issue-layouts/filters/filters-selection.tsx rename to web/components/issues/issue-layouts/header/filters/filters-selection.tsx index 01974e8d3b4..c1b92dc78a1 100644 --- a/web/components/issue-layouts/filters/filters-selection.tsx +++ b/web/components/issues/issue-layouts/header/filters/filters-selection.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; - -// mobx import { observer } from "mobx-react-lite"; + +// mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components import { @@ -11,7 +11,7 @@ import { FilterPriority, FilterState, FilterStateGroup, -} from "components/issue-layouts"; +} from "components/issues"; // icons import { Search, X } from "lucide-react"; // helpers diff --git a/web/components/issue-layouts/filters/index.ts b/web/components/issues/issue-layouts/header/filters/index.ts similarity index 100% rename from web/components/issue-layouts/filters/index.ts rename to web/components/issues/issue-layouts/header/filters/index.ts diff --git a/web/components/issue-layouts/filters/labels.tsx b/web/components/issues/issue-layouts/header/filters/labels.tsx similarity index 96% rename from web/components/issue-layouts/filters/labels.tsx rename to web/components/issues/issue-layouts/header/filters/labels.tsx index 258a8872ade..a024fcca50a 100644 --- a/web/components/issue-layouts/filters/labels.tsx +++ b/web/components/issues/issue-layouts/header/filters/labels.tsx @@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { FilterHeader, FilterOption } from "components/issue-layouts"; +import { FilterHeader, FilterOption } from "components/issues"; // ui import { Loader } from "components/ui"; diff --git a/web/components/issue-layouts/filters/priority.tsx b/web/components/issues/issue-layouts/header/filters/priority.tsx similarity index 97% rename from web/components/issue-layouts/filters/priority.tsx rename to web/components/issues/issue-layouts/header/filters/priority.tsx index 5c407d3a7dd..d6dbf1c4f06 100644 --- a/web/components/issue-layouts/filters/priority.tsx +++ b/web/components/issues/issue-layouts/header/filters/priority.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { observer } from "mobx-react-lite"; // components -import { FilterHeader, FilterOption } from "components/issue-layouts"; +import { FilterHeader, FilterOption } from "components/issues"; // icons import { AlertCircle, SignalHigh, SignalMedium, SignalLow, Ban } from "lucide-react"; // constants diff --git a/web/components/issue-layouts/filters/start-date.tsx b/web/components/issues/issue-layouts/header/filters/start-date.tsx similarity index 94% rename from web/components/issue-layouts/filters/start-date.tsx rename to web/components/issues/issue-layouts/header/filters/start-date.tsx index c93091a112d..a960f1f4c14 100644 --- a/web/components/issue-layouts/filters/start-date.tsx +++ b/web/components/issues/issue-layouts/header/filters/start-date.tsx @@ -1,10 +1,10 @@ import React, { useState } from "react"; - -// mobx import { observer } from "mobx-react-lite"; + +// mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { FilterHeader, FilterOption } from "components/issue-layouts"; +import { FilterHeader, FilterOption } from "components/issues"; export const FilterStartDate = observer(() => { const [previewEnabled, setPreviewEnabled] = useState(true); diff --git a/web/components/issue-layouts/filters/state-group.tsx b/web/components/issues/issue-layouts/header/filters/state-group.tsx similarity index 96% rename from web/components/issue-layouts/filters/state-group.tsx rename to web/components/issues/issue-layouts/header/filters/state-group.tsx index 9a32dbd7aff..fd22cd10d26 100644 --- a/web/components/issue-layouts/filters/state-group.tsx +++ b/web/components/issues/issue-layouts/header/filters/state-group.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { observer } from "mobx-react-lite"; // components -import { FilterHeader, FilterOption } from "components/issue-layouts"; +import { FilterHeader, FilterOption } from "components/issues"; // icons import { StateGroupIcon } from "components/icons"; // constants diff --git a/web/components/issue-layouts/filters/state.tsx b/web/components/issues/issue-layouts/header/filters/state.tsx similarity index 97% rename from web/components/issue-layouts/filters/state.tsx rename to web/components/issues/issue-layouts/header/filters/state.tsx index 031715bf49f..6a1232588d1 100644 --- a/web/components/issue-layouts/filters/state.tsx +++ b/web/components/issues/issue-layouts/header/filters/state.tsx @@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { FilterHeader, FilterOption } from "components/issue-layouts"; +import { FilterHeader, FilterOption } from "components/issues"; // ui import { Loader } from "components/ui"; // icons diff --git a/web/components/issue-layouts/filters/target-date.tsx b/web/components/issues/issue-layouts/header/filters/target-date.tsx similarity index 94% rename from web/components/issue-layouts/filters/target-date.tsx rename to web/components/issues/issue-layouts/header/filters/target-date.tsx index 7a00b12a4a0..21cd8f2f212 100644 --- a/web/components/issue-layouts/filters/target-date.tsx +++ b/web/components/issues/issue-layouts/header/filters/target-date.tsx @@ -1,10 +1,10 @@ import React, { useState } from "react"; - -// mobx import { observer } from "mobx-react-lite"; + +// mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { FilterHeader, FilterOption } from "components/issue-layouts"; +import { FilterHeader, FilterOption } from "components/issues"; export const FilterTargetDate = observer(() => { const [previewEnabled, setPreviewEnabled] = useState(true); diff --git a/web/components/issues/issue-layouts/header/helpers/dropdown.tsx b/web/components/issues/issue-layouts/header/helpers/dropdown.tsx new file mode 100644 index 00000000000..00c589a7665 --- /dev/null +++ b/web/components/issues/issue-layouts/header/helpers/dropdown.tsx @@ -0,0 +1,53 @@ +import React, { Fragment } from "react"; + +// headless ui +import { Popover, Transition } from "@headlessui/react"; +// icons +import { ChevronUp } from "lucide-react"; + +type Props = { + children: React.ReactNode; + title?: string; +}; + +export const FiltersDropdown: React.FC = (props) => { + const { children, title = "Dropdown" } = props; + + return ( + + {({ open }) => { + if (open) { + } + return ( + <> + +
{title}
+
+ +
+
+ + +
{children}
+
+
+ + ); + }} +
+ ); +}; diff --git a/web/components/issue-layouts/helpers/filter-header.tsx b/web/components/issues/issue-layouts/header/helpers/filter-header.tsx similarity index 100% rename from web/components/issue-layouts/helpers/filter-header.tsx rename to web/components/issues/issue-layouts/header/helpers/filter-header.tsx diff --git a/web/components/issue-layouts/helpers/filter-option.tsx b/web/components/issues/issue-layouts/header/helpers/filter-option.tsx similarity index 100% rename from web/components/issue-layouts/helpers/filter-option.tsx rename to web/components/issues/issue-layouts/header/helpers/filter-option.tsx diff --git a/web/components/issue-layouts/helpers/index.ts b/web/components/issues/issue-layouts/header/helpers/index.ts similarity index 100% rename from web/components/issue-layouts/helpers/index.ts rename to web/components/issues/issue-layouts/header/helpers/index.ts diff --git a/web/components/issues/issue-layouts/header/index.ts b/web/components/issues/issue-layouts/header/index.ts new file mode 100644 index 00000000000..61375f0ffab --- /dev/null +++ b/web/components/issues/issue-layouts/header/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/layout-selection.tsx b/web/components/issues/issue-layouts/header/layout-selection.tsx similarity index 100% rename from web/components/issue-layouts/layout-selection.tsx rename to web/components/issues/issue-layouts/header/layout-selection.tsx diff --git a/web/components/issues/issue-layouts/index.ts b/web/components/issues/issue-layouts/index.ts index 30249be797a..647e69f140d 100644 --- a/web/components/issues/issue-layouts/index.ts +++ b/web/components/issues/issue-layouts/index.ts @@ -1,2 +1,3 @@ export * from "./calendar"; +export * from "./header"; export * from "./kanban"; diff --git a/web/store/issue_filters.ts b/web/store/issue_filters.ts index 35ade307ab1..84d33dee820 100644 --- a/web/store/issue_filters.ts +++ b/web/store/issue_filters.ts @@ -203,8 +203,6 @@ class IssueFilterStore implements IIssueFilterStore { this.userDisplayFilters = newViewProps.display_filters; }); - console.log("Saving..."); - this.projectService.setProjectView(workspaceSlug, projectId, { view_props: newViewProps, }); From 4cd94cf58d726111c751a2630b134745e2e7337f Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 28 Sep 2023 17:44:51 +0530 Subject: [PATCH 03/10] refactor: change issue layout options constant structure --- web/components/headers/project-issues.tsx | 10 +- .../display-filters-selection.tsx | 8 +- web/constants/issue.ts | 141 +++++++++--------- 3 files changed, 78 insertions(+), 81 deletions(-) diff --git a/web/components/headers/project-issues.tsx b/web/components/headers/project-issues.tsx index df92ed2d0ee..6f2b3f37b5c 100644 --- a/web/components/headers/project-issues.tsx +++ b/web/components/headers/project-issues.tsx @@ -54,7 +54,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => { return (
handleLayoutChange(layout)} selectedLayout={issueFilterStore.userDisplayFilters.layout ?? "list"} /> @@ -62,7 +62,9 @@ export const ProjectIssuesHeader: React.FC = observer(() => { @@ -70,7 +72,9 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
diff --git a/web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx b/web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx index 510df434492..1ac173899b1 100644 --- a/web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx +++ b/web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx @@ -24,12 +24,12 @@ export const DisplayFiltersSelection: React.FC = observer((props) => { const { displayFilters, handleDisplayFiltersUpdate, layoutDisplayFiltersOptions } = props; const isDisplayFilterEnabled = (displayFilter: string) => - layoutDisplayFiltersOptions.display_filters[displayFilters.layout ?? "list"].includes(displayFilter); + layoutDisplayFiltersOptions.display_filters.includes(displayFilter); return (
{/* display properties */} - {layoutDisplayFiltersOptions.display_properties[displayFilters.layout ?? "list"] && ( + {layoutDisplayFiltersOptions.display_properties && (
@@ -93,7 +93,7 @@ export const DisplayFiltersSelection: React.FC = observer((props) => { )} {/* Options */} - {layoutDisplayFiltersOptions.extra_options[displayFilters.layout ?? "list"].access && ( + {layoutDisplayFiltersOptions.extra_options.access && (
= observer((props) => { [key]: val, }) } - enabledExtraOptions={layoutDisplayFiltersOptions.extra_options[displayFilters.layout ?? "list"].values} + enabledExtraOptions={layoutDisplayFiltersOptions.extra_options.values} />
)} diff --git a/web/constants/issue.ts b/web/constants/issue.ts index 5514a8b1b0f..d2a1f593cc0 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -201,114 +201,107 @@ export const ISSUE_GANTT_DISPLAY_FILTERS = [ ]; export interface ILayoutDisplayFiltersOptions { - layout: TIssueLayouts[]; - filters: { - [key in TIssueLayouts]: string[]; - }; - display_properties: { - [key in TIssueLayouts]: boolean; - }; - display_filters: { - [key in TIssueLayouts]: string[]; - }; + filters: string[]; + display_properties: boolean; + display_filters: string[]; extra_options: { - [key in TIssueLayouts]: { - access: boolean; - values: TIssueExtraOptions[]; - }; + access: boolean; + values: TIssueExtraOptions[]; }; } export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { - [layoutType: string]: ILayoutDisplayFiltersOptions; + [pageType: string]: { [layoutType: string]: ILayoutDisplayFiltersOptions }; } = { 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: { + list: { + filters: ["priority", "state_group", "labels", "start_date", "due_date"], + display_properties: true, + display_filters: ["group_by", "sub_group_by", "order_by", "issue_type"], + extra_options: { access: true, values: ["show_empty_groups", "sub_issue"], }, - kanban: { - access: true, - values: ["show_empty_groups", "sub_issue"], - }, - calendar: { + }, + kanban: { + filters: ["priority", "state_group", "labels", "start_date", "due_date"], + display_properties: true, + display_filters: ["group_by", "sub_group_by", "order_by", "issue_type"], + extra_options: { access: false, values: [], }, - spreadsheet: { + }, + calendar: { + filters: [], + display_properties: true, + display_filters: [], + extra_options: { access: false, values: [], }, - gantt_chart: { + }, + spreadsheet: { + filters: [], + display_properties: false, + display_filters: [], + extra_options: { access: false, values: [], }, }, + gantt_chart: { + filters: [], + display_properties: false, + display_filters: [], + extra_options: { + access: true, + 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", "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: { + list: { + filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], + display_properties: true, + display_filters: ["group_by", "sub_group_by", "order_by", "issue_type"], + extra_options: { access: true, values: ["show_empty_groups", "sub_issue"], }, - kanban: { + }, + kanban: { + filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], + display_properties: true, + display_filters: ["group_by", "sub_group_by", "order_by", "issue_type"], + extra_options: { access: true, values: ["show_empty_groups", "sub_issue"], }, - calendar: { + }, + calendar: { + filters: ["priority", "state", "assignees", "created_by", "labels"], + display_properties: true, + display_filters: ["issue_type"], + extra_options: { access: false, values: [], }, - spreadsheet: { + }, + spreadsheet: { + filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], + display_properties: true, + display_filters: ["issue_type"], + extra_options: { access: false, values: [], }, - gantt_chart: { + }, + gantt_chart: { + filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], + display_properties: false, + display_filters: ["order_by", "issue_type"], + extra_options: { access: true, values: ["sub_issue"], }, From 0e551964b76e0c7117a850e939eb43853874c2a4 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 28 Sep 2023 18:51:18 +0530 Subject: [PATCH 04/10] chore: display filters validations --- .../display-filters-selection.tsx | 11 +- .../header/display-filters/group-by.tsx | 26 +- .../header/display-filters/sub-group-by.tsx | 5 +- .../header/filters/filters-selection.tsx | 230 ++++++++++-------- web/constants/issue.ts | 96 ++++---- web/store/issue_filters.ts | 3 + 6 files changed, 208 insertions(+), 163 deletions(-) diff --git a/web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx b/web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx index 1ac173899b1..4bdc0da33af 100644 --- a/web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx +++ b/web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx @@ -23,8 +23,8 @@ type Props = { export const DisplayFiltersSelection: React.FC = observer((props) => { const { displayFilters, handleDisplayFiltersUpdate, layoutDisplayFiltersOptions } = props; - const isDisplayFilterEnabled = (displayFilter: string) => - layoutDisplayFiltersOptions.display_filters.includes(displayFilter); + const isDisplayFilterEnabled = (displayFilter: keyof IIssueDisplayFilterOptions) => + Object.keys(layoutDisplayFiltersOptions.display_filters).includes(displayFilter); return (
@@ -40,6 +40,8 @@ export const DisplayFiltersSelection: React.FC = observer((props) => {
handleDisplayFiltersUpdate({ group_by: val, @@ -50,7 +52,7 @@ export const DisplayFiltersSelection: React.FC = observer((props) => { )} {/* sub-group by */} - {isDisplayFilterEnabled("sub_group_by") && ( + {isDisplayFilterEnabled("sub_group_by") && displayFilters.group_by !== null && (
= observer((props) => { sub_group_by: val, }) } + subGroupByOptions={layoutDisplayFiltersOptions.display_filters.sub_group_by} />
)} @@ -79,7 +82,7 @@ export const DisplayFiltersSelection: React.FC = observer((props) => { )} {/* issue type */} - {isDisplayFilterEnabled("issue_type") && ( + {isDisplayFilterEnabled("type") && (
void; }; export const FilterGroupBy: React.FC = observer((props) => { - const { selectedGroupBy, handleUpdate } = props; + const { selectedGroupBy, selectedSubGroupBy, groupByOptions, handleUpdate } = props; const [previewEnabled, setPreviewEnabled] = useState(true); @@ -27,15 +29,19 @@ export const FilterGroupBy: React.FC = observer((props) => { /> {previewEnabled && (
- {ISSUE_GROUP_BY_OPTIONS.map((groupBy) => ( - handleUpdate(groupBy.key)} - title={groupBy.title} - multiple={false} - /> - ))} + {ISSUE_GROUP_BY_OPTIONS.filter((option) => groupByOptions.includes(option.key)).map((groupBy) => { + if (selectedSubGroupBy !== null && groupBy.key === selectedSubGroupBy) return null; + + return ( + handleUpdate(groupBy.key)} + title={groupBy.title} + multiple={false} + /> + ); + })}
)} diff --git a/web/components/issues/issue-layouts/header/display-filters/sub-group-by.tsx b/web/components/issues/issue-layouts/header/display-filters/sub-group-by.tsx index 3a4a06a11f1..83f09092d92 100644 --- a/web/components/issues/issue-layouts/header/display-filters/sub-group-by.tsx +++ b/web/components/issues/issue-layouts/header/display-filters/sub-group-by.tsx @@ -12,10 +12,11 @@ type Props = { selectedGroupBy: TIssueGroupByOptions | undefined; selectedSubGroupBy: TIssueGroupByOptions | undefined; handleUpdate: (val: TIssueGroupByOptions) => void; + subGroupByOptions: TIssueGroupByOptions[]; }; export const FilterSubGroupBy: React.FC = observer((props) => { - const { selectedGroupBy, selectedSubGroupBy, handleUpdate } = props; + const { selectedGroupBy, selectedSubGroupBy, handleUpdate, subGroupByOptions } = props; const [previewEnabled, setPreviewEnabled] = useState(true); @@ -28,7 +29,7 @@ export const FilterSubGroupBy: React.FC = observer((props) => { /> {previewEnabled && (
- {ISSUE_GROUP_BY_OPTIONS.map((subGroupBy) => { + {ISSUE_GROUP_BY_OPTIONS.filter((option) => subGroupByOptions.includes(option.key)).map((subGroupBy) => { if (selectedGroupBy !== null && subGroupBy.key === selectedGroupBy) return null; return ( diff --git a/web/components/issues/issue-layouts/header/filters/filters-selection.tsx b/web/components/issues/issue-layouts/header/filters/filters-selection.tsx index c1b92dc78a1..fa4c557e332 100644 --- a/web/components/issues/issue-layouts/header/filters/filters-selection.tsx +++ b/web/components/issues/issue-layouts/header/filters/filters-selection.tsx @@ -92,6 +92,8 @@ export const FilterSelection: React.FC = observer((props) => { return filterDetails.currentLength < filterDetails.totalLength; }; + const isFilterEnabled = (filter: keyof IIssueFilterOptions) => layoutDisplayFiltersOptions.filters.includes(filter); + return (
@@ -114,126 +116,142 @@ export const FilterSelection: React.FC = observer((props) => {
{/* priority */} -
- handleFiltersUpdate("priority", val)} - itemsToRender={filtersToRender.priority?.currentLength ?? 0} - searchQuery={filtersSearchQuery} - /> - {isViewMoreVisible("priority") && ( - - )} -
+ {isFilterEnabled("priority") && ( +
+ handleFiltersUpdate("priority", val)} + itemsToRender={filtersToRender.priority?.currentLength ?? 0} + searchQuery={filtersSearchQuery} + /> + {isViewMoreVisible("priority") && ( + + )} +
+ )} {/* state group */} -
- handleFiltersUpdate("state_group", val)} - itemsToRender={filtersToRender.state_group?.currentLength ?? 0} - searchQuery={filtersSearchQuery} - /> - {isViewMoreVisible("state_group") && ( - - )} -
+ {isFilterEnabled("state_group") && ( +
+ handleFiltersUpdate("state_group", val)} + itemsToRender={filtersToRender.state_group?.currentLength ?? 0} + searchQuery={filtersSearchQuery} + /> + {isViewMoreVisible("state_group") && ( + + )} +
+ )} {/* state */} -
- handleFiltersUpdate("state", val)} - itemsToRender={filtersToRender.state?.currentLength ?? 0} - searchQuery={filtersSearchQuery} - projectId={projectId} - /> - {isViewMoreVisible("state") && ( - - )} -
+ {isFilterEnabled("state") && ( +
+ handleFiltersUpdate("state", val)} + itemsToRender={filtersToRender.state?.currentLength ?? 0} + searchQuery={filtersSearchQuery} + projectId={projectId} + /> + {isViewMoreVisible("state") && ( + + )} +
+ )} {/* assignees */} -
- handleFiltersUpdate("assignees", val)} - itemsToRender={filtersToRender.assignees?.currentLength ?? 0} - projectId={projectId} - searchQuery={filtersSearchQuery} - /> - {isViewMoreVisible("assignees") && ( - - )} -
+ {isFilterEnabled("assignees") && ( +
+ handleFiltersUpdate("assignees", val)} + itemsToRender={filtersToRender.assignees?.currentLength ?? 0} + projectId={projectId} + searchQuery={filtersSearchQuery} + /> + {isViewMoreVisible("assignees") && ( + + )} +
+ )} {/* created_by */} -
- handleFiltersUpdate("created_by", val)} - itemsToRender={filtersToRender.created_by?.currentLength ?? 0} - projectId={projectId} - searchQuery={filtersSearchQuery} - /> - {isViewMoreVisible("created_by") && ( - - )} -
+ {isFilterEnabled("created_by") && ( +
+ handleFiltersUpdate("created_by", val)} + itemsToRender={filtersToRender.created_by?.currentLength ?? 0} + projectId={projectId} + searchQuery={filtersSearchQuery} + /> + {isViewMoreVisible("created_by") && ( + + )} +
+ )} {/* labels */} -
- handleFiltersUpdate("labels", val)} - itemsToRender={filtersToRender.labels?.currentLength ?? 0} - projectId={projectId} - searchQuery={filtersSearchQuery} - /> - {isViewMoreVisible("labels") && ( - - )} -
+ {isFilterEnabled("labels") && ( +
+ handleFiltersUpdate("labels", val)} + itemsToRender={filtersToRender.labels?.currentLength ?? 0} + projectId={projectId} + searchQuery={filtersSearchQuery} + /> + {isViewMoreVisible("labels") && ( + + )} +
+ )} {/* start_date */} - {/*
- -
*/} + {/* {isFilterEnabled("start_date") && ( +
+ +
+ )} */} {/* due_date */} - {/*
- -
*/} + {/* {isFilterEnabled("target_date") && ( +
+ +
+ )} */}
); diff --git a/web/constants/issue.ts b/web/constants/issue.ts index d2a1f593cc0..701602f3b55 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -3,6 +3,7 @@ import { Calendar, GanttChart, Kanban, List, Sheet } from "lucide-react"; // types import { IIssueDisplayProperties, + IIssueFilterOptions, TIssueExtraOptions, TIssueGroupByOptions, TIssueLayouts, @@ -201,9 +202,14 @@ export const ISSUE_GANTT_DISPLAY_FILTERS = [ ]; export interface ILayoutDisplayFiltersOptions { - filters: string[]; + filters: (keyof IIssueFilterOptions)[]; display_properties: boolean; - display_filters: string[]; + display_filters: { + group_by: TIssueGroupByOptions[]; + sub_group_by: TIssueGroupByOptions[]; + order_by: TIssueOrderByOptions[]; + type: TIssueTypeFilters[]; + }; extra_options: { access: boolean; values: TIssueExtraOptions[]; @@ -215,65 +221,58 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { } = { my_issues: { list: { - filters: ["priority", "state_group", "labels", "start_date", "due_date"], + filters: ["priority", "state_group", "labels", "start_date", "target_date"], display_properties: true, - display_filters: ["group_by", "sub_group_by", "order_by", "issue_type"], + display_filters: { + group_by: ["state_detail.group", "project", "priority", "labels", null], + sub_group_by: ["state_detail.group", "project", "priority", "labels", null], + order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], + type: [null, "active", "backlog"], + }, extra_options: { access: true, values: ["show_empty_groups", "sub_issue"], }, }, kanban: { - filters: ["priority", "state_group", "labels", "start_date", "due_date"], + filters: ["priority", "state_group", "labels", "start_date", "target_date"], display_properties: true, - display_filters: ["group_by", "sub_group_by", "order_by", "issue_type"], - extra_options: { - access: false, - values: [], + display_filters: { + group_by: ["state_detail.group", "project", "priority", "labels", null], + sub_group_by: ["state_detail.group", "project", "priority", "labels", null], + order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], + type: [null, "active", "backlog"], }, - }, - calendar: { - filters: [], - display_properties: true, - display_filters: [], extra_options: { access: false, values: [], }, }, - spreadsheet: { - filters: [], - display_properties: false, - display_filters: [], - extra_options: { - access: false, - values: [], - }, - }, - gantt_chart: { - filters: [], - display_properties: false, - display_filters: [], - extra_options: { - access: true, - values: [], - }, - }, }, issues: { list: { - filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], + filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "target_date"], display_properties: true, - display_filters: ["group_by", "sub_group_by", "order_by", "issue_type"], + display_filters: { + group_by: ["state", "priority", "labels", "assignees", "created_by", null], + sub_group_by: ["state", "priority", "labels", "assignees", "created_by", null], + order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], + type: [null, "active", "backlog"], + }, extra_options: { access: true, values: ["show_empty_groups", "sub_issue"], }, }, kanban: { - filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], + filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "target_date"], display_properties: true, - display_filters: ["group_by", "sub_group_by", "order_by", "issue_type"], + display_filters: { + group_by: ["state", "priority", "labels", "assignees", "created_by", null], + sub_group_by: ["state", "priority", "labels", "assignees", "created_by", null], + order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], + type: [null, "active", "backlog"], + }, extra_options: { access: true, values: ["show_empty_groups", "sub_issue"], @@ -282,25 +281,40 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { calendar: { filters: ["priority", "state", "assignees", "created_by", "labels"], display_properties: true, - display_filters: ["issue_type"], + display_filters: { + group_by: [], + sub_group_by: [], + order_by: [], + type: [null, "active", "backlog"], + }, extra_options: { access: false, values: [], }, }, spreadsheet: { - filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], + filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "target_date"], display_properties: true, - display_filters: ["issue_type"], + display_filters: { + group_by: [], + sub_group_by: [], + order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], + type: [null, "active", "backlog"], + }, extra_options: { access: false, values: [], }, }, gantt_chart: { - filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"], + filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "target_date"], display_properties: false, - display_filters: ["order_by", "issue_type"], + display_filters: { + group_by: [], + sub_group_by: [], + order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], + type: [null, "active", "backlog"], + }, extra_options: { access: true, values: ["sub_issue"], diff --git a/web/store/issue_filters.ts b/web/store/issue_filters.ts index 84d33dee820..d9e58b34184 100644 --- a/web/store/issue_filters.ts +++ b/web/store/issue_filters.ts @@ -197,6 +197,9 @@ class IssueFilterStore implements IIssueFilterStore { }, }; + // set sub_group_by to null if group_by is set to null + if (newViewProps.display_filters.group_by === null) newViewProps.display_filters.sub_group_by = null; + try { runInAction(() => { this.userFilters = newViewProps.filters; From 4b3a5dea53a536d45c48962da11387444f5df17a Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 28 Sep 2023 22:49:09 +0530 Subject: [PATCH 05/10] chore: view less filters functionality --- .../header/filters/filters-selection.tsx | 176 +++++++++++++----- 1 file changed, 128 insertions(+), 48 deletions(-) diff --git a/web/components/issues/issue-layouts/header/filters/filters-selection.tsx b/web/components/issues/issue-layouts/header/filters/filters-selection.tsx index fa4c557e332..f53b51a4b5e 100644 --- a/web/components/issues/issue-layouts/header/filters/filters-selection.tsx +++ b/web/components/issues/issue-layouts/header/filters/filters-selection.tsx @@ -84,6 +84,20 @@ export const FilterSelection: React.FC = observer((props) => { })); }; + const handleViewLess = (filterName: keyof IIssueFilterOptions) => { + const filterDetails = filtersToRender[filterName]; + + if (!filterDetails) return; + + setFiltersToRender((prev) => ({ + ...prev, + [filterName]: { + ...prev[filterName], + currentLength: 5, + }, + })); + }; + const isViewMoreVisible = (filterName: keyof IIssueFilterOptions): boolean => { const filterDetails = filtersToRender[filterName]; @@ -92,6 +106,14 @@ export const FilterSelection: React.FC = observer((props) => { return filterDetails.currentLength < filterDetails.totalLength; }; + const isViewLessVisible = (filterName: keyof IIssueFilterOptions): boolean => { + const filterDetails = filtersToRender[filterName]; + + if (!filterDetails) return false; + + return filterDetails.currentLength > 5; + }; + const isFilterEnabled = (filter: keyof IIssueFilterOptions) => layoutDisplayFiltersOptions.filters.includes(filter); return ( @@ -124,14 +146,25 @@ export const FilterSelection: React.FC = observer((props) => { itemsToRender={filtersToRender.priority?.currentLength ?? 0} searchQuery={filtersSearchQuery} /> - {isViewMoreVisible("priority") && ( - - )} + {/* TODO: handle view more and less in a better way */} +
+ {isViewMoreVisible("priority") && ( + + )} + {isViewLessVisible("priority") && ( + + )} +
)} @@ -144,14 +177,24 @@ export const FilterSelection: React.FC = observer((props) => { itemsToRender={filtersToRender.state_group?.currentLength ?? 0} searchQuery={filtersSearchQuery} /> - {isViewMoreVisible("state_group") && ( - - )} +
+ {isViewMoreVisible("state_group") && ( + + )} + {isViewLessVisible("state_group") && ( + + )} +
)} @@ -165,14 +208,21 @@ export const FilterSelection: React.FC = observer((props) => { searchQuery={filtersSearchQuery} projectId={projectId} /> - {isViewMoreVisible("state") && ( - - )} +
+ {isViewMoreVisible("state") && ( + + )} + {isViewLessVisible("state") && ( + + )} +
)} @@ -186,14 +236,24 @@ export const FilterSelection: React.FC = observer((props) => { projectId={projectId} searchQuery={filtersSearchQuery} /> - {isViewMoreVisible("assignees") && ( - - )} +
+ {isViewMoreVisible("assignees") && ( + + )} + {isViewLessVisible("assignees") && ( + + )} +
)} @@ -207,14 +267,24 @@ export const FilterSelection: React.FC = observer((props) => { projectId={projectId} searchQuery={filtersSearchQuery} /> - {isViewMoreVisible("created_by") && ( - - )} +
+ {isViewMoreVisible("created_by") && ( + + )} + {isViewLessVisible("created_by") && ( + + )} +
)} @@ -228,14 +298,24 @@ export const FilterSelection: React.FC = observer((props) => { projectId={projectId} searchQuery={filtersSearchQuery} /> - {isViewMoreVisible("labels") && ( - - )} +
+ {isViewMoreVisible("labels") && ( + + )} + {isViewLessVisible("labels") && ( + + )} +
)} From 2bf869db598c62f1a5bd3e0c222ca9cfe9a5b05e Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 28 Sep 2023 22:54:37 +0530 Subject: [PATCH 06/10] fix: display filters validation --- .../display-filters/display-filters-selection.tsx | 4 ++-- web/constants/issue.ts | 15 ++++----------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx b/web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx index 4bdc0da33af..8c4d5d3926f 100644 --- a/web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx +++ b/web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx @@ -41,7 +41,7 @@ export const DisplayFiltersSelection: React.FC = observer((props) => { handleDisplayFiltersUpdate({ group_by: val, @@ -62,7 +62,7 @@ export const DisplayFiltersSelection: React.FC = observer((props) => { sub_group_by: val, }) } - subGroupByOptions={layoutDisplayFiltersOptions.display_filters.sub_group_by} + subGroupByOptions={layoutDisplayFiltersOptions.display_filters.sub_group_by ?? []} /> )} diff --git a/web/constants/issue.ts b/web/constants/issue.ts index 701602f3b55..c782013fc96 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -205,10 +205,10 @@ export interface ILayoutDisplayFiltersOptions { filters: (keyof IIssueFilterOptions)[]; display_properties: boolean; display_filters: { - group_by: TIssueGroupByOptions[]; - sub_group_by: TIssueGroupByOptions[]; - order_by: TIssueOrderByOptions[]; - type: TIssueTypeFilters[]; + group_by?: TIssueGroupByOptions[]; + sub_group_by?: TIssueGroupByOptions[]; + order_by?: TIssueOrderByOptions[]; + type?: TIssueTypeFilters[]; }; extra_options: { access: boolean; @@ -282,9 +282,6 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { filters: ["priority", "state", "assignees", "created_by", "labels"], display_properties: true, display_filters: { - group_by: [], - sub_group_by: [], - order_by: [], type: [null, "active", "backlog"], }, extra_options: { @@ -296,8 +293,6 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "target_date"], display_properties: true, display_filters: { - group_by: [], - sub_group_by: [], order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], type: [null, "active", "backlog"], }, @@ -310,8 +305,6 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "target_date"], display_properties: false, display_filters: { - group_by: [], - sub_group_by: [], order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], type: [null, "active", "backlog"], }, From ed10504795689742052f3c719c486891182b0c44 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 28 Sep 2023 23:47:57 +0530 Subject: [PATCH 07/10] refactor: wrap functions around useCallback --- web/components/headers/project-issues.tsx | 66 ++++---- .../header/filters/assignees.tsx | 12 +- .../header/filters/created-by.tsx | 12 +- .../header/filters/filters-selection.tsx | 154 +++++++++--------- .../issue-layouts/header/filters/labels.tsx | 12 +- .../issue-layouts/header/filters/priority.tsx | 12 +- 6 files changed, 147 insertions(+), 121 deletions(-) diff --git a/web/components/headers/project-issues.tsx b/web/components/headers/project-issues.tsx index 6f2b3f37b5c..32eae2ea2b0 100644 --- a/web/components/headers/project-issues.tsx +++ b/web/components/headers/project-issues.tsx @@ -1,3 +1,4 @@ +import { useCallback } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; @@ -16,40 +17,49 @@ export const ProjectIssuesHeader: React.FC = observer(() => { const { issueFilter: issueFilterStore } = useMobxStore(); - const handleLayoutChange = (layout: TIssueLayouts) => { - if (!workspaceSlug || !projectId) return; + const handleLayoutChange = useCallback( + (layout: TIssueLayouts) => { + if (!workspaceSlug || !projectId) return; - issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { - display_filters: { - layout, - }, - }); - }; + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + display_filters: { + layout, + }, + }); + }, + [issueFilterStore, projectId, workspaceSlug] + ); - const handleFiltersUpdate = (key: keyof IIssueFilterOptions, value: string) => { - if (!workspaceSlug || !projectId) return; + const handleFiltersUpdate = useCallback( + (key: keyof IIssueFilterOptions, value: string) => { + if (!workspaceSlug || !projectId) return; - const newValues = issueFilterStore.userFilters?.[key] ?? []; + const newValues = issueFilterStore.userFilters?.[key] ?? []; - if (issueFilterStore.userFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); - else newValues.push(value); + if (issueFilterStore.userFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); + else newValues.push(value); - issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { - filters: { - [key]: newValues, - }, - }); - }; + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + filters: { + [key]: newValues, + }, + }); + }, + [issueFilterStore, projectId, workspaceSlug] + ); - const handleDisplayFiltersUpdate = (updatedDisplayFilter: Partial) => { - if (!workspaceSlug || !projectId) return; + const handleDisplayFiltersUpdate = useCallback( + (updatedDisplayFilter: Partial) => { + if (!workspaceSlug || !projectId) return; - issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { - display_filters: { - ...updatedDisplayFilter, - }, - }); - }; + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + display_filters: { + ...updatedDisplayFilter, + }, + }); + }, + [issueFilterStore, projectId, workspaceSlug] + ); return (
@@ -60,7 +70,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => { /> = observer((props) => { - const { appliedFilters, handleUpdate, itemsToRender, projectId, searchQuery } = props; + const { appliedFilters, handleUpdate, itemsToRender, projectId, searchQuery, viewButtons } = props; const [previewEnabled, setPreviewEnabled] = useState(true); @@ -41,9 +42,8 @@ export const FilterAssignees: React.FC = observer((props) => {
{filteredOptions ? ( filteredOptions.length > 0 ? ( - filteredOptions - .slice(0, itemsToRender) - .map((member) => ( + <> + {filteredOptions.slice(0, itemsToRender).map((member) => ( = observer((props) => { icon={} title={member.member?.display_name} /> - )) + ))} + {viewButtons} + ) : (

No matches found

) diff --git a/web/components/issues/issue-layouts/header/filters/created-by.tsx b/web/components/issues/issue-layouts/header/filters/created-by.tsx index 2cefd106eb3..791e1e9319e 100644 --- a/web/components/issues/issue-layouts/header/filters/created-by.tsx +++ b/web/components/issues/issue-layouts/header/filters/created-by.tsx @@ -14,10 +14,11 @@ type Props = { itemsToRender: number; projectId: string; searchQuery: string; + viewButtons: React.ReactNode; }; export const FilterCreatedBy: React.FC = observer((props) => { - const { appliedFilters, handleUpdate, itemsToRender, projectId, searchQuery } = props; + const { appliedFilters, handleUpdate, itemsToRender, projectId, searchQuery, viewButtons } = props; const [previewEnabled, setPreviewEnabled] = useState(true); @@ -41,9 +42,8 @@ export const FilterCreatedBy: React.FC = observer((props) => {
{filteredOptions ? ( filteredOptions.length > 0 ? ( - filteredOptions - .slice(0, itemsToRender) - .map((member) => ( + <> + {filteredOptions.slice(0, itemsToRender).map((member) => ( = observer((props) => { icon={} title={member.member?.display_name} /> - )) + ))} + {viewButtons} + ) : (

No matches found

) diff --git a/web/components/issues/issue-layouts/header/filters/filters-selection.tsx b/web/components/issues/issue-layouts/header/filters/filters-selection.tsx index f53b51a4b5e..1d17eadfff6 100644 --- a/web/components/issues/issue-layouts/header/filters/filters-selection.tsx +++ b/web/components/issues/issue-layouts/header/filters/filters-selection.tsx @@ -145,26 +145,28 @@ export const FilterSelection: React.FC = observer((props) => { handleUpdate={(val) => handleFiltersUpdate("priority", val)} itemsToRender={filtersToRender.priority?.currentLength ?? 0} searchQuery={filtersSearchQuery} + viewButtons={ +
+ {/* TODO: handle view more and less in a better way */} + {isViewMoreVisible("priority") && ( + + )} + {isViewLessVisible("priority") && ( + + )} +
+ } /> - {/* TODO: handle view more and less in a better way */} -
- {isViewMoreVisible("priority") && ( - - )} - {isViewLessVisible("priority") && ( - - )} -
)} @@ -235,25 +237,27 @@ export const FilterSelection: React.FC = observer((props) => { itemsToRender={filtersToRender.assignees?.currentLength ?? 0} projectId={projectId} searchQuery={filtersSearchQuery} + viewButtons={ +
+ {isViewMoreVisible("assignees") && ( + + )} + {isViewLessVisible("assignees") && ( + + )} +
+ } /> -
- {isViewMoreVisible("assignees") && ( - - )} - {isViewLessVisible("assignees") && ( - - )} -
)} @@ -266,25 +270,27 @@ export const FilterSelection: React.FC = observer((props) => { itemsToRender={filtersToRender.created_by?.currentLength ?? 0} projectId={projectId} searchQuery={filtersSearchQuery} + viewButtons={ +
+ {isViewMoreVisible("created_by") && ( + + )} + {isViewLessVisible("created_by") && ( + + )} +
+ } /> -
- {isViewMoreVisible("created_by") && ( - - )} - {isViewLessVisible("created_by") && ( - - )} -
)} @@ -297,25 +303,27 @@ export const FilterSelection: React.FC = observer((props) => { itemsToRender={filtersToRender.labels?.currentLength ?? 0} projectId={projectId} searchQuery={filtersSearchQuery} + viewButtons={ +
+ {isViewMoreVisible("labels") && ( + + )} + {isViewLessVisible("labels") && ( + + )} +
+ } /> -
- {isViewMoreVisible("labels") && ( - - )} - {isViewLessVisible("labels") && ( - - )} -
)} diff --git a/web/components/issues/issue-layouts/header/filters/labels.tsx b/web/components/issues/issue-layouts/header/filters/labels.tsx index a024fcca50a..407bd707fe4 100644 --- a/web/components/issues/issue-layouts/header/filters/labels.tsx +++ b/web/components/issues/issue-layouts/header/filters/labels.tsx @@ -18,10 +18,11 @@ type Props = { itemsToRender: number; projectId: string; searchQuery: string; + viewButtons: React.ReactNode; }; export const FilterLabels: React.FC = observer((props) => { - const { appliedFilters, handleUpdate, itemsToRender, projectId, searchQuery } = props; + const { appliedFilters, handleUpdate, itemsToRender, projectId, searchQuery, viewButtons } = props; const [previewEnabled, setPreviewEnabled] = useState(true); @@ -45,9 +46,8 @@ export const FilterLabels: React.FC = observer((props) => {
{filteredOptions ? ( filteredOptions.length > 0 ? ( - filteredOptions - .slice(0, itemsToRender) - .map((label) => ( + <> + {filteredOptions.slice(0, itemsToRender).map((label) => ( = observer((props) => { icon={} title={label.name} /> - )) + ))} + {viewButtons} + ) : (

No matches found

) diff --git a/web/components/issues/issue-layouts/header/filters/priority.tsx b/web/components/issues/issue-layouts/header/filters/priority.tsx index d6dbf1c4f06..4eff176e0a6 100644 --- a/web/components/issues/issue-layouts/header/filters/priority.tsx +++ b/web/components/issues/issue-layouts/header/filters/priority.tsx @@ -53,10 +53,11 @@ type Props = { handleUpdate: (val: string) => void; itemsToRender: number; searchQuery: string; + viewButtons: React.ReactNode; }; export const FilterPriority: React.FC = observer((props) => { - const { appliedFilters, handleUpdate, itemsToRender, searchQuery } = props; + const { appliedFilters, handleUpdate, itemsToRender, searchQuery, viewButtons } = props; const [previewEnabled, setPreviewEnabled] = useState(true); @@ -74,9 +75,8 @@ export const FilterPriority: React.FC = observer((props) => { {previewEnabled && (
{filteredOptions.length > 0 ? ( - filteredOptions - .slice(0, itemsToRender) - .map((priority) => ( + <> + {filteredOptions.slice(0, itemsToRender).map((priority) => ( = observer((props) => { icon={} title={priority.title} /> - )) + ))} + {viewButtons} + ) : (

No matches found

)} From 546243ce7c39e1f93c8ae12d73b47f87d96c6acc Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 29 Sep 2023 12:06:23 +0530 Subject: [PATCH 08/10] chore: start and target date filter options added --- .../core/filters/date-filter-modal.tsx | 55 +++--------------- web/components/headers/project-issues.tsx | 12 +++- .../calendar/dropdowns/months-dropdown.tsx | 2 +- .../header/filters/filters-selection.tsx | 39 ++++++++++--- .../header/filters/start-date.tsx | 55 +++++++++++++----- .../header/filters/target-date.tsx | 57 ++++++++++++++----- .../header/helpers/filter-header.tsx | 2 +- web/constants/filters.ts | 29 ++-------- 8 files changed, 139 insertions(+), 112 deletions(-) diff --git a/web/components/core/filters/date-filter-modal.tsx b/web/components/core/filters/date-filter-modal.tsx index eb285fd6f6f..4a1ec0e8b98 100644 --- a/web/components/core/filters/date-filter-modal.tsx +++ b/web/components/core/filters/date-filter-modal.tsx @@ -1,11 +1,8 @@ import { Fragment } from "react"; - -// react-hook-form import { Controller, useForm } from "react-hook-form"; -// react-datepicker import DatePicker from "react-datepicker"; -// headless ui import { Dialog, Transition } from "@headlessui/react"; + // components import { DateFilterSelect } from "./date-filter-select"; // ui @@ -14,15 +11,12 @@ import { PrimaryButton, SecondaryButton } from "components/ui"; import { XMarkIcon } from "@heroicons/react/20/solid"; // helpers import { renderDateFormat, renderShortDateWithYearFormat } from "helpers/date-time.helper"; -import { IIssueFilterOptions } from "types"; type Props = { title: string; - field: keyof IIssueFilterOptions; - filters: IIssueFilterOptions; handleClose: () => void; isOpen: boolean; - onSelect: (option: any) => void; + onSelect: (val: string[]) => void; }; type TFormValues = { @@ -37,14 +31,7 @@ const defaultValues: TFormValues = { date2: new Date(new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()), }; -export const DateFilterModal: React.FC = ({ - title, - field, - filters, - handleClose, - isOpen, - onSelect, -}) => { +export const DateFilterModal: React.FC = ({ title, handleClose, isOpen, onSelect }) => { const { handleSubmit, watch, control } = useForm({ defaultValues, }); @@ -52,32 +39,13 @@ export const DateFilterModal: React.FC = ({ const handleFormSubmit = (formData: TFormValues) => { const { filterType, date1, date2 } = formData; - if (filterType === "range") { - onSelect({ - key: field, - value: [`${renderDateFormat(date1)};after`, `${renderDateFormat(date2)};before`], - }); - } else { - const filteredArray = (filters?.[field] as string[])?.filter((item) => { - if (item?.includes(filterType)) return false; + if (filterType === "range") onSelect([`${renderDateFormat(date1)};after`, `${renderDateFormat(date2)};before`]); + else onSelect([`${renderDateFormat(date1)};${filterType}`]); - return true; - }); - - const filterOne = filteredArray && filteredArray?.length > 0 ? filteredArray[0] : null; - if (filterOne) - onSelect({ key: field, value: [filterOne, `${renderDateFormat(date1)};${filterType}`] }); - else - onSelect({ - key: field, - value: [`${renderDateFormat(date1)};${filterType}`], - }); - } handleClose(); }; - const isInvalid = - watch("filterType") === "range" ? new Date(watch("date1")) > new Date(watch("date2")) : false; + const isInvalid = watch("filterType") === "range" ? new Date(watch("date1")) > new Date(watch("date2")) : false; const nextDay = new Date(watch("date1")); nextDay.setDate(nextDay.getDate() + 1); @@ -117,10 +85,7 @@ export const DateFilterModal: React.FC = ({ )} /> - +
= ({ Cancel - + Apply
diff --git a/web/components/headers/project-issues.tsx b/web/components/headers/project-issues.tsx index 32eae2ea2b0..d3d18729d97 100644 --- a/web/components/headers/project-issues.tsx +++ b/web/components/headers/project-issues.tsx @@ -31,13 +31,19 @@ export const ProjectIssuesHeader: React.FC = observer(() => { ); const handleFiltersUpdate = useCallback( - (key: keyof IIssueFilterOptions, value: string) => { + (key: keyof IIssueFilterOptions, value: string | string[]) => { if (!workspaceSlug || !projectId) return; const newValues = issueFilterStore.userFilters?.[key] ?? []; - if (issueFilterStore.userFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); - else newValues.push(value); + if (Array.isArray(value)) { + value.forEach((val) => { + if (!newValues.includes(val)) newValues.push(val); + }); + } else { + if (issueFilterStore.userFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); + else newValues.push(value); + } issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { filters: { diff --git a/web/components/issues/issue-layouts/calendar/dropdowns/months-dropdown.tsx b/web/components/issues/issue-layouts/calendar/dropdowns/months-dropdown.tsx index b92fe96b904..a6e410e0932 100644 --- a/web/components/issues/issue-layouts/calendar/dropdowns/months-dropdown.tsx +++ b/web/components/issues/issue-layouts/calendar/dropdowns/months-dropdown.tsx @@ -26,7 +26,7 @@ export const CalendarMonthsDropdown: React.FC = observer(() => { const lastDay = new Date(daysList[daysList.length - 1]); if (firstDay.getMonth() === lastDay.getMonth() && firstDay.getFullYear() === lastDay.getFullYear()) - return `${MONTHS_LIST[firstDay.getMonth() + 1].shortTitle} ${firstDay.getFullYear()}`; + return `${MONTHS_LIST[firstDay.getMonth() + 1].title} ${firstDay.getFullYear()}`; if (firstDay.getFullYear() !== lastDay.getFullYear()) { return `${MONTHS_LIST[firstDay.getMonth() + 1].shortTitle} ${firstDay.getFullYear()} - ${ diff --git a/web/components/issues/issue-layouts/header/filters/filters-selection.tsx b/web/components/issues/issue-layouts/header/filters/filters-selection.tsx index 1d17eadfff6..cf65b7a7024 100644 --- a/web/components/issues/issue-layouts/header/filters/filters-selection.tsx +++ b/web/components/issues/issue-layouts/header/filters/filters-selection.tsx @@ -9,8 +9,10 @@ import { FilterCreatedBy, FilterLabels, FilterPriority, + FilterStartDate, FilterState, FilterStateGroup, + FilterTargetDate, } from "components/issues"; // icons import { Search, X } from "lucide-react"; @@ -20,10 +22,11 @@ import { getStatesList } from "helpers/state.helper"; import { IIssueFilterOptions } from "types"; // constants import { ILayoutDisplayFiltersOptions, ISSUE_PRIORITIES, ISSUE_STATE_GROUPS } from "constants/issue"; +import { DATE_FILTER_OPTIONS } from "constants/filters"; type Props = { filters: IIssueFilterOptions; - handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string) => void; + handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string | string[]) => void; layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions; projectId: string; }; @@ -67,6 +70,14 @@ export const FilterSelection: React.FC = observer((props) => { currentLength: 5, totalLength: statesList?.length ?? 0, }, + start_date: { + currentLength: 5, + totalLength: DATE_FILTER_OPTIONS.length + 1, + }, + target_date: { + currentLength: 5, + totalLength: DATE_FILTER_OPTIONS.length + 1, + }, }); const handleViewMore = (filterName: keyof IIssueFilterOptions) => { @@ -118,7 +129,7 @@ export const FilterSelection: React.FC = observer((props) => { return (
-
+
= observer((props) => { )} {/* start_date */} - {/* {isFilterEnabled("start_date") && ( + {isFilterEnabled("start_date") && (
- + handleFiltersUpdate("start_date", val)} + itemsToRender={filtersToRender.start_date?.currentLength ?? 0} + searchQuery={filtersSearchQuery} + />
- )} */} + )} - {/* due_date */} - {/* {isFilterEnabled("target_date") && ( + {/* target_date */} + {isFilterEnabled("target_date") && (
- + handleFiltersUpdate("target_date", val)} + itemsToRender={filtersToRender.target_date?.currentLength ?? 0} + searchQuery={filtersSearchQuery} + />
- )} */} + )}
); diff --git a/web/components/issues/issue-layouts/header/filters/start-date.tsx b/web/components/issues/issue-layouts/header/filters/start-date.tsx index a960f1f4c14..ad5ed955af8 100644 --- a/web/components/issues/issue-layouts/header/filters/start-date.tsx +++ b/web/components/issues/issue-layouts/header/filters/start-date.tsx @@ -1,21 +1,39 @@ import React, { useState } from "react"; import { observer } from "mobx-react-lite"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; // components import { FilterHeader, FilterOption } from "components/issues"; +import { DateFilterModal } from "components/core"; +// constants +import { DATE_FILTER_OPTIONS } from "constants/filters"; + +type Props = { + appliedFilters: string[] | null; + handleUpdate: (val: string | string[]) => void; + itemsToRender: number; + searchQuery: string; +}; + +export const FilterStartDate: React.FC = observer((props) => { + const { appliedFilters, handleUpdate, itemsToRender, searchQuery } = props; -export const FilterStartDate = observer(() => { const [previewEnabled, setPreviewEnabled] = useState(true); + const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false); - const store = useMobxStore(); - const { issueFilter: issueFilterStore } = store; + const appliedFiltersCount = appliedFilters?.length ?? 0; - const appliedFiltersCount = issueFilterStore.userFilters?.start_date?.length ?? 0; + const filteredOptions = DATE_FILTER_OPTIONS.filter((d) => d.name.toLowerCase().includes(searchQuery.toLowerCase())); return ( <> + {isDateFilterModalOpen && ( + setIsDateFilterModalOpen(false)} + isOpen={isDateFilterModalOpen} + onSelect={(val) => handleUpdate(val)} + title="Start date" + /> + )} 0 ? ` (${appliedFiltersCount})` : ""}`} isPreviewEnabled={previewEnabled} @@ -23,16 +41,27 @@ export const FilterStartDate = observer(() => { /> {previewEnabled && (
- {issueFilterStore?.userFilters?.start_date && - issueFilterStore?.userFilters?.start_date.length > 0 && - issueFilterStore?.userFilters?.start_date.map((_startDate) => ( + {filteredOptions.length > 0 ? ( + <> + {filteredOptions.slice(0, itemsToRender).map((option) => ( + handleUpdate(option.value)} + title={option.name} + multiple={false} + /> + ))} setIsDateFilterModalOpen(true)} + title="Custom" multiple={false} /> - ))} + + ) : ( +

No matches found

+ )}
)} diff --git a/web/components/issues/issue-layouts/header/filters/target-date.tsx b/web/components/issues/issue-layouts/header/filters/target-date.tsx index 21cd8f2f212..c6ee45b04d3 100644 --- a/web/components/issues/issue-layouts/header/filters/target-date.tsx +++ b/web/components/issues/issue-layouts/header/filters/target-date.tsx @@ -1,38 +1,67 @@ import React, { useState } from "react"; import { observer } from "mobx-react-lite"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; // components import { FilterHeader, FilterOption } from "components/issues"; +import { DateFilterModal } from "components/core"; +// constants +import { DATE_FILTER_OPTIONS } from "constants/filters"; + +type Props = { + appliedFilters: string[] | null; + handleUpdate: (val: string | string[]) => void; + itemsToRender: number; + searchQuery: string; +}; + +export const FilterTargetDate: React.FC = observer((props) => { + const { appliedFilters, handleUpdate, itemsToRender, searchQuery } = props; -export const FilterTargetDate = observer(() => { const [previewEnabled, setPreviewEnabled] = useState(true); + const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false); - const store = useMobxStore(); - const { issueFilter: issueFilterStore } = store; + const appliedFiltersCount = appliedFilters?.length ?? 0; - const appliedFiltersCount = issueFilterStore.userFilters?.target_date?.length ?? 0; + const filteredOptions = DATE_FILTER_OPTIONS.filter((d) => d.name.toLowerCase().includes(searchQuery.toLowerCase())); return ( <> + {isDateFilterModalOpen && ( + setIsDateFilterModalOpen(false)} + isOpen={isDateFilterModalOpen} + onSelect={(val) => handleUpdate(val)} + title="Due date" + /> + )} 0 ? ` (${appliedFiltersCount})` : ""}`} isPreviewEnabled={previewEnabled} handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)} /> {previewEnabled && ( -
- {issueFilterStore?.userFilters?.target_date && - issueFilterStore?.userFilters?.target_date.length > 0 && - issueFilterStore?.userFilters?.target_date.map((_targetDate) => ( +
+ {filteredOptions.length > 0 ? ( + <> + {filteredOptions.slice(0, itemsToRender).map((option) => ( + handleUpdate(option.value)} + title={option.name} + multiple={false} + /> + ))} setIsDateFilterModalOpen(true)} + title="Custom" multiple={false} /> - ))} + + ) : ( +

No matches found

+ )}
)} diff --git a/web/components/issues/issue-layouts/header/helpers/filter-header.tsx b/web/components/issues/issue-layouts/header/helpers/filter-header.tsx index 6dd0c7a02ff..4513b079550 100644 --- a/web/components/issues/issue-layouts/header/helpers/filter-header.tsx +++ b/web/components/issues/issue-layouts/header/helpers/filter-header.tsx @@ -9,7 +9,7 @@ interface IFilterHeader { } export const FilterHeader = ({ title, isPreviewEnabled, handleIsPreviewEnabled }: IFilterHeader) => ( -
+
{title}