mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: implemented extra options in the display filter properties dropdown and hanlded the arrow functions with useCallback
This commit is contained in:
parent
6bde956166
commit
c35d650de0
2
packages/types/src/view/filter.d.ts
vendored
2
packages/types/src/view/filter.d.ts
vendored
@ -52,6 +52,8 @@ export type TViewDisplayFiltersOrderBy =
|
|||||||
| "sub_issues_count"
|
| "sub_issues_count"
|
||||||
| "-sub_issues_count";
|
| "-sub_issues_count";
|
||||||
|
|
||||||
|
export type TViewDisplayFiltersExtraOptions = "sub_issue" | "show_empty_groups";
|
||||||
|
|
||||||
export type TViewDisplayFiltersType = "active" | "backlog";
|
export type TViewDisplayFiltersType = "active" | "backlog";
|
||||||
|
|
||||||
export type TViewCalendarLayouts = "month" | "week";
|
export type TViewCalendarLayouts = "month" | "week";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FC } from "react";
|
import { FC, useCallback } from "react";
|
||||||
import { ImagePlus, X } from "lucide-react";
|
import { ImagePlus, X } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useViewDetail, useViewFilter } from "hooks/store";
|
import { useViewDetail, useViewFilter } from "hooks/store";
|
||||||
@ -22,9 +22,10 @@ export const ViewAppliedFiltersItem: FC<TViewAppliedFiltersItem> = (props) => {
|
|||||||
|
|
||||||
const propertyDetail = viewFilterHelper?.propertyDetails(filterKey, propertyId) || undefined;
|
const propertyDetail = viewFilterHelper?.propertyDetails(filterKey, propertyId) || undefined;
|
||||||
|
|
||||||
const removeFilterOption = () => {
|
const removeFilterOption = useCallback(
|
||||||
viewDetailStore?.setFilters(filterKey, propertyId);
|
() => viewDetailStore?.setFilters(filterKey, propertyId),
|
||||||
};
|
[viewDetailStore, filterKey, propertyId]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FC, Fragment } from "react";
|
import { FC, Fragment, useCallback, useMemo } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import isEmpty from "lodash/isEmpty";
|
import isEmpty from "lodash/isEmpty";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
@ -24,14 +24,23 @@ export const ViewAppliedFilters: FC<TViewAppliedFilters> = observer((props) => {
|
|||||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||||
const viewFilterStore = useViewFilter(workspaceSlug, projectId);
|
const viewFilterStore = useViewFilter(workspaceSlug, projectId);
|
||||||
|
|
||||||
const currentDefaultFilterDetails = viewFilterStore?.propertyDefaultDetails(filterKey);
|
const currentDefaultFilterDetails = useMemo(
|
||||||
|
() => viewFilterStore?.propertyDefaultDetails(filterKey),
|
||||||
|
[viewFilterStore, filterKey]
|
||||||
|
);
|
||||||
|
|
||||||
const propertyValues =
|
const propertyValues = useMemo(
|
||||||
viewDetailStore?.appliedFilters?.filters && !isEmpty(viewDetailStore?.appliedFilters?.filters)
|
() =>
|
||||||
? viewDetailStore?.appliedFilters?.filters?.[filterKey] || undefined
|
viewDetailStore?.appliedFilters?.filters && !isEmpty(viewDetailStore?.appliedFilters?.filters)
|
||||||
: undefined;
|
? viewDetailStore?.appliedFilters?.filters?.[filterKey] || undefined
|
||||||
|
: undefined,
|
||||||
|
[filterKey, viewDetailStore?.appliedFilters?.filters]
|
||||||
|
);
|
||||||
|
|
||||||
const clearPropertyFilter = () => viewDetailStore?.setFilters(filterKey, "clear_all");
|
const clearPropertyFilter = useCallback(
|
||||||
|
() => viewDetailStore?.setFilters(filterKey, "clear_all"),
|
||||||
|
[viewDetailStore, filterKey]
|
||||||
|
);
|
||||||
|
|
||||||
if (!propertyValues || propertyValues.length <= 0) return <></>;
|
if (!propertyValues || propertyValues.length <= 0) return <></>;
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FC, Fragment } from "react";
|
import { FC, Fragment, useCallback, useMemo } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
import isEmpty from "lodash/isEmpty";
|
import isEmpty from "lodash/isEmpty";
|
||||||
@ -23,12 +23,15 @@ export const ViewAppliedFiltersRoot: FC<TViewAppliedFiltersRoot> = observer((pro
|
|||||||
// hooks
|
// hooks
|
||||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||||
|
|
||||||
const filterKeys =
|
const filterKeys = useMemo(
|
||||||
viewDetailStore?.filtersToUpdate && !isEmpty(viewDetailStore?.filtersToUpdate?.filters)
|
() =>
|
||||||
? Object.keys(viewDetailStore?.filtersToUpdate?.filters)
|
viewDetailStore?.filtersToUpdate && !isEmpty(viewDetailStore?.filtersToUpdate?.filters)
|
||||||
: undefined;
|
? Object.keys(viewDetailStore?.filtersToUpdate?.filters)
|
||||||
|
: undefined,
|
||||||
|
[viewDetailStore?.filtersToUpdate]
|
||||||
|
);
|
||||||
|
|
||||||
const clearAllFilters = () => viewDetailStore?.setFilters(undefined, "clear_all");
|
const clearAllFilters = useCallback(() => viewDetailStore?.setFilters(undefined, "clear_all"), [viewDetailStore]);
|
||||||
|
|
||||||
if (!filterKeys || !viewDetailStore?.isFiltersApplied)
|
if (!filterKeys || !viewDetailStore?.isFiltersApplied)
|
||||||
return (
|
return (
|
||||||
|
54
web/components/view/display-filters/extra-options.tsx
Normal file
54
web/components/view/display-filters/extra-options.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { FC, Fragment, useCallback, useMemo } from "react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { Check } from "lucide-react";
|
||||||
|
// hooks
|
||||||
|
import { useViewDetail } from "hooks/store";
|
||||||
|
// types
|
||||||
|
import { TViewDisplayFiltersExtraOptions, TViewTypes } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { EXTRA_OPTIONS_PROPERTY } from "constants/view";
|
||||||
|
|
||||||
|
type TDisplayFilterExtraOptions = {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string | undefined;
|
||||||
|
viewId: string;
|
||||||
|
viewType: TViewTypes;
|
||||||
|
filterKey: TViewDisplayFiltersExtraOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DisplayFilterExtraOptions: FC<TDisplayFilterExtraOptions> = observer((props) => {
|
||||||
|
const { workspaceSlug, projectId, viewId, viewType, filterKey } = props;
|
||||||
|
// hooks
|
||||||
|
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||||
|
|
||||||
|
const optionTitle = useMemo(() => EXTRA_OPTIONS_PROPERTY[filterKey].label, [filterKey]);
|
||||||
|
|
||||||
|
const isSelected = viewDetailStore?.appliedFilters?.display_filters?.[filterKey] ? true : false;
|
||||||
|
|
||||||
|
const handlePropertySelection = useCallback(
|
||||||
|
() => viewDetailStore?.setDisplayFilters({ [filterKey]: !isSelected }),
|
||||||
|
[viewDetailStore, filterKey, isSelected]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<div
|
||||||
|
className="relative w-full flex items-center overflow-hidden gap-2.5 cursor-pointer p-1 py-1.5 rounded hover:bg-custom-background-80 transition-all group"
|
||||||
|
onClick={handlePropertySelection}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`flex-shrink-0 w-3 h-3 flex justify-center items-center border rounded text-bold ${
|
||||||
|
isSelected
|
||||||
|
? "border-custom-primary-100 bg-custom-primary-100"
|
||||||
|
: "border-custom-border-400 bg-custom-background-100"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isSelected && <Check size={14} />}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs block truncate line-clamp-1 text-custom-text-200 group-hover:text-custom-text-100">
|
||||||
|
{optionTitle || "Extra Option"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
});
|
@ -1,4 +1,4 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useCallback, useMemo, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ChevronUp, ChevronDown } from "lucide-react";
|
import { ChevronUp, ChevronDown } from "lucide-react";
|
||||||
import filter from "lodash/filter";
|
import filter from "lodash/filter";
|
||||||
@ -7,9 +7,9 @@ import uniq from "lodash/uniq";
|
|||||||
// hooks
|
// hooks
|
||||||
import { useViewDetail } from "hooks/store";
|
import { useViewDetail } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import { ViewDisplayPropertiesRoot, ViewDisplayFiltersItemRoot } from "../";
|
import { ViewDisplayPropertiesRoot, ViewDisplayFiltersItemRoot, DisplayFilterExtraOptions } from "../";
|
||||||
// types
|
// types
|
||||||
import { TViewDisplayFilters, TViewTypes } from "@plane/types";
|
import { TViewDisplayFilters, TViewDisplayFiltersExtraOptions, TViewTypes } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { EViewPageType, viewDefaultFilterParametersByViewTypeAndLayout } from "constants/view";
|
import { EViewPageType, viewDefaultFilterParametersByViewTypeAndLayout } from "constants/view";
|
||||||
|
|
||||||
@ -29,18 +29,22 @@ export const ViewDisplayFiltersRoot: FC<TViewDisplayFiltersRoot> = observer((pro
|
|||||||
const [filterVisibility, setFilterVisibility] = useState<(Partial<keyof TViewDisplayFilters> | "display_property")[]>(
|
const [filterVisibility, setFilterVisibility] = useState<(Partial<keyof TViewDisplayFilters> | "display_property")[]>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
const handleFilterVisibility = (key: keyof TViewDisplayFilters | "display_property") => {
|
const handleFilterVisibility = useCallback((key: keyof TViewDisplayFilters | "display_property") => {
|
||||||
setFilterVisibility((prevData = []) => {
|
setFilterVisibility((prevData = []) => {
|
||||||
if (prevData.includes(key)) return filter(prevData, (item) => item !== key);
|
if (prevData.includes(key)) return filter(prevData, (item) => item !== key);
|
||||||
return uniq(concat(prevData, [key]));
|
return uniq(concat(prevData, [key]));
|
||||||
});
|
});
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const layout = viewDetailStore?.appliedFilters?.display_filters?.layout;
|
const filtersProperties = useMemo(() => {
|
||||||
|
const layout = viewDetailStore?.appliedFilters?.display_filters?.layout;
|
||||||
|
return layout ? viewDefaultFilterParametersByViewTypeAndLayout(viewPageType, layout, "display_filters") : [];
|
||||||
|
}, [viewDetailStore, viewPageType]);
|
||||||
|
|
||||||
const filtersProperties = layout
|
const filtersExtraProperties = useMemo(() => {
|
||||||
? viewDefaultFilterParametersByViewTypeAndLayout(viewPageType, layout, "display_filters")
|
const layout = viewDetailStore?.appliedFilters?.display_filters?.layout;
|
||||||
: [];
|
return layout ? viewDefaultFilterParametersByViewTypeAndLayout(viewPageType, layout, "extra_options") : [];
|
||||||
|
}, [viewDetailStore, viewPageType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1 divide-y divide-custom-border-300">
|
<div className="space-y-1 divide-y divide-custom-border-300">
|
||||||
@ -67,7 +71,7 @@ export const ViewDisplayFiltersRoot: FC<TViewDisplayFiltersRoot> = observer((pro
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{filtersProperties.map((filterKey) => (
|
{filtersProperties.map((filterKey) => (
|
||||||
<div key={filterKey} className="relative py-1 last:pb-0">
|
<div key={filterKey} className="relative py-1">
|
||||||
<div className="sticky top-0 z-20 flex justify-between items-center gap-2 bg-custom-background-100 select-none">
|
<div className="sticky top-0 z-20 flex justify-between items-center gap-2 bg-custom-background-100 select-none">
|
||||||
<div className="font-medium text-xs text-custom-text-300 capitalize py-1">
|
<div className="font-medium text-xs text-custom-text-300 capitalize py-1">
|
||||||
{filterKey.replaceAll("_", " ")}
|
{filterKey.replaceAll("_", " ")}
|
||||||
@ -91,10 +95,16 @@ export const ViewDisplayFiltersRoot: FC<TViewDisplayFiltersRoot> = observer((pro
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* extra options */}
|
<div className="pt-1 pb-0">
|
||||||
<div>
|
{filtersExtraProperties.map((option) => (
|
||||||
<div>Show sub issues</div>
|
<DisplayFilterExtraOptions
|
||||||
<div>Show Empty groups</div>
|
workspaceSlug={workspaceSlug}
|
||||||
|
projectId={projectId}
|
||||||
|
viewId={viewId}
|
||||||
|
viewType={viewType}
|
||||||
|
filterKey={option as TViewDisplayFiltersExtraOptions}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FC } from "react";
|
import { FC, useCallback } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import { useViewDetail } from "hooks/store";
|
import { useViewDetail } from "hooks/store";
|
||||||
@ -18,9 +18,12 @@ export const ViewDisplayPropertySelection: FC<TViewDisplayPropertySelection> = o
|
|||||||
// hooks
|
// hooks
|
||||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||||
|
|
||||||
const propertyIsSelected = viewDetailStore?.appliedFilters?.display_properties?.[property];
|
const handlePropertySelection = useCallback(
|
||||||
|
() => viewDetailStore?.setDisplayProperties(property),
|
||||||
|
[viewDetailStore, property]
|
||||||
|
);
|
||||||
|
|
||||||
const handlePropertySelection = () => viewDetailStore?.setDisplayProperties(property);
|
const propertyIsSelected = viewDetailStore?.appliedFilters?.display_properties?.[property];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FC, Fragment, ReactNode, useRef, useState } from "react";
|
import { FC, Fragment, ReactNode, useCallback, useRef, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Combobox } from "@headlessui/react";
|
import { Combobox } from "@headlessui/react";
|
||||||
import { usePopper } from "react-popper";
|
import { usePopper } from "react-popper";
|
||||||
@ -64,14 +64,17 @@ export const ViewFiltersDropdown: FC<TViewFiltersDropdown> = observer((props) =>
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDropdownOpen = () => setDropdownToggle(true);
|
const handleDropdownOpen = useCallback(() => setDropdownToggle(true), []);
|
||||||
const handleDropdownClose = () => setDropdownToggle(false);
|
const handleDropdownClose = useCallback(() => setDropdownToggle(false), []);
|
||||||
const handleDropdownToggle = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
const handleDropdownToggle = useCallback(
|
||||||
event.preventDefault();
|
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
event.stopPropagation();
|
event.preventDefault();
|
||||||
if (!dropdownToggle) handleDropdownOpen();
|
event.stopPropagation();
|
||||||
else handleDropdownClose();
|
if (!dropdownToggle) handleDropdownOpen();
|
||||||
};
|
else handleDropdownClose();
|
||||||
|
},
|
||||||
|
[dropdownToggle, handleDropdownOpen, handleDropdownClose]
|
||||||
|
);
|
||||||
|
|
||||||
useOutsideClickDetector(dropdownRef, () => dateCustomFilterToggle === undefined && handleDropdownClose());
|
useOutsideClickDetector(dropdownRef, () => dateCustomFilterToggle === undefined && handleDropdownClose());
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useCallback, useMemo, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import { useViewDetail, useViewFilter } from "hooks/store";
|
import { useViewDetail, useViewFilter } from "hooks/store";
|
||||||
@ -27,28 +27,37 @@ export const ViewFiltersItemRoot: FC<TViewFiltersItemRoot> = observer((props) =>
|
|||||||
// state
|
// state
|
||||||
const [viewAll, setViewAll] = useState(false);
|
const [viewAll, setViewAll] = useState(false);
|
||||||
|
|
||||||
const propertyIds = viewFilterHelper?.filterIdsWithKey(filterKey) || [];
|
const propertyIds = useMemo(() => viewFilterHelper?.filterIdsWithKey(filterKey) || [], [viewFilterHelper, filterKey]);
|
||||||
|
|
||||||
const filterPropertyIds = propertyIds.length > 5 ? (viewAll ? propertyIds : propertyIds.slice(0, 5)) : propertyIds;
|
const filterPropertyIds = useMemo(
|
||||||
|
() => (propertyIds.length > 5 ? (viewAll ? propertyIds : propertyIds.slice(0, 5)) : propertyIds),
|
||||||
|
[propertyIds, viewAll]
|
||||||
|
);
|
||||||
|
|
||||||
const handlePropertySelection = (_propertyId: string) => {
|
const handlePropertySelection = useCallback(
|
||||||
if (["start_date", "target_date"].includes(filterKey)) {
|
(_propertyId: string) => {
|
||||||
if (_propertyId === "custom") {
|
if (["start_date", "target_date"].includes(filterKey)) {
|
||||||
const _propertyIds = viewDetailStore?.appliedFilters?.filters?.[filterKey] || [];
|
if (_propertyId === "custom") {
|
||||||
const selectedDates = _propertyIds.filter((id) => id.includes("-"));
|
const _propertyIds = viewDetailStore?.appliedFilters?.filters?.[filterKey] || [];
|
||||||
if (selectedDates.length > 0)
|
const selectedDates = _propertyIds.filter((id) => id.includes("-"));
|
||||||
selectedDates.forEach((date: string) => viewDetailStore?.setFilters(filterKey, date));
|
if (selectedDates.length > 0)
|
||||||
else setDateCustomFilterToggle(filterKey);
|
selectedDates.forEach((date: string) => viewDetailStore?.setFilters(filterKey, date));
|
||||||
|
else setDateCustomFilterToggle(filterKey);
|
||||||
|
} else viewDetailStore?.setFilters(filterKey, _propertyId);
|
||||||
} else viewDetailStore?.setFilters(filterKey, _propertyId);
|
} else viewDetailStore?.setFilters(filterKey, _propertyId);
|
||||||
} else viewDetailStore?.setFilters(filterKey, _propertyId);
|
},
|
||||||
};
|
[filterKey, viewDetailStore, setDateCustomFilterToggle]
|
||||||
|
);
|
||||||
|
|
||||||
const handleCustomDateSelection = (selectedDates: string[]) => {
|
const handleCustomDateSelection = useCallback(
|
||||||
selectedDates.forEach((date: string) => {
|
(selectedDates: string[]) => {
|
||||||
viewDetailStore?.setFilters(filterKey, date);
|
selectedDates.forEach((date: string) => {
|
||||||
setDateCustomFilterToggle(undefined);
|
viewDetailStore?.setFilters(filterKey, date);
|
||||||
});
|
setDateCustomFilterToggle(undefined);
|
||||||
};
|
});
|
||||||
|
},
|
||||||
|
[filterKey, viewDetailStore, setDateCustomFilterToggle]
|
||||||
|
);
|
||||||
|
|
||||||
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>;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FC } from "react";
|
import { FC, useMemo } from "react";
|
||||||
import { Check } from "lucide-react";
|
import { Check } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
@ -20,7 +20,10 @@ export const ViewFilterSelection: FC<TViewFilterSelection> = observer((props) =>
|
|||||||
|
|
||||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||||
|
|
||||||
const propertyIds = viewDetailStore?.appliedFilters?.filters?.[filterKey] || [];
|
const propertyIds = useMemo(
|
||||||
|
() => viewDetailStore?.appliedFilters?.filters?.[filterKey] || [],
|
||||||
|
[viewDetailStore?.appliedFilters?.filters, filterKey]
|
||||||
|
);
|
||||||
|
|
||||||
const isSelected = ["start_date", "target_date"].includes(filterKey)
|
const isSelected = ["start_date", "target_date"].includes(filterKey)
|
||||||
? propertyId === "custom"
|
? propertyId === "custom"
|
||||||
@ -30,6 +33,18 @@ export const ViewFilterSelection: FC<TViewFilterSelection> = observer((props) =>
|
|||||||
: propertyIds?.includes(propertyId)
|
: propertyIds?.includes(propertyId)
|
||||||
: propertyIds?.includes(propertyId) || false;
|
: propertyIds?.includes(propertyId) || false;
|
||||||
|
|
||||||
|
// const isSelected = useMemo(
|
||||||
|
// () =>
|
||||||
|
// ["start_date", "target_date"].includes(filterKey)
|
||||||
|
// ? propertyId === "custom"
|
||||||
|
// ? propertyIds.filter((id) => id.includes("-")).length > 0
|
||||||
|
// ? true
|
||||||
|
// : false
|
||||||
|
// : propertyIds?.includes(propertyId)
|
||||||
|
// : propertyIds?.includes(propertyId) || false,
|
||||||
|
// [filterKey, propertyId, propertyIds]
|
||||||
|
// );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex-shrink-0 w-3 h-3 flex justify-center items-center border rounded text-bold ${
|
className={`flex-shrink-0 w-3 h-3 flex justify-center items-center border rounded text-bold ${
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useCallback, useMemo, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||||
import concat from "lodash/concat";
|
import concat from "lodash/concat";
|
||||||
@ -36,20 +36,19 @@ export const ViewFiltersRoot: FC<TViewFiltersRoot> = observer((props) => {
|
|||||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||||
// state
|
// state
|
||||||
const [filterVisibility, setFilterVisibility] = useState<Partial<keyof TViewFilters>[]>([]);
|
const [filterVisibility, setFilterVisibility] = useState<Partial<keyof TViewFilters>[]>([]);
|
||||||
const handleFilterVisibility = (key: keyof TViewFilters) => {
|
const handleFilterVisibility = useCallback((key: keyof TViewFilters) => {
|
||||||
setFilterVisibility((prevData = []) => {
|
setFilterVisibility((prevData = []) => {
|
||||||
if (prevData.includes(key)) return filter(prevData, (item) => item !== key);
|
if (prevData.includes(key)) return filter(prevData, (item) => item !== key);
|
||||||
return uniq(concat(prevData, [key]));
|
return uniq(concat(prevData, [key]));
|
||||||
});
|
});
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const layout = viewDetailStore?.appliedFilters?.display_filters?.layout;
|
const filtersProperties = useMemo(() => {
|
||||||
|
const layout = viewDetailStore?.appliedFilters?.display_filters?.layout;
|
||||||
|
return layout ? viewDefaultFilterParametersByViewTypeAndLayout(viewPageType, layout, "filters") : [];
|
||||||
|
}, [viewDetailStore?.appliedFilters?.display_filters?.layout, viewPageType]);
|
||||||
|
|
||||||
const filtersProperties = layout
|
if (filtersProperties.length <= 0) return <></>;
|
||||||
? viewDefaultFilterParametersByViewTypeAndLayout(viewPageType, layout, "filters")
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if (!layout || filtersProperties.length <= 0) return <></>;
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1 divide-y divide-custom-border-300">
|
<div className="space-y-1 divide-y divide-custom-border-300">
|
||||||
{filtersProperties.map((filterKey) => (
|
{filtersProperties.map((filterKey) => (
|
||||||
|
74
web/components/view/header-tabs.tsx
Normal file
74
web/components/view/header-tabs.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { FC, Fragment, ReactNode, useMemo } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { Briefcase, CheckCircle, ChevronRight } from "lucide-react";
|
||||||
|
// hooks
|
||||||
|
import { useProject } from "hooks/store";
|
||||||
|
// types
|
||||||
|
import { TViewTypes } from "@plane/types";
|
||||||
|
|
||||||
|
type TViewHeader = {
|
||||||
|
projectId: string | undefined;
|
||||||
|
viewType: TViewTypes;
|
||||||
|
titleIcon: ReactNode;
|
||||||
|
title: string;
|
||||||
|
workspaceViewTabOptions: { key: TViewTypes; title: string; href: string }[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ViewHeader: FC<TViewHeader> = (props) => {
|
||||||
|
const { projectId, viewType, titleIcon, title, workspaceViewTabOptions } = props;
|
||||||
|
// hooks
|
||||||
|
const { getProjectById } = useProject();
|
||||||
|
|
||||||
|
const projectDetails = useMemo(
|
||||||
|
() => (projectId ? getProjectById(projectId) : undefined),
|
||||||
|
[projectId, getProjectById]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative flex items-center gap-2">
|
||||||
|
{projectDetails && (
|
||||||
|
<Fragment>
|
||||||
|
<div className="relative flex items-center gap-2 overflow-hidden">
|
||||||
|
<div className="flex-shrink-0 w-6 h-6 rounded relative flex justify-center items-center bg-custom-background-80">
|
||||||
|
{projectDetails?.icon_prop ? projectDetails?.icon_prop.toString() : <Briefcase size={12} />}
|
||||||
|
</div>
|
||||||
|
<div className="font-medium inline-block whitespace-nowrap overflow-hidden truncate line-clamp-1 text-sm">
|
||||||
|
{projectDetails?.name ? projectDetails?.name : "Project Issues"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-custom-text-200">
|
||||||
|
<ChevronRight size={12} />
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="relative flex items-center gap-2 overflow-hidden">
|
||||||
|
<div className="flex-shrink-0 w-6 h-6 rounded relative flex justify-center items-center bg-custom-background-80">
|
||||||
|
{titleIcon ? titleIcon : <CheckCircle size={12} />}
|
||||||
|
</div>
|
||||||
|
<div className="font-medium inline-block whitespace-nowrap overflow-hidden truncate line-clamp-1 text-sm">
|
||||||
|
{title ? title : "All Issues"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ml-auto relative flex items-center gap-3">
|
||||||
|
<div className="relative flex items-center rounded border border-custom-border-200 bg-custom-background-80">
|
||||||
|
{workspaceViewTabOptions.map((tab) => (
|
||||||
|
<Link
|
||||||
|
key={tab.key}
|
||||||
|
href={tab.href}
|
||||||
|
className={`p-4 py-1.5 rounded text-sm transition-all cursor-pointer font-medium
|
||||||
|
${
|
||||||
|
viewType === tab.key
|
||||||
|
? "text-custom-text-100 bg-custom-background-100"
|
||||||
|
: "text-custom-text-200 bg-custom-background-80 hover:text-custom-text-100"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tab.title}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,5 +1,7 @@
|
|||||||
export * from "./root";
|
export * from "./root";
|
||||||
|
|
||||||
|
export * from "./header-tabs";
|
||||||
|
|
||||||
// views
|
// views
|
||||||
export * from "./views/root";
|
export * from "./views/root";
|
||||||
export * from "./views/view-item";
|
export * from "./views/view-item";
|
||||||
@ -25,6 +27,7 @@ export * from "./display-filters/root";
|
|||||||
export * from "./display-filters/display-filter-item-root";
|
export * from "./display-filters/display-filter-item-root";
|
||||||
export * from "./display-filters/display-filter-item";
|
export * from "./display-filters/display-filter-item";
|
||||||
export * from "./display-filters/display-filter-selection";
|
export * from "./display-filters/display-filter-selection";
|
||||||
|
export * from "./display-filters/extra-options";
|
||||||
|
|
||||||
// view display properties
|
// view display properties
|
||||||
export * from "./display-properties/root";
|
export * from "./display-properties/root";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FC, Fragment } from "react";
|
import { FC, Fragment, useMemo } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { LucideIcon, List, Kanban, Calendar, Sheet, GanttChartSquare } from "lucide-react";
|
import { LucideIcon, List, Kanban, Calendar, Sheet, GanttChartSquare } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
@ -18,20 +18,23 @@ type TViewLayoutRoot = {
|
|||||||
viewPageType: EViewPageType;
|
viewPageType: EViewPageType;
|
||||||
};
|
};
|
||||||
|
|
||||||
const LAYOUTS_DATA: { key: EViewLayouts; title: string; icon: LucideIcon }[] = [
|
|
||||||
{ key: EViewLayouts.LIST, title: "List Layout", icon: List },
|
|
||||||
{ key: EViewLayouts.KANBAN, title: "Kanban Layout", icon: Kanban },
|
|
||||||
{ key: EViewLayouts.CALENDAR, title: "Calendar Layout", icon: Calendar },
|
|
||||||
{ key: EViewLayouts.SPREADSHEET, title: "Spreadsheet Layout", icon: Sheet },
|
|
||||||
{ key: EViewLayouts.GANTT, title: "Gantt Chart layout", icon: GanttChartSquare },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ViewLayoutRoot: FC<TViewLayoutRoot> = observer((props) => {
|
export const ViewLayoutRoot: FC<TViewLayoutRoot> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, viewId, viewType, viewPageType } = props;
|
const { workspaceSlug, projectId, viewId, viewType, viewPageType } = props;
|
||||||
// hooks
|
// hooks
|
||||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||||
|
|
||||||
const validLayouts = viewPageDefaultLayoutsByPageType(viewPageType);
|
const LAYOUTS_DATA: { key: EViewLayouts; title: string; icon: LucideIcon }[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{ key: EViewLayouts.LIST, title: "List Layout", icon: List },
|
||||||
|
{ key: EViewLayouts.KANBAN, title: "Kanban Layout", icon: Kanban },
|
||||||
|
{ key: EViewLayouts.CALENDAR, title: "Calendar Layout", icon: Calendar },
|
||||||
|
{ key: EViewLayouts.SPREADSHEET, title: "Spreadsheet Layout", icon: Sheet },
|
||||||
|
{ key: EViewLayouts.GANTT, title: "Gantt Chart layout", icon: GanttChartSquare },
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const validLayouts = useMemo(() => viewPageDefaultLayoutsByPageType(viewPageType), [viewPageType]);
|
||||||
|
|
||||||
if (!viewDetailStore || validLayouts.length <= 1) return <></>;
|
if (!viewDetailStore || validLayouts.length <= 1) return <></>;
|
||||||
return (
|
return (
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { FC, Fragment, useEffect, useMemo, useState } from "react";
|
import { FC, Fragment, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import Link from "next/link";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { CheckCircle } from "lucide-react";
|
|
||||||
import { v4 as uuidV4 } from "uuid";
|
import { v4 as uuidV4 } from "uuid";
|
||||||
import cloneDeep from "lodash/cloneDeep";
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
// hooks
|
// hooks
|
||||||
@ -35,7 +33,6 @@ type TGlobalViewRoot = {
|
|||||||
viewType: TViewTypes;
|
viewType: TViewTypes;
|
||||||
viewPageType: EViewPageType;
|
viewPageType: EViewPageType;
|
||||||
baseRoute: string;
|
baseRoute: string;
|
||||||
workspaceViewTabOptions: { key: TViewTypes; title: string; href: string }[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type TViewOperationsToggle = {
|
type TViewOperationsToggle = {
|
||||||
@ -44,7 +41,7 @@ type TViewOperationsToggle = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
|
export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, viewId, viewType, viewPageType, baseRoute, workspaceViewTabOptions } = props;
|
const { workspaceSlug, projectId, viewId, viewType, viewPageType, baseRoute } = props;
|
||||||
// hooks
|
// hooks
|
||||||
const viewStore = useView(workspaceSlug, projectId, viewType);
|
const viewStore = useView(workspaceSlug, projectId, viewType);
|
||||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||||
@ -54,9 +51,11 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
|
|||||||
type: undefined,
|
type: undefined,
|
||||||
viewId: undefined,
|
viewId: undefined,
|
||||||
});
|
});
|
||||||
const handleViewOperationsToggle = (type: TViewOperationsToggle["type"], viewId: string | undefined) =>
|
const handleViewOperationsToggle = useCallback(
|
||||||
setViewOperationsToggle({ type, viewId });
|
(type: TViewOperationsToggle["type"], viewId: string | undefined) => setViewOperationsToggle({ type, viewId }),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
// hooks
|
||||||
const viewDetailCreateEditStore = useViewDetail(
|
const viewDetailCreateEditStore = useViewDetail(
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
projectId,
|
projectId,
|
||||||
@ -153,7 +152,7 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[viewStore, viewDetailStore, setToastAlert, viewDetailCreateEditStore]
|
[viewStore, viewDetailStore, setToastAlert, viewDetailCreateEditStore, handleViewOperationsToggle]
|
||||||
);
|
);
|
||||||
|
|
||||||
// fetch all views
|
// fetch all views
|
||||||
@ -174,43 +173,13 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-full">
|
<div className="relative w-full h-full">
|
||||||
<div className="relative flex items-center gap-2 px-5 py-4">
|
|
||||||
<div className="relative flex items-center gap-2 overflow-hidden">
|
|
||||||
<div className="flex-shrink-0 w-6 h-6 rounded relative flex justify-center items-center bg-custom-background-80">
|
|
||||||
<CheckCircle size={12} />
|
|
||||||
</div>
|
|
||||||
<div className="font-medium inline-block whitespace-nowrap overflow-hidden truncate line-clamp-1">
|
|
||||||
All Issues
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="ml-auto relative flex items-center gap-3">
|
|
||||||
<div className="relative flex items-center rounded border border-custom-border-200 bg-custom-background-80">
|
|
||||||
{workspaceViewTabOptions.map((tab) => (
|
|
||||||
<Link
|
|
||||||
key={tab.key}
|
|
||||||
href={tab.href}
|
|
||||||
className={`p-4 py-1.5 rounded text-sm transition-all cursor-pointer font-medium
|
|
||||||
${
|
|
||||||
viewType === tab.key
|
|
||||||
? "text-custom-text-100 bg-custom-background-100"
|
|
||||||
: "text-custom-text-200 bg-custom-background-80 hover:text-custom-text-100"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{tab.title}
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{viewStore?.loader && viewStore?.loader === "init-loader" ? (
|
{viewStore?.loader && viewStore?.loader === "init-loader" ? (
|
||||||
<div className="relative w-full h-full flex justify-center items-center">
|
<div className="relative w-full h-full flex justify-center items-center">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="border-b border-custom-border-200">
|
<div className="border-b border-custom-border-200 pt-2">
|
||||||
<ViewRoot
|
<ViewRoot
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FC, Fragment, useEffect, useState } from "react";
|
import { FC, Fragment, useEffect, useMemo, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
@ -31,7 +31,7 @@ export const ViewRoot: FC<TViewRoot> = observer((props) => {
|
|||||||
const handleViewTabsVisibility = () => {
|
const handleViewTabsVisibility = () => {
|
||||||
const tabContainer = document.getElementById("tab-container");
|
const tabContainer = document.getElementById("tab-container");
|
||||||
const tabItemViewMore = document.getElementById("tab-item-view-more");
|
const tabItemViewMore = document.getElementById("tab-item-view-more");
|
||||||
const itemWidth = 116;
|
const itemWidth = 124;
|
||||||
if (!tabContainer || !tabItemViewMore) return;
|
if (!tabContainer || !tabItemViewMore) return;
|
||||||
|
|
||||||
const containerWidth = tabContainer.clientWidth;
|
const containerWidth = tabContainer.clientWidth;
|
||||||
@ -49,12 +49,14 @@ export const ViewRoot: FC<TViewRoot> = observer((props) => {
|
|||||||
return () => window.removeEventListener("resize", () => handleViewTabsVisibility());
|
return () => window.removeEventListener("resize", () => handleViewTabsVisibility());
|
||||||
}, [viewStore?.viewIds]);
|
}, [viewStore?.viewIds]);
|
||||||
|
|
||||||
const viewIds = viewStore?.viewIds?.slice(0, itemsToRenderViewsCount || viewStore?.viewIds.length) || [];
|
const viewIds = useMemo(() => {
|
||||||
|
const ids = viewStore?.viewIds?.slice(0, itemsToRenderViewsCount || viewStore?.viewIds.length) || [];
|
||||||
if (!viewIds.includes(viewId)) {
|
if (!ids.includes(viewId)) {
|
||||||
viewIds.pop();
|
ids.pop();
|
||||||
viewIds.push(viewId);
|
ids.push(viewId);
|
||||||
}
|
}
|
||||||
|
return ids;
|
||||||
|
}, [viewId, viewStore, itemsToRenderViewsCount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex justify-between px-5 gap-2">
|
<div className="relative flex justify-between px-5 gap-2">
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FC, Fragment, ReactNode, useRef, useState } from "react";
|
import { FC, Fragment, ReactNode, useCallback, useRef, useState } from "react";
|
||||||
import { Combobox } from "@headlessui/react";
|
import { Combobox } from "@headlessui/react";
|
||||||
import { usePopper } from "react-popper";
|
import { usePopper } from "react-popper";
|
||||||
import { Placement } from "@popperjs/core";
|
import { Placement } from "@popperjs/core";
|
||||||
@ -7,7 +7,7 @@ import { Plus, Search } from "lucide-react";
|
|||||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||||
import { useView } from "hooks/store";
|
import { useView } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import { ViewDropdownItem } from "..";
|
import { ViewDropdownItem } from "../";
|
||||||
// types
|
// types
|
||||||
import { TViewTypes } from "@plane/types";
|
import { TViewTypes } from "@plane/types";
|
||||||
import { TViewOperations } from "../types";
|
import { TViewOperations } from "../types";
|
||||||
@ -63,14 +63,17 @@ export const ViewDropdown: FC<TViewDropdown> = (props) => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDropdownOpen = () => setDropdownToggle(true);
|
const handleDropdownOpen = useCallback(() => setDropdownToggle(true), []);
|
||||||
const handleDropdownClose = () => setDropdownToggle(false);
|
const handleDropdownClose = useCallback(() => setDropdownToggle(false), []);
|
||||||
const handleDropdownToggle = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
const handleDropdownToggle = useCallback(
|
||||||
event.preventDefault();
|
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
event.stopPropagation();
|
event.preventDefault();
|
||||||
if (!dropdownToggle) handleDropdownOpen();
|
event.stopPropagation();
|
||||||
else handleDropdownClose();
|
if (!dropdownToggle) handleDropdownOpen();
|
||||||
};
|
else handleDropdownClose();
|
||||||
|
},
|
||||||
|
[dropdownToggle, handleDropdownOpen, handleDropdownClose]
|
||||||
|
);
|
||||||
|
|
||||||
useOutsideClickDetector(dropdownRef, handleDropdownClose);
|
useOutsideClickDetector(dropdownRef, handleDropdownClose);
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
TViewDisplayFiltersGrouped,
|
TViewDisplayFiltersGrouped,
|
||||||
TViewDisplayFiltersOrderBy,
|
TViewDisplayFiltersOrderBy,
|
||||||
TViewDisplayFiltersType,
|
TViewDisplayFiltersType,
|
||||||
|
TViewDisplayFiltersExtraOptions,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
|
|
||||||
// filters constants
|
// filters constants
|
||||||
@ -61,7 +62,7 @@ export const TYPE_PROPERTY: Record<TViewDisplayFiltersType | "null", { label: st
|
|||||||
backlog: { label: "Backlog issues" },
|
backlog: { label: "Backlog issues" },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EXTRA_OPTIONS_PROPERTY: Record<string, { label: string }> = {
|
export const EXTRA_OPTIONS_PROPERTY: Record<TViewDisplayFiltersExtraOptions, { label: string }> = {
|
||||||
sub_issue: { label: "Sub Issues" },
|
sub_issue: { label: "Sub Issues" },
|
||||||
show_empty_groups: { label: "Show Empty Groups" },
|
show_empty_groups: { label: "Show Empty Groups" },
|
||||||
};
|
};
|
||||||
@ -111,8 +112,9 @@ const ALL_FILTER_PERMISSIONS: TFilterPermissions["all"] = {
|
|||||||
layouts: [EViewLayouts.SPREADSHEET],
|
layouts: [EViewLayouts.SPREADSHEET],
|
||||||
[EViewLayouts.SPREADSHEET]: {
|
[EViewLayouts.SPREADSHEET]: {
|
||||||
filters: ["project", "priority", "state_group", "assignees", "created_by", "labels", "start_date", "target_date"],
|
filters: ["project", "priority", "state_group", "assignees", "created_by", "labels", "start_date", "target_date"],
|
||||||
display_filters: ["type"],
|
// display_filters: ["type"],
|
||||||
// extra_options: [],
|
// extra_options: [],
|
||||||
|
display_filters: ["group_by", "sub_group_by", "order_by", "type"],
|
||||||
extra_options: ["sub_issue", "show_empty_groups"],
|
extra_options: ["sub_issue", "show_empty_groups"],
|
||||||
display_properties: true,
|
display_properties: true,
|
||||||
},
|
},
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
GROUP_BY_PROPERTY,
|
GROUP_BY_PROPERTY,
|
||||||
ORDER_BY_PROPERTY,
|
ORDER_BY_PROPERTY,
|
||||||
TYPE_PROPERTY,
|
TYPE_PROPERTY,
|
||||||
|
EViewLayouts,
|
||||||
} from "constants/view/filters";
|
} from "constants/view/filters";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
import { renderEmoji } from "helpers/emoji.helper";
|
||||||
@ -346,12 +347,19 @@ export const useViewFilter = (workspaceSlug: string, projectId: string | undefin
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const displayFilterIdsWithKey = (displayFilterKey: keyof TViewDisplayFilters): string[] | undefined => {
|
const displayFilterIdsWithKey = (
|
||||||
|
displayFilterKey: keyof TViewDisplayFilters,
|
||||||
|
layout?: EViewLayouts
|
||||||
|
): string[] | undefined => {
|
||||||
if (!displayFilterKey) return undefined;
|
if (!displayFilterKey) return undefined;
|
||||||
|
|
||||||
switch (displayFilterKey) {
|
switch (displayFilterKey) {
|
||||||
case "group_by":
|
case "group_by":
|
||||||
return Object.keys(GROUP_BY_PROPERTY) || undefined;
|
return (
|
||||||
|
Object.keys(GROUP_BY_PROPERTY).filter((property) =>
|
||||||
|
layout === EViewLayouts.KANBAN ? (property !== "null" ? false : true) : true
|
||||||
|
) || undefined
|
||||||
|
);
|
||||||
case "sub_group_by":
|
case "sub_group_by":
|
||||||
return Object.keys(GROUP_BY_PROPERTY) || undefined;
|
return Object.keys(GROUP_BY_PROPERTY) || undefined;
|
||||||
case "order_by":
|
case "order_by":
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { ReactElement, useMemo } from "react";
|
import { ReactElement, useMemo } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { CheckCircle } from "lucide-react";
|
||||||
// layouts
|
// layouts
|
||||||
import { AppLayout } from "layouts/app-layout";
|
import { AppLayout } from "layouts/app-layout";
|
||||||
// components
|
// components
|
||||||
import { GlobalViewRoot } from "components/view";
|
import { GlobalViewRoot, ViewHeader } from "components/view";
|
||||||
// types
|
// types
|
||||||
import { NextPageWithLayout } from "lib/types";
|
import { NextPageWithLayout } from "lib/types";
|
||||||
// constants
|
// constants
|
||||||
@ -31,8 +32,20 @@ const WorkspacePrivateViewPage: 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">
|
||||||
|
{/* header */}
|
||||||
|
<div className="px-5 pt-4 pb-2 border-b border-custom-border-200">
|
||||||
|
<ViewHeader
|
||||||
|
projectId={undefined}
|
||||||
|
viewType={VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS}
|
||||||
|
titleIcon={<CheckCircle size={12} />}
|
||||||
|
title="All Issues"
|
||||||
|
workspaceViewTabOptions={workspaceViewTabOptions}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* content */}
|
||||||
<GlobalViewRoot
|
<GlobalViewRoot
|
||||||
workspaceSlug={workspaceSlug.toString()}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
projectId={undefined}
|
projectId={undefined}
|
||||||
@ -40,9 +53,12 @@ const WorkspacePrivateViewPage: NextPageWithLayout = () => {
|
|||||||
viewType={VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS}
|
viewType={VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS}
|
||||||
viewPageType={EViewPageType.ALL}
|
viewPageType={EViewPageType.ALL}
|
||||||
baseRoute={`/${workspaceSlug?.toString()}/views/private`}
|
baseRoute={`/${workspaceSlug?.toString()}/views/private`}
|
||||||
workspaceViewTabOptions={workspaceViewTabOptions}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full h-full overflow-hidden relative flex justify-center items-center text-sm text-custom-text-300">
|
||||||
|
Issues render placeholder
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { ReactElement, useMemo } from "react";
|
import { ReactElement, useMemo } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { CheckCircle } from "lucide-react";
|
||||||
// layouts
|
// layouts
|
||||||
import { AppLayout } from "layouts/app-layout";
|
import { AppLayout } from "layouts/app-layout";
|
||||||
// components
|
// components
|
||||||
import { GlobalViewRoot } from "components/view";
|
import { GlobalViewRoot, ViewHeader } from "components/view";
|
||||||
// types
|
// types
|
||||||
import { NextPageWithLayout } from "lib/types";
|
import { NextPageWithLayout } from "lib/types";
|
||||||
// constants
|
// constants
|
||||||
@ -33,6 +34,18 @@ const WorkspacePublicViewPage: NextPageWithLayout = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="w-full h-full overflow-hidden bg-custom-background-100 relative flex flex-col">
|
<div className="w-full h-full overflow-hidden bg-custom-background-100 relative flex flex-col">
|
||||||
<div className="flex-shrink-0 w-full">
|
<div className="flex-shrink-0 w-full">
|
||||||
|
{/* header */}
|
||||||
|
<div className="px-5 pt-4 pb-2 border-b border-custom-border-200">
|
||||||
|
<ViewHeader
|
||||||
|
projectId={undefined}
|
||||||
|
viewType={VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS}
|
||||||
|
titleIcon={<CheckCircle size={12} />}
|
||||||
|
title="All Issues"
|
||||||
|
workspaceViewTabOptions={workspaceViewTabOptions}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* content */}
|
||||||
<GlobalViewRoot
|
<GlobalViewRoot
|
||||||
workspaceSlug={workspaceSlug.toString()}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
projectId={undefined}
|
projectId={undefined}
|
||||||
@ -40,10 +53,12 @@ const WorkspacePublicViewPage: NextPageWithLayout = () => {
|
|||||||
viewType={VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS}
|
viewType={VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS}
|
||||||
viewPageType={EViewPageType.ALL}
|
viewPageType={EViewPageType.ALL}
|
||||||
baseRoute={`/${workspaceSlug?.toString()}/views/public`}
|
baseRoute={`/${workspaceSlug?.toString()}/views/public`}
|
||||||
workspaceViewTabOptions={workspaceViewTabOptions}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full h-full overflow-hidden">Issues render</div>
|
|
||||||
|
<div className="w-full h-full overflow-hidden relative flex justify-center items-center text-sm text-custom-text-300">
|
||||||
|
Issues render placeholder
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -14,19 +14,19 @@ import { EViewPageType, viewPageDefaultLayoutsByPageType } from "constants/view"
|
|||||||
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: get(defaultValues, "project", get(filters, "project", [])),
|
project: defaultValues?.project || filters?.project || [],
|
||||||
module: get(defaultValues, "module", get(filters, "module", [])),
|
module: defaultValues?.module || filters?.module || [],
|
||||||
cycle: get(defaultValues, "cycle", get(filters, "cycle", [])),
|
cycle: defaultValues?.cycle || filters?.cycle || [],
|
||||||
priority: get(defaultValues, "priority", get(filters, "priority", [])),
|
priority: defaultValues?.priority || filters?.priority || [],
|
||||||
state: get(defaultValues, "state", get(filters, "state", [])),
|
state: defaultValues?.state || filters?.state || [],
|
||||||
state_group: get(defaultValues, "state_group", get(filters, "state_group", [])),
|
state_group: defaultValues?.state_group || filters?.state_group || [],
|
||||||
assignees: get(defaultValues, "assignees", get(filters, "assignees", [])),
|
assignees: defaultValues?.assignees || filters?.assignees || [],
|
||||||
mentions: get(defaultValues, "mentions", get(filters, "mentions", [])),
|
mentions: defaultValues?.mentions || filters?.mentions || [],
|
||||||
subscriber: get(defaultValues, "subscriber", get(filters, "subscriber", [])),
|
subscriber: defaultValues?.subscriber || filters?.subscriber || [],
|
||||||
created_by: get(defaultValues, "created_by", get(filters, "created_by", [])),
|
created_by: defaultValues?.created_by || filters?.created_by || [],
|
||||||
labels: get(defaultValues, "labels", get(filters, "labels", [])),
|
labels: defaultValues?.labels || filters?.labels || [],
|
||||||
start_date: get(defaultValues, "start_date", get(filters, "start_date", [])),
|
start_date: defaultValues?.start_date || filters?.start_date || [],
|
||||||
target_date: get(defaultValues, "target_date", get(filters, "target_date", [])),
|
target_date: defaultValues?.target_date || filters?.target_date || [],
|
||||||
});
|
});
|
||||||
|
|
||||||
// computed display filters
|
// computed display filters
|
||||||
|
@ -21,7 +21,7 @@ import {
|
|||||||
// helpers
|
// helpers
|
||||||
import { FiltersHelper } from "./helpers/filters_helpers";
|
import { FiltersHelper } from "./helpers/filters_helpers";
|
||||||
// constants
|
// constants
|
||||||
import { EViewPageType, viewDefaultFilterParametersByViewTypeAndLayout } from "constants/view";
|
import { EViewLayouts, EViewPageType, viewDefaultFilterParametersByViewTypeAndLayout } from "constants/view";
|
||||||
|
|
||||||
type TLoader = "updating" | undefined;
|
type TLoader = "updating" | undefined;
|
||||||
|
|
||||||
@ -250,11 +250,11 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
const sub_issue = appliedFilters?.display_filters?.sub_issue;
|
const sub_issue = appliedFilters?.display_filters?.sub_issue;
|
||||||
|
|
||||||
if (group_by === undefined && display_filters.sub_group_by) display_filters.sub_group_by = undefined;
|
if (group_by === undefined && display_filters.sub_group_by) display_filters.sub_group_by = undefined;
|
||||||
if (layout === "kanban") {
|
if (layout === EViewLayouts.KANBAN) {
|
||||||
if (sub_group_by === group_by) display_filters.group_by = undefined;
|
if (sub_group_by === group_by) display_filters.group_by = undefined;
|
||||||
if (group_by === null) display_filters.group_by = "state";
|
if (group_by === null) display_filters.group_by = "state";
|
||||||
}
|
}
|
||||||
if (layout === "spreadsheet" && sub_issue === true) display_filters.sub_issue = false;
|
if (layout === EViewLayouts.SPREADSHEET && sub_issue === true) display_filters.sub_issue = false;
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
Object.keys(display_filters).forEach((key) => {
|
Object.keys(display_filters).forEach((key) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user