chore: filters dropdown (#2260)

* chore: project issues topbar

* style: theming and minor UI fixes

* refactor: file structure

* chore: layout wise authorization added

* style: filter dropdowns

* chore: add fetch keys

* feat: search option for filters

* fix: sticky headers

* chore: sub_group_by section added
This commit is contained in:
Aaryan Khandelwal 2023-09-25 19:17:40 +05:30 committed by GitHub
parent 9a8dcc349f
commit 9831418a11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 786 additions and 341 deletions

View File

@ -10,58 +10,65 @@ import {
FilterGroupBy,
FilterIssueType,
FilterOrderBy,
FilterSubGroupBy,
} from "components/issue-layouts";
// helpers
import { issueFilterVisibilityData } from "helpers/issue.helper";
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
export const DisplayFiltersSelection = observer(() => {
const { issueFilter: issueFilterStore } = useMobxStore();
const isDisplayFilterEnabled = (displayFilter: string) =>
issueFilterVisibilityData.issues.display_filters[issueFilterStore.userDisplayFilters.layout ?? "list"].includes(
displayFilter
);
ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues.display_filters[
issueFilterStore.userDisplayFilters.layout ?? "list"
].includes(displayFilter);
return (
<div className="w-full h-full overflow-hidden select-none relative flex flex-col divide-y divide-custom-border-200 px-0.5">
{/* <div className="flex-shrink-0 p-2 text-sm">Search container</div> */}
<div className="w-full h-full overflow-hidden overflow-y-auto relative pb-2 divide-y divide-custom-border-200">
{/* display properties */}
{issueFilterVisibilityData.issues.display_properties[issueFilterStore.userDisplayFilters.layout ?? "list"] && (
<div className="pb-2 px-2">
<FilterDisplayProperties />
</div>
)}
<div className="w-full h-full overflow-hidden overflow-y-auto relative px-2.5 divide-y divide-custom-border-200">
{/* display properties */}
{ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues.display_properties[
issueFilterStore.userDisplayFilters.layout ?? "list"
] && (
<div className="py-2">
<FilterDisplayProperties />
</div>
)}
{/* group by */}
{isDisplayFilterEnabled("group_by") && (
<div className="py-1 px-2">
<FilterGroupBy />
</div>
)}
{/* group by */}
{isDisplayFilterEnabled("group_by") && (
<div className="py-2">
<FilterGroupBy />
</div>
)}
{/* order by */}
{isDisplayFilterEnabled("order_by") && (
<div className="py-1 px-2">
<FilterOrderBy />
</div>
)}
{/* sub-group by */}
{isDisplayFilterEnabled("sub_group_by") && (
<div className="py-2">
<FilterSubGroupBy />
</div>
)}
{/* issue type */}
{isDisplayFilterEnabled("issue_type") && (
<div className="py-1 px-2">
<FilterIssueType />
</div>
)}
{/* order by */}
{isDisplayFilterEnabled("order_by") && (
<div className="py-2">
<FilterOrderBy />
</div>
)}
{/* Options */}
{issueFilterVisibilityData.issues.extra_options[issueFilterStore.userDisplayFilters.layout ?? "list"]
.access && (
<div className="pt-1 px-2">
<FilterExtraOptions />
</div>
)}
</div>
{/* issue type */}
{isDisplayFilterEnabled("issue_type") && (
<div className="py-2">
<FilterIssueType />
</div>
)}
{/* Options */}
{ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues.extra_options[issueFilterStore.userDisplayFilters.layout ?? "list"]
.access && (
<div className="py-2">
<FilterExtraOptions />
</div>
)}
</div>
);
});

View File

@ -28,14 +28,14 @@ export const FilterDisplayProperties = observer(() => {
};
return (
<div>
<>
<FilterHeader
title="Display Properties"
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="flex items-center gap-2 flex-wrap mx-1 mt-1">
<div className="flex items-center gap-2 flex-wrap mt-1">
{ISSUE_DISPLAY_PROPERTIES.map((displayProperty) => (
<button
key={displayProperty.key}
@ -56,6 +56,6 @@ export const FilterDisplayProperties = observer(() => {
))}
</div>
)}
</div>
</>
);
});

View File

@ -6,10 +6,8 @@ import { useMobxStore } from "lib/mobx/store-provider";
// components
import { FilterHeader, FilterOption } from "components/issue-layouts";
// helpers
import { issueFilterVisibilityData } from "helpers/issue.helper";
// constants
import { ISSUE_EXTRA_OPTIONS } from "constants/issue";
import { ISSUE_EXTRA_OPTIONS, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
export const FilterExtraOptions = observer(() => {
const [previewEnabled, setPreviewEnabled] = useState(true);
@ -18,19 +16,19 @@ export const FilterExtraOptions = observer(() => {
const { issueFilter: issueFilterStore } = store;
const isExtraOptionEnabled = (option: string) =>
issueFilterVisibilityData.issues.extra_options[
ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues.extra_options[
issueFilterStore.userDisplayFilters.layout ?? "list"
].values.includes(option);
return (
<div>
<>
<FilterHeader
title="Extra Options"
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">
<div>
{ISSUE_EXTRA_OPTIONS.map((option) => {
if (!isExtraOptionEnabled(option.key)) return null;
@ -44,6 +42,6 @@ export const FilterExtraOptions = observer(() => {
})}
</div>
)}
</div>
</>
);
});

View File

@ -6,8 +6,7 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { FilterHeader } from "../helpers/filter-header";
import { FilterOption } from "../helpers/filter-option";
import { FilterHeader, FilterOption } from "components/issue-layouts";
// types
import { TIssueGroupByOptions } from "types";
// constants
@ -33,14 +32,14 @@ export const FilterGroupBy = observer(() => {
};
return (
<div>
<>
<FilterHeader
title="Group By"
title="Group by"
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-1 pt-1">
<div>
{ISSUE_GROUP_BY_OPTIONS.map((groupBy) => (
<FilterOption
key={groupBy?.key}
@ -52,6 +51,6 @@ export const FilterGroupBy = observer(() => {
))}
</div>
)}
</div>
</>
);
});

View File

@ -4,3 +4,4 @@ export * from "./extra-options";
export * from "./group-by";
export * from "./issue-type";
export * from "./order-by";
export * from "./sub-group-by";

View File

@ -6,8 +6,7 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { FilterHeader } from "../helpers/filter-header";
import { FilterOption } from "../helpers/filter-option";
import { FilterHeader, FilterOption } from "components/issue-layouts";
// types
import { TIssueTypeFilters } from "types";
// constants
@ -33,14 +32,14 @@ export const FilterIssueType = observer(() => {
};
return (
<div>
<>
<FilterHeader
title="Issue Type"
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-1 pt-1">
<div>
{ISSUE_FILTER_OPTIONS.map((issueType) => (
<FilterOption
key={issueType?.key}
@ -52,6 +51,6 @@ export const FilterIssueType = observer(() => {
))}
</div>
)}
</div>
</>
);
});

View File

@ -6,8 +6,7 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { FilterHeader } from "../helpers/filter-header";
import { FilterOption } from "../helpers/filter-option";
import { FilterHeader, FilterOption } from "components/issue-layouts";
// types
import { TIssueOrderByOptions } from "types";
// constants
@ -33,14 +32,14 @@ export const FilterOrderBy = observer(() => {
};
return (
<div>
<>
<FilterHeader
title={"Order By"}
title="Order by"
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">
<div>
{ISSUE_ORDER_BY_OPTIONS.map((orderBy) => (
<FilterOption
key={orderBy?.key}
@ -52,6 +51,6 @@ export const FilterOrderBy = observer(() => {
))}
</div>
)}
</div>
</>
);
});

View File

@ -0,0 +1,64 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
// mobx
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { FilterHeader, FilterOption } from "components/issue-layouts";
// types
import { TIssueGroupByOptions } from "types";
// constants
import { ISSUE_GROUP_BY_OPTIONS } from "constants/issue";
export const FilterSubGroupBy = observer(() => {
const [previewEnabled, setPreviewEnabled] = useState(true);
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const store = useMobxStore();
const { issueFilter: issueFilterStore } = store;
const handleSubGroupBy = (value: TIssueGroupByOptions) => {
if (!workspaceSlug || !projectId) return;
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
display_filters: {
sub_group_by: value,
},
});
};
return (
<>
<FilterHeader
title="Sub-group by"
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div>
{ISSUE_GROUP_BY_OPTIONS.map((subGroupBy) => {
if (
issueFilterStore.userDisplayFilters.group_by !== null &&
issueFilterStore.userDisplayFilters.sub_group_by === issueFilterStore.userDisplayFilters.group_by
)
return null;
return (
<FilterOption
key={subGroupBy?.key}
isChecked={issueFilterStore?.userDisplayFilters?.sub_group_by === subGroupBy?.key ? true : false}
onClick={() => handleSubGroupBy(subGroupBy.key)}
title={subGroupBy.title}
multiple={false}
/>
);
})}
</div>
)}
</>
);
});

View File

@ -6,15 +6,12 @@ import { useMobxStore } from "lib/mobx/store-provider";
// components
import { FilterHeader, FilterOption } from "components/issue-layouts";
// ui
import { Avatar } from "components/ui";
import { Avatar, Loader } from "components/ui";
type Props = {
workspaceSlug: string;
projectId: string;
};
type Props = { workspaceSlug: string; projectId: string; itemsToRender: number };
export const FilterAssignees: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId } = props;
const { workspaceSlug, projectId, itemsToRender } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
@ -34,31 +31,51 @@ export const FilterAssignees: React.FC<Props> = observer((props) => {
});
};
const appliedFiltersCount = issueFilterStore.userFilters?.assignees?.length ?? 0;
const filteredOptions = projectStore.members?.[projectId?.toString() ?? ""]?.filter((member) =>
member.member.display_name.toLowerCase().includes(issueFilterStore.filtersSearchQuery.toLowerCase())
);
return (
<div>
<>
<FilterHeader
title={`Assignees (${issueFilterStore?.userFilters.assignees?.length ?? 0})`}
title={`Assignee${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">
{projectStore.members?.[projectId?.toString() ?? ""]?.map((member) => (
<FilterOption
key={`assignees-${member?.member?.id}`}
isChecked={
issueFilterStore?.userFilters?.assignees != null &&
issueFilterStore?.userFilters?.assignees.includes(member.member?.id)
? true
: false
}
onClick={() => handleUpdateAssignees(member.member?.id)}
icon={<Avatar user={member.member} height="18px" width="18px" />}
title={member.member?.display_name}
/>
))}
<div>
{filteredOptions ? (
filteredOptions.length > 0 ? (
filteredOptions
.slice(0, itemsToRender)
.map((member) => (
<FilterOption
key={`assignees-${member?.member?.id}`}
isChecked={
issueFilterStore?.userFilters?.assignees != null &&
issueFilterStore?.userFilters?.assignees.includes(member.member?.id)
? true
: false
}
onClick={() => handleUpdateAssignees(member.member?.id)}
icon={<Avatar user={member.member} height="18px" width="18px" />}
title={member.member?.display_name}
/>
))
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>
)
) : (
<Loader className="space-y-2">
<Loader.Item height="20px" />
<Loader.Item height="20px" />
<Loader.Item height="20px" />
</Loader>
)}
</div>
)}
</div>
</>
);
});

View File

@ -6,15 +6,12 @@ import { useMobxStore } from "lib/mobx/store-provider";
// components
import { FilterHeader, FilterOption } from "components/issue-layouts";
// ui
import { Avatar } from "components/ui";
import { Avatar, Loader } from "components/ui";
type Props = {
workspaceSlug: string;
projectId: string;
};
type Props = { workspaceSlug: string; projectId: string; itemsToRender: number };
export const FilterCreatedBy: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId } = props;
const { workspaceSlug, projectId, itemsToRender } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
@ -34,26 +31,46 @@ export const FilterCreatedBy: React.FC<Props> = observer((props) => {
});
};
const appliedFiltersCount = issueFilterStore.userFilters?.created_by?.length ?? 0;
const filteredOptions = projectStore.members?.[projectId?.toString() ?? ""]?.filter((member) =>
member.member.display_name.toLowerCase().includes(issueFilterStore.filtersSearchQuery.toLowerCase())
);
return (
<div>
<>
<FilterHeader
title={`Created By (${issueFilterStore?.userFilters.created_by?.length ?? 0})`}
title={`Created by${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-1 pt-1">
{projectStore.members?.[projectId?.toString() ?? ""]?.map((member) => (
<FilterOption
key={`created-by-${member.member?.id}`}
isChecked={issueFilterStore?.userFilters?.created_by?.includes(member.member?.id) ? true : false}
onClick={() => handleUpdateCreatedBy(member.member?.id)}
icon={<Avatar user={member.member} height="18px" width="18px" />}
title={member.member?.display_name}
/>
))}
<div>
{filteredOptions ? (
filteredOptions.length > 0 ? (
filteredOptions
.slice(0, itemsToRender)
.map((member) => (
<FilterOption
key={`created-by-${member.member?.id}`}
isChecked={issueFilterStore?.userFilters?.created_by?.includes(member.member?.id) ? true : false}
onClick={() => handleUpdateCreatedBy(member.member?.id)}
icon={<Avatar user={member.member} height="18px" width="18px" />}
title={member.member?.display_name}
/>
))
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>
)
) : (
<Loader className="space-y-2">
<Loader.Item height="20px" />
<Loader.Item height="20px" />
<Loader.Item height="20px" />
</Loader>
)}
</div>
)}
</div>
</>
);
});

View File

@ -0,0 +1,230 @@
import React, { useState } from "react";
// mobx
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// components
import {
FilterAssignees,
FilterCreatedBy,
FilterLabels,
FilterPriority,
FilterState,
FilterStateGroup,
} from "components/issue-layouts";
// icons
import { Search, X } from "lucide-react";
// helpers
import { getStatesList } from "helpers/state.helper";
// types
import { IIssueFilterOptions } from "types";
// constants
import { ISSUE_PRIORITIES, ISSUE_STATE_GROUPS } from "constants/issue";
type Props = {
workspaceSlug: string;
projectId: string;
};
export const FilterSelection: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId } = props;
const { issueFilter: issueFilterStore, project: projectStore } = useMobxStore();
const statesList = getStatesList(projectStore.states?.[projectId?.toString() ?? ""]);
const [filtersToRender, setFiltersToRender] = useState<{
[key in keyof IIssueFilterOptions]: {
currentLength: number;
totalLength: number;
};
}>({
assignees: {
currentLength: 5,
totalLength: projectStore.members?.[projectId]?.length ?? 0,
},
created_by: {
currentLength: 5,
totalLength: projectStore.members?.[projectId]?.length ?? 0,
},
labels: {
currentLength: 5,
totalLength: projectStore.labels?.[projectId]?.length ?? 0,
},
priority: {
currentLength: 5,
totalLength: ISSUE_PRIORITIES.length,
},
state_group: {
currentLength: 5,
totalLength: ISSUE_STATE_GROUPS.length,
},
state: {
currentLength: 5,
totalLength: statesList?.length ?? 0,
},
});
const handleViewMore = (filterName: keyof IIssueFilterOptions) => {
const filterDetails = filtersToRender[filterName];
if (!filterDetails) return;
if (filterDetails.currentLength <= filterDetails.totalLength)
setFiltersToRender((prev) => ({
...prev,
[filterName]: {
...prev[filterName],
currentLength: filterDetails.currentLength + 5,
},
}));
};
const isViewMoreVisible = (filterName: keyof IIssueFilterOptions): boolean => {
const filterDetails = filtersToRender[filterName];
if (!filterDetails) return false;
return filterDetails.currentLength < filterDetails.totalLength;
};
return (
<div className="w-full h-full flex flex-col overflow-hidden">
<div className="p-2.5 bg-custom-background-100 sticky top-0 z-[1]">
<div className="bg-custom-background-90 border-[0.5px] border-custom-border-200 text-xs rounded flex items-center gap-1.5 px-1.5 py-1">
<Search className="text-custom-text-400" size={12} strokeWidth={2} />
<input
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)}
autoFocus
/>
{issueFilterStore.filtersSearchQuery !== "" && (
<button
type="button"
className="grid place-items-center"
onClick={() => issueFilterStore.updateFiltersSearchQuery("")}
>
<X className="text-custom-text-300" size={12} strokeWidth={2} />
</button>
)}
</div>
</div>
<div className="w-full h-full divide-y divide-custom-border-20 px-2.5 overflow-y-auto">
{/* priority */}
<div className="py-2">
<FilterPriority
workspaceSlug={workspaceSlug}
projectId={projectId}
itemsToRender={filtersToRender.priority?.currentLength ?? 0}
/>
{isViewMoreVisible("priority") && (
<button
className="text-custom-primary-100 text-xs font-medium ml-7"
onClick={() => handleViewMore("priority")}
>
View more
</button>
)}
</div>
{/* state group */}
<div className="py-2">
<FilterStateGroup
workspaceSlug={workspaceSlug}
projectId={projectId}
itemsToRender={filtersToRender.state_group?.currentLength ?? 0}
/>
{isViewMoreVisible("state_group") && (
<button
className="text-custom-primary-100 text-xs font-medium ml-7"
onClick={() => handleViewMore("state_group")}
>
View more
</button>
)}
</div>
{/* state */}
<div className="py-2">
<FilterState
workspaceSlug={workspaceSlug}
projectId={projectId}
itemsToRender={filtersToRender.state?.currentLength ?? 0}
/>
{isViewMoreVisible("state") && (
<button
className="text-custom-primary-100 text-xs font-medium ml-7"
onClick={() => handleViewMore("state")}
>
View more
</button>
)}
</div>
{/* assignees */}
<div className="py-2">
<FilterAssignees
workspaceSlug={workspaceSlug}
projectId={projectId}
itemsToRender={filtersToRender.assignees?.currentLength ?? 0}
/>
{isViewMoreVisible("assignees") && (
<button
className="text-custom-primary-100 text-xs font-medium ml-7"
onClick={() => handleViewMore("assignees")}
>
View more
</button>
)}
</div>
{/* created_by */}
<div className="py-2">
<FilterCreatedBy
workspaceSlug={workspaceSlug}
projectId={projectId}
itemsToRender={filtersToRender.created_by?.currentLength ?? 0}
/>
{isViewMoreVisible("created_by") && (
<button
className="text-custom-primary-100 text-xs font-medium ml-7"
onClick={() => handleViewMore("created_by")}
>
View more
</button>
)}
</div>
{/* labels */}
<div className="py-2">
<FilterLabels
workspaceSlug={workspaceSlug}
projectId={projectId}
itemsToRender={filtersToRender.labels?.currentLength ?? 0}
/>
{isViewMoreVisible("labels") && (
<button
className="text-custom-primary-100 text-xs font-medium ml-7"
onClick={() => handleViewMore("labels")}
>
View more
</button>
)}
</div>
{/* start_date */}
{/* <div>
<FilterStartDate />
</div> */}
{/* due_date */}
{/* <div>
<FilterTargetDate />
</div> */}
</div>
</div>
);
});

View File

@ -1,6 +1,6 @@
export * from "./assignees";
export * from "./created-by";
export * from "./filter-selection";
export * from "./filters-selection";
export * from "./labels";
export * from "./priority";
export * from "./start-date";

View File

@ -1,23 +1,21 @@
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";
// mobx react lite
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// ui
import { Loader } from "components/ui";
const LabelIcons = ({ color }: { color: string }) => (
<span className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: color }} />
);
type Props = {
workspaceSlug: string;
projectId: string;
};
type Props = { workspaceSlug: string; projectId: string; itemsToRender: number };
export const FilterLabels: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId } = props;
const { workspaceSlug, projectId, itemsToRender } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
@ -37,26 +35,46 @@ export const FilterLabels: React.FC<Props> = observer((props) => {
});
};
const appliedFiltersCount = issueFilterStore.userFilters?.labels?.length ?? 0;
const filteredOptions = projectStore.labels?.[projectId?.toString() ?? ""]?.filter((label) =>
label.name.toLowerCase().includes(issueFilterStore.filtersSearchQuery.toLowerCase())
);
return (
<div>
<>
<FilterHeader
title={`Labels (${issueFilterStore.userFilters?.labels?.length ?? 0})`}
title={`Label${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">
{projectStore.labels?.[projectId?.toString() ?? ""]?.map((label) => (
<FilterOption
key={label?.id}
isChecked={issueFilterStore?.userFilters?.labels?.includes(label?.id) ? true : false}
onClick={() => handleUpdateLabels(label?.id)}
icon={<LabelIcons color={label.color} />}
title={label.name}
/>
))}
<div>
{filteredOptions ? (
filteredOptions.length > 0 ? (
filteredOptions
.slice(0, itemsToRender)
.map((label) => (
<FilterOption
key={label?.id}
isChecked={issueFilterStore?.userFilters?.labels?.includes(label?.id) ? true : false}
onClick={() => handleUpdateLabels(label?.id)}
icon={<LabelIcons color={label.color} />}
title={label.name}
/>
))
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>
)
) : (
<Loader className="space-y-2">
<Loader.Item height="20px" />
<Loader.Item height="20px" />
<Loader.Item height="20px" />
</Loader>
)}
</div>
)}
</div>
</>
);
});

View File

@ -50,10 +50,10 @@ const PriorityIcons = ({
);
};
type Props = { workspaceSlug: string; projectId: string };
type Props = { workspaceSlug: string; projectId: string; itemsToRender: number };
export const FilterPriority: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId } = props;
const { workspaceSlug, projectId, itemsToRender } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
@ -73,24 +73,36 @@ export const FilterPriority: React.FC<Props> = observer((props) => {
});
};
const appliedFiltersCount = issueFilterStore.userFilters?.priority?.length ?? 0;
const filteredOptions = ISSUE_PRIORITIES.filter((p) =>
p.key.includes(issueFilterStore.filtersSearchQuery.toLowerCase())
);
return (
<>
<FilterHeader
title={`Priority (${issueFilterStore.userFilters?.priority?.length ?? 0})`}
title={`Priority${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-1 pt-1">
{ISSUE_PRIORITIES.map((priority) => (
<FilterOption
key={priority.key}
isChecked={issueFilterStore.userFilters?.priority?.includes(priority.key) ? true : false}
onClick={() => handleUpdatePriority(priority.key)}
icon={<PriorityIcons priority={priority.key} />}
title={priority.title}
/>
))}
<div>
{filteredOptions.length > 0 ? (
filteredOptions
.slice(0, itemsToRender)
.map((priority) => (
<FilterOption
key={priority.key}
isChecked={issueFilterStore.userFilters?.priority?.includes(priority.key) ? true : false}
onClick={() => handleUpdatePriority(priority.key)}
icon={<PriorityIcons priority={priority.key} />}
title={priority.title}
/>
))
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>
)}
</div>
)}
</>

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useState } from "react";
// mobx
import { observer } from "mobx-react-lite";
@ -7,20 +7,22 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { FilterHeader, FilterOption } from "components/issue-layouts";
export const FilterStartDate = observer(() => {
const [previewEnabled, setPreviewEnabled] = useState(true);
const store = useMobxStore();
const { issueFilter: issueFilterStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(true);
const appliedFiltersCount = issueFilterStore.userFilters?.start_date?.length ?? 0;
return (
<div>
<>
<FilterHeader
title={`Start Date (${issueFilterStore?.userFilters?.start_date?.length})`}
title={`Start date${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-1 pt-1">
<div>
{issueFilterStore?.userFilters?.start_date &&
issueFilterStore?.userFilters?.start_date.length > 0 &&
issueFilterStore?.userFilters?.start_date.map((_startDate) => (
@ -33,6 +35,6 @@ export const FilterStartDate = observer(() => {
))}
</div>
)}
</div>
</>
);
});

View File

@ -10,13 +10,10 @@ import { StateGroupIcon } from "components/icons";
// constants
import { ISSUE_STATE_GROUPS } from "constants/issue";
type Props = {
workspaceSlug: string;
projectId: string;
};
type Props = { workspaceSlug: string; projectId: string; itemsToRender: number };
export const FilterStateGroup: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId } = props;
const { workspaceSlug, projectId, itemsToRender } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
@ -36,26 +33,38 @@ export const FilterStateGroup: React.FC<Props> = observer((props) => {
});
};
const appliedFiltersCount = issueFilterStore.userFilters?.state_group?.length ?? 0;
const filteredOptions = ISSUE_STATE_GROUPS.filter((s) =>
s.key.includes(issueFilterStore.filtersSearchQuery.toLowerCase())
);
return (
<div>
<>
<FilterHeader
title={`State Group (${issueFilterStore.userFilters?.state_group?.length ?? 0})`}
title={`State group${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-1 pt-1">
{ISSUE_STATE_GROUPS.map((stateGroup) => (
<FilterOption
key={stateGroup.key}
isChecked={issueFilterStore.userFilters?.state_group?.includes(stateGroup.key) ? true : false}
onClick={() => handleUpdateStateGroup(stateGroup.key)}
icon={<StateGroupIcon stateGroup={stateGroup.key} />}
title={stateGroup.title}
/>
))}
<div>
{filteredOptions.length > 0 ? (
filteredOptions
.slice(0, itemsToRender)
.map((stateGroup) => (
<FilterOption
key={stateGroup.key}
isChecked={issueFilterStore.userFilters?.state_group?.includes(stateGroup.key) ? true : false}
onClick={() => handleUpdateStateGroup(stateGroup.key)}
icon={<StateGroupIcon stateGroup={stateGroup.key} />}
title={stateGroup.title}
/>
))
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>
)}
</div>
)}
</div>
</>
);
});

View File

@ -1,28 +1,27 @@
import React from "react";
import React, { useState } from "react";
// mobx
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { FilterHeader, FilterOption } from "components/issue-layouts";
// ui
import { Loader } from "components/ui";
// icons
import { StateGroupIcon } from "components/icons";
// helpers
import { getStatesList } from "helpers/state.helper";
type Props = {
workspaceSlug: string;
projectId: string;
};
type Props = { workspaceSlug: string; projectId: string; itemsToRender: number };
export const FilterState: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId } = props;
const { workspaceSlug, projectId, itemsToRender } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
const store = useMobxStore();
const { issueFilter: issueFilterStore, project: projectStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(true);
const statesByGroups = projectStore.states?.[projectId?.toString() ?? ""];
const statesList = getStatesList(statesByGroups);
@ -39,26 +38,46 @@ export const FilterState: React.FC<Props> = observer((props) => {
});
};
const appliedFiltersCount = issueFilterStore.userFilters?.state?.length ?? 0;
const filteredOptions = statesList?.filter((s) =>
s.name.toLowerCase().includes(issueFilterStore.filtersSearchQuery.toLowerCase())
);
return (
<div>
<>
<FilterHeader
title={`State (${issueFilterStore.userFilters?.state?.length ?? 0})`}
title={`State${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-1 pt-1">
{statesList?.map((state) => (
<FilterOption
key={state.id}
isChecked={issueFilterStore?.userFilters?.state?.includes(state?.id) ? true : false}
onClick={() => handleUpdateState(state?.id)}
icon={<StateGroupIcon stateGroup={state?.group} color={state?.color} />}
title={state?.name}
/>
))}
<div>
{filteredOptions ? (
filteredOptions.length > 0 ? (
<>
{filteredOptions.slice(0, itemsToRender).map((state) => (
<FilterOption
key={state.id}
isChecked={issueFilterStore?.userFilters?.state?.includes(state?.id) ? true : false}
onClick={() => handleUpdateState(state?.id)}
icon={<StateGroupIcon stateGroup={state?.group} color={state?.color} />}
title={state?.name}
/>
))}
</>
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>
)
) : (
<Loader className="space-y-2">
<Loader.Item height="20px" />
<Loader.Item height="20px" />
<Loader.Item height="20px" />
</Loader>
)}
</div>
)}
</div>
</>
);
});

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useState } from "react";
// mobx
import { observer } from "mobx-react-lite";
@ -7,15 +7,17 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { FilterHeader, FilterOption } from "components/issue-layouts";
export const FilterTargetDate = observer(() => {
const [previewEnabled, setPreviewEnabled] = useState(true);
const store = useMobxStore();
const { issueFilter: issueFilterStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(true);
const appliedFiltersCount = issueFilterStore.userFilters?.target_date?.length ?? 0;
return (
<div>
<>
<FilterHeader
title={`Target Date (${issueFilterStore?.userFilters?.target_date?.length})`}
title={`Target date${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
@ -33,6 +35,6 @@ export const FilterTargetDate = observer(() => {
))}
</div>
)}
</div>
</>
);
});

View File

@ -36,8 +36,8 @@ export const IssueDropdown = ({ children, title = "Dropdown" }: IIssueDropdown)
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute right-0 z-10 mt-1 w-[18.75rem] h-auto max-h-[37.5rem] bg-custom-background-100 border border-custom-border-200 shadow-custom-shadow-rg rounded overflow-y-auto">
{children}
<Popover.Panel className="absolute right-0 z-10 mt-1 bg-custom-background-100 border border-custom-border-200 shadow-custom-shadow-rg rounded overflow-hidden">
<div className="w-[18.75rem] max-h-[37.5rem] flex flex-col overflow-hidden">{children}</div>
</Popover.Panel>
</Transition>
</>

View File

@ -9,7 +9,7 @@ interface IFilterHeader {
}
export const FilterHeader = ({ title, isPreviewEnabled, handleIsPreviewEnabled }: IFilterHeader) => (
<div className="flex items-center justify-between gap-2 p-1.5 pb-1 bg-custom-background-100 sticky top-0">
<div className="flex items-center justify-between gap-2 pb-1 bg-custom-background-100 sticky top-0">
<div className="text-custom-text-300 text-xs font-medium flex-grow truncate">{title}</div>
<button
type="button"

View File

@ -16,7 +16,7 @@ export const FilterOption: React.FC<Props> = (props) => {
return (
<button
type="button"
className="flex items-center gap-2 rounded p-1.5 hover:bg-custom-background-80 w-full"
className="flex items-center gap-2 rounded hover:bg-custom-background-80 w-full p-1.5"
onClick={onClick}
>
<div

View File

@ -1,5 +1,7 @@
import React from "react";
// ui
import { Tooltip } from "components/ui";
// types
import { TIssueLayouts } from "types";
// constants
@ -17,20 +19,22 @@ export const LayoutSelection: React.FC<Props> = (props) => {
return (
<div className="flex items-center gap-1 p-1 rounded bg-custom-background-80">
{ISSUE_LAYOUTS.filter((l) => layouts.includes(l.key)).map((layout) => (
<button
key={layout.key}
type="button"
className={`w-7 h-[22px] rounded grid place-items-center transition-all hover:bg-custom-background-100 overflow-hidden group ${
selectedLayout == layout.key ? "bg-custom-background-100 shadow-custom-shadow-2xs" : ""
}`}
onClick={() => onChange(layout.key)}
>
<layout.icon
size={14}
strokeWidth={2}
className={`${selectedLayout == layout.key ? "text-custom-text-100" : "text-custom-text-200"}`}
/>
</button>
<Tooltip tooltipContent={layout.title}>
<button
key={layout.key}
type="button"
className={`w-7 h-[22px] rounded grid place-items-center transition-all hover:bg-custom-background-100 overflow-hidden group ${
selectedLayout == layout.key ? "bg-custom-background-100 shadow-custom-shadow-2xs" : ""
}`}
onClick={() => onChange(layout.key)}
>
<layout.icon
size={14}
strokeWidth={2}
className={`${selectedLayout == layout.key ? "text-custom-text-100" : "text-custom-text-200"}`}
/>
</button>
</Tooltip>
))}
</div>
);

View File

@ -2,7 +2,7 @@ import React from "react";
// components
import { LayoutSelection } from "./layout-selection";
import { IssueDropdown } from "./helpers/dropdown";
import { FilterSelection } from "./filters/filter-selection";
import { FilterSelection } from "./filters/filters-selection";
import { DisplayFiltersSelection } from "./display-filters";
import { FilterPreview } from "./filters-preview";
@ -64,13 +64,13 @@ export const IssuesRoot = observer(() => {
<div>Filter Header</div>
</div>
<div className="relative flex items-center gap-2">
<IssueDropdown title={"Filters"}>
{/* <IssueDropdown title={"Filters"}>
<FilterSelection />
</IssueDropdown>
<IssueDropdown title={"View"}>
<DisplayFiltersSelection />
</IssueDropdown>
<LayoutSelection />
<LayoutSelection /> */}
</div>
</div>
</div>
@ -78,11 +78,11 @@ export const IssuesRoot = observer(() => {
<FilterPreview />
</div>
<div className="w-full h-full relative overflow-hidden">
{issueFilterStore?.issueLayout === "list" && <IssueListViewRoot />}
{issueFilterStore?.issueLayout === "kanban" && <IssueKanBanViewRoot />}
{issueFilterStore?.issueLayout === "calendar" && <IssueCalendarViewRoot />}
{issueFilterStore?.issueLayout === "spreadsheet" && <IssueSpreadsheetViewRoot />}
{issueFilterStore?.issueLayout === "gantt_chart" && <IssueGanttViewRoot />}
{issueFilterStore?.userDisplayFilters.layout === "list" && <IssueListViewRoot />}
{issueFilterStore?.userDisplayFilters.layout === "kanban" && <IssueKanBanViewRoot />}
{issueFilterStore?.userDisplayFilters.layout === "calendar" && <IssueCalendarViewRoot />}
{issueFilterStore?.userDisplayFilters.layout === "spreadsheet" && <IssueSpreadsheetViewRoot />}
{issueFilterStore?.userDisplayFilters.layout === "gantt_chart" && <IssueGanttViewRoot />}
</div>
</div>
);

View File

@ -2,17 +2,8 @@ import { objToQueryParams } from "helpers/string.helper";
import { IAnalyticsParams, IJiraMetadata, INotificationParams } from "types";
const paramsToKey = (params: any) => {
const {
state,
priority,
assignees,
created_by,
labels,
start_date,
target_date,
sub_issue,
start_target_date,
} = params;
const { state, priority, assignees, created_by, labels, start_date, target_date, sub_issue, start_target_date } =
params;
let stateKey = state ? state.split(",") : [];
let priorityKey = priority ? priority.split(",") : [];
@ -50,16 +41,7 @@ const inboxParamsToKey = (params: any) => {
};
const myIssuesParamsToKey = (params: any) => {
const {
assignees,
created_by,
labels,
priority,
state_group,
subscriber,
start_date,
target_date,
} = params;
const { assignees, created_by, labels, priority, state_group, subscriber, start_date, target_date } = params;
let assigneesKey = assignees ? assignees.split(",") : [];
let createdByKey = created_by ? created_by.split(",") : [];
@ -88,15 +70,12 @@ export const CURRENT_USER = "CURRENT_USER";
export const USER_WORKSPACE_INVITATIONS = "USER_WORKSPACE_INVITATIONS";
export const USER_WORKSPACES = "USER_WORKSPACES";
export const WORKSPACE_DETAILS = (workspaceSlug: string) =>
`WORKSPACE_DETAILS_${workspaceSlug.toUpperCase()}`;
export const WORKSPACE_DETAILS = (workspaceSlug: string) => `WORKSPACE_DETAILS_${workspaceSlug.toUpperCase()}`;
export const WORKSPACE_MEMBERS = (workspaceSlug: string) =>
`WORKSPACE_MEMBERS_${workspaceSlug.toUpperCase()}`;
export const WORKSPACE_MEMBERS = (workspaceSlug: string) => `WORKSPACE_MEMBERS_${workspaceSlug.toUpperCase()}`;
export const WORKSPACE_MEMBERS_WITH_EMAIL = (workspaceSlug: string) =>
`WORKSPACE_MEMBERS_WITH_EMAIL_${workspaceSlug.toUpperCase()}`;
export const WORKSPACE_MEMBERS_ME = (workspaceSlug: string) =>
`WORKSPACE_MEMBERS_ME${workspaceSlug.toUpperCase()}`;
export const WORKSPACE_MEMBERS_ME = (workspaceSlug: string) => `WORKSPACE_MEMBERS_ME${workspaceSlug.toUpperCase()}`;
export const WORKSPACE_INVITATIONS = "WORKSPACE_INVITATIONS";
export const WORKSPACE_INVITATION_WITH_EMAIL = (workspaceSlug: string) =>
`WORKSPACE_INVITATION_WITH_EMAIL_${workspaceSlug.toUpperCase()}`;
@ -111,9 +90,7 @@ export const PROJECTS_LIST = (
) => {
if (!params) return `PROJECTS_LIST_${workspaceSlug.toUpperCase()}`;
return `PROJECTS_LIST_${workspaceSlug.toUpperCase()}_${params.is_favorite
.toString()
.toUpperCase()}`;
return `PROJECTS_LIST_${workspaceSlug.toUpperCase()}_${params.is_favorite.toString().toUpperCase()}`;
};
export const PROJECT_DETAILS = (projectId: string) => `PROJECT_DETAILS_${projectId.toUpperCase()}`;
@ -149,35 +126,22 @@ export const PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS = (projectId: string, params?
return `PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS${projectId.toUpperCase()}_${paramsKey}`;
};
export const PROJECT_ISSUES_DETAILS = (issueId: string) =>
`PROJECT_ISSUES_DETAILS_${issueId.toUpperCase()}`;
export const PROJECT_ISSUES_PROPERTIES = (projectId: string) =>
`PROJECT_ISSUES_PROPERTIES_${projectId.toUpperCase()}`;
export const PROJECT_ISSUES_COMMENTS = (issueId: string) =>
`PROJECT_ISSUES_COMMENTS_${issueId.toUpperCase()}`;
export const PROJECT_ISSUES_ACTIVITY = (issueId: string) =>
`PROJECT_ISSUES_ACTIVITY_${issueId.toUpperCase()}`;
export const PROJECT_ISSUE_BY_STATE = (projectId: string) =>
`PROJECT_ISSUE_BY_STATE_${projectId.toUpperCase()}`;
export const PROJECT_ISSUE_LABELS = (projectId: string) =>
`PROJECT_ISSUE_LABELS_${projectId.toUpperCase()}`;
export const WORKSPACE_LABELS = (workspaceSlug: string) =>
`WORKSPACE_LABELS_${workspaceSlug.toUpperCase()}`;
export const PROJECT_GITHUB_REPOSITORY = (projectId: string) =>
`PROJECT_GITHUB_REPOSITORY_${projectId.toUpperCase()}`;
export const PROJECT_ISSUES_DETAILS = (issueId: string) => `PROJECT_ISSUES_DETAILS_${issueId.toUpperCase()}`;
export const PROJECT_ISSUES_PROPERTIES = (projectId: string) => `PROJECT_ISSUES_PROPERTIES_${projectId.toUpperCase()}`;
export const PROJECT_ISSUES_COMMENTS = (issueId: string) => `PROJECT_ISSUES_COMMENTS_${issueId.toUpperCase()}`;
export const PROJECT_ISSUES_ACTIVITY = (issueId: string) => `PROJECT_ISSUES_ACTIVITY_${issueId.toUpperCase()}`;
export const PROJECT_ISSUE_BY_STATE = (projectId: string) => `PROJECT_ISSUE_BY_STATE_${projectId.toUpperCase()}`;
export const PROJECT_ISSUE_LABELS = (projectId: string) => `PROJECT_ISSUE_LABELS_${projectId.toUpperCase()}`;
export const WORKSPACE_LABELS = (workspaceSlug: string) => `WORKSPACE_LABELS_${workspaceSlug.toUpperCase()}`;
export const PROJECT_GITHUB_REPOSITORY = (projectId: string) => `PROJECT_GITHUB_REPOSITORY_${projectId.toUpperCase()}`;
// cycles
export const CYCLES_LIST = (projectId: string) => `CYCLE_LIST_${projectId.toUpperCase()}`;
export const INCOMPLETE_CYCLES_LIST = (projectId: string) =>
`INCOMPLETE_CYCLES_LIST_${projectId.toUpperCase()}`;
export const CURRENT_CYCLE_LIST = (projectId: string) =>
`CURRENT_CYCLE_LIST_${projectId.toUpperCase()}`;
export const UPCOMING_CYCLES_LIST = (projectId: string) =>
`UPCOMING_CYCLES_LIST_${projectId.toUpperCase()}`;
export const DRAFT_CYCLES_LIST = (projectId: string) =>
`DRAFT_CYCLES_LIST_${projectId.toUpperCase()}`;
export const COMPLETED_CYCLES_LIST = (projectId: string) =>
`COMPLETED_CYCLES_LIST_${projectId.toUpperCase()}`;
export const INCOMPLETE_CYCLES_LIST = (projectId: string) => `INCOMPLETE_CYCLES_LIST_${projectId.toUpperCase()}`;
export const CURRENT_CYCLE_LIST = (projectId: string) => `CURRENT_CYCLE_LIST_${projectId.toUpperCase()}`;
export const UPCOMING_CYCLES_LIST = (projectId: string) => `UPCOMING_CYCLES_LIST_${projectId.toUpperCase()}`;
export const DRAFT_CYCLES_LIST = (projectId: string) => `DRAFT_CYCLES_LIST_${projectId.toUpperCase()}`;
export const COMPLETED_CYCLES_LIST = (projectId: string) => `COMPLETED_CYCLES_LIST_${projectId.toUpperCase()}`;
export const CYCLE_ISSUES = (cycleId: string) => `CYCLE_ISSUES_${cycleId.toUpperCase()}`;
export const CYCLE_ISSUES_WITH_PARAMS = (cycleId: string, params?: any) => {
if (!params) return `CYCLE_ISSUES_WITH_PARAMS_${cycleId.toUpperCase()}`;
@ -199,8 +163,7 @@ export const USER_ISSUES = (workspaceSlug: string, params: any) => {
export const USER_ACTIVITY = "USER_ACTIVITY";
export const USER_WORKSPACE_DASHBOARD = (workspaceSlug: string) =>
`USER_WORKSPACE_DASHBOARD_${workspaceSlug.toUpperCase()}`;
export const USER_PROJECT_VIEW = (projectId: string) =>
`USER_PROJECT_VIEW_${projectId.toUpperCase()}`;
export const USER_PROJECT_VIEW = (projectId: string) => `USER_PROJECT_VIEW_${projectId.toUpperCase()}`;
export const MODULE_LIST = (projectId: string) => `MODULE_LIST_${projectId.toUpperCase()}`;
export const MODULE_ISSUES = (moduleId: string) => `MODULE_ISSUES_${moduleId.toUpperCase()}`;
@ -240,8 +203,7 @@ export const INBOX_ISSUE_DETAILS = (inboxId: string, issueId: string) =>
export const ISSUE_DETAILS = (issueId: string) => `ISSUE_DETAILS_${issueId.toUpperCase()}`;
export const SUB_ISSUES = (issueId: string) => `SUB_ISSUES_${issueId.toUpperCase()}`;
export const ISSUE_ATTACHMENTS = (issueId: string) => `ISSUE_ATTACHMENTS_${issueId.toUpperCase()}`;
export const ARCHIVED_ISSUE_DETAILS = (issueId: string) =>
`ARCHIVED_ISSUE_DETAILS_${issueId.toUpperCase()}`;
export const ARCHIVED_ISSUE_DETAILS = (issueId: string) => `ARCHIVED_ISSUE_DETAILS_${issueId.toUpperCase()}`;
// integrations
export const APP_INTEGRATIONS = "APP_INTEGRATIONS";
@ -271,22 +233,18 @@ export const SLACK_CHANNEL_INFO = (workspaceSlug: string, projectId: string) =>
`SLACK_CHANNEL_INFO_${workspaceSlug.toString().toUpperCase()}_${projectId.toUpperCase()}`;
// Pages
export const RECENT_PAGES_LIST = (projectId: string) =>
`RECENT_PAGES_LIST_${projectId.toUpperCase()}`;
export const RECENT_PAGES_LIST = (projectId: string) => `RECENT_PAGES_LIST_${projectId.toUpperCase()}`;
export const ALL_PAGES_LIST = (projectId: string) => `ALL_PAGES_LIST_${projectId.toUpperCase()}`;
export const FAVORITE_PAGES_LIST = (projectId: string) =>
`FAVORITE_PAGES_LIST_${projectId.toUpperCase()}`;
export const FAVORITE_PAGES_LIST = (projectId: string) => `FAVORITE_PAGES_LIST_${projectId.toUpperCase()}`;
export const MY_PAGES_LIST = (projectId: string) => `MY_PAGES_LIST_${projectId.toUpperCase()}`;
export const OTHER_PAGES_LIST = (projectId: string) =>
`OTHER_PAGES_LIST_${projectId.toUpperCase()}`;
export const OTHER_PAGES_LIST = (projectId: string) => `OTHER_PAGES_LIST_${projectId.toUpperCase()}`;
export const PAGE_DETAILS = (pageId: string) => `PAGE_DETAILS_${pageId.toUpperCase()}`;
export const PAGE_BLOCKS_LIST = (pageId: string) => `PAGE_BLOCK_LIST_${pageId.toUpperCase()}`;
export const PAGE_BLOCK_DETAILS = (pageId: string) => `PAGE_BLOCK_DETAILS_${pageId.toUpperCase()}`;
// estimates
export const ESTIMATES_LIST = (projectId: string) => `ESTIMATES_LIST_${projectId.toUpperCase()}`;
export const ESTIMATE_DETAILS = (estimateId: string) =>
`ESTIMATE_DETAILS_${estimateId.toUpperCase()}`;
export const ESTIMATE_DETAILS = (estimateId: string) => `ESTIMATE_DETAILS_${estimateId.toUpperCase()}`;
// analytics
export const ANALYTICS = (workspaceSlug: string, params: IAnalyticsParams) =>
@ -294,15 +252,10 @@ export const ANALYTICS = (workspaceSlug: string, params: IAnalyticsParams) =>
params.segment
}_${params.project?.toString()}`;
export const DEFAULT_ANALYTICS = (workspaceSlug: string, params?: Partial<IAnalyticsParams>) =>
`DEFAULT_ANALYTICS_${workspaceSlug.toUpperCase()}_${params?.project?.toString()}_${
params?.cycle
}_${params?.module}`;
`DEFAULT_ANALYTICS_${workspaceSlug.toUpperCase()}_${params?.project?.toString()}_${params?.cycle}_${params?.module}`;
// notifications
export const USER_WORKSPACE_NOTIFICATIONS = (
workspaceSlug: string,
params: INotificationParams
) => {
export const USER_WORKSPACE_NOTIFICATIONS = (workspaceSlug: string, params: INotificationParams) => {
const { type, snoozed, archived, read } = params;
return `USER_WORKSPACE_NOTIFICATIONS_${workspaceSlug?.toUpperCase()}_TYPE_${(
@ -310,21 +263,13 @@ export const USER_WORKSPACE_NOTIFICATIONS = (
)?.toUpperCase()}_SNOOZED_${snoozed}_ARCHIVED_${archived}_READ_${read}`;
};
export const USER_WORKSPACE_NOTIFICATIONS_DETAILS = (
workspaceSlug: string,
notificationId: string
) =>
export const USER_WORKSPACE_NOTIFICATIONS_DETAILS = (workspaceSlug: string, notificationId: string) =>
`USER_WORKSPACE_NOTIFICATIONS_DETAILS_${workspaceSlug?.toUpperCase()}_${notificationId?.toUpperCase()}`;
export const UNREAD_NOTIFICATIONS_COUNT = (workspaceSlug: string) =>
`UNREAD_NOTIFICATIONS_COUNT_${workspaceSlug?.toUpperCase()}`;
export const getPaginatedNotificationKey = (
index: number,
prevData: any,
workspaceSlug: string,
params: any
) => {
export const getPaginatedNotificationKey = (index: number, prevData: any, workspaceSlug: string, params: any) => {
if (prevData && !prevData?.results?.length) return null;
if (index === 0)
@ -360,9 +305,5 @@ export const USER_PROFILE_ISSUES = (workspaceSlug: string, userId: string, param
// reactions
export const ISSUE_REACTION_LIST = (workspaceSlug: string, projectId: string, issueId: string) =>
`ISSUE_REACTION_LIST_${workspaceSlug.toUpperCase()}_${projectId.toUpperCase()}_${issueId.toUpperCase()}`;
export const COMMENT_REACTION_LIST = (
workspaceSlug: string,
projectId: string,
commendId: string
) =>
export const COMMENT_REACTION_LIST = (workspaceSlug: string, projectId: string, commendId: string) =>
`COMMENT_REACTION_LIST_${workspaceSlug.toUpperCase()}_${projectId.toUpperCase()}_${commendId.toUpperCase()}`;

View File

@ -117,11 +117,11 @@ export const ISSUE_LAYOUTS: {
title: string;
icon: any;
}[] = [
{ key: "list", title: "List View", icon: List },
{ key: "kanban", title: "Kanban View", icon: Kanban },
{ key: "calendar", title: "Calendar View", icon: Calendar },
{ key: "spreadsheet", title: "Spreadsheet View", icon: Sheet },
{ key: "gantt_chart", title: "Gantt Chart View", icon: GanttChart },
{ key: "list", title: "List Layout", icon: List },
{ key: "kanban", title: "Kanban Layout", icon: Kanban },
{ key: "calendar", title: "Calendar Layout", icon: Calendar },
{ key: "spreadsheet", title: "Spreadsheet Layout", icon: Sheet },
{ key: "gantt_chart", title: "Gantt Chart Layout", icon: GanttChart },
];
export const ISSUE_LIST_FILTERS = [
@ -198,26 +198,116 @@ export const ISSUE_GANTT_DISPLAY_FILTERS = [
{ key: "sub_issue", title: "Sub Issue" },
];
// TODO: update this later
export const ISSUE_EXTRA_DISPLAY_PROPERTIES = {
list: {
access: true,
values: ["show_empty_groups", "sub_issue"],
export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
[key: string]: {
layout: TIssueLayouts[];
filters: {
[key in TIssueLayouts]: string[];
};
display_properties: {
[key in TIssueLayouts]: boolean;
};
display_filters: {
[key in TIssueLayouts]: string[];
};
extra_options: {
[key in TIssueLayouts]: {
access: boolean;
values: string[];
};
};
};
} = {
my_issues: {
layout: ["list", "kanban"],
filters: {
list: ["priority", "state_group", "labels", "start_date", "due_date"],
kanban: ["priority", "state_group", "labels", "start_date", "due_date"],
calendar: [],
spreadsheet: [],
gantt_chart: [],
},
display_properties: {
list: true,
kanban: true,
calendar: true,
spreadsheet: true,
gantt_chart: false,
},
display_filters: {
list: ["group_by", "sub_group_by", "order_by", "issue_type"],
kanban: ["group_by", "sub_group_by", "order_by", "issue_type"],
calendar: ["issue_type"],
spreadsheet: ["issue_type"],
gantt_chart: ["order_by", "issue_type"],
},
extra_options: {
list: {
access: true,
values: ["show_empty_groups", "sub_issue"],
},
kanban: {
access: true,
values: ["show_empty_groups", "sub_issue"],
},
calendar: {
access: false,
values: [],
},
spreadsheet: {
access: false,
values: [],
},
gantt_chart: {
access: false,
values: [],
},
},
},
kanban: {
access: true,
values: ["show_empty_groups", "sub_issue"],
},
calendar: {
access: false,
values: [],
},
spreadsheet: {
access: false,
values: [],
},
gantt_chart: {
access: true,
values: ["sub_issue"],
issues: {
layout: ["list", "kanban", "calendar", "spreadsheet", "gantt_chart"],
filters: {
list: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"],
kanban: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"],
calendar: ["priority", "state", "assignees", "created_by", "labels"],
spreadsheet: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"],
gantt_chart: ["priority", "state", "assignees", "created_by", "labels", "start_date", "due_date"],
},
display_properties: {
list: true,
kanban: true,
calendar: true,
spreadsheet: true,
gantt_chart: false,
},
display_filters: {
list: ["group_by", "sub_group_by", "order_by", "issue_type"],
kanban: ["group_by", "sub_group_by", "order_by", "issue_type"],
calendar: ["issue_type"],
spreadsheet: ["issue_type"],
gantt_chart: ["order_by", "issue_type"],
},
extra_options: {
list: {
access: true,
values: ["show_empty_groups", "sub_issue"],
},
kanban: {
access: true,
values: ["show_empty_groups", "sub_issue"],
},
calendar: {
access: false,
values: [],
},
spreadsheet: {
access: false,
values: [],
},
gantt_chart: {
access: true,
values: ["sub_issue"],
},
},
},
};

View File

@ -91,6 +91,7 @@ export const handleIssueQueryParamsByLayout = (_layout: TIssueLayouts | undefine
"start_date",
"target_date",
"group_by",
"sub_group_by",
"order_by",
"type",
"sub_issue",
@ -107,6 +108,7 @@ export const handleIssueQueryParamsByLayout = (_layout: TIssueLayouts | undefine
"start_date",
"target_date",
"group_by",
"sub_group_by",
"order_by",
"type",
"sub_issue",

View File

@ -54,29 +54,30 @@ const ProjectIssues: NextPage = () => {
workspaceSlug && projectId ? () => inboxService.getInboxes(workspaceSlug as string, projectId as string) : null
);
// TODO: update the fetch keys
useSWR(
workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId.toString()) : null,
workspaceSlug && projectId ? "REVALIDATE_USER_PROJECT_FILTERS" : null,
workspaceSlug && projectId
? () => issueFilterStore.fetchUserProjectFilters(workspaceSlug.toString(), projectId.toString())
: null
);
useSWR(
workspaceSlug && projectId ? STATES_LIST(projectId.toString()) : null,
workspaceSlug && projectId ? "REVALIDATE_PROJECT_STATES_LIST" : null,
workspaceSlug && projectId
? () => projectStore.fetchProjectStates(workspaceSlug.toString(), projectId.toString())
: null
);
useSWR(
workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId.toString()) : null,
workspaceSlug && projectId ? "REVALIDATE_PROJECT_LABELS_LIST" : null,
workspaceSlug && projectId
? () => projectStore.fetchProjectLabels(workspaceSlug.toString(), projectId.toString())
: null
);
useSWR(
workspaceSlug && projectId ? PROJECT_MEMBERS(projectId.toString()) : null,
workspaceSlug && projectId ? "REVALIDATE_PROJECT_MEMBERS_LIST" : null,
workspaceSlug && projectId
? () => projectStore.fetchProjectMembers(workspaceSlug.toString(), projectId.toString())
: null

View File

@ -2,6 +2,8 @@ import { observable, action, computed, makeObservable, runInAction } from "mobx"
// services
import { ProjectService } from "services/project.service";
import { IssueService } from "services/issue.service";
// helpers
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
// types
import { RootStore } from "./root";
import {
@ -11,7 +13,6 @@ import {
IProjectViewProps,
TIssueParams,
} from "types";
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
export interface IIssueFilterStore {
loader: boolean;
@ -21,6 +22,7 @@ export interface IIssueFilterStore {
userFilters: IIssueFilterOptions;
defaultDisplayFilters: IIssueDisplayFilterOptions;
defaultFilters: IIssueFilterOptions;
filtersSearchQuery: string;
// action
fetchUserProjectFilters: (workspaceSlug: string, projectSlug: string) => Promise<void>;
@ -34,6 +36,7 @@ export interface IIssueFilterStore {
projectSlug: string,
properties: Partial<IIssueDisplayProperties>
) => Promise<void>;
updateFiltersSearchQuery: (query: string) => void;
// computed
appliedFilters: TIssueParams[] | null;
@ -64,6 +67,7 @@ class IssueFilterStore implements IIssueFilterStore {
created_on: true,
updated_on: true,
};
filtersSearchQuery: string = "";
// root store
rootStore;
@ -83,11 +87,13 @@ class IssueFilterStore implements IIssueFilterStore {
userDisplayProperties: observable.ref,
userDisplayFilters: observable.ref,
userFilters: observable.ref,
filtersSearchQuery: observable.ref,
// actions
fetchUserProjectFilters: action,
updateUserFilters: action,
updateDisplayProperties: action,
updateFiltersSearchQuery: action,
// computed
appliedFilters: computed,
@ -183,6 +189,12 @@ class IssueFilterStore implements IIssueFilterStore {
console.log("Failed to update user filters in issue filter store", error);
}
};
updateFiltersSearchQuery: (query: string) => void = (query) => {
runInAction(() => {
this.filtersSearchQuery = query;
});
};
}
export default IssueFilterStore;

View File

@ -40,6 +40,7 @@ export type TIssueParams =
| "start_date"
| "target_date"
| "group_by"
| "sub_group_by"
| "order_by"
| "type"
| "sub_issue"
@ -62,6 +63,7 @@ export interface IIssueFilterOptions {
export interface IIssueDisplayFilterOptions {
calendar_date_range?: string;
group_by?: TIssueGroupByOptions;
sub_group_by?: TIssueGroupByOptions;
layout?: TIssueLayouts;
order_by?: TIssueOrderByOptions;
show_empty_groups?: boolean;