chore: updated ui and store

This commit is contained in:
gurusainath 2024-02-09 14:33:22 +05:30
parent 1ce7f20c2d
commit f90595ca31
20 changed files with 221 additions and 286 deletions

View File

@ -10,10 +10,8 @@ import {
ViewRoot, ViewRoot,
ViewCreateEditForm, ViewCreateEditForm,
ViewLayoutRoot, ViewLayoutRoot,
ViewFiltersRoot,
ViewFiltersDropdown, ViewFiltersDropdown,
ViewDisplayFiltersDropdown, ViewDisplayFiltersDropdown,
ViewDisplayPropertiesRoot,
ViewAppliedFiltersRoot, ViewAppliedFiltersRoot,
ViewDuplicateConfirmationModal, ViewDuplicateConfirmationModal,
ViewDeleteConfirmationModal, ViewDeleteConfirmationModal,
@ -73,11 +71,12 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
() => ({ () => ({
setName: (name: string) => viewDetailStore?.setName(name), setName: (name: string) => viewDetailStore?.setName(name),
setDescription: (name: string) => viewDetailStore?.setDescription(name), setDescription: (name: string) => viewDetailStore?.setDescription(name),
setFilters: (filters: Partial<TViewFilters>) => viewDetailStore?.setFilters(filters), setFilters: (filterKey: keyof TViewFilters, filterValue: "clear_all" | string) =>
viewDetailStore?.setFilters(filterKey, filterValue),
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) =>
viewDetailStore?.setDisplayFilters(display_filters), viewDetailStore?.setDisplayFilters(display_filters),
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => setDisplayProperties: (displayPropertyKey: keyof TViewDisplayProperties) =>
viewDetailStore?.setDisplayProperties(display_properties), viewDetailStore?.setDisplayProperties(displayPropertyKey),
localViewCreateEdit: (viewId: string | undefined) => { localViewCreateEdit: (viewId: string | undefined) => {
if (viewId === undefined) { if (viewId === undefined) {
const viewPayload = viewLocalPayload; const viewPayload = viewLocalPayload;
@ -171,8 +170,9 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
/> />
</div> </div>
<div className="p-5 border-b border-custom-border-300"> <div className="p-5 py-2 border-b border-custom-border-200 relative flex gap-2">
<ViewDisplayPropertiesRoot <div className="w-full overflow-hidden">
<ViewAppliedFiltersRoot
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
projectId={projectId} projectId={projectId}
viewId={viewId} viewId={viewId}
@ -181,29 +181,7 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
/> />
</div> </div>
{/* <div className="m-5 px-2 rounded border border-custom-border-300 w-[400px] max-h-[500px] overflow-hidden overflow-y-auto mx-auto bg-custom-background-100"> <div className="flex-shrink-0">
<ViewFiltersRoot
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={viewId}
viewType={viewType}
viewOperations={viewOperations}
baseRoute={baseRoute}
/>
</div> */}
<div className="p-5 border-b border-custom-border-200 relative flex gap-2">
{/* <div className="w-full">
<ViewAppliedFiltersRoot
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={viewId}
viewType={viewType}
viewOperations={viewOperations}
/>
</div> */}
<div className="flex-shrink-0 h-full">
<ViewLayoutRoot <ViewLayoutRoot
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
projectId={projectId} projectId={projectId}
@ -220,7 +198,6 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
viewId={viewId} viewId={viewId}
viewType={viewType} viewType={viewType}
viewOperations={viewOperations} viewOperations={viewOperations}
baseRoute={baseRoute}
displayDropdownText={false} displayDropdownText={false}
/> />
</div> </div>
@ -232,12 +209,11 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
viewId={viewId} viewId={viewId}
viewType={viewType} viewType={viewType}
viewOperations={viewOperations} viewOperations={viewOperations}
baseRoute={baseRoute}
displayDropdownText={false} displayDropdownText={false}
/> />
</div> </div>
<div className="border border-custom-border-300 relative flex items-center gap-1 h-8 rounded px-2 transition-all text-custom-text-200 hover:text-custom-text-100 bg-custom-background-100 hover:bg-custom-background-80 cursor-pointer shadow-custom-shadow-2xs"> <div className="border border-custom-border-300 relative flex items-center gap-1 h-7 rounded px-2 transition-all text-custom-text-200 hover:text-custom-text-100 bg-custom-background-100 hover:bg-custom-background-80 cursor-pointer shadow-custom-shadow-2xs">
<div className="w-4 h-4 relative flex justify-center items-center overflow-hidden"> <div className="w-4 h-4 relative flex justify-center items-center overflow-hidden">
<Pencil size={12} /> <Pencil size={12} />
</div> </div>

View File

@ -1,9 +1,10 @@
import { FC } from "react"; import { FC } from "react";
import { User, X } from "lucide-react"; import { ImagePlus, X } from "lucide-react";
// hooks // hooks
import { useViewDetail } from "hooks/store"; import { useViewFilter } from "hooks/store";
// types // types
import { TViewFilters, TViewTypes } from "@plane/types"; import { TViewFilters, TViewTypes } from "@plane/types";
import { TViewOperations } from "../types";
type TViewAppliedFiltersItem = { type TViewAppliedFiltersItem = {
workspaceSlug: string; workspaceSlug: string;
@ -11,33 +12,30 @@ type TViewAppliedFiltersItem = {
viewId: string; viewId: string;
viewType: TViewTypes; viewType: TViewTypes;
filterKey: keyof TViewFilters; filterKey: keyof TViewFilters;
filterId: string; propertyId: string;
viewOperations: TViewOperations;
}; };
export const ViewAppliedFiltersItem: FC<TViewAppliedFiltersItem> = (props) => { export const ViewAppliedFiltersItem: FC<TViewAppliedFiltersItem> = (props) => {
const { workspaceSlug, projectId, viewId, viewType, filterKey, filterId } = props; const { workspaceSlug, projectId, filterKey, propertyId, viewOperations } = props;
// hooks // hooks
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); const viewFilterHelper = useViewFilter(workspaceSlug, projectId);
const propertyDetail = viewFilterHelper?.propertyDetails(filterKey, propertyId) || undefined;
const removeFilterOption = () => { const removeFilterOption = () => {
const filters = viewDetailStore?.appliedFilters?.filters; viewOperations?.setFilters(filterKey, propertyId);
if (!filters) return;
const filterValues = filters[filterKey];
const updatedFilterValues = filterValues.filter((value) => value !== filterId);
viewDetailStore?.setFilters({ [filterKey]: updatedFilterValues });
}; };
return ( return (
<div <div
key={`filter_value_${filterKey}_${filterId}`} key={`filter_value_${filterKey}_${propertyId}`}
className="border border-custom-border-200 rounded relative flex items-center gap-1 px-1 py-0.5" className="bg-custom-background-80 rounded relative flex items-center gap-1.5 p-1.5 py-1"
> >
<div className="flex-shrink-0 w-4 h-4 relative flex justify-center items-center overflow-hidden"> <div className="flex-shrink-0 w-4 h-4 relative flex justify-center items-center overflow-hidden">
<User size={12} /> {propertyDetail?.icon || <ImagePlus size={14} />}
</div>
<div className="text-xs">
{filterKey} - {filterId}
</div> </div>
<div className="text-xs">{propertyDetail?.label || propertyId}</div>
<div <div
className="flex-shrink-0 w-3.5 h-3.5 relative flex justify-center items-center overflow-hidden rounded-full transition-all cursor-pointer bg-custom-background-80 hover:bg-custom-background-90 text-custom-text-300 hover:text-custom-text-200" className="flex-shrink-0 w-3.5 h-3.5 relative flex justify-center items-center overflow-hidden rounded-full transition-all cursor-pointer bg-custom-background-80 hover:bg-custom-background-90 text-custom-text-300 hover:text-custom-text-200"
onClick={removeFilterOption} onClick={removeFilterOption}

View File

@ -6,10 +6,9 @@ import { X } from "lucide-react";
import { useViewDetail } from "hooks/store"; import { useViewDetail } from "hooks/store";
// components // components
import { ViewAppliedFiltersItem } from "./filter-item"; import { ViewAppliedFiltersItem } from "./filter-item";
// helpers
import { generateTitle } from "./helper";
// types // types
import { TViewFilters, TViewTypes } from "@plane/types"; import { TViewFilters, TViewTypes } from "@plane/types";
import { TViewOperations } from "../types";
type TViewAppliedFilters = { type TViewAppliedFilters = {
workspaceSlug: string; workspaceSlug: string;
@ -17,41 +16,51 @@ type TViewAppliedFilters = {
viewId: string; viewId: string;
viewType: TViewTypes; viewType: TViewTypes;
filterKey: keyof TViewFilters; filterKey: keyof TViewFilters;
viewOperations: TViewOperations;
}; };
export const ViewAppliedFilters: FC<TViewAppliedFilters> = observer((props) => { export const ViewAppliedFilters: FC<TViewAppliedFilters> = observer((props) => {
const { workspaceSlug, projectId, viewId, viewType, filterKey } = props; const { workspaceSlug, projectId, viewId, viewType, filterKey, viewOperations } = props;
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
const filterKeyValue = const propertyValues =
viewDetailStore?.appliedFilters?.filters && !isEmpty(viewDetailStore?.appliedFilters?.filters) viewDetailStore?.appliedFilters?.filters && !isEmpty(viewDetailStore?.appliedFilters?.filters)
? viewDetailStore?.appliedFilters?.filters?.[filterKey] || undefined ? viewDetailStore?.appliedFilters?.filters?.[filterKey] || undefined
: undefined; : undefined;
const clearFilter = () => viewDetailStore?.setFilters({ [filterKey]: [] }); const clearPropertyFilter = () => viewDetailStore?.setFilters(filterKey, "clear_all");
if (!filterKeyValue || filterKeyValue.length <= -1) return <></>; if (!propertyValues || propertyValues.length <= 0) return <></>;
return ( return (
<div className="relative flex items-center gap-2 border border-custom-border-300 rounded p-1.5 py-1 min-h-[32px]"> <div className="relative flex items-center gap-2 border border-custom-border-300 rounded p-1.5 px-2 min-h-[32px]">
<div className="flex-shrink-0 text-xs text-custom-text-200">{generateTitle(filterKey)}</div> <div className="flex-shrink-0 text-xs text-custom-text-200 capitalize">{filterKey.replaceAll("_", " ")}</div>
<div className="relative flex items-center gap-1 flex-wrap"> <div className="relative flex items-center gap-1.5 flex-wrap">
{["1", "2", "3", "4"].map((filterId) => ( {propertyValues.length >= 100 ? (
<Fragment key={filterId}> <div className="text-xs font-medium bg-custom-primary-100/20 rounded relative flex items-center gap-1 p-1 px-2">
{propertyValues.length} {filterKey.replaceAll("_", " ")}s
</div>
) : (
<>
{propertyValues.map((propertyId) => (
<Fragment key={propertyId}>
<ViewAppliedFiltersItem <ViewAppliedFiltersItem
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
projectId={projectId} projectId={projectId}
viewId={viewId} viewId={viewId}
viewType={viewType} viewType={viewType}
filterKey={filterKey} filterKey={filterKey}
filterId={filterId} propertyId={propertyId}
viewOperations={viewOperations}
/> />
</Fragment> </Fragment>
))} ))}
</>
)}
</div> </div>
<div <div
className="flex-shrink-0 relative flex justify-center items-center w-4 h-4 rounded-full cursor-pointer transition-all bg-custom-background-80 hover:bg-custom-background-90 text-custom-text-300 hover:text-custom-text-200" className="flex-shrink-0 relative flex justify-center items-center w-4 h-4 rounded-full cursor-pointer transition-all bg-custom-background-80 hover:bg-custom-background-90 text-custom-text-300 hover:text-custom-text-200"
onClick={clearFilter} onClick={clearPropertyFilter}
> >
<X size={10} /> <X size={10} />
</div> </div>

View File

@ -1,66 +0,0 @@
import { ReactNode } from "react";
import isEmpty from "lodash/isEmpty";
// types
import { TViewFilters } from "@plane/types";
type TComputedAppliedFilters = {
key: string;
title: string;
selectedOptions?: { id: string; icon: ""; title: ""; component: ReactNode }[];
dropdownOptions?: { id: string; icon: ""; title: ""; component: ReactNode }[];
}[];
export const filterOptions = (key: keyof TViewFilters, selectedFilters: string[]) => {
switch (key) {
case "project":
return [];
case "priority":
return [];
case "state":
return [];
case "state_group":
return [];
case "assignees":
return [];
case "mentions":
return [];
case "subscriber":
return [];
case "created_by":
return [];
case "labels":
return [];
case "start_date":
return [];
case "target_date":
return [];
default:
return [];
}
};
export const generateTitle = (title: string) =>
title
.split("_")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
export const constructAppliedFilters = (filters: TViewFilters): TComputedAppliedFilters => {
const appliedFilters: TComputedAppliedFilters = [];
if (filters && !isEmpty(filters)) {
Object.keys(filters).forEach((_filterKey) => {
const _key = _filterKey as keyof TViewFilters;
const _value = filters[_key];
if (_value && !isEmpty(_value)) {
appliedFilters.push({
key: _key,
title: generateTitle(_key),
});
}
});
}
return appliedFilters;
};

View File

@ -19,7 +19,7 @@ type TViewAppliedFiltersRoot = {
}; };
export const ViewAppliedFiltersRoot: FC<TViewAppliedFiltersRoot> = observer((props) => { export const ViewAppliedFiltersRoot: FC<TViewAppliedFiltersRoot> = observer((props) => {
const { workspaceSlug, projectId, viewId, viewType } = props; const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
// hooks // hooks
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
@ -28,16 +28,14 @@ export const ViewAppliedFiltersRoot: FC<TViewAppliedFiltersRoot> = observer((pro
? Object.keys(viewDetailStore?.appliedFilters?.filters) ? Object.keys(viewDetailStore?.appliedFilters?.filters)
: undefined; : undefined;
const clearAllFilters = () => { const clearAllFilters = () => viewDetailStore?.setFilters(undefined, "clear_all");
const clearedFilters: Partial<Record<keyof TViewFilters, string[]>> = {};
filterKeys?.forEach((key) => {
const _key = key as keyof TViewFilters;
clearedFilters[_key] = [];
});
viewDetailStore?.setFilters(clearedFilters);
};
if (!filterKeys) return <></>; if (!filterKeys || !viewDetailStore?.isFiltersApplied)
return (
<div className="relative w-full text-sm text-custom-text-200 inline-block truncate line-clamp-1 pt-1.5">
No filters applied. Apply filters to create views.
</div>
);
return ( return (
<div className="relative flex items-center gap-2 flex-wrap"> <div className="relative flex items-center gap-2 flex-wrap">
{filterKeys.map((key) => { {filterKeys.map((key) => {
@ -50,12 +48,14 @@ export const ViewAppliedFiltersRoot: FC<TViewAppliedFiltersRoot> = observer((pro
viewId={viewId} viewId={viewId}
viewType={viewType} viewType={viewType}
filterKey={filterKey} filterKey={filterKey}
viewOperations={viewOperations}
/> />
</Fragment> </Fragment>
); );
})} })}
<div <div
className="relative flex items-center gap-2 border border-custom-border-300 rounded p-1.5 py-1 cursor-pointer transition-all hover:bg-custom-background-80 text-custom-text-200 hover:text-custom-text-100 min-h-[32px]" className="relative flex items-center gap-2 border border-custom-border-300 rounded p-1.5 px-2 cursor-pointer transition-all hover:bg-custom-background-80 text-custom-text-200 hover:text-custom-text-100 min-h-[36px]"
onClick={clearAllFilters} onClick={clearAllFilters}
> >
<div className="text-xs">Clear All</div> <div className="text-xs">Clear All</div>

View File

@ -7,6 +7,8 @@ import { MonitorDot } from "lucide-react";
import useOutsideClickDetector from "hooks/use-outside-click-detector"; import useOutsideClickDetector from "hooks/use-outside-click-detector";
// components // components
import { ViewDisplayPropertiesRoot } from "../"; import { ViewDisplayPropertiesRoot } from "../";
// ui
import { Tooltip } from "@plane/ui";
// types // types
import { TViewOperations } from "../types"; import { TViewOperations } from "../types";
import { TViewTypes } from "@plane/types"; import { TViewTypes } from "@plane/types";
@ -17,22 +19,12 @@ type TViewDisplayFiltersDropdown = {
viewId: string; viewId: string;
viewType: TViewTypes; viewType: TViewTypes;
viewOperations: TViewOperations; viewOperations: TViewOperations;
baseRoute: string;
children?: ReactNode; children?: ReactNode;
displayDropdownText?: boolean; displayDropdownText?: boolean;
}; };
export const ViewDisplayFiltersDropdown: FC<TViewDisplayFiltersDropdown> = observer((props) => { export const ViewDisplayFiltersDropdown: FC<TViewDisplayFiltersDropdown> = observer((props) => {
const { const { workspaceSlug, projectId, viewId, viewType, viewOperations, children, displayDropdownText = true } = props;
workspaceSlug,
projectId,
viewId,
viewType,
viewOperations,
baseRoute,
children,
displayDropdownText = true,
} = props;
// state // state
const [dropdownToggle, setDropdownToggle] = useState(false); const [dropdownToggle, setDropdownToggle] = useState(false);
// refs // refs
@ -74,10 +66,11 @@ export const ViewDisplayFiltersDropdown: FC<TViewDisplayFiltersDropdown> = obser
onClick={handleDropdownToggle} onClick={handleDropdownToggle}
> >
{children ? ( {children ? (
<span className="relative inline-block">{children}</span> <div className="relative inline-block">{children}</div>
) : ( ) : (
<Tooltip tooltipContent={"Display"} position="bottom">
<div <div
className={`relative flex items-center gap-1 h-8 rounded px-2 transition-all className={`relative flex items-center gap-1 h-7 rounded px-2 transition-all
${ ${
displayDropdownText displayDropdownText
? `border border-custom-border-300 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80` ? `border border-custom-border-300 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80`
@ -90,6 +83,7 @@ export const ViewDisplayFiltersDropdown: FC<TViewDisplayFiltersDropdown> = obser
</div> </div>
{displayDropdownText && <div className="text-sm whitespace-nowrap">Display</div>} {displayDropdownText && <div className="text-sm whitespace-nowrap">Display</div>}
</div> </div>
</Tooltip>
)} )}
</button> </button>
</Combobox.Button> </Combobox.Button>

View File

@ -0,0 +1,40 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
// hooks
import { useViewDetail } from "hooks/store";
// types
import { TViewDisplayProperties, TViewTypes } from "@plane/types";
import { TViewOperations } from "../types";
type TViewDisplayPropertySelection = {
workspaceSlug: string;
projectId: string | undefined;
viewId: string;
viewType: TViewTypes;
viewOperations: TViewOperations;
property: keyof TViewDisplayProperties;
};
export const ViewDisplayPropertySelection: FC<TViewDisplayPropertySelection> = observer((props) => {
const { workspaceSlug, projectId, viewId, viewType, viewOperations, property } = props;
// hooks
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
const propertyIsSelected = viewDetailStore?.appliedFilters?.display_properties?.[property];
const handlePropertySelection = () => viewOperations?.setDisplayProperties(property);
return (
<div
className={`relative flex items-center gap-1 text-xs rounded p-0.5 px-2 border transition-all capitalize cursor-pointer
${
propertyIsSelected
? `border-custom-primary-100 bg-custom-primary-100`
: `border-custom-border-300 hover:bg-custom-background-80`
}`}
onClick={handlePropertySelection}
>
{["key"].includes(property) ? "ID" : property.replaceAll("_", " ")}
</div>
);
});

View File

@ -1,4 +1,7 @@
import { FC } from "react"; import { FC, Fragment } from "react";
import { observer } from "mobx-react-lite";
// components
import { ViewDisplayPropertySelection } from "../";
// types // types
import { TViewDisplayProperties, TViewTypes } from "@plane/types"; import { TViewDisplayProperties, TViewTypes } from "@plane/types";
import { TViewOperations } from "../types"; import { TViewOperations } from "../types";
@ -11,7 +14,7 @@ type TViewDisplayPropertiesRoot = {
viewOperations: TViewOperations; viewOperations: TViewOperations;
}; };
export const ViewDisplayPropertiesRoot: FC<TViewDisplayPropertiesRoot> = (props) => { export const ViewDisplayPropertiesRoot: FC<TViewDisplayPropertiesRoot> = observer((props) => {
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props; const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
const displayProperties: Partial<keyof TViewDisplayProperties>[] = [ const displayProperties: Partial<keyof TViewDisplayProperties>[] = [
@ -31,20 +34,17 @@ export const ViewDisplayPropertiesRoot: FC<TViewDisplayPropertiesRoot> = (props)
return ( return (
<div className="relative flex items-center flex-wrap gap-2"> <div className="relative flex items-center flex-wrap gap-2">
{displayProperties.map((property) => ( {displayProperties.map((property) => (
<div <Fragment key={property}>
key={property} <ViewDisplayPropertySelection
className={`relative flex items-center gap-1 text-xs rounded p-0.5 px-2 border transition-all capitalize cursor-pointer workspaceSlug={workspaceSlug}
${ projectId={projectId}
false viewId={viewId}
? `border-custom-primary-100 bg-custom-primary-100` viewType={viewType}
: `border-custom-border-300 hover:bg-custom-background-80` viewOperations={viewOperations}
} property={property}
`} />
onClick={() => {}} </Fragment>
>
{["key"].includes(property) ? "ID" : property.replaceAll("_", " ")}
</div>
))} ))}
</div> </div>
); );
}; });

View File

@ -7,6 +7,8 @@ import { ListFilter, Search } from "lucide-react";
import useOutsideClickDetector from "hooks/use-outside-click-detector"; import useOutsideClickDetector from "hooks/use-outside-click-detector";
// components // components
import { ViewFiltersRoot } from "../"; import { ViewFiltersRoot } from "../";
// ui
import { Tooltip } from "@plane/ui";
// types // types
import { TViewOperations } from "../types"; import { TViewOperations } from "../types";
import { TViewTypes } from "@plane/types"; import { TViewTypes } from "@plane/types";
@ -17,22 +19,12 @@ type TViewFiltersDropdown = {
viewId: string; viewId: string;
viewType: TViewTypes; viewType: TViewTypes;
viewOperations: TViewOperations; viewOperations: TViewOperations;
baseRoute: string;
children?: ReactNode; children?: ReactNode;
displayDropdownText?: boolean; displayDropdownText?: boolean;
}; };
export const ViewFiltersDropdown: FC<TViewFiltersDropdown> = observer((props) => { export const ViewFiltersDropdown: FC<TViewFiltersDropdown> = observer((props) => {
const { const { workspaceSlug, projectId, viewId, viewType, viewOperations, children, displayDropdownText = true } = props;
workspaceSlug,
projectId,
viewId,
viewType,
viewOperations,
baseRoute,
children,
displayDropdownText = true,
} = props;
// state // state
const [dropdownToggle, setDropdownToggle] = useState(false); const [dropdownToggle, setDropdownToggle] = useState(false);
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
@ -77,8 +69,9 @@ export const ViewFiltersDropdown: FC<TViewFiltersDropdown> = observer((props) =>
{children ? ( {children ? (
<span className="relative inline-block">{children}</span> <span className="relative inline-block">{children}</span>
) : ( ) : (
<Tooltip tooltipContent={"Filters"} position="bottom">
<div <div
className={`relative flex items-center gap-1 h-8 rounded px-2 transition-all className={`relative flex items-center gap-1 h-7 rounded px-2 transition-all
${ ${
displayDropdownText displayDropdownText
? `border border-custom-border-300 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80` ? `border border-custom-border-300 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80`
@ -91,6 +84,7 @@ export const ViewFiltersDropdown: FC<TViewFiltersDropdown> = observer((props) =>
</div> </div>
{displayDropdownText && <div className="text-sm whitespace-nowrap">Filters</div>} {displayDropdownText && <div className="text-sm whitespace-nowrap">Filters</div>}
</div> </div>
</Tooltip>
)} )}
</button> </button>
</Combobox.Button> </Combobox.Button>
@ -122,7 +116,6 @@ export const ViewFiltersDropdown: FC<TViewFiltersDropdown> = observer((props) =>
viewId={viewId} viewId={viewId}
viewType={viewType} viewType={viewType}
viewOperations={viewOperations} viewOperations={viewOperations}
baseRoute={baseRoute}
/> />
</div> </div>
</div> </div>

View File

@ -1,10 +1,7 @@
import { FC, useState } from "react"; import { FC, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import concat from "lodash/concat";
import pull from "lodash/pull";
import uniq from "lodash/uniq";
// hooks // hooks
import { useViewFilter, useViewDetail } from "hooks/store"; import { useViewFilter } from "hooks/store";
// components // components
import { ViewFiltersItem, ViewFilterSelection } from "../"; import { ViewFiltersItem, ViewFilterSelection } from "../";
// types // types
@ -17,15 +14,13 @@ type TViewFiltersItemRoot = {
viewId: string; viewId: string;
viewType: TViewTypes; viewType: TViewTypes;
viewOperations: TViewOperations; viewOperations: TViewOperations;
baseRoute: string;
filterKey: keyof TViewFilters; filterKey: keyof TViewFilters;
}; };
export const ViewFiltersItemRoot: FC<TViewFiltersItemRoot> = observer((props) => { export const ViewFiltersItemRoot: FC<TViewFiltersItemRoot> = observer((props) => {
const { workspaceSlug, projectId, viewId, viewType, viewOperations, baseRoute, filterKey } = props; const { workspaceSlug, projectId, viewId, viewType, viewOperations, filterKey } = props;
// hooks // hooks
const viewFilterHelper = useViewFilter(workspaceSlug, projectId); const viewFilterHelper = useViewFilter(workspaceSlug, projectId);
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
// state // state
const [viewAll, setViewAll] = useState(false); const [viewAll, setViewAll] = useState(false);
@ -33,13 +28,7 @@ export const ViewFiltersItemRoot: FC<TViewFiltersItemRoot> = observer((props) =>
const filterPropertyIds = propertyIds.length > 5 ? (viewAll ? propertyIds : propertyIds.slice(0, 5)) : propertyIds; const filterPropertyIds = propertyIds.length > 5 ? (viewAll ? propertyIds : propertyIds.slice(0, 5)) : propertyIds;
const handlePropertySelection = (_propertyId: string) => { const handlePropertySelection = (_propertyId: string) => viewOperations?.setFilters(filterKey, _propertyId);
const _propertyIds = viewDetailStore?.appliedFilters?.filters?.[filterKey] || [];
const isSelected = _propertyIds?.includes(_propertyId) || false;
viewOperations?.setFilters({
[filterKey]: isSelected ? pull(_propertyIds, _propertyId) : uniq(concat(_propertyIds, [_propertyId])),
});
};
if (propertyIds.length <= 0) if (propertyIds.length <= 0)
return <div className="text-xs italic py-1 text-custom-text-300">No items are available.</div>; return <div className="text-xs italic py-1 text-custom-text-300">No items are available.</div>;
@ -56,18 +45,12 @@ export const ViewFiltersItemRoot: FC<TViewFiltersItemRoot> = observer((props) =>
projectId={projectId} projectId={projectId}
viewId={viewId} viewId={viewId}
viewType={viewType} viewType={viewType}
viewOperations={viewOperations}
baseRoute={baseRoute}
filterKey={filterKey} filterKey={filterKey}
propertyId={propertyId} propertyId={propertyId}
/> />
<ViewFiltersItem <ViewFiltersItem
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
projectId={projectId} projectId={projectId}
viewId={viewId}
viewType={viewType}
viewOperations={viewOperations}
baseRoute={baseRoute}
filterKey={filterKey} filterKey={filterKey}
propertyId={propertyId} propertyId={propertyId}
/> />

View File

@ -1,26 +1,20 @@
import { FC, Fragment } from "react"; import { FC, Fragment } from "react";
import { CheckSquare } from "lucide-react"; import { ImagePlus } from "lucide-react";
// hooks // hooks
import { useViewFilter } from "hooks/store"; import { useViewFilter } from "hooks/store";
// types // types
import { TViewFilters, TViewTypes } from "@plane/types"; import { TViewFilters } from "@plane/types";
import { TViewOperations } from "../types";
// helpers
// import { filterPropertyItemByFilterKeyAndId } from "../helpers/filters";
type TViewFiltersItem = { type TViewFiltersItem = {
workspaceSlug: string; workspaceSlug: string;
projectId: string | undefined; projectId: string | undefined;
viewId: string;
viewType: TViewTypes;
viewOperations: TViewOperations;
baseRoute: string;
filterKey: keyof TViewFilters; filterKey: keyof TViewFilters;
propertyId: string; propertyId: string;
}; };
export const ViewFiltersItem: FC<TViewFiltersItem> = (props) => { export const ViewFiltersItem: FC<TViewFiltersItem> = (props) => {
const { workspaceSlug, projectId, viewId, viewType, viewOperations, baseRoute, filterKey, propertyId } = props; const { workspaceSlug, projectId, filterKey, propertyId } = props;
// hooks // hooks
const viewFilterHelper = useViewFilter(workspaceSlug, projectId); const viewFilterHelper = useViewFilter(workspaceSlug, projectId);
@ -30,7 +24,7 @@ export const ViewFiltersItem: FC<TViewFiltersItem> = (props) => {
return ( return (
<Fragment> <Fragment>
<div className="flex-shrink-0 w-4 h-4 flex justify-center items-center"> <div className="flex-shrink-0 w-4 h-4 flex justify-center items-center">
{propertyDetail?.icon || <CheckSquare size={14} />} {propertyDetail?.icon || <ImagePlus size={14} />}
</div> </div>
<div className="text-xs block truncate line-clamp-1 text-custom-text-200 group-hover:text-custom-text-100"> <div className="text-xs block truncate line-clamp-1 text-custom-text-200 group-hover:text-custom-text-100">
{propertyDetail?.label || propertyId} {propertyDetail?.label || propertyId}

View File

@ -5,21 +5,18 @@ import { observer } from "mobx-react-lite";
import { useViewDetail } from "hooks/store"; import { useViewDetail } from "hooks/store";
// types // types
import { TViewFilters, TViewTypes } from "@plane/types"; import { TViewFilters, TViewTypes } from "@plane/types";
import { TViewOperations } from "../types";
type TViewFilterSelection = { type TViewFilterSelection = {
workspaceSlug: string; workspaceSlug: string;
projectId: string | undefined; projectId: string | undefined;
viewId: string; viewId: string;
viewType: TViewTypes; viewType: TViewTypes;
viewOperations: TViewOperations;
baseRoute: string;
filterKey: keyof TViewFilters; filterKey: keyof TViewFilters;
propertyId: string; propertyId: string;
}; };
export const ViewFilterSelection: FC<TViewFilterSelection> = observer((props) => { export const ViewFilterSelection: FC<TViewFilterSelection> = observer((props) => {
const { workspaceSlug, projectId, viewId, viewType, viewOperations, baseRoute, filterKey, propertyId } = props; const { workspaceSlug, projectId, viewId, viewType, filterKey, propertyId } = props;
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);

View File

@ -1,6 +1,6 @@
import { FC, useState } from "react"; import { FC, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { ChevronDown, ChevronUp, Search } from "lucide-react"; import { ChevronDown, ChevronUp } from "lucide-react";
import concat from "lodash/concat"; import concat from "lodash/concat";
import uniq from "lodash/uniq"; import uniq from "lodash/uniq";
import filter from "lodash/filter"; import filter from "lodash/filter";
@ -19,11 +19,10 @@ type TViewFiltersRoot = {
viewId: string; viewId: string;
viewType: TViewTypes; viewType: TViewTypes;
viewOperations: TViewOperations; viewOperations: TViewOperations;
baseRoute: string;
}; };
export const ViewFiltersRoot: FC<TViewFiltersRoot> = observer((props) => { export const ViewFiltersRoot: FC<TViewFiltersRoot> = observer((props) => {
const { workspaceSlug, projectId, viewId, viewType, viewOperations, baseRoute } = props; const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
// hooks // hooks
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
// state // state
@ -62,7 +61,6 @@ export const ViewFiltersRoot: FC<TViewFiltersRoot> = observer((props) => {
viewId={viewId} viewId={viewId}
viewType={viewType} viewType={viewType}
viewOperations={viewOperations} viewOperations={viewOperations}
baseRoute={baseRoute}
filterKey={filterKey} filterKey={filterKey}
/> />
)} )}

View File

@ -25,6 +25,7 @@ export * from "./display-filters/root";
// view display properties // view display properties
export * from "./display-properties/root"; export * from "./display-properties/root";
export * from "./display-properties/property-selection";
// view applied filters // view applied filters
export * from "./applied-filters/root"; export * from "./applied-filters/root";

View File

@ -31,7 +31,7 @@ export const ViewLayoutRoot: FC<TViewLayoutRoot> = observer((props) => {
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
return ( return (
<div className="relative flex gap-0.5 items-center bg-custom-background-80 rounded p-1 h-8 shadow-custom-shadow-2xs"> <div className="relative flex gap-0.5 items-center bg-custom-background-80 rounded p-1 h-7 shadow-custom-shadow-2xs">
{LAYOUTS_DATA.map((layout) => ( {LAYOUTS_DATA.map((layout) => (
<Fragment key={layout.key}> <Fragment key={layout.key}>
<Tooltip tooltipContent={layout.title} position="bottom"> <Tooltip tooltipContent={layout.title} position="bottom">

View File

@ -1,11 +1,11 @@
import { TView } from "@plane/types"; import { TView, TViewFilters, TViewDisplayFilters, TViewDisplayProperties } from "@plane/types";
export type TViewOperations = { export type TViewOperations = {
setName: (name: string) => void; setName: (name: string) => void;
setDescription: (description: string) => void; setDescription: (description: string) => void;
setFilters: (filters: Partial<TViewFilters>) => void; setFilters: (filterKey: keyof TViewFilters, filterValue: "clear_all" | string) => void;
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void; setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void; setDisplayProperties: (displayPropertyKey: keyof TViewDisplayProperties) => void;
localViewCreateEdit: (viewId: string | undefined) => void; localViewCreateEdit: (viewId: string | undefined) => void;
localViewCreateEditClear: (viewId: string | undefined) => Promise<void>; localViewCreateEditClear: (viewId: string | undefined) => Promise<void>;

View File

@ -15,8 +15,8 @@ const GlobalViewIssuesPage: NextPageWithLayout = () => {
if (!workspaceSlug || !viewId) return <></>; if (!workspaceSlug || !viewId) return <></>;
return ( return (
<div className="h-full overflow-hidden bg-custom-background-100"> <div className="w-full h-full overflow-hidden bg-custom-background-100 relative flex flex-col">
<div className="flex h-full w-full flex-col border-b border-custom-border-300"> <div className="flex-shrink-0 w-full">
<AllIssuesViewRoot <AllIssuesViewRoot
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug.toString()}
projectId={undefined} projectId={undefined}
@ -25,6 +25,7 @@ const GlobalViewIssuesPage: NextPageWithLayout = () => {
baseRoute={`/${workspaceSlug?.toString()}/views/public`} baseRoute={`/${workspaceSlug?.toString()}/views/public`}
/> />
</div> </div>
<div className="w-full h-full overflow-hidden">Issues render</div>
</div> </div>
); );
}; };

View File

@ -1,4 +1,5 @@
import isEmpty from "lodash/isEmpty"; import isEmpty from "lodash/isEmpty";
import get from "lodash/get";
// types // types
import { import {
TViewFilters, TViewFilters,
@ -11,19 +12,19 @@ import {
export class FiltersHelper { export class FiltersHelper {
// computed filters // computed filters
computedFilters = (filters: TViewFilters, defaultValues?: Partial<TViewFilters>): TViewFilters => ({ computedFilters = (filters: TViewFilters, defaultValues?: Partial<TViewFilters>): TViewFilters => ({
project: defaultValues?.project || filters?.project || [], project: get(defaultValues, "project", get(filters, "project", [])),
module: defaultValues?.module || filters?.module || [], module: get(defaultValues, "module", get(filters, "module", [])),
cycle: defaultValues?.cycle || filters?.cycle || [], cycle: get(defaultValues, "cycle", get(filters, "cycle", [])),
priority: defaultValues?.priority || filters?.priority || [], priority: get(defaultValues, "priority", get(filters, "priority", [])),
state: defaultValues?.state || filters?.state || [], state: get(defaultValues, "state", get(filters, "state", [])),
state_group: defaultValues?.state_group || filters?.state_group || [], state_group: get(defaultValues, "state_group", get(filters, "state_group", [])),
assignees: defaultValues?.assignees || filters?.assignees || [], assignees: get(defaultValues, "assignees", get(filters, "assignees", [])),
mentions: defaultValues?.mentions || filters?.mentions || [], mentions: get(defaultValues, "mentions", get(filters, "mentions", [])),
subscriber: defaultValues?.subscriber || filters?.subscriber || [], subscriber: get(defaultValues, "subscriber", get(filters, "subscriber", [])),
created_by: defaultValues?.created_by || filters?.created_by || [], created_by: get(defaultValues, "created_by", get(filters, "created_by", [])),
labels: defaultValues?.labels || filters?.labels || [], labels: get(defaultValues, "labels", get(filters, "labels", [])),
start_date: defaultValues?.start_date || filters?.start_date || [], start_date: get(defaultValues, "start_date", get(filters, "start_date", [])),
target_date: defaultValues?.target_date || filters?.target_date || [], target_date: get(defaultValues, "target_date", get(filters, "target_date", [])),
}); });
// computed display filters // computed display filters
@ -49,19 +50,19 @@ export class FiltersHelper {
displayProperties: TViewDisplayProperties, displayProperties: TViewDisplayProperties,
defaultValues?: Partial<TViewDisplayProperties> defaultValues?: Partial<TViewDisplayProperties>
): TViewDisplayProperties => ({ ): TViewDisplayProperties => ({
assignee: defaultValues?.assignee || displayProperties?.assignee || true, assignee: get(defaultValues, "assignee", get(displayProperties, "assignee", true)),
start_date: defaultValues?.start_date || displayProperties?.start_date || true, start_date: get(defaultValues, "start_date", get(displayProperties, "start_date", true)),
due_date: defaultValues?.due_date || displayProperties?.due_date || true, due_date: get(defaultValues, "due_date", get(displayProperties, "due_date", true)),
labels: defaultValues?.labels || displayProperties?.labels || true, labels: get(defaultValues, "labels", get(displayProperties, "labels", true)),
priority: defaultValues?.priority || displayProperties?.priority || true, priority: get(defaultValues, "priority", get(displayProperties, "priority", true)),
state: defaultValues?.state || displayProperties?.state || true, state: get(defaultValues, "state", get(displayProperties, "state", true)),
sub_issue_count: defaultValues?.sub_issue_count || displayProperties?.sub_issue_count || true, sub_issue_count: get(defaultValues, "sub_issue_count", get(displayProperties, "sub_issue_count", true)),
attachment_count: defaultValues?.attachment_count || displayProperties?.attachment_count || true, attachment_count: get(defaultValues, "attachment_count", get(displayProperties, "attachment_count", true)),
link: defaultValues?.link || displayProperties?.link || true, link: get(defaultValues, "link", get(displayProperties, "link", true)),
estimate: defaultValues?.estimate || displayProperties?.estimate || true, estimate: get(defaultValues, "estimate", get(displayProperties, "estimate", true)),
key: defaultValues?.key || displayProperties?.key || true, key: get(defaultValues, "key", get(displayProperties, "key", true)),
created_on: defaultValues?.created_on || displayProperties?.created_on || true, created_on: get(defaultValues, "created_on", get(displayProperties, "created_on", true)),
updated_on: defaultValues?.updated_on || displayProperties?.updated_on || true, updated_on: get(defaultValues, "updated_on", get(displayProperties, "updated_on", true)),
}); });
// compute filters and display_filters issue query parameters // compute filters and display_filters issue query parameters

View File

@ -1,5 +1,4 @@
// stores // stores
import { autorun, makeObservable, observable } from "mobx";
import { ViewRootStore } from "./view-root.store"; import { ViewRootStore } from "./view-root.store";
// services // services
import { import {

View File

@ -1,5 +1,8 @@
import { action, computed, makeObservable, observable, runInAction } from "mobx"; import { action, computed, makeObservable, observable, runInAction } from "mobx";
import set from "lodash/set"; import set from "lodash/set";
import update from "lodash/update";
import concat from "lodash/concat";
import pull from "lodash/pull";
// store // store
import { RootStore } from "store/root.store"; import { RootStore } from "store/root.store";
// types // types
@ -24,12 +27,13 @@ export type TViewStore = TView & {
// computed // computed
appliedFilters: TViewFilterProps | undefined; appliedFilters: TViewFilterProps | undefined;
appliedFiltersQueryParams: string | undefined; appliedFiltersQueryParams: string | undefined;
isFiltersApplied: boolean;
// helper actions // helper actions
setName: (name: string) => void; setName: (name: string) => void;
setDescription: (description: string) => void; setDescription: (description: string) => void;
setFilters: (filters: Partial<TViewFilters>) => void; setFilters: (filterKey: keyof TViewFilters | undefined, filterValue: "clear_all" | string) => void;
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void; setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void; setDisplayProperties: (displayPropertyKey: keyof TViewDisplayProperties) => void;
resetChanges: () => void; resetChanges: () => void;
saveChanges: () => Promise<void>; saveChanges: () => Promise<void>;
// actions // actions
@ -132,6 +136,7 @@ export class ViewStore extends FiltersHelper implements TViewStore {
// computed // computed
appliedFilters: computed, appliedFilters: computed,
appliedFiltersQueryParams: computed, appliedFiltersQueryParams: computed,
isFiltersApplied: computed,
// helper actions // helper actions
setName: action, setName: action,
setFilters: action, setFilters: action,
@ -164,6 +169,16 @@ export class ViewStore extends FiltersHelper implements TViewStore {
return this.computeAppliedFiltersQueryParameters(filters, [])?.query || undefined; return this.computeAppliedFiltersQueryParameters(filters, [])?.query || undefined;
} }
get isFiltersApplied() {
const filters = this.appliedFilters?.filters;
let isFiltersApplied = false;
Object.keys(filters).forEach((key) => {
const _key = key as keyof TViewFilters;
if (filters[_key]?.length > 0) isFiltersApplied = true;
});
return isFiltersApplied;
}
// helper actions // helper actions
setName = (name: string) => { setName = (name: string) => {
runInAction(() => { runInAction(() => {
@ -177,12 +192,17 @@ export class ViewStore extends FiltersHelper implements TViewStore {
}); });
}; };
setFilters = (filters: Partial<TViewFilters>) => { setFilters = (filterKey: keyof TViewFilters | undefined = undefined, filterValue: "clear_all" | string) => {
runInAction(() => { runInAction(() => {
this.loader = "submit"; this.loader = "submit";
Object.keys(filters).forEach((key) => { if (filterKey === undefined) {
const _key = key as keyof TViewFilters; if (filterValue === "clear_all") set(this.filtersToUpdate, ["filters"], {});
set(this.filtersToUpdate, ["filters", _key], filters[_key]); this.loader = undefined;
} else
update(this.filtersToUpdate, ["filters", filterKey], (_values = []) => {
if (filterValue === "clear_all") return [];
if (_values.includes(filterValue)) return pull(_values, filterValue);
return concat(_values, filterValue);
}); });
}); });
}; };
@ -210,12 +230,9 @@ export class ViewStore extends FiltersHelper implements TViewStore {
}); });
}; };
setDisplayProperties = async (display_properties: Partial<TViewDisplayProperties>) => { setDisplayProperties = async (displayPropertyKey: keyof TViewDisplayProperties) => {
runInAction(() => { runInAction(() => {
Object.keys(display_properties).forEach((key) => { update(this.filtersToUpdate, ["display_properties", displayPropertyKey], (_value: boolean = true) => !_value);
const _key = key as keyof TViewDisplayProperties;
set(this.filtersToUpdate, ["display_properties", _key], display_properties[_key]);
});
}); });
}; };