Merge branch 'develop' of gurusainath:makeplane/plane into feat/mobx-global-views

This commit is contained in:
gurusainath 2024-01-30 14:39:33 +05:30
commit 5674acd985
76 changed files with 614 additions and 327 deletions

View File

@ -304,6 +304,7 @@ class IssueRelationSerializer(BaseSerializer):
sequence_id = serializers.IntegerField(
source="related_issue.sequence_id", read_only=True
)
name = serializers.CharField(source="related_issue.name", read_only=True)
relation_type = serializers.CharField(read_only=True)
class Meta:
@ -313,6 +314,7 @@ class IssueRelationSerializer(BaseSerializer):
"project_id",
"sequence_id",
"relation_type",
"name",
]
read_only_fields = [
"workspace",
@ -328,6 +330,7 @@ class RelatedIssueSerializer(BaseSerializer):
sequence_id = serializers.IntegerField(
source="issue.sequence_id", read_only=True
)
name = serializers.CharField(source="issue.name", read_only=True)
relation_type = serializers.CharField(read_only=True)
class Meta:
@ -337,6 +340,7 @@ class RelatedIssueSerializer(BaseSerializer):
"project_id",
"sequence_id",
"relation_type",
"name",
]
read_only_fields = [
"workspace",

View File

@ -64,8 +64,7 @@ export type TIssueParams =
| "order_by"
| "type"
| "sub_issue"
| "show_empty_groups"
| "start_target_date";
| "show_empty_groups";
export type TCalendarLayouts = "month" | "week";
@ -93,7 +92,6 @@ export interface IIssueDisplayFilterOptions {
layout?: TIssueLayouts;
order_by?: TIssueOrderByOptions;
show_empty_groups?: boolean;
start_target_date?: boolean;
sub_issue?: boolean;
type?: TIssueTypeFilters;
}

View File

@ -1,13 +1,20 @@
import React from "react";
import { Tooltip } from "../tooltip";
import { cn } from "../../helpers";
type Props = {
data: any;
noTooltip?: boolean;
inPercentage?: boolean;
size?: "sm" | "md" | "lg";
};
export const LinearProgressIndicator: React.FC<Props> = ({ data, noTooltip = false, inPercentage = false }) => {
export const LinearProgressIndicator: React.FC<Props> = ({
data,
noTooltip = false,
inPercentage = false,
size = "sm",
}) => {
const total = data.reduce((acc: any, cur: any) => acc + cur.value, 0);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let progress = 0;
@ -23,18 +30,24 @@ export const LinearProgressIndicator: React.FC<Props> = ({ data, noTooltip = fal
if (noTooltip) return <div style={style} />;
else
return (
<Tooltip key={item.id} tooltipContent={`${item.name} ${Math.round(item.value)}%`}>
<div style={style} className="first:rounded-l-full last:rounded-r-full" />
<Tooltip key={item.id} tooltipContent={`${item.name} ${Math.round(item.value)}${inPercentage ? "%" : ""}`}>
<div style={style} className="first:rounded-l-sm last:rounded-r-sm" />
</Tooltip>
);
});
return (
<div className="flex h-1 w-full items-center justify-between gap-1">
<div
className={cn("flex w-full items-center justify-between gap-[1px] rounded-sm", {
"h-2": size === "sm",
"h-3": size === "md",
"h-3.5": size === "lg",
})}
>
{total === 0 ? (
<div className="flex h-full w-full gap-1 bg-neutral-500">{bars}</div>
<div className="flex h-full w-full gap-[1.5px] p-[2px] bg-custom-background-90 rounded-sm">{bars}</div>
) : (
<div className="flex h-full w-full gap-1">{bars}</div>
<div className="flex h-full w-full gap-[1.5px] p-[2px] bg-custom-background-90 rounded-sm">{bars}</div>
)}
</div>
);

View File

@ -40,9 +40,9 @@ export const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
}`}`}
target={activity.issue === null ? "_self" : "_blank"}
rel={activity.issue === null ? "" : "noopener noreferrer"}
className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline whitespace-nowrap"
className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline"
>
{`${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}`}{" "}
<span className="whitespace-nowrap">{`${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}`}</span>{" "}
<span className="font-normal">{activity.issue_detail?.name}</span>
</a>
) : (
@ -267,7 +267,7 @@ const activityDetails: {
<span className="flex-shrink truncate font-medium text-custom-text-100">{activity.new_value}</span>
</span>
{showIssue && (
<span>
<span className="">
{" "}
to <IssueLink activity={activity} />
</span>

View File

@ -2,6 +2,7 @@ import { MouseEvent } from "react";
import Link from "next/link";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { useTheme } from "next-themes";
// hooks
import { useCycle, useIssues, useProject, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
@ -43,6 +44,7 @@ interface IActiveCycleDetails {
export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props) => {
// props
const { workspaceSlug, projectId } = props;
const { resolvedTheme } = useTheme();
// store hooks
const { currentUser } = useUser();
const {
@ -76,7 +78,9 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
);
const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS["active"];
const emptyStateImage = getEmptyStateImagePath("cycle", "active", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("cycle", "active", isLightMode);
if (!activeCycle && isLoading)
return (
@ -161,7 +165,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
<h3 className="break-words text-lg font-semibold">{truncateText(activeCycle.name, 70)}</h3>
</Tooltip>
</span>
<span className="flex items-center gap-1 capitalize">
<span className="flex items-center gap-1">
<span className="flex gap-1 whitespace-nowrap rounded-sm text-sm px-3 py-0.5 bg-amber-500/10 text-amber-500">
{`${daysLeft} ${daysLeft > 1 ? "days" : "day"} left`}
</span>
@ -251,7 +255,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
<div className="flex h-full w-full flex-col p-4 text-custom-text-200">
<div className="flex w-full items-center gap-2 py-1">
<span>Progress</span>
<LinearProgressIndicator data={progressIndicatorData} inPercentage />
<LinearProgressIndicator size="md" data={progressIndicatorData} inPercentage />
</div>
<div className="mt-2 flex flex-col items-center gap-1">
{Object.keys(groupedIssues).map((group, index) => (

View File

@ -1,5 +1,6 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// hooks
import { useUser } from "hooks/store";
// components
@ -18,11 +19,15 @@ export interface ICyclesBoard {
export const CyclesBoard: FC<ICyclesBoard> = observer((props) => {
const { cycleIds, filter, workspaceSlug, projectId, peekCycle } = props;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const { currentUser } = useUser();
const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS[filter as keyof typeof CYCLE_EMPTY_STATE_DETAILS];
const emptyStateImage = getEmptyStateImagePath("cycle", filter, currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("cycle", filter, isLightMode);
return (
<>

View File

@ -1,5 +1,6 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// hooks
import { useUser } from "hooks/store";
// components
@ -19,11 +20,15 @@ export interface ICyclesList {
export const CyclesList: FC<ICyclesList> = observer((props) => {
const { cycleIds, filter, workspaceSlug, projectId } = props;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const { currentUser } = useUser();
const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS[filter as keyof typeof CYCLE_EMPTY_STATE_DETAILS];
const emptyStateImage = getEmptyStateImagePath("cycle", filter, currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("cycle", filter, isLightMode);
return (
<>

View File

@ -1,3 +1,4 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
// components
import { DateDropdown, ProjectDropdown } from "components/dropdowns";
@ -11,19 +12,28 @@ import { ICycle } from "@plane/types";
type Props = {
handleFormSubmit: (values: Partial<ICycle>) => Promise<void>;
handleClose: () => void;
status: boolean;
projectId: string;
setActiveProject: (projectId: string) => void;
data?: ICycle | null;
};
const defaultValues: Partial<ICycle> = {
name: "",
description: "",
start_date: null,
end_date: null,
};
export const CycleForm: React.FC<Props> = (props) => {
const { handleFormSubmit, handleClose, projectId, setActiveProject, data } = props;
const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data } = props;
// form data
const {
formState: { errors, isSubmitting },
handleSubmit,
control,
watch,
reset,
} = useForm<ICycle>({
defaultValues: {
project: projectId,
@ -34,6 +44,13 @@ export const CycleForm: React.FC<Props> = (props) => {
},
});
useEffect(() => {
reset({
...defaultValues,
...data,
});
}, [data, reset]);
const startDate = watch("start_date");
const endDate = watch("end_date");

View File

@ -1,9 +1,9 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
// services
import { CycleService } from "services/cycle.service";
// hooks
import { useApplication, useCycle } from "hooks/store";
import { useApplication, useCycle, useProject } from "hooks/store";
import useToast from "hooks/use-toast";
import useLocalStorage from "hooks/use-local-storage";
// components
@ -25,11 +25,12 @@ const cycleService = new CycleService();
export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
const { isOpen, handleClose, data, workspaceSlug, projectId } = props;
// states
const [activeProject, setActiveProject] = useState<string>(projectId);
const [activeProject, setActiveProject] = useState<string | null>(null);
// store hooks
const {
eventTracker: { postHogEventTracker },
} = useApplication();
const { workspaceProjectIds } = useProject();
const { createCycle, updateCycleDetails } = useCycle();
// toast alert
const { setToastAlert } = useToast();
@ -134,6 +135,27 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
});
};
useEffect(() => {
// if modal is closed, reset active project to null
// and return to avoid activeProject being set to some other project
if (!isOpen) {
setActiveProject(null);
return;
}
// if data is present, set active project to the project of the
// issue. This has more priority than the project in the url.
if (data && data.project) {
setActiveProject(data.project);
return;
}
// if data is not present, set active project to the project
// in the url. This has the least priority.
if (workspaceProjectIds && workspaceProjectIds.length > 0 && !activeProject)
setActiveProject(projectId ?? workspaceProjectIds?.[0] ?? null);
}, [activeProject, data, projectId, workspaceProjectIds, isOpen]);
return (
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
@ -164,7 +186,8 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
<CycleForm
handleFormSubmit={handleFormSubmit}
handleClose={handleClose}
projectId={activeProject}
status={data ? true : false}
projectId={activeProject ?? ""}
setActiveProject={setActiveProject}
data={data}
/>

View File

@ -50,11 +50,11 @@ export const DashboardWidgets = observer(() => {
// if the widget is full width, return it in a 2 column grid
if (widget.fullWidth)
return (
<div className="lg:col-span-2">
<div key={key} className="lg:col-span-2">
<WidgetComponent dashboardId={homeDashboardId} workspaceSlug={workspaceSlug} />
</div>
);
else return <WidgetComponent dashboardId={homeDashboardId} workspaceSlug={workspaceSlug} />;
else return <WidgetComponent key={key} dashboardId={homeDashboardId} workspaceSlug={workspaceSlug} />;
})}
</div>
);

View File

@ -99,7 +99,7 @@ export const CreatedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
</div>
<Tab.Panels as="div" className="h-full">
{ISSUES_TABS_LIST.map((tab) => (
<Tab.Panel as="div" className="h-full flex flex-col">
<Tab.Panel key={tab.key} as="div" className="h-full flex flex-col">
<WidgetIssuesList
issues={widgetStats.issues}
tab={tab.key}

View File

@ -86,7 +86,7 @@ export const OverviewStatsWidget: React.FC<WidgetProps> = observer((props) => {
)}
>
<h5 className="font-semibold text-xl">{stat.count}</h5>
<p className="text-custom-text-300">{stat.title}</p>
<p className="text-custom-text-300 text-sm xl:text-base">{stat.title}</p>
</Link>
</div>
);

View File

@ -57,7 +57,7 @@ const ProjectListItem: React.FC<ProjectListItemProps> = observer((props) => {
<div className="mt-2">
<AvatarGroup>
{projectDetails.members?.map((member) => (
<Avatar src={member.member__avatar} name={member.member__display_name} />
<Avatar key={member.member_id} src={member.member__avatar} name={member.member__display_name} />
))}
</AvatarGroup>
</div>

View File

@ -1,23 +0,0 @@
import React, { useState, useEffect } from "react";
// react beautiful dnd
import { Droppable, DroppableProps } from "@hello-pangea/dnd";
const StrictModeDroppable = ({ children, ...props }: DroppableProps) => {
const [enabled, setEnabled] = useState(false);
useEffect(() => {
const animation = requestAnimationFrame(() => setEnabled(true));
return () => {
cancelAnimationFrame(animation);
setEnabled(false);
};
}, []);
if (!enabled) return null;
return <Droppable {...props}>{children}</Droppable>;
};
export default StrictModeDroppable;

View File

@ -30,6 +30,7 @@ type ButtonProps = {
hideIcon: boolean;
hideText?: boolean;
dropdownArrow: boolean;
isActive?: boolean;
dropdownArrowClassName: string;
placeholder: string;
tooltip: boolean;
@ -51,6 +52,7 @@ const BorderButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
placeholder,
tooltip,
} = props;
@ -60,6 +62,7 @@ const BorderButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
{ "bg-custom-background-80": isActive },
className
)}
>
@ -111,6 +114,7 @@ const TransparentButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
placeholder,
tooltip,
} = props;
@ -120,6 +124,7 @@ const TransparentButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
{ "bg-custom-background-80": isActive },
className
)}
>
@ -268,6 +273,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "border-without-text" ? (
@ -279,6 +285,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
hideIcon={hideIcon}
hideText
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "background-with-text" ? (
@ -310,6 +317,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "transparent-without-text" ? (
@ -321,6 +329,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
hideIcon={hideIcon}
hideText
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : null}

View File

@ -2,7 +2,7 @@ import React, { useRef, useState } from "react";
import { Combobox } from "@headlessui/react";
import DatePicker from "react-datepicker";
import { usePopper } from "react-popper";
import { CalendarDays, X } from "lucide-react";
import { Calendar, CalendarDays, X } from "lucide-react";
// hooks
import { useDropdownKeyDown } from "hooks/use-dropdown-key-down";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
@ -23,6 +23,7 @@ type Props = TDropdownProps & {
onChange: (val: Date | null) => void;
value: Date | string | null;
closeOnSelect?: boolean;
showPlaceholderIcon?: boolean;
};
type ButtonProps = {
@ -33,9 +34,11 @@ type ButtonProps = {
isClearable: boolean;
hideIcon?: boolean;
hideText?: boolean;
isActive?: boolean;
onClear: () => void;
placeholder: string;
tooltip: boolean;
showPlaceholderIcon?: boolean;
};
const BorderButton = (props: ButtonProps) => {
@ -47,6 +50,7 @@ const BorderButton = (props: ButtonProps) => {
isClearable,
hideIcon = false,
hideText = false,
isActive = false,
onClear,
placeholder,
tooltip,
@ -61,6 +65,7 @@ const BorderButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
{ "bg-custom-background-80": isActive },
className
)}
>
@ -131,9 +136,11 @@ const TransparentButton = (props: ButtonProps) => {
isClearable,
hideIcon = false,
hideText = false,
isActive = false,
onClear,
placeholder,
tooltip,
showPlaceholderIcon = false,
} = props;
return (
@ -145,11 +152,16 @@ const TransparentButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
{ "bg-custom-background-80": isActive },
className
)}
>
{!hideIcon && icon}
{!hideText && <span className="flex-grow truncate">{date ? renderFormattedDate(date) : placeholder}</span>}
{showPlaceholderIcon && !date && (
<Calendar className="h-2.5 w-2.5 flex-shrink-0 hidden group-hover:inline text-custom-text-400" />
)}
{isClearable && (
<X
className={cn("h-2 w-2 flex-shrink-0", clearIconClassName)}
@ -183,6 +195,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
placement,
tabIndex,
tooltip = false,
showPlaceholderIcon = false,
value,
} = props;
const [isOpen, setIsOpen] = useState(false);
@ -246,6 +259,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
placeholder={placeholder}
isClearable={isClearable && isDateSelected}
onClear={() => onChange(null)}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "border-without-text" ? (
@ -258,6 +272,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
placeholder={placeholder}
isClearable={isClearable && isDateSelected}
onClear={() => onChange(null)}
isActive={isOpen}
tooltip={tooltip}
hideText
/>
@ -296,7 +311,9 @@ export const DateDropdown: React.FC<Props> = (props) => {
placeholder={placeholder}
isClearable={isClearable && isDateSelected}
onClear={() => onChange(null)}
isActive={isOpen}
tooltip={tooltip}
showPlaceholderIcon={showPlaceholderIcon}
/>
) : buttonVariant === "transparent-without-text" ? (
<TransparentButton
@ -308,8 +325,10 @@ export const DateDropdown: React.FC<Props> = (props) => {
placeholder={placeholder}
isClearable={isClearable && isDateSelected}
onClear={() => onChange(null)}
isActive={isOpen}
tooltip={tooltip}
hideText
showPlaceholderIcon={showPlaceholderIcon}
/>
) : null}
</button>

View File

@ -31,6 +31,7 @@ type ButtonProps = {
dropdownArrowClassName: string;
hideIcon?: boolean;
hideText?: boolean;
isActive?: boolean;
placeholder: string;
tooltip: boolean;
};
@ -51,6 +52,7 @@ const BorderButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
placeholder,
tooltip,
} = props;
@ -64,6 +66,7 @@ const BorderButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
{ "bg-custom-background-80": isActive },
className
)}
>
@ -123,6 +126,7 @@ const TransparentButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
placeholder,
tooltip,
} = props;
@ -136,6 +140,7 @@ const TransparentButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
{ "bg-custom-background-80": isActive },
className
)}
>
@ -276,6 +281,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "border-without-text" ? (
@ -287,6 +293,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
hideIcon={hideIcon}
hideText
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "background-with-text" ? (
@ -318,6 +325,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "transparent-without-text" ? (
@ -329,6 +337,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
hideIcon={hideIcon}
hideText
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : null}

View File

@ -14,6 +14,7 @@ type ButtonProps = {
placeholder: string;
hideIcon?: boolean;
hideText?: boolean;
isActive?: boolean;
tooltip: boolean;
userIds: string | string[] | null;
};
@ -50,6 +51,7 @@ export const BorderButton = observer((props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
placeholder,
userIds,
tooltip,
@ -57,7 +59,7 @@ export const BorderButton = observer((props: ButtonProps) => {
// store hooks
const { getUserDetails } = useMember();
const isMultiple = Array.isArray(userIds);
const isArray = Array.isArray(userIds);
return (
<Tooltip
@ -68,13 +70,18 @@ export const BorderButton = observer((props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
{ "bg-custom-background-80": isActive },
className
)}
>
{!hideIcon && <ButtonAvatars tooltip={tooltip} userIds={userIds} />}
{!hideText && (
<span className="flex-grow truncate">
{userIds ? (isMultiple ? placeholder : getUserDetails(userIds)?.display_name) : placeholder}
<span className="flex-grow truncate text-sm leading-5">
{isArray && userIds.length > 0
? userIds.length === 1
? getUserDetails(userIds[0])?.display_name
: ""
: placeholder}
</span>
)}
{dropdownArrow && (
@ -99,7 +106,7 @@ export const BackgroundButton = observer((props: ButtonProps) => {
// store hooks
const { getUserDetails } = useMember();
const isMultiple = Array.isArray(userIds);
const isArray = Array.isArray(userIds);
return (
<Tooltip
@ -115,8 +122,12 @@ export const BackgroundButton = observer((props: ButtonProps) => {
>
{!hideIcon && <ButtonAvatars tooltip={tooltip} userIds={userIds} />}
{!hideText && (
<span className="flex-grow truncate">
{userIds ? (isMultiple ? placeholder : getUserDetails(userIds)?.display_name) : placeholder}
<span className="flex-grow truncate text-sm leading-5">
{isArray && userIds.length > 0
? userIds.length === 1
? getUserDetails(userIds[0])?.display_name
: ""
: placeholder}
</span>
)}
{dropdownArrow && (
@ -134,6 +145,7 @@ export const TransparentButton = observer((props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
placeholder,
userIds,
tooltip,
@ -141,7 +153,7 @@ export const TransparentButton = observer((props: ButtonProps) => {
// store hooks
const { getUserDetails } = useMember();
const isMultiple = Array.isArray(userIds);
const isArray = Array.isArray(userIds);
return (
<Tooltip
@ -152,13 +164,18 @@ export const TransparentButton = observer((props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
{ "bg-custom-background-80": isActive },
className
)}
>
{!hideIcon && <ButtonAvatars tooltip={tooltip} userIds={userIds} />}
{!hideText && (
<span className="flex-grow truncate">
{userIds ? (isMultiple ? placeholder : getUserDetails(userIds)?.display_name) : placeholder}
<span className="flex-grow truncate text-sm leading-5">
{isArray && userIds.length > 0
? userIds.length === 1
? getUserDetails(userIds[0])?.display_name
: ""
: placeholder}
</span>
)}
{dropdownArrow && (

View File

@ -147,6 +147,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "border-without-text" ? (
@ -157,6 +158,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
hideText
/>
@ -189,6 +191,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "transparent-without-text" ? (
@ -199,6 +202,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
hideText
/>

View File

@ -38,6 +38,7 @@ type ButtonProps = {
dropdownArrowClassName: string;
hideIcon?: boolean;
hideText?: boolean;
isActive?: boolean;
module: IModule | null;
placeholder: string;
tooltip: boolean;
@ -50,6 +51,7 @@ const BorderButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
module,
placeholder,
tooltip,
@ -60,6 +62,7 @@ const BorderButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
{ "bg-custom-background-80": isActive },
className
)}
>
@ -110,6 +113,7 @@ const TransparentButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
module,
placeholder,
tooltip,
@ -120,6 +124,7 @@ const TransparentButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
{ "bg-custom-background-80": isActive },
className
)}
>
@ -267,6 +272,7 @@ export const ModuleDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "border-without-text" ? (
@ -278,6 +284,7 @@ export const ModuleDropdown: React.FC<Props> = observer((props) => {
hideIcon={hideIcon}
hideText
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "background-with-text" ? (
@ -309,6 +316,7 @@ export const ModuleDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "transparent-without-text" ? (
@ -320,6 +328,7 @@ export const ModuleDropdown: React.FC<Props> = observer((props) => {
hideIcon={hideIcon}
hideText
placeholder={placeholder}
isActive={isOpen}
tooltip={tooltip}
/>
) : null}

View File

@ -31,6 +31,7 @@ type ButtonProps = {
dropdownArrowClassName: string;
hideIcon?: boolean;
hideText?: boolean;
isActive?: boolean;
highlightUrgent: boolean;
priority: TIssuePriorities;
tooltip: boolean;
@ -181,6 +182,7 @@ const TransparentButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
highlightUrgent,
priority,
tooltip,
@ -207,6 +209,7 @@ const TransparentButton = (props: ButtonProps) => {
"px-0.5": hideText,
// highlight the whole button if text is hidden and priority is urgent
"bg-red-500 border-red-500": priority === "urgent" && hideText && highlightUrgent,
"bg-custom-background-80": isActive,
},
className
)}
@ -312,7 +315,13 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
as="div"
ref={dropdownRef}
tabIndex={tabIndex}
className={cn("h-full", className)}
className={cn(
"h-full",
{
"bg-custom-background-80": isOpen,
},
className
)}
value={value}
onChange={onChange}
disabled={disabled}
@ -402,6 +411,7 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "transparent-without-text" ? (
@ -414,6 +424,7 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
hideText
/>

View File

@ -30,6 +30,7 @@ type ButtonProps = {
dropdownArrowClassName: string;
hideIcon?: boolean;
hideText?: boolean;
isActive?: boolean;
state: IState | undefined;
tooltip: boolean;
};
@ -41,6 +42,7 @@ const BorderButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
state,
tooltip,
} = props;
@ -50,6 +52,9 @@ const BorderButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
{
"bg-custom-background-80": isActive,
},
className
)}
>
@ -111,6 +116,7 @@ const TransparentButton = (props: ButtonProps) => {
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
state,
tooltip,
} = props;
@ -120,6 +126,9 @@ const TransparentButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
{
"bg-custom-background-80": isActive,
},
className
)}
>
@ -251,6 +260,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "border-without-text" ? (
@ -260,6 +270,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
hideText
/>
@ -289,6 +300,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "transparent-without-text" ? (
@ -298,6 +310,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
hideText
/>

View File

@ -2,7 +2,7 @@ import { FC } from "react";
// hooks
import { useChart } from "../hooks";
// helpers
import { ChartDraggable } from "../helpers/draggable";
import { ChartAddBlock, ChartDraggable } from "components/gantt-chart";
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
// types
import { IBlockUpdateData, IGanttBlock } from "../types";
@ -15,6 +15,7 @@ export type GanttChartBlocksProps = {
enableBlockLeftResize: boolean;
enableBlockRightResize: boolean;
enableBlockMove: boolean;
showAllBlocks: boolean;
};
export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
@ -26,6 +27,7 @@ export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
enableBlockLeftResize,
enableBlockRightResize,
enableBlockMove,
showAllBlocks,
} = props;
const { activeBlock, dispatch } = useChart();
@ -45,6 +47,8 @@ export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
totalBlockShifts: number,
dragDirection: "left" | "right" | "move"
) => {
if (!block.start_date || !block.target_date) return;
const originalStartDate = new Date(block.start_date);
const updatedStartDate = new Date(originalStartDate);
@ -75,27 +79,31 @@ export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
>
{blocks &&
blocks.length > 0 &&
blocks.map(
(block) =>
block.start_date &&
block.target_date && (
<div
key={`block-${block.id}`}
className={`h-11 ${activeBlock?.id === block.id ? "bg-custom-background-80" : ""}`}
onMouseEnter={() => updateActiveBlock(block)}
onMouseLeave={() => updateActiveBlock(null)}
>
<ChartDraggable
block={block}
blockToRender={blockToRender}
handleBlock={(...args) => handleChartBlockPosition(block, ...args)}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove}
/>
</div>
)
)}
blocks.map((block) => {
// hide the block if it doesn't have start and target dates and showAllBlocks is false
if (!showAllBlocks && !(block.start_date && block.target_date)) return;
const isBlockVisibleOnChart = block.start_date && block.target_date;
return (
<div
key={`block-${block.id}`}
className={`h-11 ${activeBlock?.id === block.id ? "bg-custom-background-80" : ""}`}
onMouseEnter={() => updateActiveBlock(block)}
onMouseLeave={() => updateActiveBlock(null)}
>
{!isBlockVisibleOnChart && <ChartAddBlock block={block} blockUpdateHandler={blockUpdateHandler} />}
<ChartDraggable
block={block}
blockToRender={blockToRender}
handleBlock={(...args) => handleChartBlockPosition(block, ...args)}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove}
/>
</div>
);
})}
</div>
);
};

View File

@ -46,22 +46,25 @@ type ChartViewRootProps = {
enableBlockMove: boolean;
enableReorder: boolean;
bottomSpacing: boolean;
showAllBlocks: boolean;
};
export const ChartViewRoot: FC<ChartViewRootProps> = ({
border,
title,
blocks = null,
loaderTitle,
blockUpdateHandler,
sidebarToRender,
blockToRender,
enableBlockLeftResize,
enableBlockRightResize,
enableBlockMove,
enableReorder,
bottomSpacing,
}) => {
export const ChartViewRoot: FC<ChartViewRootProps> = (props) => {
const {
border,
title,
blocks = null,
loaderTitle,
blockUpdateHandler,
sidebarToRender,
blockToRender,
enableBlockLeftResize,
enableBlockRightResize,
enableBlockMove,
enableReorder,
bottomSpacing,
showAllBlocks,
} = props;
// states
const [itemsContainerWidth, setItemsContainerWidth] = useState<number>(0);
const [fullScreenMode, setFullScreenMode] = useState<boolean>(false);
@ -311,6 +314,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove}
showAllBlocks={showAllBlocks}
/>
)}
</div>

View File

@ -1,5 +1,4 @@
import { FC } from "react";
// hooks
import { useChart } from "../hooks";
// types

View File

@ -0,0 +1,91 @@
import { useEffect, useRef, useState } from "react";
import { addDays } from "date-fns";
import { Plus } from "lucide-react";
// hooks
import { useChart } from "../hooks";
// ui
import { Tooltip } from "@plane/ui";
// helpers
import { renderFormattedDate, renderFormattedPayloadDate } from "helpers/date-time.helper";
// types
import { IBlockUpdateData, IGanttBlock } from "../types";
type Props = {
block: IGanttBlock;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
};
export const ChartAddBlock: React.FC<Props> = (props) => {
const { block, blockUpdateHandler } = props;
// states
const [isButtonVisible, setIsButtonVisible] = useState(false);
const [buttonXPosition, setButtonXPosition] = useState(0);
const [buttonStartDate, setButtonStartDate] = useState<Date | null>(null);
// refs
const containerRef = useRef<HTMLDivElement>(null);
// chart hook
const { currentViewData } = useChart();
const handleButtonClick = () => {
if (!currentViewData) return;
const { startDate: chartStartDate, width } = currentViewData.data;
const columnNumber = buttonXPosition / width;
const startDate = addDays(chartStartDate, columnNumber);
const endDate = addDays(startDate, 1);
blockUpdateHandler(block.data, {
start_date: renderFormattedPayloadDate(startDate) ?? undefined,
target_date: renderFormattedPayloadDate(endDate) ?? undefined,
});
};
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const handleMouseMove = (e: MouseEvent) => {
if (!currentViewData) return;
setButtonXPosition(e.offsetX);
const { startDate: chartStartDate, width } = currentViewData.data;
const columnNumber = buttonXPosition / width;
const startDate = addDays(chartStartDate, columnNumber);
setButtonStartDate(startDate);
};
container.addEventListener("mousemove", handleMouseMove);
return () => {
container?.removeEventListener("mousemove", handleMouseMove);
};
}, [buttonXPosition, currentViewData]);
return (
<div
className="relative h-full w-full"
onMouseEnter={() => setIsButtonVisible(true)}
onMouseLeave={() => setIsButtonVisible(false)}
>
<div ref={containerRef} className="h-full w-full" />
{isButtonVisible && (
<Tooltip tooltipContent={buttonStartDate && renderFormattedDate(buttonStartDate)}>
<button
type="button"
className="absolute top-1/2 -translate-x-1/2 -translate-y-1/2 h-8 w-8 bg-custom-background-80 p-1.5 rounded border border-custom-border-300 grid place-items-center text-custom-text-200 hover:text-custom-text-100"
style={{
marginLeft: `${buttonXPosition}px`,
}}
onClick={handleButtonClick}
>
<Plus className="h-3.5 w-3.5" />
</button>
</Tooltip>
)}
</div>
);
};

View File

@ -3,14 +3,11 @@ import { TIssue } from "@plane/types";
import { IGanttBlock } from "components/gantt-chart";
export const renderIssueBlocksStructure = (blocks: TIssue[]): IGanttBlock[] =>
blocks && blocks.length > 0
? blocks
.filter((b) => new Date(b?.start_date ?? "") <= new Date(b?.target_date ?? ""))
.map((block) => ({
data: block,
id: block.id,
sort_order: block.sort_order,
start_date: new Date(block.start_date ?? ""),
target_date: new Date(block.target_date ?? ""),
}))
: [];
blocks &&
blocks.map((block) => ({
data: block,
id: block.id,
sort_order: block.sort_order,
start_date: block.start_date ? new Date(block.start_date) : null,
target_date: block.target_date ? new Date(block.target_date) : null,
}));

View File

@ -1,6 +1,4 @@
import React, { useEffect, useRef, useState } from "react";
// icons
import { ArrowLeft, ArrowRight } from "lucide-react";
// hooks
import { useChart } from "../hooks";
@ -16,23 +14,17 @@ type Props = {
enableBlockMove: boolean;
};
export const ChartDraggable: React.FC<Props> = ({
block,
blockToRender,
handleBlock,
enableBlockLeftResize,
enableBlockRightResize,
enableBlockMove,
}) => {
export const ChartDraggable: React.FC<Props> = (props) => {
const { block, blockToRender, handleBlock, enableBlockLeftResize, enableBlockRightResize, enableBlockMove } = props;
// states
const [isLeftResizing, setIsLeftResizing] = useState(false);
const [isRightResizing, setIsRightResizing] = useState(false);
const [isMoving, setIsMoving] = useState(false);
const [posFromLeft, setPosFromLeft] = useState<number | null>(null);
// refs
const resizableRef = useRef<HTMLDivElement>(null);
// chart hook
const { currentViewData, scrollLeft } = useChart();
// check if cursor reaches either end while resizing/dragging
const checkScrollEnd = (e: MouseEvent): number => {
const SCROLL_THRESHOLD = 70;
@ -68,7 +60,6 @@ export const ChartDraggable: React.FC<Props> = ({
return delWidth;
};
// handle block resize from the left end
const handleBlockLeftResize = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (!currentViewData || !resizableRef.current || !block.position) return;
@ -120,7 +111,6 @@ export const ChartDraggable: React.FC<Props> = ({
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
};
// handle block resize from the right end
const handleBlockRightResize = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (!currentViewData || !resizableRef.current || !block.position) return;
@ -163,7 +153,6 @@ export const ChartDraggable: React.FC<Props> = ({
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
};
// handle block x-axis move
const handleBlockMove = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (!enableBlockMove || !currentViewData || !resizableRef.current || !block.position) return;
@ -210,7 +199,6 @@ export const ChartDraggable: React.FC<Props> = ({
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
};
// scroll to a hidden block
const handleScrollToBlock = () => {
const scrollContainer = document.querySelector("#scroll-container") as HTMLElement;
@ -220,7 +208,6 @@ export const ChartDraggable: React.FC<Props> = ({
// update container's scroll position to the block's position
scrollContainer.scrollLeft = block.position.marginLeft - 4;
};
// update block position from viewport's left end on scroll
useEffect(() => {
const block = resizableRef.current;
@ -229,7 +216,6 @@ export const ChartDraggable: React.FC<Props> = ({
setPosFromLeft(block.getBoundingClientRect().left);
}, [scrollLeft]);
// check if block is hidden on either side
const isBlockHiddenOnLeft =
block.position?.marginLeft &&

View File

@ -1 +1,3 @@
export * from "./add-block";
export * from "./block-structure";
export * from "./draggable";

View File

@ -19,36 +19,43 @@ type GanttChartRootProps = {
enableBlockMove?: boolean;
enableReorder?: boolean;
bottomSpacing?: boolean;
showAllBlocks?: boolean;
};
export const GanttChartRoot: FC<GanttChartRootProps> = ({
border = true,
title,
blocks,
loaderTitle = "blocks",
blockUpdateHandler,
sidebarToRender,
blockToRender,
enableBlockLeftResize = true,
enableBlockRightResize = true,
enableBlockMove = true,
enableReorder = true,
bottomSpacing = false,
}) => (
<ChartContextProvider>
<ChartViewRoot
border={border}
title={title}
blocks={blocks}
loaderTitle={loaderTitle}
blockUpdateHandler={blockUpdateHandler}
sidebarToRender={sidebarToRender}
blockToRender={blockToRender}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove}
enableReorder={enableReorder}
bottomSpacing={bottomSpacing}
/>
</ChartContextProvider>
);
export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
const {
border = true,
title,
blocks,
loaderTitle = "blocks",
blockUpdateHandler,
sidebarToRender,
blockToRender,
enableBlockLeftResize = true,
enableBlockRightResize = true,
enableBlockMove = true,
enableReorder = true,
bottomSpacing = false,
showAllBlocks = false,
} = props;
return (
<ChartContextProvider>
<ChartViewRoot
border={border}
title={title}
blocks={blocks}
loaderTitle={loaderTitle}
blockUpdateHandler={blockUpdateHandler}
sidebarToRender={sidebarToRender}
blockToRender={blockToRender}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove}
enableReorder={enableReorder}
bottomSpacing={bottomSpacing}
showAllBlocks={showAllBlocks}
/>
</ChartContextProvider>
);
};

View File

@ -1,6 +1,5 @@
import { useRouter } from "next/router";
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { DragDropContext, Draggable, DropResult, Droppable } from "@hello-pangea/dnd";
import { MoreVertical } from "lucide-react";
// hooks
import { useChart } from "components/gantt-chart/hooks";
@ -83,7 +82,7 @@ export const CycleGanttSidebar: React.FC<Props> = (props) => {
return (
<DragDropContext onDragEnd={handleOrderChange}>
<StrictModeDroppable droppableId="gantt-sidebar">
<Droppable droppableId="gantt-sidebar">
{(droppableProvided) => (
<div
id={`gantt-sidebar-${cycleId}`}
@ -153,7 +152,7 @@ export const CycleGanttSidebar: React.FC<Props> = (props) => {
</>
</div>
)}
</StrictModeDroppable>
</Droppable>
</DragDropContext>
);
};

View File

@ -1,6 +1,5 @@
import { useRouter } from "next/router";
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea/dnd";
import { MoreVertical } from "lucide-react";
// hooks
import { useChart } from "components/gantt-chart/hooks";
@ -83,7 +82,7 @@ export const ModuleGanttSidebar: React.FC<Props> = (props) => {
return (
<DragDropContext onDragEnd={handleOrderChange}>
<StrictModeDroppable droppableId="gantt-sidebar">
<Droppable droppableId="gantt-sidebar">
{(droppableProvided) => (
<div
id={`gantt-sidebar-${cycleId}`}
@ -153,7 +152,7 @@ export const ModuleGanttSidebar: React.FC<Props> = (props) => {
</>
</div>
)}
</StrictModeDroppable>
</Droppable>
</DragDropContext>
);
};

View File

@ -1,6 +1,5 @@
import { useRouter } from "next/router";
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea/dnd";
import { MoreVertical } from "lucide-react";
// hooks
import { useChart } from "components/gantt-chart/hooks";
@ -84,7 +83,7 @@ export const ProjectViewGanttSidebar: React.FC<Props> = (props) => {
return (
<DragDropContext onDragEnd={handleOrderChange}>
<StrictModeDroppable droppableId="gantt-sidebar">
<Droppable droppableId="gantt-sidebar">
{(droppableProvided) => (
<div
id={`gantt-sidebar-${cycleId}`}
@ -154,7 +153,7 @@ export const ProjectViewGanttSidebar: React.FC<Props> = (props) => {
</>
</div>
)}
</StrictModeDroppable>
</Droppable>
</DragDropContext>
);
};

View File

@ -1,6 +1,5 @@
import { useRouter } from "next/router";
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea/dnd";
import { MoreVertical } from "lucide-react";
// hooks
import { useChart } from "components/gantt-chart/hooks";
@ -27,10 +26,10 @@ type Props = {
) => Promise<TIssue | undefined>;
viewId?: string;
disableIssueCreation?: boolean;
showAllBlocks?: boolean;
};
export const IssueGanttSidebar: React.FC<Props> = (props) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {
blockUpdateHandler,
blocks,
@ -39,6 +38,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
quickAddCallback,
viewId,
disableIssueCreation,
showAllBlocks = false,
} = props;
const router = useRouter();
@ -100,7 +100,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
return (
<DragDropContext onDragEnd={handleOrderChange}>
<StrictModeDroppable droppableId="gantt-sidebar">
<Droppable droppableId="gantt-sidebar">
{(droppableProvided) => (
<div
id={`gantt-sidebar-${cycleId}`}
@ -111,7 +111,15 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
<>
{blocks ? (
blocks.map((block, index) => {
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "");
const isBlockVisibleOnSidebar = block.start_date && block.target_date;
// hide the block if it doesn't have start and target dates and showAllBlocks is false
if (!showAllBlocks && !isBlockVisibleOnSidebar) return;
const duration =
!block.start_date || !block.target_date
? null
: findTotalDaysInRange(block.start_date, block.target_date);
return (
<Draggable
@ -149,7 +157,11 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
<IssueGanttSidebarBlock data={block.data} />
</div>
<div className="flex-shrink-0 text-sm text-custom-text-200">
{duration} day{duration > 1 ? "s" : ""}
{duration && (
<span>
{duration} day{duration > 1 ? "s" : ""}
</span>
)}
</div>
</div>
</div>
@ -173,7 +185,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
)}
</div>
)}
</StrictModeDroppable>
</Droppable>
</DragDropContext>
);
};

View File

@ -13,8 +13,8 @@ export interface IGanttBlock {
width: number;
};
sort_order: number;
start_date: Date;
target_date: Date;
start_date: Date | null;
target_date: Date | null;
}
export interface IBlockUpdateData {

View File

@ -167,6 +167,8 @@ export const getMonthChartItemPositionWidthInMonth = (chartData: ChartDataType,
const { startDate } = chartData.data;
const { start_date: itemStartDate, target_date: itemTargetDate } = itemData;
if (!itemStartDate || !itemTargetDate) return null;
startDate.setHours(0, 0, 0, 0);
itemStartDate.setHours(0, 0, 0, 0);
itemTargetDate.setHours(0, 0, 0, 0);

View File

@ -12,12 +12,7 @@ import { Breadcrumbs, Button, LayersIcon, PhotoFilterIcon, Tooltip } from "@plan
// icons
import { List, PlusIcon, Sheet } from "lucide-react";
// types
import {
IIssueDisplayFilterOptions,
IIssueDisplayProperties,
IIssueFilterOptions,
TStaticViewTypes,
} from "@plane/types";
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
// constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
import { EUserWorkspaceRoles } from "constants/workspace";

View File

@ -1,4 +1,4 @@
import { ChangeEvent, FC, useCallback, useContext, useEffect, useState } from "react";
import { ChangeEvent, FC, useCallback, useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
// hooks
import useReloadConfirmations from "hooks/use-reload-confirmation";

View File

@ -64,6 +64,7 @@ export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props)
{
"cursor-not-allowed": disabled,
"hover:bg-custom-background-80": !disabled,
"bg-custom-background-80": isParentIssueModalOpen,
},
className
)}
@ -72,15 +73,20 @@ export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props)
>
{issue.parent_id && parentIssue ? (
<div className="flex items-center gap-1 bg-green-500/20 text-green-700 rounded px-1.5 py-1">
<Link
href={`/${workspaceSlug}/projects/${projectId}/issues/${parentIssue?.id}`}
className="text-xs font-medium"
>
{parentIssueProjectDetails?.identifier}-{parentIssue.sequence_id}
</Link>
<Tooltip tooltipHeading="Title" tooltipContent={parentIssue.name}>
<Link
href={`/${workspaceSlug}/projects/${projectId}/issues/${parentIssue?.id}`}
target="_blank"
rel="noopener noreferrer"
className="text-xs font-medium mt-0.5"
onClick={(e) => e.stopPropagation()}
>
{parentIssueProjectDetails?.identifier}-{parentIssue.sequence_id}
</Link>
</Tooltip>
{!disabled && (
<Tooltip tooltipContent="Remove">
<Tooltip tooltipContent="Remove" position="bottom">
<span
onClick={(e) => {
e.preventDefault();
@ -96,7 +102,15 @@ export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props)
) : (
<span className="text-sm text-custom-text-400">Add parent issue</span>
)}
{!disabled && <Pencil className="h-4 w-4 flex-shrink-0 hidden group-hover:inline" />}
{!disabled && (
<span
className={cn("p-1 flex-shrink-0 opacity-0 group-hover:opacity-100", {
"text-custom-text-400": !issue.parent_id && !parentIssue,
})}
>
<Pencil className="h-2.5 w-2.5 flex-shrink-0" />
</span>
)}
</button>
</>
);

View File

@ -1,6 +1,5 @@
import { FC } from "react";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
// components
import { IssueParentSiblingItem } from "./sibling-item";
// hooks

View File

@ -1,4 +1,5 @@
import React from "react";
import Link from "next/link";
import { observer } from "mobx-react-lite";
import { CircleDot, CopyPlus, Pencil, X, XCircle } from "lucide-react";
// hooks
@ -101,59 +102,72 @@ export const IssueRelationSelect: React.FC<TIssueRelationSelect> = observer((pro
<button
type="button"
className={cn(
"group flex items-center justify-between gap-2 px-2 py-0.5 rounded outline-none",
"group flex items-center gap-2 px-2 py-0.5 rounded outline-none",
{
"cursor-not-allowed": disabled,
"hover:bg-custom-background-80": !disabled,
"bg-custom-background-80": isRelationModalOpen === relationKey,
},
className
)}
onClick={() => toggleRelationModal(relationKey)}
disabled={disabled}
>
{relationIssueIds.length > 0 ? (
<div className="flex items-center gap-2 flex-wrap">
{relationIssueIds.map((relationIssueId) => {
const currentIssue = issueMap[relationIssueId];
if (!currentIssue) return;
<div className="flex items-start justify-between w-full">
{relationIssueIds.length > 0 ? (
<div className="flex items-center gap-2 py-0.5 flex-wrap">
{relationIssueIds.map((relationIssueId) => {
const currentIssue = issueMap[relationIssueId];
if (!currentIssue) return;
const projectDetails = getProjectById(currentIssue.project_id);
const projectDetails = getProjectById(currentIssue.project_id);
return (
<div
key={relationIssueId}
className={`group flex items-center gap-1 rounded px-1.5 py-1 ${issueRelationObject[relationKey].className}`}
>
<a
href={`/${workspaceSlug}/projects/${projectDetails?.id}/issues/${currentIssue.id}`}
target="_blank"
rel="noopener noreferrer"
className="text-xs font-medium"
onClick={(e) => e.stopPropagation()}
return (
<div
key={relationIssueId}
className={`group flex items-center gap-1 rounded px-1.5 pt-1 pb-1 leading-3 hover:bg-custom-background-90 ${issueRelationObject[relationKey].className}`}
>
{`${projectDetails?.identifier}-${currentIssue?.sequence_id}`}
</a>
{!disabled && (
<Tooltip tooltipContent="Remove">
<span
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
removeRelation(workspaceSlug, projectId, issueId, relationKey, relationIssueId);
}}
<Tooltip tooltipHeading="Title" tooltipContent={currentIssue.name}>
<Link
href={`/${workspaceSlug}/projects/${projectDetails?.id}/issues/${currentIssue.id}`}
target="_blank"
rel="noopener noreferrer"
className="text-xs font-medium mt-0.5"
onClick={(e) => e.stopPropagation()}
>
<X className="h-2.5 w-2.5 text-custom-text-300 hover:text-red-500" />
</span>
{`${projectDetails?.identifier}-${currentIssue?.sequence_id}`}
</Link>
</Tooltip>
)}
</div>
);
})}
</div>
) : (
<span className="text-sm text-custom-text-400">{issueRelationObject[relationKey].placeholder}</span>
)}
{!disabled && <Pencil className="h-4 w-4 flex-shrink-0 hidden group-hover:inline" />}
{!disabled && (
<Tooltip tooltipContent="Remove" position="bottom">
<span
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
removeRelation(workspaceSlug, projectId, issueId, relationKey, relationIssueId);
}}
>
<X className="h-2.5 w-2.5 text-custom-text-300 hover:text-red-500" />
</span>
</Tooltip>
)}
</div>
);
})}
</div>
) : (
<span className="text-sm text-custom-text-400">{issueRelationObject[relationKey].placeholder}</span>
)}
{!disabled && (
<span
className={cn("p-1 flex-shrink-0 opacity-0 group-hover:opacity-100", {
"text-custom-text-400": relationIssueIds.length === 0,
})}
>
<Pencil className="h-2.5 w-2.5 flex-shrink-0" />
</span>
)}
</div>
</button>
</>
);

View File

@ -184,7 +184,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
projectId={projectId?.toString() ?? ""}
placeholder="Add assignees"
multiple
buttonVariant={issue?.assignee_ids?.length > 0 ? "transparent-without-text" : "transparent-with-text"}
buttonVariant={issue?.assignee_ids?.length > 1 ? "transparent-without-text" : "transparent-with-text"}
className="w-3/5 flex-grow group"
buttonContainerClassName="w-full text-left"
buttonClassName={`text-sm justify-between ${
@ -233,6 +233,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
buttonClassName={`text-sm ${issue?.start_date ? "" : "text-custom-text-400"}`}
hideIcon
clearIconClassName="h-3 w-3 hidden group-hover:inline"
showPlaceholderIcon
/>
</div>
@ -257,6 +258,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
buttonClassName={`text-sm ${issue?.target_date ? "" : "text-custom-text-400"}`}
hideIcon
clearIconClassName="h-3 w-3 hidden group-hover:inline"
showPlaceholderIcon
/>
</div>
@ -332,8 +334,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
/>
</div>
<div className="flex items-center gap-2 min-h-8">
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-2 min-h-8">
<div className="flex gap-1 pt-2 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<RelatedIcon className="h-4 w-4 flex-shrink-0" />
<span>Relates to</span>
</div>
@ -347,8 +349,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
/>
</div>
<div className="flex items-center gap-2 min-h-8">
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-2 min-h-8">
<div className="flex gap-1 pt-2 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<XCircle className="h-4 w-4 flex-shrink-0" />
<span>Blocking</span>
</div>
@ -362,8 +364,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
/>
</div>
<div className="flex items-center gap-2 min-h-8">
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-2 min-h-8">
<div className="flex gap-1 pt-2 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<CircleDot className="h-4 w-4 flex-shrink-0" />
<span>Blocked by</span>
</div>
@ -377,8 +379,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
/>
</div>
<div className="flex items-center gap-2 min-h-8">
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-2 min-h-8">
<div className="flex gap-1 pt-2 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<CopyPlus className="h-4 w-4 flex-shrink-0" />
<span>Duplicate of</span>
</div>

View File

@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import { observer } from "mobx-react-lite";
// hooks
import { useProject, useWorkspace } from "hooks/store";
import { useProject } from "hooks/store";
import useToast from "hooks/use-toast";
import useKeypress from "hooks/use-keypress";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
@ -57,14 +57,13 @@ const Inputs = (props: any) => {
};
export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
const { formKey, groupId, prePopulatedData, quickAddCallback, viewId, onOpen } = props;
const { formKey, prePopulatedData, quickAddCallback, viewId, onOpen } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store hooks
const { getProjectById } = useProject();
const { getWorkspaceBySlug } = useWorkspace();
// refs
const ref = useRef<HTMLDivElement>(null);
// states
@ -73,7 +72,6 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
const { setToastAlert } = useToast();
// derived values
const workspaceDetail = (workspaceSlug && getWorkspaceBySlug(workspaceSlug.toString())) || null;
const projectDetail = projectId ? getProjectById(projectId.toString()) : null;
const {

View File

@ -1,6 +1,7 @@
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
import size from "lodash/size";
import { useTheme } from "next-themes";
// hooks
import { useIssues, useUser } from "hooks/store";
// components
@ -26,6 +27,9 @@ export const ProjectArchivedEmptyState: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
membership: { currentProjectRole },
currentUser,
@ -35,16 +39,13 @@ export const ProjectArchivedEmptyState: React.FC = observer(() => {
const userFilters = issuesFilter?.issueFilters?.filters;
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath(
"empty-filters",
activeLayout ?? "list",
currentUser?.theme.theme === "light"
);
const EmptyStateImagePath = getEmptyStateImagePath("archived", "empty-issues", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode);
const EmptyStateImagePath = getEmptyStateImagePath("archived", "empty-issues", isLightMode);
const issueFilterCount = size(
Object.fromEntries(
Object.entries(userFilters ?? {}).filter(([key, value]) => value && Array.isArray(value) && value.length > 0)
Object.entries(userFilters ?? {}).filter(([, value]) => value && Array.isArray(value) && value.length > 0)
)
);

View File

@ -1,6 +1,7 @@
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
import size from "lodash/size";
import { useTheme } from "next-themes";
// hooks
import { useIssues, useUser } from "hooks/store";
// components
@ -26,6 +27,9 @@ export const ProjectDraftEmptyState: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
membership: { currentProjectRole },
currentUser,
@ -35,16 +39,13 @@ export const ProjectDraftEmptyState: React.FC = observer(() => {
const userFilters = issuesFilter?.issueFilters?.filters;
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath(
"empty-filters",
activeLayout ?? "list",
currentUser?.theme.theme === "light"
);
const EmptyStateImagePath = getEmptyStateImagePath("draft", "empty-issues", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode);
const EmptyStateImagePath = getEmptyStateImagePath("draft", "empty-issues", isLightMode);
const issueFilterCount = size(
Object.fromEntries(
Object.entries(userFilters ?? {}).filter(([key, value]) => value && Array.isArray(value) && value.length > 0)
Object.entries(userFilters ?? {}).filter(([, value]) => value && Array.isArray(value) && value.length > 0)
)
);

View File

@ -1,6 +1,7 @@
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
import size from "lodash/size";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useIssues, useUser } from "hooks/store";
// components
@ -26,6 +27,8 @@ export const ProjectEmptyState: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
commandPalette: commandPaletteStore,
@ -40,16 +43,13 @@ export const ProjectEmptyState: React.FC = observer(() => {
const userFilters = issuesFilter?.issueFilters?.filters;
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath(
"empty-filters",
activeLayout ?? "list",
currentUser?.theme.theme === "light"
);
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "issues", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode);
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "issues", isLightMode);
const issueFilterCount = size(
Object.fromEntries(
Object.entries(userFilters ?? {}).filter(([key, value]) => value && Array.isArray(value) && value.length > 0)
Object.entries(userFilters ?? {}).filter(([, value]) => value && Array.isArray(value) && value.length > 0)
)
);

View File

@ -13,11 +13,12 @@ import {
} from "components/gantt-chart";
// types
import { TIssue, TUnGroupedIssues } from "@plane/types";
import { EUserProjectRoles } from "constants/project";
import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle";
import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module";
import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project";
import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views";
// constants
import { EUserProjectRoles } from "constants/project";
import { EIssueActions } from "../types";
interface IBaseGanttRoot {
@ -76,12 +77,14 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
viewId={viewId}
enableQuickIssueCreate
disableIssueCreation={!enableIssueCreation || !isAllowed}
showAllBlocks
/>
)}
enableBlockLeftResize={isAllowed}
enableBlockRightResize={isAllowed}
enableBlockMove={isAllowed}
enableReorder={appliedDisplayFilters?.order_by === "sort_order" && isAllowed}
showAllBlocks
/>
</div>
</>

View File

@ -1,4 +1,3 @@
import { useRouter } from "next/router";
// ui
import { Tooltip, StateGroupIcon, ControlLink } from "@plane/ui";
// helpers

View File

@ -54,7 +54,7 @@ const defaultValues: Partial<TIssue> = {
};
export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = observer((props) => {
const { formKey, groupId, prePopulatedData, quickAddCallback, viewId } = props;
const { formKey, prePopulatedData, quickAddCallback, viewId } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;

View File

@ -3,6 +3,7 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
import isEmpty from "lodash/isEmpty";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useGlobalView, useIssues, useProject, useUser } from "hooks/store";
import { useWorkspaceIssueProperties } from "hooks/use-workspace-issue-properties";
@ -25,6 +26,8 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, globalViewId } = router.query;
// theme
const { resolvedTheme } = useTheme();
//swr hook for fetching issue properties
useWorkspaceIssueProperties(workspaceSlug);
// store
@ -46,7 +49,8 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
const currentView = isDefaultView ? groupedIssueIds.dataViewId : "custom-view";
const currentViewDetails = ALL_ISSUES_EMPTY_STATE_DETAILS[currentView as keyof typeof ALL_ISSUES_EMPTY_STATE_DETAILS];
const emptyStateImage = getEmptyStateImagePath("all-issues", currentView, currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("all-issues", currentView, isLightMode);
// filter init from the query params
@ -57,6 +61,7 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
["all-issues", "assigned", "created", "subscribed"].includes(globalViewId.toString())
) {
const routerQueryParams = { ...router.query };
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { ["workspaceSlug"]: _workspaceSlug, ["globalViewId"]: _globalViewId, ...filters } = routerQueryParams;
let issueFilters: any = {};

View File

@ -2,7 +2,7 @@ import { FC, useCallback } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// hooks
import { useIssues, useUser } from "hooks/store";
import { useUser } from "hooks/store";
// views
import { SpreadsheetView } from "./spreadsheet-view";
// types
@ -36,7 +36,6 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
// store hooks
const { issueMap } = useIssues();
const {
membership: { currentProjectRole },
} = useUser();
@ -55,7 +54,6 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
[canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed]
);
const issueIds = (issueStore.groupedIssueIds ?? []) as TUnGroupedIssues;
const handleIssues = useCallback(

View File

@ -99,7 +99,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
projectId={projectId}
placeholder="Add assignees"
multiple
buttonVariant={issue?.assignee_ids?.length > 0 ? "transparent-without-text" : "transparent-with-text"}
buttonVariant={issue?.assignee_ids?.length > 1 ? "transparent-without-text" : "transparent-with-text"}
className="w-3/4 flex-grow group"
buttonContainerClassName="w-full text-left"
buttonClassName={`text-sm justify-between ${issue?.assignee_ids.length > 0 ? "" : "text-custom-text-400"}`}
@ -148,6 +148,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
buttonClassName={`text-sm ${issue?.start_date ? "" : "text-custom-text-400"}`}
hideIcon
clearIconClassName="h-3 w-3 hidden group-hover:inline"
showPlaceholderIcon
/>
</div>
@ -173,6 +174,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
buttonClassName={`text-sm ${issue?.target_date ? "" : "text-custom-text-400"}`}
hideIcon
clearIconClassName="h-3 w-3 hidden group-hover:inline"
showPlaceholderIcon
/>
</div>
@ -251,8 +253,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
</div>
{/* relates to */}
<div className="flex items-center gap-3 min-h-8">
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-3 min-h-8">
<div className="flex pt-2 gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<RelatedIcon className="h-4 w-4 flex-shrink-0" />
<span>Relates to</span>
</div>
@ -267,8 +269,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
</div>
{/* blocking */}
<div className="flex items-center gap-3 min-h-8">
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-3 min-h-8">
<div className="flex pt-2 gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<XCircle className="h-4 w-4 flex-shrink-0" />
<span>Blocking</span>
</div>
@ -283,8 +285,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
</div>
{/* blocked by */}
<div className="flex items-center gap-3 min-h-8">
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-3 min-h-8">
<div className="flex pt-2 gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<CircleDot className="h-4 w-4 flex-shrink-0" />
<span>Blocked by</span>
</div>
@ -299,8 +301,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
</div>
{/* duplicate of */}
<div className="flex items-center gap-3 min-h-8">
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<div className="flex gap-3 min-h-8">
<div className="flex pt-2 gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<CopyPlus className="h-4 w-4 flex-shrink-0" />
<span>Duplicate of</span>
</div>

View File

@ -1,6 +1,5 @@
import React, { Fragment, useRef, useState } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { Combobox } from "@headlessui/react";
import { usePopper } from "react-popper";
import { observer } from "mobx-react-lite";

View File

@ -1,5 +1,6 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useModule, useUser } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
@ -15,6 +16,8 @@ export const ModulesListView: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, peekModule } = router.query;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const { commandPalette: commandPaletteStore } = useApplication();
const {
@ -25,7 +28,8 @@ export const ModulesListView: React.FC = observer(() => {
const { storedValue: modulesView } = useLocalStorage("modules_view", "grid");
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "modules", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "modules", isLightMode);
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;

View File

@ -1,4 +1,5 @@
import { useEffect } from "react";
import { useTheme } from "next-themes";
import { observer } from "mobx-react-lite";
// hooks
import { useApplication, useDashboard, useProject, useUser } from "hooks/store";
@ -14,6 +15,8 @@ import { Spinner } from "@plane/ui";
import { EUserWorkspaceRoles } from "constants/workspace";
export const WorkspaceDashboardView = observer(() => {
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
commandPalette: { toggleCreateProjectModal },
@ -28,7 +31,8 @@ export const WorkspaceDashboardView = observer(() => {
const { homeDashboardId, fetchHomeDashboardWidgets } = useDashboard();
const { joinedProjectIds } = useProject();
const emptyStateImage = getEmptyStateImagePath("onboarding", "dashboard", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("onboarding", "dashboard", isLightMode);
const handleTourCompleted = () => {
updateTourCompleted()

View File

@ -1,8 +1,6 @@
import React, { FC } from "react";
import { useRouter } from "next/router";
import { Dialog, Transition } from "@headlessui/react";
// hooks
import { useApplication } from "hooks/store";
// components
import { PageForm } from "./page-form";
// types
@ -25,10 +23,6 @@ export const CreateUpdatePageModal: FC<Props> = (props) => {
const { workspaceSlug } = router.query;
const { createPage } = useProjectPages();
// store hooks
const {
eventTracker: { postHogEventTracker },
} = useApplication();
const createProjectPage = async (payload: IPage) => {
if (!workspaceSlug) return;

View File

@ -1,5 +1,6 @@
import { FC } from "react";
import { useRouter } from "next/router";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useUser } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
@ -18,7 +19,9 @@ type IPagesListView = {
export const PagesListView: FC<IPagesListView> = (props) => {
const { pageIds: projectPageIds } = props;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
commandPalette: { toggleCreatePageModal },
} = useApplication();
@ -36,11 +39,8 @@ export const PagesListView: FC<IPagesListView> = (props) => {
? PAGE_EMPTY_STATE_DETAILS[pageTab as keyof typeof PAGE_EMPTY_STATE_DETAILS]
: PAGE_EMPTY_STATE_DETAILS["All"];
const emptyStateImage = getEmptyStateImagePath(
"pages",
currentPageTabDetails.key,
currentUser?.theme.theme === "light"
);
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("pages", currentPageTabDetails.key, isLightMode);
const isButtonVisible = currentPageTabDetails.key !== "archived" && currentPageTabDetails.key !== "favorites";

View File

@ -1,5 +1,6 @@
import React, { FC } from "react";
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useUser } from "hooks/store";
import { useProjectPages } from "hooks/store/use-project-specific-pages";
@ -14,6 +15,8 @@ import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
import { EUserProjectRoles } from "constants/project";
export const RecentPagesList: FC = observer(() => {
// theme
const { resolvedTheme } = useTheme();
// store hooks
const { commandPalette: commandPaletteStore } = useApplication();
const {
@ -22,7 +25,8 @@ export const RecentPagesList: FC = observer(() => {
} = useUser();
const { recentProjectPages } = useProjectPages();
const EmptyStateImagePath = getEmptyStateImagePath("pages", "recent", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("pages", "recent", isLightMode);
// FIXME: replace any with proper type
const isEmpty = recentProjectPages && Object.values(recentProjectPages).every((value: any) => value.length === 0);

View File

@ -2,6 +2,7 @@ import React from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// components
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root";
@ -27,6 +28,9 @@ export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => {
workspaceSlug: string;
userId: string;
};
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
membership: { currentWorkspaceRole },
currentUser,
@ -46,7 +50,8 @@ export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => {
}
);
const emptyStateImage = getEmptyStateImagePath("profile", type, currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("profile", type, isLightMode);
const activeLayout = issueFilters?.displayFilters?.layout || undefined;

View File

@ -1,16 +1,17 @@
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useProject, useUser } from "hooks/store";
// components
import { ProjectCard } from "components/project";
import { Loader } from "@plane/ui";
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
// icons
import { Plus } from "lucide-react";
// constants
import { EUserWorkspaceRoles } from "constants/workspace";
export const ProjectCardList = observer(() => {
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
commandPalette: commandPaletteStore,
@ -22,7 +23,8 @@ export const ProjectCardList = observer(() => {
} = useUser();
const { workspaceProjectIds, searchedProjects, getProjectById } = useProject();
const emptyStateImage = getEmptyStateImagePath("onboarding", "projects", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("onboarding", "projects", isLightMode);
const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;

View File

@ -1,6 +1,7 @@
import { useState } from "react";
import { observer } from "mobx-react-lite";
import { Search } from "lucide-react";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useProjectView, useUser } from "hooks/store";
// components
@ -14,6 +15,8 @@ import { EUserProjectRoles } from "constants/project";
export const ProjectViewsList = observer(() => {
// states
const [query, setQuery] = useState("");
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
commandPalette: { toggleCreateViewModal },
@ -43,7 +46,8 @@ export const ProjectViewsList = observer(() => {
const viewsList = projectViewIds.map((viewId) => getViewById(viewId));
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "views", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "views", isLightMode);
const filteredViewsList = viewsList.filter((v) => v?.name.toLowerCase().includes(query.toLowerCase()));

View File

@ -13,7 +13,6 @@ const paramsToKey = (params: any) => {
start_date,
target_date,
sub_issue,
start_target_date,
project,
layout,
subscriber,
@ -28,7 +27,6 @@ const paramsToKey = (params: any) => {
let createdByKey = created_by ? created_by.split(",") : [];
let labelsKey = labels ? labels.split(",") : [];
let subscriberKey = subscriber ? subscriber.split(",") : [];
const startTargetDate = start_target_date ? `${start_target_date}`.toUpperCase() : "FALSE";
const startDateKey = start_date ?? "";
const targetDateKey = target_date ?? "";
const type = params.type ? params.type.toUpperCase() : "NULL";
@ -47,7 +45,7 @@ const paramsToKey = (params: any) => {
labelsKey = labelsKey.sort().join("_");
subscriberKey = subscriberKey.sort().join("_");
return `${layoutKey}_${projectKey}_${stateGroupKey}_${stateKey}_${priorityKey}_${assigneesKey}_${mentionsKey}_${createdByKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${startDateKey}_${targetDateKey}_${sub_issue}_${startTargetDate}_${subscriberKey}`;
return `${layoutKey}_${projectKey}_${stateGroupKey}_${stateKey}_${priorityKey}_${assigneesKey}_${mentionsKey}_${createdByKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${startDateKey}_${targetDateKey}_${sub_issue}_${subscriberKey}`;
};
const myIssuesParamsToKey = (params: any) => {

View File

@ -105,9 +105,6 @@ export const handleIssueQueryParamsByLayout = (
});
}
// add start_target_date query param for the gantt_chart layout
if (layout === "gantt_chart") queryParams.push("start_target_date");
return queryParams;
};

View File

@ -1,6 +1,7 @@
import React, { Fragment, ReactElement } from "react";
import { observer } from "mobx-react-lite";
import { Tab } from "@headlessui/react";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useProject, useUser } from "hooks/store";
// layouts
@ -16,6 +17,8 @@ import { EUserWorkspaceRoles } from "constants/workspace";
import { NextPageWithLayout } from "lib/types";
const AnalyticsPage: NextPageWithLayout = observer(() => {
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
commandPalette: { toggleCreateProjectModal },
@ -27,7 +30,8 @@ const AnalyticsPage: NextPageWithLayout = observer(() => {
} = useUser();
const { workspaceProjectIds } = useProject();
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "analytics", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "analytics", isLightMode);
const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
return (

View File

@ -2,6 +2,7 @@ import { Fragment, useCallback, useState, ReactElement } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Tab } from "@headlessui/react";
import { useTheme } from "next-themes";
// hooks
import { useCycle, useUser } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
@ -22,6 +23,8 @@ import { EUserWorkspaceRoles } from "constants/workspace";
const ProjectCyclesPage: NextPageWithLayout = observer(() => {
const [createModal, setCreateModal] = useState(false);
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
membership: { currentProjectRole },
@ -49,7 +52,9 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
},
[handleCurrentLayout, setCycleTab]
);
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "cycles", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "cycles", isLightMode);
const totalCycles = currentProjectCycleIds?.length ?? 0;

View File

@ -4,6 +4,7 @@ import dynamic from "next/dynamic";
import { Tab } from "@headlessui/react";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useUser } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
@ -48,7 +49,9 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => {
const { workspaceSlug, projectId } = router.query;
// states
const [createUpdatePageModal, setCreateUpdatePageModal] = useState(false);
// store
// theme
const { resolvedTheme } = useTheme();
// store hooks
const {
currentUser,
currentUserLoader,
@ -94,7 +97,8 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => {
}
};
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "pages", currentUser?.theme.theme === "light");
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "pages", isLightMode);
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;

View File

@ -14,7 +14,7 @@ import { RichReadOnlyEditor } from "@plane/rich-text-editor";
// icons
import { History, MessageSquare } from "lucide-react";
// ui
import { ExternalLinkIcon, Loader } from "@plane/ui";
import { Loader } from "@plane/ui";
// fetch-keys
import { USER_ACTIVITY } from "constants/fetch-keys";
// helper

View File

@ -89,7 +89,6 @@ export class ArchivedIssuesFilter extends IssueFilterHelperStore implements IArc
filteredParams
);
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams;

View File

@ -90,7 +90,6 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
filteredParams
);
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams;

View File

@ -89,7 +89,6 @@ export class DraftIssuesFilter extends IssueFilterHelperStore implements IDraftI
filteredParams
);
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams;

View File

@ -81,7 +81,6 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
// display filters
type: displayFilters?.type || undefined,
sub_issue: displayFilters?.sub_issue ?? true,
start_target_date: displayFilters?.start_target_date ?? true,
};
const issueFiltersParams: Partial<Record<TIssueParams, boolean | string>> = {};
@ -170,7 +169,6 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
type: filters?.type || null,
sub_issue: filters?.sub_issue || false,
show_empty_groups: filters?.show_empty_groups || false,
start_target_date: filters?.start_target_date || false,
};
};

View File

@ -90,7 +90,6 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
filteredParams
);
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams;

View File

@ -93,7 +93,6 @@ export class ProfileIssuesFilter extends IssueFilterHelperStore implements IProf
filteredParams
);
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams;

View File

@ -90,7 +90,6 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
filteredParams
);
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams;

View File

@ -89,7 +89,6 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj
filteredParams
);
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams;

View File

@ -99,7 +99,6 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
filteredParams
);
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams;