diff --git a/web/components/core/views/all-views.tsx b/web/components/core/views/all-views.tsx index e8478e71a..c5fcc7300 100644 --- a/web/components/core/views/all-views.tsx +++ b/web/components/core/views/all-views.tsx @@ -6,7 +6,7 @@ import useSWR from "swr"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { CalendarLayout, GanttLayout, KanBanLayout, SpreadsheetLayout } from "components/issues"; +import { AppliedFiltersList, CalendarLayout, GanttLayout, KanBanLayout, SpreadsheetLayout } from "components/issues"; export const AllViews: React.FC = observer(() => { const router = useRouter(); @@ -34,7 +34,8 @@ export const AllViews: React.FC = observer(() => { const activeLayout = issueFilterStore.userDisplayFilters.layout; return ( -
+
+ {activeLayout === "kanban" ? ( ) : activeLayout === "calendar" ? ( diff --git a/web/components/issue-layouts/root.tsx b/web/components/issue-layouts/root.tsx index 38029c633..90e1394d4 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 "../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 { LayoutSelection } from "../issues/issue-layouts/filters/header/layout-selection"; +import { IssueDropdown } from "../issues/issue-layouts/filters/header/helpers/dropdown"; +import { FilterSelection } from "../issues/issue-layouts/filters/header/filters/filters-selection"; +import { DisplayFiltersSelection } from "../issues/issue-layouts/filters/header/display-filters"; import { FilterPreview } from "./filters-preview"; 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 a6e410e09..2b6c43b25 100644 --- a/web/components/issues/issue-layouts/calendar/dropdowns/months-dropdown.tsx +++ b/web/components/issues/issue-layouts/calendar/dropdowns/months-dropdown.tsx @@ -96,9 +96,7 @@ export const CalendarMonthsDropdown: React.FC = observer(() => { +
+ ))} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx b/web/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx new file mode 100644 index 000000000..1a103f8c7 --- /dev/null +++ b/web/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx @@ -0,0 +1,141 @@ +import { useRouter } from "next/router"; +import { observer } from "mobx-react-lite"; + +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// components +import { + AppliedDateFilters, + AppliedLabelsFilters, + AppliedMembersFilters, + AppliedPriorityFilters, + AppliedStateFilters, + AppliedStateGroupFilters, +} from "components/issues"; +// icons +import { X } from "lucide-react"; +// helpers +import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; +// types +import { IIssueFilterOptions } from "types"; + +export const AppliedFiltersList: React.FC = observer(() => { + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { issueFilter: issueFilterStore, project: projectStore } = useMobxStore(); + + const userFilters = issueFilterStore.userFilters; + + // filters whose value not null or empty array + const appliedFilters: IIssueFilterOptions = {}; + Object.entries(userFilters).forEach(([key, value]) => { + if (!value) return; + + if (Array.isArray(value) && value.length === 0) return; + + appliedFilters[key as keyof IIssueFilterOptions] = value; + }); + + const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => { + if (!workspaceSlug || !projectId) return; + + // remove all values of the key if value is null + if (!value) { + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + filters: { + [key]: null, + }, + }); + return; + } + + // remove the passed value from the key + let newValues = issueFilterStore.userFilters?.[key] ?? []; + newValues = newValues.filter((val) => val !== value); + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + filters: { + [key]: newValues, + }, + }); + }; + + const handleClearAllFilters = () => { + if (!workspaceSlug || !projectId) return; + + const newFilters: IIssueFilterOptions = {}; + Object.keys(userFilters).forEach((key) => { + newFilters[key as keyof IIssueFilterOptions] = null; + }); + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + filters: { ...newFilters }, + }); + }; + + // return if no filters are applied + if (Object.keys(appliedFilters).length === 0) return null; + + return ( +
+ {Object.entries(appliedFilters).map(([key, value]) => { + const filterKey = key as keyof IIssueFilterOptions; + + return ( +
+ {replaceUnderscoreIfSnakeCase(filterKey)} + {(filterKey === "assignees" || filterKey === "created_by" || filterKey === "subscriber") && ( + handleRemoveFilter(filterKey, val)} + members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)} + values={value} + /> + )} + {(filterKey === "start_date" || filterKey === "target_date") && ( + handleRemoveFilter(filterKey, val)} values={value} /> + )} + {filterKey === "labels" && ( + handleRemoveFilter("labels", val)} + labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []} + values={value} + /> + )} + {filterKey === "priority" && ( + handleRemoveFilter("priority", val)} values={value} /> + )} + {filterKey === "state" && ( + handleRemoveFilter("state", val)} + states={projectStore.states?.[projectId?.toString() ?? ""]} + values={value} + /> + )} + {filterKey === "state_group" && ( + handleRemoveFilter("state_group", val)} values={value} /> + )} + +
+ ); + })} + +
+ ); +}); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/index.ts b/web/components/issues/issue-layouts/filters/applied-filters/index.ts new file mode 100644 index 000000000..4f9bff886 --- /dev/null +++ b/web/components/issues/issue-layouts/filters/applied-filters/index.ts @@ -0,0 +1,7 @@ +export * from "./date"; +export * from "./filters-list"; +export * from "./label"; +export * from "./members"; +export * from "./priority"; +export * from "./state"; +export * from "./state-group"; diff --git a/web/components/issues/issue-layouts/filters/applied-filters/label.tsx b/web/components/issues/issue-layouts/filters/applied-filters/label.tsx new file mode 100644 index 000000000..abc5a8b2c --- /dev/null +++ b/web/components/issues/issue-layouts/filters/applied-filters/label.tsx @@ -0,0 +1,45 @@ +import { observer } from "mobx-react-lite"; + +// icons +import { X } from "lucide-react"; +// types +import { IIssueLabels } from "types"; + +type Props = { + handleRemove: (val: string) => void; + labels: IIssueLabels[] | undefined; + values: string[]; +}; + +export const AppliedLabelsFilters: React.FC = observer((props) => { + const { handleRemove, labels, values } = props; + + return ( +
+ {values.map((labelId) => { + const labelDetails = labels?.find((l) => l.id === labelId); + + if (!labelDetails) return null; + + return ( +
+ + {labelDetails.name} + +
+ ); + })} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/members.tsx b/web/components/issues/issue-layouts/filters/applied-filters/members.tsx new file mode 100644 index 000000000..0c6d0a6b8 --- /dev/null +++ b/web/components/issues/issue-layouts/filters/applied-filters/members.tsx @@ -0,0 +1,42 @@ +import { observer } from "mobx-react-lite"; + +// ui +import { Avatar } from "components/ui"; +// icons +import { X } from "lucide-react"; +// types +import { IUserLite } from "types"; + +type Props = { + handleRemove: (val: string) => void; + members: IUserLite[] | undefined; + values: string[]; +}; + +export const AppliedMembersFilters: React.FC = observer((props) => { + const { handleRemove, members, values } = props; + + return ( +
+ {values.map((memberId) => { + const memberDetails = members?.find((m) => m.id === memberId); + + if (!memberDetails) return null; + + return ( +
+ + {memberDetails.display_name} + +
+ ); + })} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/priority.tsx b/web/components/issues/issue-layouts/filters/applied-filters/priority.tsx new file mode 100644 index 000000000..44328afff --- /dev/null +++ b/web/components/issues/issue-layouts/filters/applied-filters/priority.tsx @@ -0,0 +1,47 @@ +import { observer } from "mobx-react-lite"; + +// icons +import { PriorityIcon } from "components/icons"; +import { X } from "lucide-react"; +// types +import { TIssuePriorities } from "types"; + +type Props = { + handleRemove: (val: string) => void; + values: string[]; +}; + +export const AppliedPriorityFilters: React.FC = observer((props) => { + const { handleRemove, values } = props; + + return ( +
+ {values.map((priority) => ( +
+ + {priority} + +
+ ))} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/state-group.tsx b/web/components/issues/issue-layouts/filters/applied-filters/state-group.tsx new file mode 100644 index 000000000..bfeab73ab --- /dev/null +++ b/web/components/issues/issue-layouts/filters/applied-filters/state-group.tsx @@ -0,0 +1,33 @@ +import { observer } from "mobx-react-lite"; + +// icons +import { StateGroupIcon } from "components/icons"; +import { X } from "lucide-react"; +import { TStateGroups } from "types"; + +type Props = { + handleRemove: (val: string) => void; + values: string[]; +}; + +export const AppliedStateGroupFilters: React.FC = observer((props) => { + const { handleRemove, values } = props; + + return ( +
+ {values.map((stateGroup) => ( +
+ + {stateGroup} + +
+ ))} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/state.tsx b/web/components/issues/issue-layouts/filters/applied-filters/state.tsx new file mode 100644 index 000000000..830c92088 --- /dev/null +++ b/web/components/issues/issue-layouts/filters/applied-filters/state.tsx @@ -0,0 +1,45 @@ +import { observer } from "mobx-react-lite"; + +// icons +import { StateGroupIcon } from "components/icons"; +import { X } from "lucide-react"; +// helpers +import { getStatesList } from "helpers/state.helper"; +// types +import { IStateResponse } from "types"; + +type Props = { + handleRemove: (val: string) => void; + states: IStateResponse | undefined; + values: string[]; +}; + +export const AppliedStateFilters: React.FC = observer((props) => { + const { handleRemove, states, values } = props; + + const statesList = getStatesList(states); + + return ( +
+ {values.map((stateId) => { + const stateDetails = statesList?.find((s) => s.id === stateId); + + if (!stateDetails) return null; + + return ( +
+ + {stateDetails.name} + +
+ ); + })} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx b/web/components/issues/issue-layouts/filters/header/display-filters/display-filters-selection.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/display-filters/display-filters-selection.tsx rename to web/components/issues/issue-layouts/filters/header/display-filters/display-filters-selection.tsx diff --git a/web/components/issues/issue-layouts/header/display-filters/display-properties.tsx b/web/components/issues/issue-layouts/filters/header/display-filters/display-properties.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/display-filters/display-properties.tsx rename to web/components/issues/issue-layouts/filters/header/display-filters/display-properties.tsx diff --git a/web/components/issues/issue-layouts/header/display-filters/extra-options.tsx b/web/components/issues/issue-layouts/filters/header/display-filters/extra-options.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/display-filters/extra-options.tsx rename to web/components/issues/issue-layouts/filters/header/display-filters/extra-options.tsx diff --git a/web/components/issues/issue-layouts/header/display-filters/group-by.tsx b/web/components/issues/issue-layouts/filters/header/display-filters/group-by.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/display-filters/group-by.tsx rename to web/components/issues/issue-layouts/filters/header/display-filters/group-by.tsx diff --git a/web/components/issues/issue-layouts/header/display-filters/index.ts b/web/components/issues/issue-layouts/filters/header/display-filters/index.ts similarity index 100% rename from web/components/issues/issue-layouts/header/display-filters/index.ts rename to web/components/issues/issue-layouts/filters/header/display-filters/index.ts diff --git a/web/components/issues/issue-layouts/header/display-filters/issue-type.tsx b/web/components/issues/issue-layouts/filters/header/display-filters/issue-type.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/display-filters/issue-type.tsx rename to web/components/issues/issue-layouts/filters/header/display-filters/issue-type.tsx diff --git a/web/components/issues/issue-layouts/header/display-filters/order-by.tsx b/web/components/issues/issue-layouts/filters/header/display-filters/order-by.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/display-filters/order-by.tsx rename to web/components/issues/issue-layouts/filters/header/display-filters/order-by.tsx diff --git a/web/components/issues/issue-layouts/header/display-filters/sub-group-by.tsx b/web/components/issues/issue-layouts/filters/header/display-filters/sub-group-by.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/display-filters/sub-group-by.tsx rename to web/components/issues/issue-layouts/filters/header/display-filters/sub-group-by.tsx diff --git a/web/components/issues/issue-layouts/header/filters/assignees.tsx b/web/components/issues/issue-layouts/filters/header/filters/assignee.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/filters/assignees.tsx rename to web/components/issues/issue-layouts/filters/header/filters/assignee.tsx diff --git a/web/components/issues/issue-layouts/header/filters/created-by.tsx b/web/components/issues/issue-layouts/filters/header/filters/created-by.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/filters/created-by.tsx rename to web/components/issues/issue-layouts/filters/header/filters/created-by.tsx diff --git a/web/components/issues/issue-layouts/header/filters/filters-selection.tsx b/web/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx similarity index 99% rename from web/components/issues/issue-layouts/header/filters/filters-selection.tsx rename to web/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx index f651f8a05..2e31a16a2 100644 --- a/web/components/issues/issue-layouts/header/filters/filters-selection.tsx +++ b/web/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx @@ -147,7 +147,7 @@ export const FilterSelection: React.FC = observer((props) => { )} -
+
{/* priority */} {isFilterEnabled("priority") && (
diff --git a/web/components/issues/issue-layouts/header/filters/index.ts b/web/components/issues/issue-layouts/filters/header/filters/index.ts similarity index 89% rename from web/components/issues/issue-layouts/header/filters/index.ts rename to web/components/issues/issue-layouts/filters/header/filters/index.ts index 40190a10e..847b30874 100644 --- a/web/components/issues/issue-layouts/header/filters/index.ts +++ b/web/components/issues/issue-layouts/filters/header/filters/index.ts @@ -1,4 +1,4 @@ -export * from "./assignees"; +export * from "./assignee"; export * from "./created-by"; export * from "./filters-selection"; export * from "./labels"; diff --git a/web/components/issues/issue-layouts/header/filters/labels.tsx b/web/components/issues/issue-layouts/filters/header/filters/labels.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/filters/labels.tsx rename to web/components/issues/issue-layouts/filters/header/filters/labels.tsx diff --git a/web/components/issues/issue-layouts/header/filters/priority.tsx b/web/components/issues/issue-layouts/filters/header/filters/priority.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/filters/priority.tsx rename to web/components/issues/issue-layouts/filters/header/filters/priority.tsx diff --git a/web/components/issues/issue-layouts/header/filters/start-date.tsx b/web/components/issues/issue-layouts/filters/header/filters/start-date.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/filters/start-date.tsx rename to web/components/issues/issue-layouts/filters/header/filters/start-date.tsx diff --git a/web/components/issues/issue-layouts/header/filters/state-group.tsx b/web/components/issues/issue-layouts/filters/header/filters/state-group.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/filters/state-group.tsx rename to web/components/issues/issue-layouts/filters/header/filters/state-group.tsx diff --git a/web/components/issues/issue-layouts/header/filters/state.tsx b/web/components/issues/issue-layouts/filters/header/filters/state.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/filters/state.tsx rename to web/components/issues/issue-layouts/filters/header/filters/state.tsx diff --git a/web/components/issues/issue-layouts/header/filters/target-date.tsx b/web/components/issues/issue-layouts/filters/header/filters/target-date.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/filters/target-date.tsx rename to web/components/issues/issue-layouts/filters/header/filters/target-date.tsx diff --git a/web/components/issues/issue-layouts/header/helpers/dropdown.tsx b/web/components/issues/issue-layouts/filters/header/helpers/dropdown.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/helpers/dropdown.tsx rename to web/components/issues/issue-layouts/filters/header/helpers/dropdown.tsx diff --git a/web/components/issues/issue-layouts/header/helpers/filter-header.tsx b/web/components/issues/issue-layouts/filters/header/helpers/filter-header.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/helpers/filter-header.tsx rename to web/components/issues/issue-layouts/filters/header/helpers/filter-header.tsx diff --git a/web/components/issues/issue-layouts/header/helpers/filter-option.tsx b/web/components/issues/issue-layouts/filters/header/helpers/filter-option.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/helpers/filter-option.tsx rename to web/components/issues/issue-layouts/filters/header/helpers/filter-option.tsx diff --git a/web/components/issues/issue-layouts/header/helpers/index.ts b/web/components/issues/issue-layouts/filters/header/helpers/index.ts similarity index 100% rename from web/components/issues/issue-layouts/header/helpers/index.ts rename to web/components/issues/issue-layouts/filters/header/helpers/index.ts diff --git a/web/components/issues/issue-layouts/header/index.ts b/web/components/issues/issue-layouts/filters/header/index.ts similarity index 100% rename from web/components/issues/issue-layouts/header/index.ts rename to web/components/issues/issue-layouts/filters/header/index.ts diff --git a/web/components/issues/issue-layouts/header/layout-selection.tsx b/web/components/issues/issue-layouts/filters/header/layout-selection.tsx similarity index 100% rename from web/components/issues/issue-layouts/header/layout-selection.tsx rename to web/components/issues/issue-layouts/filters/header/layout-selection.tsx diff --git a/web/components/issues/issue-layouts/filters/index.ts b/web/components/issues/issue-layouts/filters/index.ts new file mode 100644 index 000000000..427d96943 --- /dev/null +++ b/web/components/issues/issue-layouts/filters/index.ts @@ -0,0 +1,2 @@ +export * from "./header"; +export * from "./applied-filters"; diff --git a/web/components/issues/issue-layouts/index.ts b/web/components/issues/issue-layouts/index.ts index f000ec7bf..8840e08df 100644 --- a/web/components/issues/issue-layouts/index.ts +++ b/web/components/issues/issue-layouts/index.ts @@ -1,5 +1,5 @@ export * from "./calendar"; +export * from "./filters"; export * from "./gantt"; -export * from "./header"; export * from "./kanban"; export * from "./spreadsheet"; diff --git a/web/store/issue_filters.ts b/web/store/issue_filters.ts index ab2d6e0dd..c8af5a51f 100644 --- a/web/store/issue_filters.ts +++ b/web/store/issue_filters.ts @@ -112,22 +112,6 @@ class IssueFilterStore implements IIssueFilterStore { return computedFilters; }; - calendarLayoutDateRange = () => { - const { activeMonthDate, activeWeekDate } = this.rootStore.calendar.calendarFilters; - - const calendarLayout = this.userDisplayFilters.calendar?.layout ?? "month"; - - let filterDate = new Date(); - - if (calendarLayout === "month") filterDate = activeMonthDate; - else filterDate = activeWeekDate; - - const startOfMonth = renderDateFormat(new Date(filterDate.getFullYear(), filterDate.getMonth(), 1)); - const endOfMonth = renderDateFormat(new Date(filterDate.getFullYear(), filterDate.getMonth() + 1, 0)); - - return [`${startOfMonth};after`, `${endOfMonth};before`]; - }; - get appliedFilters(): TIssueParams[] | null { if ( !this.userFilters || @@ -155,12 +139,12 @@ class IssueFilterStore implements IIssueFilterStore { start_target_date: this.userDisplayFilters?.start_target_date || true, }; - if (this.userDisplayFilters.layout === "calendar") filteredRouteParams.target_date = this.calendarLayoutDateRange(); - if (this.userDisplayFilters.layout === "gantt_chart") filteredRouteParams.start_target_date = true; - const filteredParams = handleIssueQueryParamsByLayout(this.userDisplayFilters.layout, "issues"); if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); + if (this.userDisplayFilters.layout === "calendar") filteredRouteParams.group_by = "target_date"; + if (this.userDisplayFilters.layout === "gantt_chart") filteredRouteParams.start_target_date = true; + return filteredRouteParams; }