mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: updated ui and store
This commit is contained in:
parent
1ce7f20c2d
commit
f90595ca31
@ -10,10 +10,8 @@ import {
|
||||
ViewRoot,
|
||||
ViewCreateEditForm,
|
||||
ViewLayoutRoot,
|
||||
ViewFiltersRoot,
|
||||
ViewFiltersDropdown,
|
||||
ViewDisplayFiltersDropdown,
|
||||
ViewDisplayPropertiesRoot,
|
||||
ViewAppliedFiltersRoot,
|
||||
ViewDuplicateConfirmationModal,
|
||||
ViewDeleteConfirmationModal,
|
||||
@ -73,11 +71,12 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
||||
() => ({
|
||||
setName: (name: string) => viewDetailStore?.setName(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>) =>
|
||||
viewDetailStore?.setDisplayFilters(display_filters),
|
||||
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) =>
|
||||
viewDetailStore?.setDisplayProperties(display_properties),
|
||||
setDisplayProperties: (displayPropertyKey: keyof TViewDisplayProperties) =>
|
||||
viewDetailStore?.setDisplayProperties(displayPropertyKey),
|
||||
localViewCreateEdit: (viewId: string | undefined) => {
|
||||
if (viewId === undefined) {
|
||||
const viewPayload = viewLocalPayload;
|
||||
@ -171,8 +170,9 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-5 border-b border-custom-border-300">
|
||||
<ViewDisplayPropertiesRoot
|
||||
<div className="p-5 py-2 border-b border-custom-border-200 relative flex gap-2">
|
||||
<div className="w-full overflow-hidden">
|
||||
<ViewAppliedFiltersRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
@ -181,29 +181,7 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
||||
/>
|
||||
</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">
|
||||
<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">
|
||||
<div className="flex-shrink-0">
|
||||
<ViewLayoutRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
@ -220,7 +198,6 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
baseRoute={baseRoute}
|
||||
displayDropdownText={false}
|
||||
/>
|
||||
</div>
|
||||
@ -232,12 +209,11 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
baseRoute={baseRoute}
|
||||
displayDropdownText={false}
|
||||
/>
|
||||
</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">
|
||||
<Pencil size={12} />
|
||||
</div>
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { FC } from "react";
|
||||
import { User, X } from "lucide-react";
|
||||
import { ImagePlus, X } from "lucide-react";
|
||||
// hooks
|
||||
import { useViewDetail } from "hooks/store";
|
||||
import { useViewFilter } from "hooks/store";
|
||||
// types
|
||||
import { TViewFilters, TViewTypes } from "@plane/types";
|
||||
import { TViewOperations } from "../types";
|
||||
|
||||
type TViewAppliedFiltersItem = {
|
||||
workspaceSlug: string;
|
||||
@ -11,33 +12,30 @@ type TViewAppliedFiltersItem = {
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
filterKey: keyof TViewFilters;
|
||||
filterId: string;
|
||||
propertyId: string;
|
||||
viewOperations: TViewOperations;
|
||||
};
|
||||
|
||||
export const ViewAppliedFiltersItem: FC<TViewAppliedFiltersItem> = (props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, filterKey, filterId } = props;
|
||||
const { workspaceSlug, projectId, filterKey, propertyId, viewOperations } = props;
|
||||
// hooks
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
const viewFilterHelper = useViewFilter(workspaceSlug, projectId);
|
||||
|
||||
const propertyDetail = viewFilterHelper?.propertyDetails(filterKey, propertyId) || undefined;
|
||||
|
||||
const removeFilterOption = () => {
|
||||
const filters = viewDetailStore?.appliedFilters?.filters;
|
||||
if (!filters) return;
|
||||
const filterValues = filters[filterKey];
|
||||
const updatedFilterValues = filterValues.filter((value) => value !== filterId);
|
||||
viewDetailStore?.setFilters({ [filterKey]: updatedFilterValues });
|
||||
viewOperations?.setFilters(filterKey, propertyId);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`filter_value_${filterKey}_${filterId}`}
|
||||
className="border border-custom-border-200 rounded relative flex items-center gap-1 px-1 py-0.5"
|
||||
key={`filter_value_${filterKey}_${propertyId}`}
|
||||
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">
|
||||
<User size={12} />
|
||||
</div>
|
||||
<div className="text-xs">
|
||||
{filterKey} - {filterId}
|
||||
{propertyDetail?.icon || <ImagePlus size={14} />}
|
||||
</div>
|
||||
<div className="text-xs">{propertyDetail?.label || propertyId}</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"
|
||||
onClick={removeFilterOption}
|
||||
|
@ -6,10 +6,9 @@ import { X } from "lucide-react";
|
||||
import { useViewDetail } from "hooks/store";
|
||||
// components
|
||||
import { ViewAppliedFiltersItem } from "./filter-item";
|
||||
// helpers
|
||||
import { generateTitle } from "./helper";
|
||||
// types
|
||||
import { TViewFilters, TViewTypes } from "@plane/types";
|
||||
import { TViewOperations } from "../types";
|
||||
|
||||
type TViewAppliedFilters = {
|
||||
workspaceSlug: string;
|
||||
@ -17,41 +16,51 @@ type TViewAppliedFilters = {
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
filterKey: keyof TViewFilters;
|
||||
viewOperations: TViewOperations;
|
||||
};
|
||||
|
||||
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 filterKeyValue =
|
||||
const propertyValues =
|
||||
viewDetailStore?.appliedFilters?.filters && !isEmpty(viewDetailStore?.appliedFilters?.filters)
|
||||
? viewDetailStore?.appliedFilters?.filters?.[filterKey] || 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 (
|
||||
<div className="relative flex items-center gap-2 border border-custom-border-300 rounded p-1.5 py-1 min-h-[32px]">
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-200">{generateTitle(filterKey)}</div>
|
||||
<div className="relative flex items-center gap-1 flex-wrap">
|
||||
{["1", "2", "3", "4"].map((filterId) => (
|
||||
<Fragment key={filterId}>
|
||||
<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 capitalize">{filterKey.replaceAll("_", " ")}</div>
|
||||
<div className="relative flex items-center gap-1.5 flex-wrap">
|
||||
{propertyValues.length >= 100 ? (
|
||||
<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
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
filterKey={filterKey}
|
||||
filterId={filterId}
|
||||
propertyId={propertyId}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</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"
|
||||
onClick={clearFilter}
|
||||
onClick={clearPropertyFilter}
|
||||
>
|
||||
<X size={10} />
|
||||
</div>
|
||||
|
@ -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;
|
||||
};
|
@ -19,7 +19,7 @@ type TViewAppliedFiltersRoot = {
|
||||
};
|
||||
|
||||
export const ViewAppliedFiltersRoot: FC<TViewAppliedFiltersRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType } = props;
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
|
||||
// hooks
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
|
||||
@ -28,16 +28,14 @@ export const ViewAppliedFiltersRoot: FC<TViewAppliedFiltersRoot> = observer((pro
|
||||
? Object.keys(viewDetailStore?.appliedFilters?.filters)
|
||||
: undefined;
|
||||
|
||||
const clearAllFilters = () => {
|
||||
const clearedFilters: Partial<Record<keyof TViewFilters, string[]>> = {};
|
||||
filterKeys?.forEach((key) => {
|
||||
const _key = key as keyof TViewFilters;
|
||||
clearedFilters[_key] = [];
|
||||
});
|
||||
viewDetailStore?.setFilters(clearedFilters);
|
||||
};
|
||||
const clearAllFilters = () => viewDetailStore?.setFilters(undefined, "clear_all");
|
||||
|
||||
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 (
|
||||
<div className="relative flex items-center gap-2 flex-wrap">
|
||||
{filterKeys.map((key) => {
|
||||
@ -50,12 +48,14 @@ export const ViewAppliedFiltersRoot: FC<TViewAppliedFiltersRoot> = observer((pro
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
filterKey={filterKey}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
<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}
|
||||
>
|
||||
<div className="text-xs">Clear All</div>
|
||||
|
@ -7,6 +7,8 @@ import { MonitorDot } from "lucide-react";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// components
|
||||
import { ViewDisplayPropertiesRoot } from "../";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// types
|
||||
import { TViewOperations } from "../types";
|
||||
import { TViewTypes } from "@plane/types";
|
||||
@ -17,22 +19,12 @@ type TViewDisplayFiltersDropdown = {
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
viewOperations: TViewOperations;
|
||||
baseRoute: string;
|
||||
children?: ReactNode;
|
||||
displayDropdownText?: boolean;
|
||||
};
|
||||
|
||||
export const ViewDisplayFiltersDropdown: FC<TViewDisplayFiltersDropdown> = observer((props) => {
|
||||
const {
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
viewId,
|
||||
viewType,
|
||||
viewOperations,
|
||||
baseRoute,
|
||||
children,
|
||||
displayDropdownText = true,
|
||||
} = props;
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations, children, displayDropdownText = true } = props;
|
||||
// state
|
||||
const [dropdownToggle, setDropdownToggle] = useState(false);
|
||||
// refs
|
||||
@ -74,10 +66,11 @@ export const ViewDisplayFiltersDropdown: FC<TViewDisplayFiltersDropdown> = obser
|
||||
onClick={handleDropdownToggle}
|
||||
>
|
||||
{children ? (
|
||||
<span className="relative inline-block">{children}</span>
|
||||
<div className="relative inline-block">{children}</div>
|
||||
) : (
|
||||
<Tooltip tooltipContent={"Display"} position="bottom">
|
||||
<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
|
||||
? `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>
|
||||
{displayDropdownText && <div className="text-sm whitespace-nowrap">Display</div>}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</button>
|
||||
</Combobox.Button>
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
@ -1,4 +1,7 @@
|
||||
import { FC } from "react";
|
||||
import { FC, Fragment } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { ViewDisplayPropertySelection } from "../";
|
||||
// types
|
||||
import { TViewDisplayProperties, TViewTypes } from "@plane/types";
|
||||
import { TViewOperations } from "../types";
|
||||
@ -11,7 +14,7 @@ type TViewDisplayPropertiesRoot = {
|
||||
viewOperations: TViewOperations;
|
||||
};
|
||||
|
||||
export const ViewDisplayPropertiesRoot: FC<TViewDisplayPropertiesRoot> = (props) => {
|
||||
export const ViewDisplayPropertiesRoot: FC<TViewDisplayPropertiesRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
|
||||
|
||||
const displayProperties: Partial<keyof TViewDisplayProperties>[] = [
|
||||
@ -31,20 +34,17 @@ export const ViewDisplayPropertiesRoot: FC<TViewDisplayPropertiesRoot> = (props)
|
||||
return (
|
||||
<div className="relative flex items-center flex-wrap gap-2">
|
||||
{displayProperties.map((property) => (
|
||||
<div
|
||||
key={property}
|
||||
className={`relative flex items-center gap-1 text-xs rounded p-0.5 px-2 border transition-all capitalize cursor-pointer
|
||||
${
|
||||
false
|
||||
? `border-custom-primary-100 bg-custom-primary-100`
|
||||
: `border-custom-border-300 hover:bg-custom-background-80`
|
||||
}
|
||||
`}
|
||||
onClick={() => {}}
|
||||
>
|
||||
{["key"].includes(property) ? "ID" : property.replaceAll("_", " ")}
|
||||
</div>
|
||||
<Fragment key={property}>
|
||||
<ViewDisplayPropertySelection
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
property={property}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -7,6 +7,8 @@ import { ListFilter, Search } from "lucide-react";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// components
|
||||
import { ViewFiltersRoot } from "../";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// types
|
||||
import { TViewOperations } from "../types";
|
||||
import { TViewTypes } from "@plane/types";
|
||||
@ -17,22 +19,12 @@ type TViewFiltersDropdown = {
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
viewOperations: TViewOperations;
|
||||
baseRoute: string;
|
||||
children?: ReactNode;
|
||||
displayDropdownText?: boolean;
|
||||
};
|
||||
|
||||
export const ViewFiltersDropdown: FC<TViewFiltersDropdown> = observer((props) => {
|
||||
const {
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
viewId,
|
||||
viewType,
|
||||
viewOperations,
|
||||
baseRoute,
|
||||
children,
|
||||
displayDropdownText = true,
|
||||
} = props;
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations, children, displayDropdownText = true } = props;
|
||||
// state
|
||||
const [dropdownToggle, setDropdownToggle] = useState(false);
|
||||
const [query, setQuery] = useState("");
|
||||
@ -77,8 +69,9 @@ export const ViewFiltersDropdown: FC<TViewFiltersDropdown> = observer((props) =>
|
||||
{children ? (
|
||||
<span className="relative inline-block">{children}</span>
|
||||
) : (
|
||||
<Tooltip tooltipContent={"Filters"} position="bottom">
|
||||
<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
|
||||
? `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>
|
||||
{displayDropdownText && <div className="text-sm whitespace-nowrap">Filters</div>}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</button>
|
||||
</Combobox.Button>
|
||||
@ -122,7 +116,6 @@ export const ViewFiltersDropdown: FC<TViewFiltersDropdown> = observer((props) =>
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
baseRoute={baseRoute}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import concat from "lodash/concat";
|
||||
import pull from "lodash/pull";
|
||||
import uniq from "lodash/uniq";
|
||||
// hooks
|
||||
import { useViewFilter, useViewDetail } from "hooks/store";
|
||||
import { useViewFilter } from "hooks/store";
|
||||
// components
|
||||
import { ViewFiltersItem, ViewFilterSelection } from "../";
|
||||
// types
|
||||
@ -17,15 +14,13 @@ type TViewFiltersItemRoot = {
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
viewOperations: TViewOperations;
|
||||
baseRoute: string;
|
||||
filterKey: keyof TViewFilters;
|
||||
};
|
||||
|
||||
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
|
||||
const viewFilterHelper = useViewFilter(workspaceSlug, projectId);
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
// state
|
||||
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 handlePropertySelection = (_propertyId: string) => {
|
||||
const _propertyIds = viewDetailStore?.appliedFilters?.filters?.[filterKey] || [];
|
||||
const isSelected = _propertyIds?.includes(_propertyId) || false;
|
||||
viewOperations?.setFilters({
|
||||
[filterKey]: isSelected ? pull(_propertyIds, _propertyId) : uniq(concat(_propertyIds, [_propertyId])),
|
||||
});
|
||||
};
|
||||
const handlePropertySelection = (_propertyId: string) => viewOperations?.setFilters(filterKey, _propertyId);
|
||||
|
||||
if (propertyIds.length <= 0)
|
||||
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}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
baseRoute={baseRoute}
|
||||
filterKey={filterKey}
|
||||
propertyId={propertyId}
|
||||
/>
|
||||
<ViewFiltersItem
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
baseRoute={baseRoute}
|
||||
filterKey={filterKey}
|
||||
propertyId={propertyId}
|
||||
/>
|
||||
|
@ -1,26 +1,20 @@
|
||||
import { FC, Fragment } from "react";
|
||||
import { CheckSquare } from "lucide-react";
|
||||
import { ImagePlus } from "lucide-react";
|
||||
// hooks
|
||||
import { useViewFilter } from "hooks/store";
|
||||
// types
|
||||
import { TViewFilters, TViewTypes } from "@plane/types";
|
||||
import { TViewOperations } from "../types";
|
||||
// helpers
|
||||
// import { filterPropertyItemByFilterKeyAndId } from "../helpers/filters";
|
||||
import { TViewFilters } from "@plane/types";
|
||||
|
||||
type TViewFiltersItem = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
viewOperations: TViewOperations;
|
||||
baseRoute: string;
|
||||
|
||||
filterKey: keyof TViewFilters;
|
||||
propertyId: string;
|
||||
};
|
||||
|
||||
export const ViewFiltersItem: FC<TViewFiltersItem> = (props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations, baseRoute, filterKey, propertyId } = props;
|
||||
const { workspaceSlug, projectId, filterKey, propertyId } = props;
|
||||
// hooks
|
||||
const viewFilterHelper = useViewFilter(workspaceSlug, projectId);
|
||||
|
||||
@ -30,7 +24,7 @@ export const ViewFiltersItem: FC<TViewFiltersItem> = (props) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<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 className="text-xs block truncate line-clamp-1 text-custom-text-200 group-hover:text-custom-text-100">
|
||||
{propertyDetail?.label || propertyId}
|
||||
|
@ -5,21 +5,18 @@ import { observer } from "mobx-react-lite";
|
||||
import { useViewDetail } from "hooks/store";
|
||||
// types
|
||||
import { TViewFilters, TViewTypes } from "@plane/types";
|
||||
import { TViewOperations } from "../types";
|
||||
|
||||
type TViewFilterSelection = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
viewOperations: TViewOperations;
|
||||
baseRoute: string;
|
||||
filterKey: keyof TViewFilters;
|
||||
propertyId: string;
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FC, useState } from "react";
|
||||
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 uniq from "lodash/uniq";
|
||||
import filter from "lodash/filter";
|
||||
@ -19,11 +19,10 @@ type TViewFiltersRoot = {
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
viewOperations: TViewOperations;
|
||||
baseRoute: string;
|
||||
};
|
||||
|
||||
export const ViewFiltersRoot: FC<TViewFiltersRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations, baseRoute } = props;
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
|
||||
// hooks
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
// state
|
||||
@ -62,7 +61,6 @@ export const ViewFiltersRoot: FC<TViewFiltersRoot> = observer((props) => {
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
baseRoute={baseRoute}
|
||||
filterKey={filterKey}
|
||||
/>
|
||||
)}
|
||||
|
@ -25,6 +25,7 @@ export * from "./display-filters/root";
|
||||
|
||||
// view display properties
|
||||
export * from "./display-properties/root";
|
||||
export * from "./display-properties/property-selection";
|
||||
|
||||
// view applied filters
|
||||
export * from "./applied-filters/root";
|
||||
|
@ -31,7 +31,7 @@ export const ViewLayoutRoot: FC<TViewLayoutRoot> = observer((props) => {
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
|
||||
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) => (
|
||||
<Fragment key={layout.key}>
|
||||
<Tooltip tooltipContent={layout.title} position="bottom">
|
||||
|
6
web/components/view/types.d.ts
vendored
6
web/components/view/types.d.ts
vendored
@ -1,11 +1,11 @@
|
||||
import { TView } from "@plane/types";
|
||||
import { TView, TViewFilters, TViewDisplayFilters, TViewDisplayProperties } from "@plane/types";
|
||||
|
||||
export type TViewOperations = {
|
||||
setName: (name: 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;
|
||||
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
|
||||
setDisplayProperties: (displayPropertyKey: keyof TViewDisplayProperties) => void;
|
||||
|
||||
localViewCreateEdit: (viewId: string | undefined) => void;
|
||||
localViewCreateEditClear: (viewId: string | undefined) => Promise<void>;
|
||||
|
@ -15,8 +15,8 @@ const GlobalViewIssuesPage: NextPageWithLayout = () => {
|
||||
|
||||
if (!workspaceSlug || !viewId) return <></>;
|
||||
return (
|
||||
<div className="h-full overflow-hidden bg-custom-background-100">
|
||||
<div className="flex h-full w-full flex-col border-b border-custom-border-300">
|
||||
<div className="w-full h-full overflow-hidden bg-custom-background-100 relative flex flex-col">
|
||||
<div className="flex-shrink-0 w-full">
|
||||
<AllIssuesViewRoot
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={undefined}
|
||||
@ -25,6 +25,7 @@ const GlobalViewIssuesPage: NextPageWithLayout = () => {
|
||||
baseRoute={`/${workspaceSlug?.toString()}/views/public`}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full h-full overflow-hidden">Issues render</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import get from "lodash/get";
|
||||
// types
|
||||
import {
|
||||
TViewFilters,
|
||||
@ -11,19 +12,19 @@ import {
|
||||
export class FiltersHelper {
|
||||
// computed filters
|
||||
computedFilters = (filters: TViewFilters, defaultValues?: Partial<TViewFilters>): TViewFilters => ({
|
||||
project: defaultValues?.project || filters?.project || [],
|
||||
module: defaultValues?.module || filters?.module || [],
|
||||
cycle: defaultValues?.cycle || filters?.cycle || [],
|
||||
priority: defaultValues?.priority || filters?.priority || [],
|
||||
state: defaultValues?.state || filters?.state || [],
|
||||
state_group: defaultValues?.state_group || filters?.state_group || [],
|
||||
assignees: defaultValues?.assignees || filters?.assignees || [],
|
||||
mentions: defaultValues?.mentions || filters?.mentions || [],
|
||||
subscriber: defaultValues?.subscriber || filters?.subscriber || [],
|
||||
created_by: defaultValues?.created_by || filters?.created_by || [],
|
||||
labels: defaultValues?.labels || filters?.labels || [],
|
||||
start_date: defaultValues?.start_date || filters?.start_date || [],
|
||||
target_date: defaultValues?.target_date || filters?.target_date || [],
|
||||
project: get(defaultValues, "project", get(filters, "project", [])),
|
||||
module: get(defaultValues, "module", get(filters, "module", [])),
|
||||
cycle: get(defaultValues, "cycle", get(filters, "cycle", [])),
|
||||
priority: get(defaultValues, "priority", get(filters, "priority", [])),
|
||||
state: get(defaultValues, "state", get(filters, "state", [])),
|
||||
state_group: get(defaultValues, "state_group", get(filters, "state_group", [])),
|
||||
assignees: get(defaultValues, "assignees", get(filters, "assignees", [])),
|
||||
mentions: get(defaultValues, "mentions", get(filters, "mentions", [])),
|
||||
subscriber: get(defaultValues, "subscriber", get(filters, "subscriber", [])),
|
||||
created_by: get(defaultValues, "created_by", get(filters, "created_by", [])),
|
||||
labels: get(defaultValues, "labels", get(filters, "labels", [])),
|
||||
start_date: get(defaultValues, "start_date", get(filters, "start_date", [])),
|
||||
target_date: get(defaultValues, "target_date", get(filters, "target_date", [])),
|
||||
});
|
||||
|
||||
// computed display filters
|
||||
@ -49,19 +50,19 @@ export class FiltersHelper {
|
||||
displayProperties: TViewDisplayProperties,
|
||||
defaultValues?: Partial<TViewDisplayProperties>
|
||||
): TViewDisplayProperties => ({
|
||||
assignee: defaultValues?.assignee || displayProperties?.assignee || true,
|
||||
start_date: defaultValues?.start_date || displayProperties?.start_date || true,
|
||||
due_date: defaultValues?.due_date || displayProperties?.due_date || true,
|
||||
labels: defaultValues?.labels || displayProperties?.labels || true,
|
||||
priority: defaultValues?.priority || displayProperties?.priority || true,
|
||||
state: defaultValues?.state || displayProperties?.state || true,
|
||||
sub_issue_count: defaultValues?.sub_issue_count || displayProperties?.sub_issue_count || true,
|
||||
attachment_count: defaultValues?.attachment_count || displayProperties?.attachment_count || true,
|
||||
link: defaultValues?.link || displayProperties?.link || true,
|
||||
estimate: defaultValues?.estimate || displayProperties?.estimate || true,
|
||||
key: defaultValues?.key || displayProperties?.key || true,
|
||||
created_on: defaultValues?.created_on || displayProperties?.created_on || true,
|
||||
updated_on: defaultValues?.updated_on || displayProperties?.updated_on || true,
|
||||
assignee: get(defaultValues, "assignee", get(displayProperties, "assignee", true)),
|
||||
start_date: get(defaultValues, "start_date", get(displayProperties, "start_date", true)),
|
||||
due_date: get(defaultValues, "due_date", get(displayProperties, "due_date", true)),
|
||||
labels: get(defaultValues, "labels", get(displayProperties, "labels", true)),
|
||||
priority: get(defaultValues, "priority", get(displayProperties, "priority", true)),
|
||||
state: get(defaultValues, "state", get(displayProperties, "state", true)),
|
||||
sub_issue_count: get(defaultValues, "sub_issue_count", get(displayProperties, "sub_issue_count", true)),
|
||||
attachment_count: get(defaultValues, "attachment_count", get(displayProperties, "attachment_count", true)),
|
||||
link: get(defaultValues, "link", get(displayProperties, "link", true)),
|
||||
estimate: get(defaultValues, "estimate", get(displayProperties, "estimate", true)),
|
||||
key: get(defaultValues, "key", get(displayProperties, "key", true)),
|
||||
created_on: get(defaultValues, "created_on", get(displayProperties, "created_on", true)),
|
||||
updated_on: get(defaultValues, "updated_on", get(displayProperties, "updated_on", true)),
|
||||
});
|
||||
|
||||
// compute filters and display_filters issue query parameters
|
||||
|
@ -1,5 +1,4 @@
|
||||
// stores
|
||||
import { autorun, makeObservable, observable } from "mobx";
|
||||
import { ViewRootStore } from "./view-root.store";
|
||||
// services
|
||||
import {
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
import set from "lodash/set";
|
||||
import update from "lodash/update";
|
||||
import concat from "lodash/concat";
|
||||
import pull from "lodash/pull";
|
||||
// store
|
||||
import { RootStore } from "store/root.store";
|
||||
// types
|
||||
@ -24,12 +27,13 @@ export type TViewStore = TView & {
|
||||
// computed
|
||||
appliedFilters: TViewFilterProps | undefined;
|
||||
appliedFiltersQueryParams: string | undefined;
|
||||
isFiltersApplied: boolean;
|
||||
// helper actions
|
||||
setName: (name: 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;
|
||||
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
|
||||
setDisplayProperties: (displayPropertyKey: keyof TViewDisplayProperties) => void;
|
||||
resetChanges: () => void;
|
||||
saveChanges: () => Promise<void>;
|
||||
// actions
|
||||
@ -132,6 +136,7 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
// computed
|
||||
appliedFilters: computed,
|
||||
appliedFiltersQueryParams: computed,
|
||||
isFiltersApplied: computed,
|
||||
// helper actions
|
||||
setName: action,
|
||||
setFilters: action,
|
||||
@ -164,6 +169,16 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
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
|
||||
setName = (name: string) => {
|
||||
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(() => {
|
||||
this.loader = "submit";
|
||||
Object.keys(filters).forEach((key) => {
|
||||
const _key = key as keyof TViewFilters;
|
||||
set(this.filtersToUpdate, ["filters", _key], filters[_key]);
|
||||
if (filterKey === undefined) {
|
||||
if (filterValue === "clear_all") set(this.filtersToUpdate, ["filters"], {});
|
||||
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(() => {
|
||||
Object.keys(display_properties).forEach((key) => {
|
||||
const _key = key as keyof TViewDisplayProperties;
|
||||
set(this.filtersToUpdate, ["display_properties", _key], display_properties[_key]);
|
||||
});
|
||||
update(this.filtersToUpdate, ["display_properties", displayPropertyKey], (_value: boolean = true) => !_value);
|
||||
});
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user