chore: implemented filters and views in kanaban

This commit is contained in:
gurusainath 2023-09-13 19:40:35 +05:30
parent 698021ab8b
commit 0ec0ad6aba
24 changed files with 667 additions and 261 deletions

View File

@ -0,0 +1,43 @@
import React from "react";
// components
import { FilterHeader } from "../helpers/filter-header";
// mobx react lite
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
export const FilterDisplayProperties = observer(() => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore, issueView: issueStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(true);
return (
<div>
<FilterHeader
title={"Display Properties"}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1 px-1 flex items-center whitespace-nowrap gap-2 flex-wrap">
{issueFilterStore?.issueRenderFilters?.display_properties &&
issueFilterStore?.issueRenderFilters?.display_properties.length > 0 &&
issueFilterStore?.issueRenderFilters?.display_properties.map((_displayProperties) => (
<div
key={_displayProperties?.key}
className={`cursor-pointer rounded-sm transition-all text-xs border p-0.5 px-1.5 ${
issueFilterStore?.userFilters?.display_properties?.[_displayProperties?.key]
? `bg-custom-primary-200 border-custom-primary-200 text-white`
: `hover:bg-custom-border-100 border-custom-border-100`
}`}
>
{_displayProperties?.title}
</div>
))}
</div>
)}
</div>
);
});

View File

@ -0,0 +1,43 @@
import React from "react";
// components
import { FilterHeader } from "../helpers/filter-header";
import { FilterOption } from "../helpers/filter-option";
// mobx react lite
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
export const FilterExtraOptions = observer(() => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore, issueView: issueStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(true);
return (
<div>
<FilterHeader
title={"Extra Options"}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">
{issueFilterStore?.issueRenderFilters?.extra_properties &&
issueFilterStore?.issueRenderFilters?.extra_properties.length > 0 &&
issueFilterStore?.issueRenderFilters?.extra_properties.map((_extraProperties) => (
<FilterOption
key={_extraProperties?.key}
isChecked={
issueFilterStore?.userFilters?.display_filters?.[_extraProperties?.key]
? true
: false
}
title={_extraProperties.title}
/>
))}
</div>
)}
</div>
);
});

View File

@ -0,0 +1,44 @@
import React from "react";
// components
import { FilterHeader } from "../helpers/filter-header";
import { FilterOption } from "../helpers/filter-option";
// mobx react lite
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
export const FilterGroupBy = observer(() => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore, issueView: issueStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(true);
return (
<div>
<FilterHeader
title={"Group By"}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">
{issueFilterStore?.issueRenderFilters?.group_by &&
issueFilterStore?.issueRenderFilters?.group_by.length > 0 &&
issueFilterStore?.issueRenderFilters?.group_by.map((_groupBy) => (
<FilterOption
key={_groupBy?.key}
isChecked={
issueFilterStore?.userFilters?.display_filters?.group_by === _groupBy?.key
? true
: false
}
title={_groupBy.title}
multiple={false}
/>
))}
</div>
)}
</div>
);
});

View File

@ -1,15 +1,45 @@
import React from "react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
// components
import { FilterDisplayProperties } from "./display-properties";
import { FilterGroupBy } from "./group-by";
import { FilterOrderBy } from "./order-by";
import { FilterIssueType } from "./issue-type";
import { FilterExtraOptions } from "./extra-options";
// // mobx react lite
// import { observer } from "mobx-react-lite";
// // mobx store
// import { useMobxStore } from "lib/mobx/store-provider";
// import { RootStore } from "store/root";
export const DisplayPropertiesSelection = () => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore, issueView: issueStore } = store;
// const store: RootStore = useMobxStore();
// const { issueFilters: issueFilterStore, issueView: issueViewStore } = store;
return (
<div>
<div>Filter Selection</div>
export const DisplayFiltersSelection = () => (
<div className="w-full h-full overflow-hidden select-none relative flex flex-col">
<div className="flex-shrink-0 p-2 text-sm border-b border-custom-border-200">
Search container
</div>
<div className="w-full h-full overflow-hidden overflow-y-auto relative pb-2">
{/* display properties */}
<div className="pb-2 px-2 border-b border-custom-border-200">
<FilterDisplayProperties />
</div>
{/* group by */}
<div className="py-1 px-2 border-b border-custom-border-200">
<FilterGroupBy />
</div>
{/* order by */}
<div className="py-1 px-2 border-b border-custom-border-200">
<FilterOrderBy />
</div>
{/* issue type */}
<div className="py-1 px-2 border-b border-custom-border-200">
<FilterIssueType />
</div>
{/* Options */}
<div className="pt-1 px-2">
<FilterExtraOptions />
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,44 @@
import React from "react";
// components
import { FilterHeader } from "../helpers/filter-header";
import { FilterOption } from "../helpers/filter-option";
// mobx react lite
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
export const FilterIssueType = observer(() => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore, issueView: issueStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(true);
return (
<div>
<FilterHeader
title={"Issue Type"}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">
{issueFilterStore?.issueRenderFilters?.issue_type &&
issueFilterStore?.issueRenderFilters?.issue_type.length > 0 &&
issueFilterStore?.issueRenderFilters?.issue_type.map((_issueType) => (
<FilterOption
key={_issueType?.key}
isChecked={
issueFilterStore?.userFilters?.display_filters?.type === _issueType?.key
? true
: false
}
title={_issueType.title}
multiple={false}
/>
))}
</div>
)}
</div>
);
});

View File

@ -0,0 +1,44 @@
import React from "react";
// components
import { FilterHeader } from "../helpers/filter-header";
import { FilterOption } from "../helpers/filter-option";
// mobx react lite
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
export const FilterOrderBy = observer(() => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore, issueView: issueStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(true);
return (
<div>
<FilterHeader
title={"Order By"}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">
{issueFilterStore?.issueRenderFilters?.order_by &&
issueFilterStore?.issueRenderFilters?.order_by.length > 0 &&
issueFilterStore?.issueRenderFilters?.order_by.map((_orderBy) => (
<FilterOption
key={_orderBy?.key}
isChecked={
issueFilterStore?.userFilters?.display_filters?.order_by === _orderBy?.key
? true
: false
}
title={_orderBy.title}
multiple={false}
/>
))}
</div>
)}
</div>
);
});

View File

@ -17,7 +17,7 @@ export const MemberIcons = ({
display_name: string;
avatar: string | null;
}) => (
<div className="flex-shrink-0 rounded-sm overflow-hidden w-[20px] h-[20px] flex justify-center items-center bg-custom-background-80">
<div className="flex-shrink-0 rounded-sm overflow-hidden w-[20px] h-[20px] flex justify-center items-center">
{avatar ? (
<img src={avatar} alt={display_name || ""} className="" />
) : (
@ -32,14 +32,14 @@ export const FilterAssignees = observer(() => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore, issueView: issueStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(false);
const [previewEnabled, setPreviewEnabled] = React.useState(true);
return (
<div>
<FilterHeader
title={"Assignees"}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={setPreviewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">
@ -48,7 +48,12 @@ export const FilterAssignees = observer(() => {
issueFilterStore?.projectMembers.map((_member) => (
<FilterOption
key={`assignees-${_member?.member?.id}`}
isChecked={false}
isChecked={
issueFilterStore?.userFilters?.filters?.assignees != null &&
issueFilterStore?.userFilters?.filters?.assignees.includes(_member?.member?.id)
? true
: false
}
icon={
<MemberIcons
display_name={_member?.member.display_name}

View File

@ -15,14 +15,14 @@ export const FilterCreatedBy = observer(() => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore, issueView: issueStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(false);
const [previewEnabled, setPreviewEnabled] = React.useState(true);
return (
<div>
<FilterHeader
title={"Created By"}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={setPreviewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">
@ -31,7 +31,12 @@ export const FilterCreatedBy = observer(() => {
issueFilterStore?.projectMembers.map((_member) => (
<FilterOption
key={`create-by-${_member?.member?.id}`}
isChecked={false}
isChecked={
issueFilterStore?.userFilters?.filters?.created_by != null &&
issueFilterStore?.userFilters?.filters?.created_by.includes(_member?.member?.id)
? true
: false
}
icon={
<MemberIcons
display_name={_member?.member.display_name}

View File

@ -8,50 +8,53 @@ import { FilterCreatedBy } from "./created-by";
import { FilterLabels } from "./labels";
import { FilterStartDate } from "./start-date";
import { FilterTargetDate } from "./target-date";
// mobx react lite
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
// // mobx react lite
// import { observer } from "mobx-react-lite";
// // mobx store
// import { useMobxStore } from "lib/mobx/store-provider";
// import { RootStore } from "store/root";
export const FilterSelection = observer(() => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore, issueView: issueStore } = store;
// const store: RootStore = useMobxStore();
// const { issueFilters: issueFilterStore, issueView: issueViewStore } = store;
return (
<div className="container w-full h-full overflow-y-auto mx-auto max-w-[400px] relative select-none">
export const FilterSelection = () => (
<div className="w-full h-full overflow-hidden select-none relative flex flex-col">
<div className="flex-shrink-0 p-2 text-sm border-b border-custom-border-200">
Search container
</div>
<div className="w-full h-full overflow-hidden overflow-y-auto relative pb-2">
{/* priority */}
<div className="py-2 border-b border-custom-border-100">
<div className="pb-1 px-2 border-b border-custom-border-200">
<FilterPriority />
</div>
{/* state group */}
<div className="py-2 border-b border-custom-border-100">
<div className="py-1 px-2 border-b border-custom-border-200">
<FilterStateGroup />
</div>
{/* state */}
<div className="py-2 border-b border-custom-border-100">
<div className="py-1 px-2 border-b border-custom-border-200">
<FilterState />
</div>
{/* assignees */}
<div className="py-2 border-b border-custom-border-100">
<div className="py-1 px-2 border-b border-custom-border-200">
<FilterAssignees />
</div>
{/* created_by */}
<div className="py-2 border-b border-custom-border-100">
<div className="py-1 px-2 border-b border-custom-border-200">
<FilterCreatedBy />
</div>
{/* labels */}
<div className="py-2 border-b border-custom-border-100">
<div className="py-1 px-2 border-b border-custom-border-200">
<FilterLabels />
</div>
{/* start_date */}
<div className="py-2 border-b border-custom-border-100">
<div className="py-1 px-2 border-b border-custom-border-200">
<FilterStartDate />
</div>
{/* due_date */}
<div className="py-2">
<div className="pt-1 px-2">
<FilterTargetDate />
</div>
</div>
</div>
);
});

View File

@ -20,14 +20,14 @@ export const FilterLabels = observer(() => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore, issueView: issueStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(false);
const [previewEnabled, setPreviewEnabled] = React.useState(true);
return (
<div>
<FilterHeader
title={"labels"}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={setPreviewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">
@ -35,8 +35,13 @@ export const FilterLabels = observer(() => {
issueFilterStore?.projectLabels.length > 0 &&
issueFilterStore?.projectLabels.map((_label) => (
<FilterOption
key={_label?.key}
isChecked={false}
key={_label?.id}
isChecked={
issueFilterStore?.userFilters?.filters?.labels != null &&
issueFilterStore?.userFilters?.filters?.labels.includes(_label?.id)
? true
: false
}
icon={<LabelIcons color={_label.color} />}
title={_label.name}
/>

View File

@ -1,6 +1,6 @@
import React from "react";
// lucide icons
import { AlertCircle, SignalHigh, SignalMedium, SignalLow, Ban, Check } from "lucide-react";
import { AlertCircle, SignalHigh, SignalMedium, SignalLow, Ban } from "lucide-react";
// components
import { FilterHeader } from "../helpers/filter-header";
import { FilterOption } from "../helpers/filter-option";
@ -54,14 +54,14 @@ export const FilterPriority = observer(() => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore, issueView: issueStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(false);
const [previewEnabled, setPreviewEnabled] = React.useState(true);
return (
<div>
<FilterHeader
title={"Priority"}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={setPreviewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">
@ -70,11 +70,21 @@ export const FilterPriority = observer(() => {
issueFilterStore?.issueRenderFilters?.priority.map((_priority) => (
<FilterOption
key={_priority?.key}
isChecked={false}
isChecked={
issueFilterStore?.userFilters?.filters?.priority != null &&
issueFilterStore?.userFilters?.filters?.priority.includes(_priority?.key)
? true
: false
}
icon={<PriorityIcons priority={_priority.key} />}
title={_priority.title}
/>
))}
<div className="pl-[32px] flex items-center gap-2 py-[6px] text-xs text-custom-primary-100">
<div>View more</div>
<div>View less</div>
<div>View all</div>
</div>
</div>
)}
</div>

View File

@ -14,14 +14,14 @@ export const FilterStartDate = observer(() => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore, issueView: issueStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(false);
const [previewEnabled, setPreviewEnabled] = React.useState(true);
return (
<div>
<FilterHeader
title={"Start Date"}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={setPreviewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">

View File

@ -87,14 +87,14 @@ export const FilterStateGroup = observer(() => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore, issueView: issueStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(false);
const [previewEnabled, setPreviewEnabled] = React.useState(true);
return (
<div>
<FilterHeader
title={"State Group"}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={setPreviewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">
@ -103,7 +103,12 @@ export const FilterStateGroup = observer(() => {
issueFilterStore?.issueRenderFilters?.state_group.map((_stateGroup) => (
<FilterOption
key={_stateGroup?.key}
isChecked={false}
isChecked={
issueFilterStore?.userFilters?.filters?.state_group != null &&
issueFilterStore?.userFilters?.filters?.state_group.includes(_stateGroup?.key)
? true
: false
}
icon={<StateGroupIcons stateGroup={_stateGroup.key} />}
title={_stateGroup.title}
/>

View File

@ -17,14 +17,14 @@ export const FilterState = observer(() => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore, issueView: issueStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(false);
const [previewEnabled, setPreviewEnabled] = React.useState(true);
return (
<div>
<FilterHeader
title={"State"}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={setPreviewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">
@ -36,7 +36,12 @@ export const FilterState = observer(() => {
issueFilterStore?.projectStates[_stateGroup].map((_state: any) => (
<FilterOption
key={_state?.id}
isChecked={false}
isChecked={
issueFilterStore?.userFilters?.filters?.state != null &&
issueFilterStore?.userFilters?.filters?.state.includes(_state?.id)
? true
: false
}
icon={<StateGroupIcons stateGroup={_stateGroup} color={_state?.color} />}
title={_state?.name}
/>

View File

@ -14,14 +14,14 @@ export const FilterTargetDate = observer(() => {
const store: RootStore = useMobxStore();
const { issueFilters: issueFilterStore, issueView: issueStore } = store;
const [previewEnabled, setPreviewEnabled] = React.useState(false);
const [previewEnabled, setPreviewEnabled] = React.useState(true);
return (
<div>
<FilterHeader
title={"Target Date"}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={setPreviewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div className="space-y-[2px] pt-1">

View File

@ -0,0 +1,50 @@
import { Fragment } from "react";
// headless ui
import { Popover, Transition } from "@headlessui/react";
// lucide icons
import { ChevronDown, ChevronUp } from "lucide-react";
interface IIssueDropdown {
children: React.ReactNode;
title?: string;
}
export const IssueDropdown = ({ children, title = "Dropdown" }: IIssueDropdown) => (
<Popover className="relative">
{({ open }) => {
if (open) {
}
return (
<>
<Popover.Button
className={`outline-none border border-custom-border-200 text-xs rounded flex items-center gap-2 p-2 py-1.5 hover:bg-custom-background-100`}
>
<div className="font-medium">{title}</div>
<div className="w-[14px] h-[14px] flex justify-center items-center">
{open ? (
<ChevronUp width={14} strokeWidth={2} />
) : (
<ChevronDown width={14} strokeWidth={2} />
)}
</div>
</Popover.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute right-0 z-10 mt-1 w-[300px] h-[700px]">
<div className="w-full h-full overflow-hidden rounded border border-custom-border-200 bg-custom-background-100 shadow-xl">
{children}
</div>
</Popover.Panel>
</Transition>
</>
);
}}
</Popover>
);

View File

@ -5,7 +5,7 @@ import { ChevronDown, ChevronUp } from "lucide-react";
interface IFilterHeader {
title: string;
isPreviewEnabled: boolean;
handleIsPreviewEnabled: (isPreviewEnabled: boolean) => void;
handleIsPreviewEnabled: () => void;
}
export const FilterHeader = ({
@ -13,11 +13,11 @@ export const FilterHeader = ({
isPreviewEnabled,
handleIsPreviewEnabled,
}: IFilterHeader) => (
<div className="flex items-center justify-between gap-2 p-[6px] pb-2 bg-custom-background-80 sticky top-0">
<div className="text-gray-500 text-sm text-custom-text-300 font-medium">{title}</div>
<div className="flex items-center justify-between gap-2 p-[6px] pb-1 bg-custom-background-100 sticky top-0">
<div className="text-gray-500 text-xs text-custom-text-300 font-medium">{title}</div>
<div
className="flex-shrink-0 w-[20px] h-[20px] flex justify-center items-center rounded-sm transition-all hover:bg-custom-background-100 cursor-pointer"
onClick={() => handleIsPreviewEnabled(!isPreviewEnabled)}
className="flex-shrink-0 w-[20px] h-[20px] flex justify-center items-center rounded transition-all hover:bg-custom-background-80 cursor-pointer"
onClick={handleIsPreviewEnabled}
>
{isPreviewEnabled ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
</div>

View File

@ -10,7 +10,7 @@ interface IFilterOption {
}
export const FilterOption = ({ isChecked, icon, title, multiple = true }: IFilterOption) => (
<div className="flex items-center gap-3 cursor-pointer rounded-sm p-[6px] py-[5px] transition-all hover:bg-custom-border-100">
<div className="flex items-center gap-3 cursor-pointer rounded p-[6px] py-[5px] transition-all hover:bg-custom-border-100">
<div
className={`flex-shrink-0 w-[14px] h-[14px] flex justify-center items-center border border-custom-border-300 bg-custom-background-90 ${
isChecked ? `bg-custom-primary-300 text-white` : ``
@ -19,6 +19,6 @@ export const FilterOption = ({ isChecked, icon, title, multiple = true }: IFilte
{isChecked && <Check size={10} strokeWidth={2} />}
</div>
{icon}
<div className="hyphens-auto line-clamp-2 text-custom-text-200 text-sm w-full">{title}</div>
<div className="hyphens-auto line-clamp-1 text-custom-text-200 text-xs w-full">{title}</div>
</div>
);

View File

@ -28,28 +28,6 @@ export const IssueKanBanViewRoot = observer(() => {
console.log("result", result);
};
console.log("------");
console.log("workspace id -->", issueFilterStore?.workspaceId);
console.log("project id -->", issueFilterStore?.projectId);
console.log("module id -->", issueFilterStore?.moduleId);
console.log("cycle id -->", issueFilterStore?.cycleId);
console.log("view id -->", issueFilterStore?.viewId);
console.log("<-- workspace level -->");
console.log("workspace projects -->", issueFilterStore?.workspaceProjects);
console.log("workspace labels -->", issueFilterStore?.workspaceLabels);
console.log("<-- project level -->");
console.log("project states -->", issueFilterStore?.projectStates);
console.log("project labels -->", issueFilterStore?.projectLabels);
console.log("project members -->", issueFilterStore?.projectMembers);
console.log("project display properties -->", issueFilterStore?.projectDisplayProperties);
console.log("issue layout -->", issueFilterStore?.issueLayout);
console.log("issues -->", issueViewStore?.getIssues);
console.log("------");
return (
<div className="relative w-full h-full">
{issueViewStore.loader || issueViewStore?.getIssues === null ? (

View File

@ -94,7 +94,7 @@ export const LayoutSelection = observer(() => {
{layoutSelectionFilters.map((_layout) => (
<div
key={_layout?.key}
className={`w-[32px] h-[26px] rounded flex justify-center items-center cursor-pointer transition-all hover:bg-custom-background-100 overflow-hidden group ${
className={`w-[28px] h-[22px] rounded flex justify-center items-center cursor-pointer transition-all hover:bg-custom-background-100 overflow-hidden group ${
issueFilterStore?.issueLayout == _layout?.key
? `bg-custom-background-100 shadow shadow-gray-200`
: ``
@ -102,7 +102,7 @@ export const LayoutSelection = observer(() => {
onClick={() => handleLayoutSelection(_layout?.key)}
>
<_layout.icon
size={15}
size={14}
strokeWidth={2}
className={`${
issueFilterStore?.issueLayout == _layout?.key

View File

@ -4,7 +4,11 @@ import useSWR from "swr";
// components
import { IssueKanBanViewRoot } from "components/issue-layouts/kanban";
import { LayoutSelection } from "components/issue-layouts/layout-selection";
// issue dropdowns
import { IssueDropdown } from "components/issue-layouts/helpers/dropdown";
// filter components
import { FilterSelection } from "components/issue-layouts/filters";
import { DisplayFiltersSelection } from "components/issue-layouts/display-filters";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
@ -29,8 +33,8 @@ const KanBanViewRoot = () => {
// await issueViewStore.getMyIssuesAsync(workspaceSlug, "my_issues", "spreadsheet");
// await issueViewStore.getMyIssuesAsync(workspaceSlug, "my_issues", "gantt");
// project issues under and workspace and project
await issueViewStore.getProjectIssuesAsync(workspaceSlug, projectSlug, "issues", "list");
// await issueViewStore.getProjectIssuesAsync(workspaceSlug, projectSlug, "issues", "kanban");
// await issueViewStore.getProjectIssuesAsync(workspaceSlug, projectSlug, "issues", "list");
await issueViewStore.getProjectIssuesAsync(workspaceSlug, projectSlug, "issues", "kanban");
// await issueViewStore.getProjectIssuesAsync(workspaceSlug, projectSlug, "issues", "calendar");
// await issueViewStore.getProjectIssuesAsync(
// workspaceSlug,
@ -165,16 +169,18 @@ const KanBanViewRoot = () => {
<div>Filter Header</div>
</div>
<div className="relative flex items-center gap-2">
<div>{/* <FilterSelection /> */}</div>
<div>
<IssueDropdown title={"Filters"}>
<FilterSelection />
</IssueDropdown>
<IssueDropdown title={"View"}>
<DisplayFiltersSelection />
</IssueDropdown>
<LayoutSelection />
</div>
</div>
</div>
</div>
<div className="w-full h-full relative overflow-hidden">
<FilterSelection />
{/* <IssueKanBanViewRoot /> */}
<IssueKanBanViewRoot />
</div>
</div>
</div>

View File

@ -131,9 +131,9 @@ class IssueViewStore implements IIssueViewStore {
const currentLayout: TIssueLayouts = currentProjectId
? this.rootStore.issueFilters.issueFilters?.[currentWorkspaceId]
?.project_issue_properties?.[currentProjectId]?.renderLayout
?.project_issue_properties?.[currentProjectId]?.issues?.display_filters?.layout
: this.rootStore.issueFilters.issueFilters?.[currentWorkspaceId]?.my_issue_properties
?.renderLayout;
?.display_filters?.layout;
if (currentView === "my_issues")
return this.issues?.[currentWorkspaceId]?.my_issues?.[currentLayout];

View File

@ -12,7 +12,7 @@ export const filtersPriority: { key: string; title: string }[] = [
{ key: "high", title: "High" },
{ key: "medium", title: "Medium" },
{ key: "low", title: "Low" },
{ key: "null", title: "None" },
{ key: "none", title: "None" },
];
export const filterStateGroup: { key: TStateGroup; title: string }[] = [
@ -43,7 +43,7 @@ export const displayPropertyGroupBy: { key: string; title: string }[] = [
{ key: "state", title: "States" },
{ key: "state_detail.group", title: "State Groups" },
{ key: "priority", title: "Priority" },
{ key: "Project", title: "project" }, // required this on my issues
{ key: "Project", title: "Project" }, // required this on my issues
{ key: "labels", title: "Labels" },
{ key: "assignees", title: "Assignees" },
{ key: "created_by", title: "Created By" },
@ -67,7 +67,7 @@ export const displayProperties: { key: string; title: string }[] = [
{ key: "assignee", title: "Assignee" },
{ key: "start_date", title: "Start Date" },
{ key: "due_date", title: "Due Date" },
{ key: "key", title: "Id" },
{ key: "key", title: "ID" },
{ key: "labels", title: "Labels" },
{ key: "priority", title: "Priority" },
{ key: "state", title: "State" },
@ -76,3 +76,10 @@ export const displayProperties: { key: string; title: string }[] = [
{ key: "link", title: "Link" },
{ key: "estimate", title: "Estimate" },
];
export const extraProperties: { key: string; title: string }[] = [
{ key: "sub_issues", title: "Show sub-issues" }, // in spreadsheet its always false
{ key: "show_empty_groups", title: "Show empty states" }, // filter on front-end
{ key: "calendar_date_range", title: "Calendar Date Range" }, // calendar date range yyyy-mm-dd;before range yyyy-mm-dd;after
{ key: "start_target_date", title: "Start target Date" }, // gantt always be true
];

View File

@ -16,6 +16,7 @@ import {
displayPropertyOrderBy,
displayPropertyIssueType,
displayProperties,
extraProperties,
} from "./issue_data";
export type TIssueViews = "my_issues" | "issues" | "modules" | "views" | "cycles";
@ -70,6 +71,7 @@ export interface IIssueRenderFilters {
order_by: { key: string; title: string }[];
issue_type: { key: string; title: string }[];
display_properties: { key: string; title: string }[];
extra_properties: { key: string; title: string }[];
workspace_properties: {
[key: string]: {
projects: any[];
@ -91,7 +93,6 @@ export interface IIssueFilters {
filters: IIssueFilter;
display_filters: IIssueDisplayFilters;
display_properties: IIssueDisplayProperties;
renderLayout: TIssueLayouts;
};
project_issue_properties: {
[key: string]: {
@ -99,11 +100,22 @@ export interface IIssueFilters {
filters: IIssueFilter;
display_filters: IIssueDisplayFilters;
};
cycles: { filters: IIssueFilter; display_filters: IIssueDisplayFilters };
modules: { filters: IIssueFilter; display_filters: IIssueDisplayFilters };
views: { filters: IIssueFilter; display_filters: IIssueDisplayFilters };
cycles: {
[key: string]: {
filters: IIssueFilter;
};
};
modules: {
[key: string]: {
filters: IIssueFilter;
};
};
views: {
[key: string]: {
filters: IIssueFilter;
};
};
display_properties: IIssueDisplayProperties;
renderLayout: TIssueLayouts;
};
};
};
@ -124,6 +136,16 @@ export interface IIssueFilterStore {
issueRenderFilters: IIssueRenderFilters;
issueFilters: IIssueFilters;
filterRenderProperties:
| {
[key: string]: {
isPreviewEnabled: boolean;
totalElements: number;
elementsVisible: number;
};
}[]
| null;
// actions
getWorkspaceMyIssuesFilters: (workspaceId: string) => Promise<any>;
updateWorkspaceMyIssuesFilters: () => any | Promise<any>;
@ -173,10 +195,21 @@ class IssueFilterStore implements IIssueFilterStore {
order_by: displayPropertyOrderBy,
issue_type: displayPropertyIssueType,
display_properties: displayProperties,
extra_properties: extraProperties,
workspace_properties: {},
};
issueFilters: IIssueFilters = {};
filterRenderProperties:
| {
[key: string]: {
isPreviewEnabled: boolean;
totalElements: number;
elementsVisible: number;
};
}[]
| null = null;
// root store
rootStore;
// service
@ -211,6 +244,8 @@ class IssueFilterStore implements IIssueFilterStore {
projectMembers: computed,
projectDisplayProperties: computed,
userFilters: computed,
// action
setWorkspaceId: action,
setProjectId: action,
@ -253,12 +288,11 @@ class IssueFilterStore implements IIssueFilterStore {
// computed
get issueLayout() {
if (!this.workspaceId) return null;
if (!this.projectId)
return this.issueFilters?.[this.workspaceId]?.my_issue_properties?.renderLayout;
return this.issueFilters?.[this.workspaceId]?.my_issue_properties?.display_filters?.layout;
if (this.projectId)
return this.issueFilters?.[this.workspaceId]?.project_issue_properties?.[this.projectId]
?.renderLayout;
?.issues?.display_filters?.layout;
}
get workspaceProjects() {
@ -291,9 +325,68 @@ class IssueFilterStore implements IIssueFilterStore {
get projectDisplayProperties() {
if (!this.workspaceId || !this.projectId) return null;
return this.issueFilters?.[this.workspaceId]?.project_issue_properties?.[this.projectId]
?.display_properties;
?.display_properties as any;
}
get userFilters() {
if (!this.workspaceId) return null;
if (this.issueView === "my_issues")
return this.issueFilters?.[this.workspaceId]?.my_issue_properties;
if (!this.projectId) return null;
let _issueFilters: {
filters: IIssueFilter | null;
display_filters: IIssueDisplayFilters;
display_properties: IIssueDisplayProperties;
} = {
filters: null,
display_filters:
this.issueFilters?.[this.workspaceId]?.project_issue_properties?.[this.projectId]?.issues
?.display_filters,
display_properties:
this.issueFilters?.[this.workspaceId]?.project_issue_properties?.[this.projectId]
?.display_properties,
};
if (this.issueView === "issues") {
_issueFilters = {
..._issueFilters,
filters:
this.issueFilters?.[this.workspaceId]?.project_issue_properties?.[this.projectId]?.issues
?.filters,
};
return _issueFilters;
}
if (this.issueView === "modules" && this.moduleId) {
_issueFilters = {
..._issueFilters,
filters:
this.issueFilters?.[this.workspaceId]?.project_issue_properties?.[this.projectId]
?.modules?.[this.moduleId]?.filters,
};
return _issueFilters;
}
if (this.issueView === "cycles" && this.cycleId) {
_issueFilters = {
..._issueFilters,
filters:
this.issueFilters?.[this.workspaceId]?.project_issue_properties?.[this.projectId]
?.cycles?.[this.cycleId]?.filters,
};
return _issueFilters;
}
if (this.issueView === "views" && this.viewId) {
_issueFilters = {
..._issueFilters,
filters:
this.issueFilters?.[this.workspaceId]?.project_issue_properties?.[this.projectId]
?.views?.[this.viewId]?.filters,
};
return _issueFilters;
}
return null;
}
handleUserFilter = () => {};
computedFilter = (filters: any, filteredParams: any) => {
const computedFilters: any = {};
Object.keys(filters).map((key) => {
@ -322,53 +415,51 @@ class IssueFilterStore implements IIssueFilterStore {
this.setViewId(_viewId);
this.setIssueView(_issueView);
if (_workspaceId) {
if (!_projectId)
this.issueFilters = {
...this.issueFilters,
[_workspaceId]: {
...this.issueFilters[_workspaceId],
my_issue_properties: {
...this.issueFilters[_workspaceId]?.my_issue_properties,
renderLayout: _issueLayout,
},
},
};
else
this.issueFilters = {
...this.issueFilters,
[_workspaceId]: {
...this.issueFilters[_workspaceId],
project_issue_properties: {
...this.issueFilters[_workspaceId]?.project_issue_properties,
[_projectId]: {
...this.issueFilters[_workspaceId]?.project_issue_properties?.[_projectId],
renderLayout: _issueLayout,
},
},
},
};
}
const _layout = this.userFilters?.display_filters?.layout;
let filteredRouteParams: any = {
priority: [] || undefined,
state: [] || undefined,
assignees: [] || undefined, // ['user_id', 'user_id']
created_by: [] || undefined, // ['user_id', 'user_id']
labels: [] || undefined, // ['label_id', 'label_id']
start_date: [] || undefined, // ['yyyy-mm-dd:after/before', 'yyyy-mm-dd:after/before']
target_date: [] || undefined, // [yyyy-mm-dd:after, yyyy-mm-dd:before]
type: "" || undefined, // 'active' (started, un_started) || 'backlog' || 'null' (all_the_issues)
group_by: "state", // TIssueGroupByOptions
order_by: "-created_at", // TIssueOrderByOptions
sub_issue: true, // true for all other views except spreadsheet
priority: this.userFilters?.filters?.priority || undefined,
state_group: this.userFilters?.filters?.state_group || undefined,
state: this.userFilters?.filters?.state || undefined,
assignees: this.userFilters?.filters?.assignees || undefined,
created_by: this.userFilters?.filters?.created_by || undefined,
labels: this.userFilters?.filters?.labels || undefined,
start_date: this.userFilters?.filters?.start_date || undefined,
target_date: this.userFilters?.filters?.target_date || undefined,
type: this.userFilters?.display_filters?.type || undefined,
group_by: this.userFilters?.display_filters?.group_by || "state",
order_by: this.userFilters?.display_filters?.order_by || "-created_at",
sub_issue: this.userFilters?.display_filters?.sub_issue || true,
show_empty_groups: this.userFilters?.display_filters?.show_empty_groups || true,
calendar_date_range: this.userFilters?.display_filters?.calendar_date_range || undefined,
start_target_date: this.userFilters?.display_filters?.start_target_date || true,
};
let filteredParams: any = {};
console.log("filteredRouteParams", filteredRouteParams);
if (_issueLayout === "list")
// start date and target date we have to construct the format here
let filteredParams: any = {};
if (_layout === "list")
filteredParams = [
"priority",
"state_group",
"state",
"assignees",
"created_by",
"labels",
"start_date",
"target_date",
"group_by",
"order_by",
"type",
"sub_issue",
"show_empty_groups",
];
if (_layout === "kanban")
filteredParams = [
"priority",
"state_group",
"state",
"assignees",
"created_by",
@ -380,7 +471,33 @@ class IssueFilterStore implements IIssueFilterStore {
"order_by",
"sub_issue",
];
if (_issueLayout === "kanban")
if (_layout === "calendar")
filteredParams = [
"priority",
"state_group",
"state",
"assignees",
"created_by",
"labels",
"start_date",
"target_date",
"type",
"calendar_date_range",
];
if (_layout === "spreadsheet")
filteredParams = [
"priority",
"state_group",
"state",
"assignees",
"created_by",
"labels",
"start_date",
"target_date",
"type",
"sub_issues",
];
if (_layout === "gantt")
filteredParams = [
"priority",
"state",
@ -389,49 +506,17 @@ class IssueFilterStore implements IIssueFilterStore {
"labels",
"start_date",
"target_date",
"type",
"group_by",
"order_by",
"sub_issue",
];
if (_issueLayout === "calendar")
filteredParams = [
"priority",
"state",
"assignees",
"created_by",
"labels",
"start_date",
"target_date",
"type",
];
if (_issueLayout === "spreadsheet")
filteredParams = [
"priority",
"state",
"assignees",
"created_by",
"labels",
"start_date",
"target_date",
"type",
];
if (_issueLayout === "gantt")
filteredParams = [
"priority",
"state",
"assignees",
"created_by",
"labels",
"start_date",
"target_date",
"type",
"order_by",
"sub_issue_id",
"start_target_date",
];
filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams);
// remove few attributes from the object when we are in workspace issues
console.log("filteredRouteParams", filteredRouteParams);
return filteredRouteParams;
};
@ -470,7 +555,6 @@ class IssueFilterStore implements IIssueFilterStore {
return error;
}
};
getWorkspaceMyIssuesLabels = async (workspaceId: string) => {
try {
this.loader = true;
@ -523,40 +607,48 @@ class IssueFilterStore implements IIssueFilterStore {
my_issue_properties: {
...this?.issueFilters?.[workspaceId]?.my_issue_properties,
filters: {
priority: undefined,
state: undefined,
state_group: undefined,
assignees: undefined,
created_by: undefined,
labels: undefined,
start_date: undefined,
target_date: undefined,
subscriber: undefined,
priority: issuesFiltersResponse?.view_props?.filters?.priority ?? null,
state: issuesFiltersResponse?.view_props?.filters?.state ?? null,
state_group: issuesFiltersResponse?.view_props?.filters?.state_group ?? null,
assignees: issuesFiltersResponse?.view_props?.filters?.assignees ?? null,
created_by: issuesFiltersResponse?.view_props?.filters?.created_by ?? null,
labels: issuesFiltersResponse?.view_props?.filters?.labels ?? null,
start_date: issuesFiltersResponse?.view_props?.filters?.start_date ?? null,
target_date: issuesFiltersResponse?.view_props?.filters?.target_date ?? null,
subscriber: issuesFiltersResponse?.view_props?.filters?.subscriber ?? null,
},
display_filters: {
group_by: undefined,
order_by: undefined,
type: undefined,
sub_issue: undefined,
show_empty_groups: undefined,
layout: undefined,
calendar_date_range: undefined,
start_target_date: undefined,
group_by: issuesFiltersResponse?.view_props?.display_filters?.group_by ?? null,
order_by: issuesFiltersResponse?.view_props?.display_filters?.order_by ?? null,
type: issuesFiltersResponse?.view_props?.display_filters?.type ?? null,
sub_issue: issuesFiltersResponse?.view_props?.display_filters?.sub_issue ?? false,
show_empty_groups:
issuesFiltersResponse?.view_props?.display_filters?.show_empty_groups ?? false,
layout: issuesFiltersResponse?.view_props?.display_filters?.layout ?? "list",
calendar_date_range:
issuesFiltersResponse?.view_props?.display_filters?.calendar_date_range ?? false,
start_target_date:
issuesFiltersResponse?.view_props?.display_filters?.start_target_date ?? true,
},
display_properties: {
assignee: false,
attachment_count: false,
created_on: false,
due_date: false,
estimate: false,
key: false,
labels: false,
link: false,
priority: false,
start_date: false,
state: false,
sub_issue_count: false,
updated_on: false,
assignee: issuesFiltersResponse?.view_props?.display_properties?.assignee ?? false,
attachment_count:
issuesFiltersResponse?.view_props?.display_properties?.attachment_count ?? false,
created_on:
issuesFiltersResponse?.view_props?.display_properties?.created_on ?? false,
due_date: issuesFiltersResponse?.view_props?.display_properties?.due_date ?? false,
estimate: issuesFiltersResponse?.view_props?.display_properties?.estimate ?? false,
key: issuesFiltersResponse?.view_props?.display_properties?.key ?? false,
labels: issuesFiltersResponse?.view_props?.display_properties?.labels ?? false,
link: issuesFiltersResponse?.view_props?.display_properties?.link ?? false,
priority: issuesFiltersResponse?.view_props?.display_properties?.priority ?? false,
start_date:
issuesFiltersResponse?.view_props?.display_properties?.start_date ?? false,
state: issuesFiltersResponse?.view_props?.display_properties?.state ?? false,
sub_issue_count:
issuesFiltersResponse?.view_props?.display_properties?.sub_issue_count ?? false,
updated_on:
issuesFiltersResponse?.view_props?.display_properties?.updated_on ?? false,
},
},
},
@ -652,7 +744,6 @@ class IssueFilterStore implements IIssueFilterStore {
return error;
}
};
getProjectLevelLabels = async (workspaceId: string, projectId: string) => {
try {
this.loader = true;
@ -692,7 +783,6 @@ class IssueFilterStore implements IIssueFilterStore {
return error;
}
};
getProjectLevelMembers = async (workspaceId: string, projectId: string) => {
try {
this.loader = true;
@ -755,7 +845,7 @@ class IssueFilterStore implements IIssueFilterStore {
...this?.issueFilters[workspaceId]?.project_issue_properties,
[projectId]: {
...this?.issueFilters[workspaceId]?.project_issue_properties?.[projectId],
display_properties: issuesDisplayPropertiesResponse,
display_properties: issuesDisplayPropertiesResponse?.properties,
},
},
},
@ -822,25 +912,35 @@ class IssueFilterStore implements IIssueFilterStore {
this.loader = true;
this.error = null;
const workspaceId = "1";
const issuesDisplayFiltersResponse = await this.projectService.projectMemberMe(
workspaceId,
projectId
);
const issuesFiltersResponse = await this.workspaceService.workspaceMemberMe(workspaceId);
if (issuesDisplayFiltersResponse) {
const _filters = { ...issuesDisplayFiltersResponse?.view_props?.filters };
const _displayFilters = { ...issuesDisplayFiltersResponse?.view_props?.display_filters };
if (issuesFiltersResponse) {
// const _issuesFiltersResponse = this.issueFilters;
// const _issuesFiltersResponse: any = {
// ...this.issues,
// [workspaceId]: {
// ...this?.issues[workspaceId],
// my_issues: {
// ...this?.issues[workspaceId]?.my_issues,
// [_layout as string]: issuesResponse,
// },
// },
// };
const _issuesDisplayFiltersResponse: any = {
...this.issueFilters,
[workspaceId]: {
...this?.issueFilters[workspaceId],
project_issue_properties: {
...this?.issueFilters[workspaceId]?.project_issue_properties,
[projectId]: {
...this?.issueFilters[workspaceId]?.project_issue_properties?.[projectId],
issues: {
...this?.issueFilters[workspaceId]?.project_issue_properties?.[projectId]?.issues,
filters: _filters,
display_filters: _displayFilters,
},
},
},
},
};
runInAction(() => {
// this.issueFilters = _issuesFiltersResponse;
this.issueFilters = _issuesDisplayFiltersResponse;
this.loader = false;
this.error = null;
});
@ -901,28 +1001,12 @@ class IssueFilterStore implements IIssueFilterStore {
await this.getProjectLevelLabels(workspaceId, projectId);
await this.getProjectLevelMembers(workspaceId, projectId);
await this.getProjectDisplayProperties(workspaceId, projectId);
const issuesFiltersResponse = await this.workspaceService.workspaceMemberMe(workspaceId);
if (issuesFiltersResponse) {
// const _issuesFiltersResponse = this.issueFilters;
// const _issuesFiltersResponse: any = {
// ...this.issues,
// [workspaceId]: {
// ...this?.issues[workspaceId],
// my_issues: {
// ...this?.issues[workspaceId]?.my_issues,
// [_layout as string]: issuesResponse,
// },
// },
// };
await this.getProjectDisplayFilters(workspaceId, projectId);
runInAction(() => {
// this.issueFilters = _issuesFiltersResponse;
this.loader = false;
this.error = null;
});
}
// return issuesResponse;
} catch (error) {
@ -937,11 +1021,6 @@ class IssueFilterStore implements IIssueFilterStore {
this.loader = true;
this.error = null;
await this.getProjectLevelStates(workspaceId, projectId);
await this.getProjectLevelLabels(workspaceId, projectId);
await this.getProjectLevelMembers(workspaceId, projectId);
// await this.getProjectDisplayProperties(workspaceId, projectId);
const issuesFiltersResponse = await this.workspaceService.workspaceMemberMe(workspaceId);
if (issuesFiltersResponse) {