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

View File

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

View File

@ -1,13 +1,20 @@
import React from "react"; import React from "react";
import { Tooltip } from "../tooltip"; import { Tooltip } from "../tooltip";
import { cn } from "../../helpers";
type Props = { type Props = {
data: any; data: any;
noTooltip?: boolean; noTooltip?: boolean;
inPercentage?: 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); const total = data.reduce((acc: any, cur: any) => acc + cur.value, 0);
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
let progress = 0; let progress = 0;
@ -23,18 +30,24 @@ export const LinearProgressIndicator: React.FC<Props> = ({ data, noTooltip = fal
if (noTooltip) return <div style={style} />; if (noTooltip) return <div style={style} />;
else else
return ( return (
<Tooltip key={item.id} tooltipContent={`${item.name} ${Math.round(item.value)}%`}> <Tooltip key={item.id} tooltipContent={`${item.name} ${Math.round(item.value)}${inPercentage ? "%" : ""}`}>
<div style={style} className="first:rounded-l-full last:rounded-r-full" /> <div style={style} className="first:rounded-l-sm last:rounded-r-sm" />
</Tooltip> </Tooltip>
); );
}); });
return ( 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 ? ( {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> </div>
); );

View File

@ -40,9 +40,9 @@ export const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
}`}`} }`}`}
target={activity.issue === null ? "_self" : "_blank"} target={activity.issue === null ? "_self" : "_blank"}
rel={activity.issue === null ? "" : "noopener noreferrer"} 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> <span className="font-normal">{activity.issue_detail?.name}</span>
</a> </a>
) : ( ) : (
@ -267,7 +267,7 @@ const activityDetails: {
<span className="flex-shrink truncate font-medium text-custom-text-100">{activity.new_value}</span> <span className="flex-shrink truncate font-medium text-custom-text-100">{activity.new_value}</span>
</span> </span>
{showIssue && ( {showIssue && (
<span> <span className="">
{" "} {" "}
to <IssueLink activity={activity} /> to <IssueLink activity={activity} />
</span> </span>

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// components // components
import { DateDropdown, ProjectDropdown } from "components/dropdowns"; import { DateDropdown, ProjectDropdown } from "components/dropdowns";
@ -11,19 +12,28 @@ import { ICycle } from "@plane/types";
type Props = { type Props = {
handleFormSubmit: (values: Partial<ICycle>) => Promise<void>; handleFormSubmit: (values: Partial<ICycle>) => Promise<void>;
handleClose: () => void; handleClose: () => void;
status: boolean;
projectId: string; projectId: string;
setActiveProject: (projectId: string) => void; setActiveProject: (projectId: string) => void;
data?: ICycle | null; data?: ICycle | null;
}; };
const defaultValues: Partial<ICycle> = {
name: "",
description: "",
start_date: null,
end_date: null,
};
export const CycleForm: React.FC<Props> = (props) => { export const CycleForm: React.FC<Props> = (props) => {
const { handleFormSubmit, handleClose, projectId, setActiveProject, data } = props; const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data } = props;
// form data // form data
const { const {
formState: { errors, isSubmitting }, formState: { errors, isSubmitting },
handleSubmit, handleSubmit,
control, control,
watch, watch,
reset,
} = useForm<ICycle>({ } = useForm<ICycle>({
defaultValues: { defaultValues: {
project: projectId, 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 startDate = watch("start_date");
const endDate = watch("end_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"; import { Dialog, Transition } from "@headlessui/react";
// services // services
import { CycleService } from "services/cycle.service"; import { CycleService } from "services/cycle.service";
// hooks // hooks
import { useApplication, useCycle } from "hooks/store"; import { useApplication, useCycle, useProject } from "hooks/store";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
import useLocalStorage from "hooks/use-local-storage"; import useLocalStorage from "hooks/use-local-storage";
// components // components
@ -25,11 +25,12 @@ const cycleService = new CycleService();
export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => { export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
const { isOpen, handleClose, data, workspaceSlug, projectId } = props; const { isOpen, handleClose, data, workspaceSlug, projectId } = props;
// states // states
const [activeProject, setActiveProject] = useState<string>(projectId); const [activeProject, setActiveProject] = useState<string | null>(null);
// store hooks // store hooks
const { const {
eventTracker: { postHogEventTracker }, eventTracker: { postHogEventTracker },
} = useApplication(); } = useApplication();
const { workspaceProjectIds } = useProject();
const { createCycle, updateCycleDetails } = useCycle(); const { createCycle, updateCycleDetails } = useCycle();
// toast alert // toast alert
const { setToastAlert } = useToast(); 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 ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}> <Dialog as="div" className="relative z-20" onClose={handleClose}>
@ -164,7 +186,8 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
<CycleForm <CycleForm
handleFormSubmit={handleFormSubmit} handleFormSubmit={handleFormSubmit}
handleClose={handleClose} handleClose={handleClose}
projectId={activeProject} status={data ? true : false}
projectId={activeProject ?? ""}
setActiveProject={setActiveProject} setActiveProject={setActiveProject}
data={data} 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 the widget is full width, return it in a 2 column grid
if (widget.fullWidth) if (widget.fullWidth)
return ( return (
<div className="lg:col-span-2"> <div key={key} className="lg:col-span-2">
<WidgetComponent dashboardId={homeDashboardId} workspaceSlug={workspaceSlug} /> <WidgetComponent dashboardId={homeDashboardId} workspaceSlug={workspaceSlug} />
</div> </div>
); );
else return <WidgetComponent dashboardId={homeDashboardId} workspaceSlug={workspaceSlug} />; else return <WidgetComponent key={key} dashboardId={homeDashboardId} workspaceSlug={workspaceSlug} />;
})} })}
</div> </div>
); );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,4 @@
import { FC } from "react"; import { FC } from "react";
// hooks // hooks
import { useChart } from "../hooks"; import { useChart } from "../hooks";
// types // 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"; import { IGanttBlock } from "components/gantt-chart";
export const renderIssueBlocksStructure = (blocks: TIssue[]): IGanttBlock[] => export const renderIssueBlocksStructure = (blocks: TIssue[]): IGanttBlock[] =>
blocks && blocks.length > 0 blocks &&
? blocks blocks.map((block) => ({
.filter((b) => new Date(b?.start_date ?? "") <= new Date(b?.target_date ?? "")) data: block,
.map((block) => ({ id: block.id,
data: block, sort_order: block.sort_order,
id: block.id, start_date: block.start_date ? new Date(block.start_date) : null,
sort_order: block.sort_order, target_date: block.target_date ? new Date(block.target_date) : null,
start_date: new Date(block.start_date ?? ""), }));
target_date: new Date(block.target_date ?? ""),
}))
: [];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd"; import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { MoreVertical } from "lucide-react"; import { MoreVertical } from "lucide-react";
// hooks // hooks
import { useChart } from "components/gantt-chart/hooks"; import { useChart } from "components/gantt-chart/hooks";
@ -27,10 +26,10 @@ type Props = {
) => Promise<TIssue | undefined>; ) => Promise<TIssue | undefined>;
viewId?: string; viewId?: string;
disableIssueCreation?: boolean; disableIssueCreation?: boolean;
showAllBlocks?: boolean;
}; };
export const IssueGanttSidebar: React.FC<Props> = (props) => { export const IssueGanttSidebar: React.FC<Props> = (props) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { const {
blockUpdateHandler, blockUpdateHandler,
blocks, blocks,
@ -39,6 +38,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
quickAddCallback, quickAddCallback,
viewId, viewId,
disableIssueCreation, disableIssueCreation,
showAllBlocks = false,
} = props; } = props;
const router = useRouter(); const router = useRouter();
@ -100,7 +100,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
return ( return (
<DragDropContext onDragEnd={handleOrderChange}> <DragDropContext onDragEnd={handleOrderChange}>
<StrictModeDroppable droppableId="gantt-sidebar"> <Droppable droppableId="gantt-sidebar">
{(droppableProvided) => ( {(droppableProvided) => (
<div <div
id={`gantt-sidebar-${cycleId}`} id={`gantt-sidebar-${cycleId}`}
@ -111,7 +111,15 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
<> <>
{blocks ? ( {blocks ? (
blocks.map((block, index) => { 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 ( return (
<Draggable <Draggable
@ -149,7 +157,11 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
<IssueGanttSidebarBlock data={block.data} /> <IssueGanttSidebarBlock data={block.data} />
</div> </div>
<div className="flex-shrink-0 text-sm text-custom-text-200"> <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> </div>
</div> </div>
@ -173,7 +185,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
)} )}
</div> </div>
)} )}
</StrictModeDroppable> </Droppable>
</DragDropContext> </DragDropContext>
); );
}; };

View File

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

View File

@ -167,6 +167,8 @@ export const getMonthChartItemPositionWidthInMonth = (chartData: ChartDataType,
const { startDate } = chartData.data; const { startDate } = chartData.data;
const { start_date: itemStartDate, target_date: itemTargetDate } = itemData; const { start_date: itemStartDate, target_date: itemTargetDate } = itemData;
if (!itemStartDate || !itemTargetDate) return null;
startDate.setHours(0, 0, 0, 0); startDate.setHours(0, 0, 0, 0);
itemStartDate.setHours(0, 0, 0, 0); itemStartDate.setHours(0, 0, 0, 0);
itemTargetDate.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 // icons
import { List, PlusIcon, Sheet } from "lucide-react"; import { List, PlusIcon, Sheet } from "lucide-react";
// types // types
import { import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
IIssueDisplayFilterOptions,
IIssueDisplayProperties,
IIssueFilterOptions,
TStaticViewTypes,
} from "@plane/types";
// constants // constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
import { EUserWorkspaceRoles } from "constants/workspace"; 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"; import { Controller, useForm } from "react-hook-form";
// hooks // hooks
import useReloadConfirmations from "hooks/use-reload-confirmation"; 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, "cursor-not-allowed": disabled,
"hover:bg-custom-background-80": !disabled, "hover:bg-custom-background-80": !disabled,
"bg-custom-background-80": isParentIssueModalOpen,
}, },
className className
)} )}
@ -72,15 +73,20 @@ export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props)
> >
{issue.parent_id && parentIssue ? ( {issue.parent_id && parentIssue ? (
<div className="flex items-center gap-1 bg-green-500/20 text-green-700 rounded px-1.5 py-1"> <div className="flex items-center gap-1 bg-green-500/20 text-green-700 rounded px-1.5 py-1">
<Link <Tooltip tooltipHeading="Title" tooltipContent={parentIssue.name}>
href={`/${workspaceSlug}/projects/${projectId}/issues/${parentIssue?.id}`} <Link
className="text-xs font-medium" href={`/${workspaceSlug}/projects/${projectId}/issues/${parentIssue?.id}`}
> target="_blank"
{parentIssueProjectDetails?.identifier}-{parentIssue.sequence_id} rel="noopener noreferrer"
</Link> className="text-xs font-medium mt-0.5"
onClick={(e) => e.stopPropagation()}
>
{parentIssueProjectDetails?.identifier}-{parentIssue.sequence_id}
</Link>
</Tooltip>
{!disabled && ( {!disabled && (
<Tooltip tooltipContent="Remove"> <Tooltip tooltipContent="Remove" position="bottom">
<span <span
onClick={(e) => { onClick={(e) => {
e.preventDefault(); 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> <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> </button>
</> </>
); );

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import size from "lodash/size"; import size from "lodash/size";
import { useTheme } from "next-themes";
// hooks // hooks
import { useIssues, useUser } from "hooks/store"; import { useIssues, useUser } from "hooks/store";
// components // components
@ -26,6 +27,9 @@ export const ProjectArchivedEmptyState: React.FC = observer(() => {
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const { const {
membership: { currentProjectRole }, membership: { currentProjectRole },
currentUser, currentUser,
@ -35,16 +39,13 @@ export const ProjectArchivedEmptyState: React.FC = observer(() => {
const userFilters = issuesFilter?.issueFilters?.filters; const userFilters = issuesFilter?.issueFilters?.filters;
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout; const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath( const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
"empty-filters", const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode);
activeLayout ?? "list", const EmptyStateImagePath = getEmptyStateImagePath("archived", "empty-issues", isLightMode);
currentUser?.theme.theme === "light"
);
const EmptyStateImagePath = getEmptyStateImagePath("archived", "empty-issues", currentUser?.theme.theme === "light");
const issueFilterCount = size( const issueFilterCount = size(
Object.fromEntries( 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 { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import size from "lodash/size"; import size from "lodash/size";
import { useTheme } from "next-themes";
// hooks // hooks
import { useIssues, useUser } from "hooks/store"; import { useIssues, useUser } from "hooks/store";
// components // components
@ -26,6 +27,9 @@ export const ProjectDraftEmptyState: React.FC = observer(() => {
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const { const {
membership: { currentProjectRole }, membership: { currentProjectRole },
currentUser, currentUser,
@ -35,16 +39,13 @@ export const ProjectDraftEmptyState: React.FC = observer(() => {
const userFilters = issuesFilter?.issueFilters?.filters; const userFilters = issuesFilter?.issueFilters?.filters;
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout; const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath( const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
"empty-filters", const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode);
activeLayout ?? "list", const EmptyStateImagePath = getEmptyStateImagePath("draft", "empty-issues", isLightMode);
currentUser?.theme.theme === "light"
);
const EmptyStateImagePath = getEmptyStateImagePath("draft", "empty-issues", currentUser?.theme.theme === "light");
const issueFilterCount = size( const issueFilterCount = size(
Object.fromEntries( 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 { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import size from "lodash/size"; import size from "lodash/size";
import { useTheme } from "next-themes";
// hooks // hooks
import { useApplication, useIssues, useUser } from "hooks/store"; import { useApplication, useIssues, useUser } from "hooks/store";
// components // components
@ -26,6 +27,8 @@ export const ProjectEmptyState: React.FC = observer(() => {
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
// theme
const { resolvedTheme } = useTheme();
// store hooks // store hooks
const { const {
commandPalette: commandPaletteStore, commandPalette: commandPaletteStore,
@ -40,16 +43,13 @@ export const ProjectEmptyState: React.FC = observer(() => {
const userFilters = issuesFilter?.issueFilters?.filters; const userFilters = issuesFilter?.issueFilters?.filters;
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout; const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath( const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
"empty-filters", const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode);
activeLayout ?? "list", const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "issues", isLightMode);
currentUser?.theme.theme === "light"
);
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "issues", currentUser?.theme.theme === "light");
const issueFilterCount = size( const issueFilterCount = size(
Object.fromEntries( 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"; } from "components/gantt-chart";
// types // types
import { TIssue, TUnGroupedIssues } from "@plane/types"; import { TIssue, TUnGroupedIssues } from "@plane/types";
import { EUserProjectRoles } from "constants/project";
import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle"; import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle";
import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module"; import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module";
import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project"; import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project";
import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views"; import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views";
// constants
import { EUserProjectRoles } from "constants/project";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
interface IBaseGanttRoot { interface IBaseGanttRoot {
@ -76,12 +77,14 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
viewId={viewId} viewId={viewId}
enableQuickIssueCreate enableQuickIssueCreate
disableIssueCreation={!enableIssueCreation || !isAllowed} disableIssueCreation={!enableIssueCreation || !isAllowed}
showAllBlocks
/> />
)} )}
enableBlockLeftResize={isAllowed} enableBlockLeftResize={isAllowed}
enableBlockRightResize={isAllowed} enableBlockRightResize={isAllowed}
enableBlockMove={isAllowed} enableBlockMove={isAllowed}
enableReorder={appliedDisplayFilters?.order_by === "sort_order" && isAllowed} enableReorder={appliedDisplayFilters?.order_by === "sort_order" && isAllowed}
showAllBlocks
/> />
</div> </div>
</> </>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// hooks // hooks
import { useApplication, useUser } from "hooks/store"; import { useApplication, useUser } from "hooks/store";
import { useProjectPages } from "hooks/store/use-project-specific-pages"; import { useProjectPages } from "hooks/store/use-project-specific-pages";
@ -14,6 +15,8 @@ import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
export const RecentPagesList: FC = observer(() => { export const RecentPagesList: FC = observer(() => {
// theme
const { resolvedTheme } = useTheme();
// store hooks // store hooks
const { commandPalette: commandPaletteStore } = useApplication(); const { commandPalette: commandPaletteStore } = useApplication();
const { const {
@ -22,7 +25,8 @@ export const RecentPagesList: FC = observer(() => {
} = useUser(); } = useUser();
const { recentProjectPages } = useProjectPages(); 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 // FIXME: replace any with proper type
const isEmpty = recentProjectPages && Object.values(recentProjectPages).every((value: any) => value.length === 0); 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 { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// components // components
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root"; import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/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; workspaceSlug: string;
userId: string; userId: string;
}; };
// theme
const { resolvedTheme } = useTheme();
// store hooks
const { const {
membership: { currentWorkspaceRole }, membership: { currentWorkspaceRole },
currentUser, 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; const activeLayout = issueFilters?.displayFilters?.layout || undefined;

View File

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

View File

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

View File

@ -13,7 +13,6 @@ const paramsToKey = (params: any) => {
start_date, start_date,
target_date, target_date,
sub_issue, sub_issue,
start_target_date,
project, project,
layout, layout,
subscriber, subscriber,
@ -28,7 +27,6 @@ const paramsToKey = (params: any) => {
let createdByKey = created_by ? created_by.split(",") : []; let createdByKey = created_by ? created_by.split(",") : [];
let labelsKey = labels ? labels.split(",") : []; let labelsKey = labels ? labels.split(",") : [];
let subscriberKey = subscriber ? subscriber.split(",") : []; let subscriberKey = subscriber ? subscriber.split(",") : [];
const startTargetDate = start_target_date ? `${start_target_date}`.toUpperCase() : "FALSE";
const startDateKey = start_date ?? ""; const startDateKey = start_date ?? "";
const targetDateKey = target_date ?? ""; const targetDateKey = target_date ?? "";
const type = params.type ? params.type.toUpperCase() : "NULL"; const type = params.type ? params.type.toUpperCase() : "NULL";
@ -47,7 +45,7 @@ const paramsToKey = (params: any) => {
labelsKey = labelsKey.sort().join("_"); labelsKey = labelsKey.sort().join("_");
subscriberKey = subscriberKey.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) => { 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; return queryParams;
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -81,7 +81,6 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
// display filters // display filters
type: displayFilters?.type || undefined, type: displayFilters?.type || undefined,
sub_issue: displayFilters?.sub_issue ?? true, sub_issue: displayFilters?.sub_issue ?? true,
start_target_date: displayFilters?.start_target_date ?? true,
}; };
const issueFiltersParams: Partial<Record<TIssueParams, boolean | string>> = {}; const issueFiltersParams: Partial<Record<TIssueParams, boolean | string>> = {};
@ -170,7 +169,6 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
type: filters?.type || null, type: filters?.type || null,
sub_issue: filters?.sub_issue || false, sub_issue: filters?.sub_issue || false,
show_empty_groups: filters?.show_empty_groups || 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 filteredParams
); );
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false; if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams; return filteredRouteParams;

View File

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

View File

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

View File

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

View File

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