forked from github/plane
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:
parent
9a8dcc349f
commit
9831418a11
@ -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>
|
||||
);
|
||||
});
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -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";
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
@ -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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
230
web/components/issue-layouts/filters/filters-selection.tsx
Normal file
230
web/components/issue-layouts/filters/filters-selection.tsx
Normal 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>
|
||||
);
|
||||
});
|
@ -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";
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -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>
|
||||
</>
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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()}`;
|
||||
|
@ -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"],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
2
web/types/view-props.d.ts
vendored
2
web/types/view-props.d.ts
vendored
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user