mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'develop' of gurusainath:makeplane/plane into feat/mobx-global-views
This commit is contained in:
commit
5674acd985
@ -304,6 +304,7 @@ class IssueRelationSerializer(BaseSerializer):
|
||||
sequence_id = serializers.IntegerField(
|
||||
source="related_issue.sequence_id", read_only=True
|
||||
)
|
||||
name = serializers.CharField(source="related_issue.name", read_only=True)
|
||||
relation_type = serializers.CharField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
@ -313,6 +314,7 @@ class IssueRelationSerializer(BaseSerializer):
|
||||
"project_id",
|
||||
"sequence_id",
|
||||
"relation_type",
|
||||
"name",
|
||||
]
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
@ -328,6 +330,7 @@ class RelatedIssueSerializer(BaseSerializer):
|
||||
sequence_id = serializers.IntegerField(
|
||||
source="issue.sequence_id", read_only=True
|
||||
)
|
||||
name = serializers.CharField(source="issue.name", read_only=True)
|
||||
relation_type = serializers.CharField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
@ -337,6 +340,7 @@ class RelatedIssueSerializer(BaseSerializer):
|
||||
"project_id",
|
||||
"sequence_id",
|
||||
"relation_type",
|
||||
"name",
|
||||
]
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
|
4
packages/types/src/view-props.d.ts
vendored
4
packages/types/src/view-props.d.ts
vendored
@ -64,8 +64,7 @@ export type TIssueParams =
|
||||
| "order_by"
|
||||
| "type"
|
||||
| "sub_issue"
|
||||
| "show_empty_groups"
|
||||
| "start_target_date";
|
||||
| "show_empty_groups";
|
||||
|
||||
export type TCalendarLayouts = "month" | "week";
|
||||
|
||||
@ -93,7 +92,6 @@ export interface IIssueDisplayFilterOptions {
|
||||
layout?: TIssueLayouts;
|
||||
order_by?: TIssueOrderByOptions;
|
||||
show_empty_groups?: boolean;
|
||||
start_target_date?: boolean;
|
||||
sub_issue?: boolean;
|
||||
type?: TIssueTypeFilters;
|
||||
}
|
||||
|
@ -1,13 +1,20 @@
|
||||
import React from "react";
|
||||
import { Tooltip } from "../tooltip";
|
||||
import { cn } from "../../helpers";
|
||||
|
||||
type Props = {
|
||||
data: any;
|
||||
noTooltip?: boolean;
|
||||
inPercentage?: boolean;
|
||||
size?: "sm" | "md" | "lg";
|
||||
};
|
||||
|
||||
export const LinearProgressIndicator: React.FC<Props> = ({ data, noTooltip = false, inPercentage = false }) => {
|
||||
export const LinearProgressIndicator: React.FC<Props> = ({
|
||||
data,
|
||||
noTooltip = false,
|
||||
inPercentage = false,
|
||||
size = "sm",
|
||||
}) => {
|
||||
const total = data.reduce((acc: any, cur: any) => acc + cur.value, 0);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
let progress = 0;
|
||||
@ -23,18 +30,24 @@ export const LinearProgressIndicator: React.FC<Props> = ({ data, noTooltip = fal
|
||||
if (noTooltip) return <div style={style} />;
|
||||
else
|
||||
return (
|
||||
<Tooltip key={item.id} tooltipContent={`${item.name} ${Math.round(item.value)}%`}>
|
||||
<div style={style} className="first:rounded-l-full last:rounded-r-full" />
|
||||
<Tooltip key={item.id} tooltipContent={`${item.name} ${Math.round(item.value)}${inPercentage ? "%" : ""}`}>
|
||||
<div style={style} className="first:rounded-l-sm last:rounded-r-sm" />
|
||||
</Tooltip>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex h-1 w-full items-center justify-between gap-1">
|
||||
<div
|
||||
className={cn("flex w-full items-center justify-between gap-[1px] rounded-sm", {
|
||||
"h-2": size === "sm",
|
||||
"h-3": size === "md",
|
||||
"h-3.5": size === "lg",
|
||||
})}
|
||||
>
|
||||
{total === 0 ? (
|
||||
<div className="flex h-full w-full gap-1 bg-neutral-500">{bars}</div>
|
||||
<div className="flex h-full w-full gap-[1.5px] p-[2px] bg-custom-background-90 rounded-sm">{bars}</div>
|
||||
) : (
|
||||
<div className="flex h-full w-full gap-1">{bars}</div>
|
||||
<div className="flex h-full w-full gap-[1.5px] p-[2px] bg-custom-background-90 rounded-sm">{bars}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -40,9 +40,9 @@ export const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
|
||||
}`}`}
|
||||
target={activity.issue === null ? "_self" : "_blank"}
|
||||
rel={activity.issue === null ? "" : "noopener noreferrer"}
|
||||
className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline whitespace-nowrap"
|
||||
className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline"
|
||||
>
|
||||
{`${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}`}{" "}
|
||||
<span className="whitespace-nowrap">{`${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}`}</span>{" "}
|
||||
<span className="font-normal">{activity.issue_detail?.name}</span>
|
||||
</a>
|
||||
) : (
|
||||
@ -267,7 +267,7 @@ const activityDetails: {
|
||||
<span className="flex-shrink truncate font-medium text-custom-text-100">{activity.new_value}</span>
|
||||
</span>
|
||||
{showIssue && (
|
||||
<span>
|
||||
<span className="">
|
||||
{" "}
|
||||
to <IssueLink activity={activity} />
|
||||
</span>
|
||||
|
@ -2,6 +2,7 @@ import { MouseEvent } from "react";
|
||||
import Link from "next/link";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useCycle, useIssues, useProject, useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
@ -43,6 +44,7 @@ interface IActiveCycleDetails {
|
||||
export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props) => {
|
||||
// props
|
||||
const { workspaceSlug, projectId } = props;
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
const {
|
||||
@ -76,7 +78,9 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
);
|
||||
|
||||
const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS["active"];
|
||||
const emptyStateImage = getEmptyStateImagePath("cycle", "active", currentUser?.theme.theme === "light");
|
||||
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const emptyStateImage = getEmptyStateImagePath("cycle", "active", isLightMode);
|
||||
|
||||
if (!activeCycle && isLoading)
|
||||
return (
|
||||
@ -161,7 +165,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
<h3 className="break-words text-lg font-semibold">{truncateText(activeCycle.name, 70)}</h3>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<span className="flex items-center gap-1 capitalize">
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="flex gap-1 whitespace-nowrap rounded-sm text-sm px-3 py-0.5 bg-amber-500/10 text-amber-500">
|
||||
{`${daysLeft} ${daysLeft > 1 ? "days" : "day"} left`}
|
||||
</span>
|
||||
@ -251,7 +255,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
<div className="flex h-full w-full flex-col p-4 text-custom-text-200">
|
||||
<div className="flex w-full items-center gap-2 py-1">
|
||||
<span>Progress</span>
|
||||
<LinearProgressIndicator data={progressIndicatorData} inPercentage />
|
||||
<LinearProgressIndicator size="md" data={progressIndicatorData} inPercentage />
|
||||
</div>
|
||||
<div className="mt-2 flex flex-col items-center gap-1">
|
||||
{Object.keys(groupedIssues).map((group, index) => (
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// components
|
||||
@ -18,11 +19,15 @@ export interface ICyclesBoard {
|
||||
|
||||
export const CyclesBoard: FC<ICyclesBoard> = observer((props) => {
|
||||
const { cycleIds, filter, workspaceSlug, projectId, peekCycle } = props;
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS[filter as keyof typeof CYCLE_EMPTY_STATE_DETAILS];
|
||||
const emptyStateImage = getEmptyStateImagePath("cycle", filter, currentUser?.theme.theme === "light");
|
||||
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const emptyStateImage = getEmptyStateImagePath("cycle", filter, isLightMode);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// components
|
||||
@ -19,11 +20,15 @@ export interface ICyclesList {
|
||||
|
||||
export const CyclesList: FC<ICyclesList> = observer((props) => {
|
||||
const { cycleIds, filter, workspaceSlug, projectId } = props;
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS[filter as keyof typeof CYCLE_EMPTY_STATE_DETAILS];
|
||||
const emptyStateImage = getEmptyStateImagePath("cycle", filter, currentUser?.theme.theme === "light");
|
||||
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const emptyStateImage = getEmptyStateImagePath("cycle", filter, isLightMode);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
// components
|
||||
import { DateDropdown, ProjectDropdown } from "components/dropdowns";
|
||||
@ -11,19 +12,28 @@ import { ICycle } from "@plane/types";
|
||||
type Props = {
|
||||
handleFormSubmit: (values: Partial<ICycle>) => Promise<void>;
|
||||
handleClose: () => void;
|
||||
status: boolean;
|
||||
projectId: string;
|
||||
setActiveProject: (projectId: string) => void;
|
||||
data?: ICycle | null;
|
||||
};
|
||||
|
||||
const defaultValues: Partial<ICycle> = {
|
||||
name: "",
|
||||
description: "",
|
||||
start_date: null,
|
||||
end_date: null,
|
||||
};
|
||||
|
||||
export const CycleForm: React.FC<Props> = (props) => {
|
||||
const { handleFormSubmit, handleClose, projectId, setActiveProject, data } = props;
|
||||
const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data } = props;
|
||||
// form data
|
||||
const {
|
||||
formState: { errors, isSubmitting },
|
||||
handleSubmit,
|
||||
control,
|
||||
watch,
|
||||
reset,
|
||||
} = useForm<ICycle>({
|
||||
defaultValues: {
|
||||
project: projectId,
|
||||
@ -34,6 +44,13 @@ export const CycleForm: React.FC<Props> = (props) => {
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
reset({
|
||||
...defaultValues,
|
||||
...data,
|
||||
});
|
||||
}, [data, reset]);
|
||||
|
||||
const startDate = watch("start_date");
|
||||
const endDate = watch("end_date");
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// services
|
||||
import { CycleService } from "services/cycle.service";
|
||||
// hooks
|
||||
import { useApplication, useCycle } from "hooks/store";
|
||||
import { useApplication, useCycle, useProject } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
// components
|
||||
@ -25,11 +25,12 @@ const cycleService = new CycleService();
|
||||
export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||
const { isOpen, handleClose, data, workspaceSlug, projectId } = props;
|
||||
// states
|
||||
const [activeProject, setActiveProject] = useState<string>(projectId);
|
||||
const [activeProject, setActiveProject] = useState<string | null>(null);
|
||||
// store hooks
|
||||
const {
|
||||
eventTracker: { postHogEventTracker },
|
||||
} = useApplication();
|
||||
const { workspaceProjectIds } = useProject();
|
||||
const { createCycle, updateCycleDetails } = useCycle();
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
@ -134,6 +135,27 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// if modal is closed, reset active project to null
|
||||
// and return to avoid activeProject being set to some other project
|
||||
if (!isOpen) {
|
||||
setActiveProject(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// if data is present, set active project to the project of the
|
||||
// issue. This has more priority than the project in the url.
|
||||
if (data && data.project) {
|
||||
setActiveProject(data.project);
|
||||
return;
|
||||
}
|
||||
|
||||
// if data is not present, set active project to the project
|
||||
// in the url. This has the least priority.
|
||||
if (workspaceProjectIds && workspaceProjectIds.length > 0 && !activeProject)
|
||||
setActiveProject(projectId ?? workspaceProjectIds?.[0] ?? null);
|
||||
}, [activeProject, data, projectId, workspaceProjectIds, isOpen]);
|
||||
|
||||
return (
|
||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
||||
@ -164,7 +186,8 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||
<CycleForm
|
||||
handleFormSubmit={handleFormSubmit}
|
||||
handleClose={handleClose}
|
||||
projectId={activeProject}
|
||||
status={data ? true : false}
|
||||
projectId={activeProject ?? ""}
|
||||
setActiveProject={setActiveProject}
|
||||
data={data}
|
||||
/>
|
||||
|
@ -50,11 +50,11 @@ export const DashboardWidgets = observer(() => {
|
||||
// if the widget is full width, return it in a 2 column grid
|
||||
if (widget.fullWidth)
|
||||
return (
|
||||
<div className="lg:col-span-2">
|
||||
<div key={key} className="lg:col-span-2">
|
||||
<WidgetComponent dashboardId={homeDashboardId} workspaceSlug={workspaceSlug} />
|
||||
</div>
|
||||
);
|
||||
else return <WidgetComponent dashboardId={homeDashboardId} workspaceSlug={workspaceSlug} />;
|
||||
else return <WidgetComponent key={key} dashboardId={homeDashboardId} workspaceSlug={workspaceSlug} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
@ -99,7 +99,7 @@ export const CreatedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
|
||||
</div>
|
||||
<Tab.Panels as="div" className="h-full">
|
||||
{ISSUES_TABS_LIST.map((tab) => (
|
||||
<Tab.Panel as="div" className="h-full flex flex-col">
|
||||
<Tab.Panel key={tab.key} as="div" className="h-full flex flex-col">
|
||||
<WidgetIssuesList
|
||||
issues={widgetStats.issues}
|
||||
tab={tab.key}
|
||||
|
@ -86,7 +86,7 @@ export const OverviewStatsWidget: React.FC<WidgetProps> = observer((props) => {
|
||||
)}
|
||||
>
|
||||
<h5 className="font-semibold text-xl">{stat.count}</h5>
|
||||
<p className="text-custom-text-300">{stat.title}</p>
|
||||
<p className="text-custom-text-300 text-sm xl:text-base">{stat.title}</p>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
@ -57,7 +57,7 @@ const ProjectListItem: React.FC<ProjectListItemProps> = observer((props) => {
|
||||
<div className="mt-2">
|
||||
<AvatarGroup>
|
||||
{projectDetails.members?.map((member) => (
|
||||
<Avatar src={member.member__avatar} name={member.member__display_name} />
|
||||
<Avatar key={member.member_id} src={member.member__avatar} name={member.member__display_name} />
|
||||
))}
|
||||
</AvatarGroup>
|
||||
</div>
|
||||
|
@ -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;
|
@ -30,6 +30,7 @@ type ButtonProps = {
|
||||
hideIcon: boolean;
|
||||
hideText?: boolean;
|
||||
dropdownArrow: boolean;
|
||||
isActive?: boolean;
|
||||
dropdownArrowClassName: string;
|
||||
placeholder: string;
|
||||
tooltip: boolean;
|
||||
@ -51,6 +52,7 @@ const BorderButton = (props: ButtonProps) => {
|
||||
dropdownArrowClassName,
|
||||
hideIcon = false,
|
||||
hideText = false,
|
||||
isActive = false,
|
||||
placeholder,
|
||||
tooltip,
|
||||
} = props;
|
||||
@ -60,6 +62,7 @@ const BorderButton = (props: ButtonProps) => {
|
||||
<div
|
||||
className={cn(
|
||||
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
|
||||
{ "bg-custom-background-80": isActive },
|
||||
className
|
||||
)}
|
||||
>
|
||||
@ -111,6 +114,7 @@ const TransparentButton = (props: ButtonProps) => {
|
||||
dropdownArrowClassName,
|
||||
hideIcon = false,
|
||||
hideText = false,
|
||||
isActive = false,
|
||||
placeholder,
|
||||
tooltip,
|
||||
} = props;
|
||||
@ -120,6 +124,7 @@ const TransparentButton = (props: ButtonProps) => {
|
||||
<div
|
||||
className={cn(
|
||||
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
|
||||
{ "bg-custom-background-80": isActive },
|
||||
className
|
||||
)}
|
||||
>
|
||||
@ -268,6 +273,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : buttonVariant === "border-without-text" ? (
|
||||
@ -279,6 +285,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
|
||||
hideIcon={hideIcon}
|
||||
hideText
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : buttonVariant === "background-with-text" ? (
|
||||
@ -310,6 +317,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : buttonVariant === "transparent-without-text" ? (
|
||||
@ -321,6 +329,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
|
||||
hideIcon={hideIcon}
|
||||
hideText
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : null}
|
||||
|
@ -2,7 +2,7 @@ import React, { useRef, useState } from "react";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
import DatePicker from "react-datepicker";
|
||||
import { usePopper } from "react-popper";
|
||||
import { CalendarDays, X } from "lucide-react";
|
||||
import { Calendar, CalendarDays, X } from "lucide-react";
|
||||
// hooks
|
||||
import { useDropdownKeyDown } from "hooks/use-dropdown-key-down";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
@ -23,6 +23,7 @@ type Props = TDropdownProps & {
|
||||
onChange: (val: Date | null) => void;
|
||||
value: Date | string | null;
|
||||
closeOnSelect?: boolean;
|
||||
showPlaceholderIcon?: boolean;
|
||||
};
|
||||
|
||||
type ButtonProps = {
|
||||
@ -33,9 +34,11 @@ type ButtonProps = {
|
||||
isClearable: boolean;
|
||||
hideIcon?: boolean;
|
||||
hideText?: boolean;
|
||||
isActive?: boolean;
|
||||
onClear: () => void;
|
||||
placeholder: string;
|
||||
tooltip: boolean;
|
||||
showPlaceholderIcon?: boolean;
|
||||
};
|
||||
|
||||
const BorderButton = (props: ButtonProps) => {
|
||||
@ -47,6 +50,7 @@ const BorderButton = (props: ButtonProps) => {
|
||||
isClearable,
|
||||
hideIcon = false,
|
||||
hideText = false,
|
||||
isActive = false,
|
||||
onClear,
|
||||
placeholder,
|
||||
tooltip,
|
||||
@ -61,6 +65,7 @@ const BorderButton = (props: ButtonProps) => {
|
||||
<div
|
||||
className={cn(
|
||||
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
|
||||
{ "bg-custom-background-80": isActive },
|
||||
className
|
||||
)}
|
||||
>
|
||||
@ -131,9 +136,11 @@ const TransparentButton = (props: ButtonProps) => {
|
||||
isClearable,
|
||||
hideIcon = false,
|
||||
hideText = false,
|
||||
isActive = false,
|
||||
onClear,
|
||||
placeholder,
|
||||
tooltip,
|
||||
showPlaceholderIcon = false,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
@ -145,11 +152,16 @@ const TransparentButton = (props: ButtonProps) => {
|
||||
<div
|
||||
className={cn(
|
||||
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
|
||||
{ "bg-custom-background-80": isActive },
|
||||
className
|
||||
)}
|
||||
>
|
||||
{!hideIcon && icon}
|
||||
{!hideText && <span className="flex-grow truncate">{date ? renderFormattedDate(date) : placeholder}</span>}
|
||||
{showPlaceholderIcon && !date && (
|
||||
<Calendar className="h-2.5 w-2.5 flex-shrink-0 hidden group-hover:inline text-custom-text-400" />
|
||||
)}
|
||||
|
||||
{isClearable && (
|
||||
<X
|
||||
className={cn("h-2 w-2 flex-shrink-0", clearIconClassName)}
|
||||
@ -183,6 +195,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
|
||||
placement,
|
||||
tabIndex,
|
||||
tooltip = false,
|
||||
showPlaceholderIcon = false,
|
||||
value,
|
||||
} = props;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@ -246,6 +259,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
|
||||
placeholder={placeholder}
|
||||
isClearable={isClearable && isDateSelected}
|
||||
onClear={() => onChange(null)}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : buttonVariant === "border-without-text" ? (
|
||||
@ -258,6 +272,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
|
||||
placeholder={placeholder}
|
||||
isClearable={isClearable && isDateSelected}
|
||||
onClear={() => onChange(null)}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
hideText
|
||||
/>
|
||||
@ -296,7 +311,9 @@ export const DateDropdown: React.FC<Props> = (props) => {
|
||||
placeholder={placeholder}
|
||||
isClearable={isClearable && isDateSelected}
|
||||
onClear={() => onChange(null)}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
showPlaceholderIcon={showPlaceholderIcon}
|
||||
/>
|
||||
) : buttonVariant === "transparent-without-text" ? (
|
||||
<TransparentButton
|
||||
@ -308,8 +325,10 @@ export const DateDropdown: React.FC<Props> = (props) => {
|
||||
placeholder={placeholder}
|
||||
isClearable={isClearable && isDateSelected}
|
||||
onClear={() => onChange(null)}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
hideText
|
||||
showPlaceholderIcon={showPlaceholderIcon}
|
||||
/>
|
||||
) : null}
|
||||
</button>
|
||||
|
@ -31,6 +31,7 @@ type ButtonProps = {
|
||||
dropdownArrowClassName: string;
|
||||
hideIcon?: boolean;
|
||||
hideText?: boolean;
|
||||
isActive?: boolean;
|
||||
placeholder: string;
|
||||
tooltip: boolean;
|
||||
};
|
||||
@ -51,6 +52,7 @@ const BorderButton = (props: ButtonProps) => {
|
||||
dropdownArrowClassName,
|
||||
hideIcon = false,
|
||||
hideText = false,
|
||||
isActive = false,
|
||||
placeholder,
|
||||
tooltip,
|
||||
} = props;
|
||||
@ -64,6 +66,7 @@ const BorderButton = (props: ButtonProps) => {
|
||||
<div
|
||||
className={cn(
|
||||
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
|
||||
{ "bg-custom-background-80": isActive },
|
||||
className
|
||||
)}
|
||||
>
|
||||
@ -123,6 +126,7 @@ const TransparentButton = (props: ButtonProps) => {
|
||||
dropdownArrowClassName,
|
||||
hideIcon = false,
|
||||
hideText = false,
|
||||
isActive = false,
|
||||
placeholder,
|
||||
tooltip,
|
||||
} = props;
|
||||
@ -136,6 +140,7 @@ const TransparentButton = (props: ButtonProps) => {
|
||||
<div
|
||||
className={cn(
|
||||
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
|
||||
{ "bg-custom-background-80": isActive },
|
||||
className
|
||||
)}
|
||||
>
|
||||
@ -276,6 +281,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : buttonVariant === "border-without-text" ? (
|
||||
@ -287,6 +293,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
|
||||
hideIcon={hideIcon}
|
||||
hideText
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : buttonVariant === "background-with-text" ? (
|
||||
@ -318,6 +325,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : buttonVariant === "transparent-without-text" ? (
|
||||
@ -329,6 +337,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
|
||||
hideIcon={hideIcon}
|
||||
hideText
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : null}
|
||||
|
@ -14,6 +14,7 @@ type ButtonProps = {
|
||||
placeholder: string;
|
||||
hideIcon?: boolean;
|
||||
hideText?: boolean;
|
||||
isActive?: boolean;
|
||||
tooltip: boolean;
|
||||
userIds: string | string[] | null;
|
||||
};
|
||||
@ -50,6 +51,7 @@ export const BorderButton = observer((props: ButtonProps) => {
|
||||
dropdownArrowClassName,
|
||||
hideIcon = false,
|
||||
hideText = false,
|
||||
isActive = false,
|
||||
placeholder,
|
||||
userIds,
|
||||
tooltip,
|
||||
@ -57,7 +59,7 @@ export const BorderButton = observer((props: ButtonProps) => {
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
|
||||
const isMultiple = Array.isArray(userIds);
|
||||
const isArray = Array.isArray(userIds);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
@ -68,13 +70,18 @@ export const BorderButton = observer((props: ButtonProps) => {
|
||||
<div
|
||||
className={cn(
|
||||
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
|
||||
{ "bg-custom-background-80": isActive },
|
||||
className
|
||||
)}
|
||||
>
|
||||
{!hideIcon && <ButtonAvatars tooltip={tooltip} userIds={userIds} />}
|
||||
{!hideText && (
|
||||
<span className="flex-grow truncate">
|
||||
{userIds ? (isMultiple ? placeholder : getUserDetails(userIds)?.display_name) : placeholder}
|
||||
<span className="flex-grow truncate text-sm leading-5">
|
||||
{isArray && userIds.length > 0
|
||||
? userIds.length === 1
|
||||
? getUserDetails(userIds[0])?.display_name
|
||||
: ""
|
||||
: placeholder}
|
||||
</span>
|
||||
)}
|
||||
{dropdownArrow && (
|
||||
@ -99,7 +106,7 @@ export const BackgroundButton = observer((props: ButtonProps) => {
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
|
||||
const isMultiple = Array.isArray(userIds);
|
||||
const isArray = Array.isArray(userIds);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
@ -115,8 +122,12 @@ export const BackgroundButton = observer((props: ButtonProps) => {
|
||||
>
|
||||
{!hideIcon && <ButtonAvatars tooltip={tooltip} userIds={userIds} />}
|
||||
{!hideText && (
|
||||
<span className="flex-grow truncate">
|
||||
{userIds ? (isMultiple ? placeholder : getUserDetails(userIds)?.display_name) : placeholder}
|
||||
<span className="flex-grow truncate text-sm leading-5">
|
||||
{isArray && userIds.length > 0
|
||||
? userIds.length === 1
|
||||
? getUserDetails(userIds[0])?.display_name
|
||||
: ""
|
||||
: placeholder}
|
||||
</span>
|
||||
)}
|
||||
{dropdownArrow && (
|
||||
@ -134,6 +145,7 @@ export const TransparentButton = observer((props: ButtonProps) => {
|
||||
dropdownArrowClassName,
|
||||
hideIcon = false,
|
||||
hideText = false,
|
||||
isActive = false,
|
||||
placeholder,
|
||||
userIds,
|
||||
tooltip,
|
||||
@ -141,7 +153,7 @@ export const TransparentButton = observer((props: ButtonProps) => {
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
|
||||
const isMultiple = Array.isArray(userIds);
|
||||
const isArray = Array.isArray(userIds);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
@ -152,13 +164,18 @@ export const TransparentButton = observer((props: ButtonProps) => {
|
||||
<div
|
||||
className={cn(
|
||||
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
|
||||
{ "bg-custom-background-80": isActive },
|
||||
className
|
||||
)}
|
||||
>
|
||||
{!hideIcon && <ButtonAvatars tooltip={tooltip} userIds={userIds} />}
|
||||
{!hideText && (
|
||||
<span className="flex-grow truncate">
|
||||
{userIds ? (isMultiple ? placeholder : getUserDetails(userIds)?.display_name) : placeholder}
|
||||
<span className="flex-grow truncate text-sm leading-5">
|
||||
{isArray && userIds.length > 0
|
||||
? userIds.length === 1
|
||||
? getUserDetails(userIds[0])?.display_name
|
||||
: ""
|
||||
: placeholder}
|
||||
</span>
|
||||
)}
|
||||
{dropdownArrow && (
|
||||
|
@ -147,6 +147,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : buttonVariant === "border-without-text" ? (
|
||||
@ -157,6 +158,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
hideText
|
||||
/>
|
||||
@ -189,6 +191,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : buttonVariant === "transparent-without-text" ? (
|
||||
@ -199,6 +202,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
hideText
|
||||
/>
|
||||
|
@ -38,6 +38,7 @@ type ButtonProps = {
|
||||
dropdownArrowClassName: string;
|
||||
hideIcon?: boolean;
|
||||
hideText?: boolean;
|
||||
isActive?: boolean;
|
||||
module: IModule | null;
|
||||
placeholder: string;
|
||||
tooltip: boolean;
|
||||
@ -50,6 +51,7 @@ const BorderButton = (props: ButtonProps) => {
|
||||
dropdownArrowClassName,
|
||||
hideIcon = false,
|
||||
hideText = false,
|
||||
isActive = false,
|
||||
module,
|
||||
placeholder,
|
||||
tooltip,
|
||||
@ -60,6 +62,7 @@ const BorderButton = (props: ButtonProps) => {
|
||||
<div
|
||||
className={cn(
|
||||
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
|
||||
{ "bg-custom-background-80": isActive },
|
||||
className
|
||||
)}
|
||||
>
|
||||
@ -110,6 +113,7 @@ const TransparentButton = (props: ButtonProps) => {
|
||||
dropdownArrowClassName,
|
||||
hideIcon = false,
|
||||
hideText = false,
|
||||
isActive = false,
|
||||
module,
|
||||
placeholder,
|
||||
tooltip,
|
||||
@ -120,6 +124,7 @@ const TransparentButton = (props: ButtonProps) => {
|
||||
<div
|
||||
className={cn(
|
||||
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
|
||||
{ "bg-custom-background-80": isActive },
|
||||
className
|
||||
)}
|
||||
>
|
||||
@ -267,6 +272,7 @@ export const ModuleDropdown: React.FC<Props> = observer((props) => {
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : buttonVariant === "border-without-text" ? (
|
||||
@ -278,6 +284,7 @@ export const ModuleDropdown: React.FC<Props> = observer((props) => {
|
||||
hideIcon={hideIcon}
|
||||
hideText
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : buttonVariant === "background-with-text" ? (
|
||||
@ -309,6 +316,7 @@ export const ModuleDropdown: React.FC<Props> = observer((props) => {
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : buttonVariant === "transparent-without-text" ? (
|
||||
@ -320,6 +328,7 @@ export const ModuleDropdown: React.FC<Props> = observer((props) => {
|
||||
hideIcon={hideIcon}
|
||||
hideText
|
||||
placeholder={placeholder}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : null}
|
||||
|
@ -31,6 +31,7 @@ type ButtonProps = {
|
||||
dropdownArrowClassName: string;
|
||||
hideIcon?: boolean;
|
||||
hideText?: boolean;
|
||||
isActive?: boolean;
|
||||
highlightUrgent: boolean;
|
||||
priority: TIssuePriorities;
|
||||
tooltip: boolean;
|
||||
@ -181,6 +182,7 @@ const TransparentButton = (props: ButtonProps) => {
|
||||
dropdownArrowClassName,
|
||||
hideIcon = false,
|
||||
hideText = false,
|
||||
isActive = false,
|
||||
highlightUrgent,
|
||||
priority,
|
||||
tooltip,
|
||||
@ -207,6 +209,7 @@ const TransparentButton = (props: ButtonProps) => {
|
||||
"px-0.5": hideText,
|
||||
// highlight the whole button if text is hidden and priority is urgent
|
||||
"bg-red-500 border-red-500": priority === "urgent" && hideText && highlightUrgent,
|
||||
"bg-custom-background-80": isActive,
|
||||
},
|
||||
className
|
||||
)}
|
||||
@ -312,7 +315,13 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
|
||||
as="div"
|
||||
ref={dropdownRef}
|
||||
tabIndex={tabIndex}
|
||||
className={cn("h-full", className)}
|
||||
className={cn(
|
||||
"h-full",
|
||||
{
|
||||
"bg-custom-background-80": isOpen,
|
||||
},
|
||||
className
|
||||
)}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
@ -402,6 +411,7 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
|
||||
dropdownArrow={dropdownArrow && !disabled}
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : buttonVariant === "transparent-without-text" ? (
|
||||
@ -414,6 +424,7 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
|
||||
dropdownArrow={dropdownArrow && !disabled}
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
hideText
|
||||
/>
|
||||
|
@ -30,6 +30,7 @@ type ButtonProps = {
|
||||
dropdownArrowClassName: string;
|
||||
hideIcon?: boolean;
|
||||
hideText?: boolean;
|
||||
isActive?: boolean;
|
||||
state: IState | undefined;
|
||||
tooltip: boolean;
|
||||
};
|
||||
@ -41,6 +42,7 @@ const BorderButton = (props: ButtonProps) => {
|
||||
dropdownArrowClassName,
|
||||
hideIcon = false,
|
||||
hideText = false,
|
||||
isActive = false,
|
||||
state,
|
||||
tooltip,
|
||||
} = props;
|
||||
@ -50,6 +52,9 @@ const BorderButton = (props: ButtonProps) => {
|
||||
<div
|
||||
className={cn(
|
||||
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
|
||||
{
|
||||
"bg-custom-background-80": isActive,
|
||||
},
|
||||
className
|
||||
)}
|
||||
>
|
||||
@ -111,6 +116,7 @@ const TransparentButton = (props: ButtonProps) => {
|
||||
dropdownArrowClassName,
|
||||
hideIcon = false,
|
||||
hideText = false,
|
||||
isActive = false,
|
||||
state,
|
||||
tooltip,
|
||||
} = props;
|
||||
@ -120,6 +126,9 @@ const TransparentButton = (props: ButtonProps) => {
|
||||
<div
|
||||
className={cn(
|
||||
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
|
||||
{
|
||||
"bg-custom-background-80": isActive,
|
||||
},
|
||||
className
|
||||
)}
|
||||
>
|
||||
@ -251,6 +260,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
||||
dropdownArrow={dropdownArrow && !disabled}
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : buttonVariant === "border-without-text" ? (
|
||||
@ -260,6 +270,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
||||
dropdownArrow={dropdownArrow && !disabled}
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
hideText
|
||||
/>
|
||||
@ -289,6 +300,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
||||
dropdownArrow={dropdownArrow && !disabled}
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
) : buttonVariant === "transparent-without-text" ? (
|
||||
@ -298,6 +310,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
||||
dropdownArrow={dropdownArrow && !disabled}
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
isActive={isOpen}
|
||||
tooltip={tooltip}
|
||||
hideText
|
||||
/>
|
||||
|
@ -2,7 +2,7 @@ import { FC } from "react";
|
||||
// hooks
|
||||
import { useChart } from "../hooks";
|
||||
// helpers
|
||||
import { ChartDraggable } from "../helpers/draggable";
|
||||
import { ChartAddBlock, ChartDraggable } from "components/gantt-chart";
|
||||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||
// types
|
||||
import { IBlockUpdateData, IGanttBlock } from "../types";
|
||||
@ -15,6 +15,7 @@ export type GanttChartBlocksProps = {
|
||||
enableBlockLeftResize: boolean;
|
||||
enableBlockRightResize: boolean;
|
||||
enableBlockMove: boolean;
|
||||
showAllBlocks: boolean;
|
||||
};
|
||||
|
||||
export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
|
||||
@ -26,6 +27,7 @@ export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
|
||||
enableBlockLeftResize,
|
||||
enableBlockRightResize,
|
||||
enableBlockMove,
|
||||
showAllBlocks,
|
||||
} = props;
|
||||
|
||||
const { activeBlock, dispatch } = useChart();
|
||||
@ -45,6 +47,8 @@ export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
|
||||
totalBlockShifts: number,
|
||||
dragDirection: "left" | "right" | "move"
|
||||
) => {
|
||||
if (!block.start_date || !block.target_date) return;
|
||||
|
||||
const originalStartDate = new Date(block.start_date);
|
||||
const updatedStartDate = new Date(originalStartDate);
|
||||
|
||||
@ -75,27 +79,31 @@ export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
|
||||
>
|
||||
{blocks &&
|
||||
blocks.length > 0 &&
|
||||
blocks.map(
|
||||
(block) =>
|
||||
block.start_date &&
|
||||
block.target_date && (
|
||||
<div
|
||||
key={`block-${block.id}`}
|
||||
className={`h-11 ${activeBlock?.id === block.id ? "bg-custom-background-80" : ""}`}
|
||||
onMouseEnter={() => updateActiveBlock(block)}
|
||||
onMouseLeave={() => updateActiveBlock(null)}
|
||||
>
|
||||
<ChartDraggable
|
||||
block={block}
|
||||
blockToRender={blockToRender}
|
||||
handleBlock={(...args) => handleChartBlockPosition(block, ...args)}
|
||||
enableBlockLeftResize={enableBlockLeftResize}
|
||||
enableBlockRightResize={enableBlockRightResize}
|
||||
enableBlockMove={enableBlockMove}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
blocks.map((block) => {
|
||||
// hide the block if it doesn't have start and target dates and showAllBlocks is false
|
||||
if (!showAllBlocks && !(block.start_date && block.target_date)) return;
|
||||
|
||||
const isBlockVisibleOnChart = block.start_date && block.target_date;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`block-${block.id}`}
|
||||
className={`h-11 ${activeBlock?.id === block.id ? "bg-custom-background-80" : ""}`}
|
||||
onMouseEnter={() => updateActiveBlock(block)}
|
||||
onMouseLeave={() => updateActiveBlock(null)}
|
||||
>
|
||||
{!isBlockVisibleOnChart && <ChartAddBlock block={block} blockUpdateHandler={blockUpdateHandler} />}
|
||||
<ChartDraggable
|
||||
block={block}
|
||||
blockToRender={blockToRender}
|
||||
handleBlock={(...args) => handleChartBlockPosition(block, ...args)}
|
||||
enableBlockLeftResize={enableBlockLeftResize}
|
||||
enableBlockRightResize={enableBlockRightResize}
|
||||
enableBlockMove={enableBlockMove}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -46,22 +46,25 @@ type ChartViewRootProps = {
|
||||
enableBlockMove: boolean;
|
||||
enableReorder: boolean;
|
||||
bottomSpacing: boolean;
|
||||
showAllBlocks: boolean;
|
||||
};
|
||||
|
||||
export const ChartViewRoot: FC<ChartViewRootProps> = ({
|
||||
border,
|
||||
title,
|
||||
blocks = null,
|
||||
loaderTitle,
|
||||
blockUpdateHandler,
|
||||
sidebarToRender,
|
||||
blockToRender,
|
||||
enableBlockLeftResize,
|
||||
enableBlockRightResize,
|
||||
enableBlockMove,
|
||||
enableReorder,
|
||||
bottomSpacing,
|
||||
}) => {
|
||||
export const ChartViewRoot: FC<ChartViewRootProps> = (props) => {
|
||||
const {
|
||||
border,
|
||||
title,
|
||||
blocks = null,
|
||||
loaderTitle,
|
||||
blockUpdateHandler,
|
||||
sidebarToRender,
|
||||
blockToRender,
|
||||
enableBlockLeftResize,
|
||||
enableBlockRightResize,
|
||||
enableBlockMove,
|
||||
enableReorder,
|
||||
bottomSpacing,
|
||||
showAllBlocks,
|
||||
} = props;
|
||||
// states
|
||||
const [itemsContainerWidth, setItemsContainerWidth] = useState<number>(0);
|
||||
const [fullScreenMode, setFullScreenMode] = useState<boolean>(false);
|
||||
@ -311,6 +314,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
|
||||
enableBlockLeftResize={enableBlockLeftResize}
|
||||
enableBlockRightResize={enableBlockRightResize}
|
||||
enableBlockMove={enableBlockMove}
|
||||
showAllBlocks={showAllBlocks}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { FC } from "react";
|
||||
|
||||
// hooks
|
||||
import { useChart } from "../hooks";
|
||||
// types
|
||||
|
91
web/components/gantt-chart/helpers/add-block.tsx
Normal file
91
web/components/gantt-chart/helpers/add-block.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -3,14 +3,11 @@ import { TIssue } from "@plane/types";
|
||||
import { IGanttBlock } from "components/gantt-chart";
|
||||
|
||||
export const renderIssueBlocksStructure = (blocks: TIssue[]): IGanttBlock[] =>
|
||||
blocks && blocks.length > 0
|
||||
? blocks
|
||||
.filter((b) => new Date(b?.start_date ?? "") <= new Date(b?.target_date ?? ""))
|
||||
.map((block) => ({
|
||||
data: block,
|
||||
id: block.id,
|
||||
sort_order: block.sort_order,
|
||||
start_date: new Date(block.start_date ?? ""),
|
||||
target_date: new Date(block.target_date ?? ""),
|
||||
}))
|
||||
: [];
|
||||
blocks &&
|
||||
blocks.map((block) => ({
|
||||
data: block,
|
||||
id: block.id,
|
||||
sort_order: block.sort_order,
|
||||
start_date: block.start_date ? new Date(block.start_date) : null,
|
||||
target_date: block.target_date ? new Date(block.target_date) : null,
|
||||
}));
|
||||
|
@ -1,6 +1,4 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
// icons
|
||||
import { ArrowLeft, ArrowRight } from "lucide-react";
|
||||
// hooks
|
||||
import { useChart } from "../hooks";
|
||||
@ -16,23 +14,17 @@ type Props = {
|
||||
enableBlockMove: boolean;
|
||||
};
|
||||
|
||||
export const ChartDraggable: React.FC<Props> = ({
|
||||
block,
|
||||
blockToRender,
|
||||
handleBlock,
|
||||
enableBlockLeftResize,
|
||||
enableBlockRightResize,
|
||||
enableBlockMove,
|
||||
}) => {
|
||||
export const ChartDraggable: React.FC<Props> = (props) => {
|
||||
const { block, blockToRender, handleBlock, enableBlockLeftResize, enableBlockRightResize, enableBlockMove } = props;
|
||||
// states
|
||||
const [isLeftResizing, setIsLeftResizing] = useState(false);
|
||||
const [isRightResizing, setIsRightResizing] = useState(false);
|
||||
const [isMoving, setIsMoving] = useState(false);
|
||||
const [posFromLeft, setPosFromLeft] = useState<number | null>(null);
|
||||
|
||||
// refs
|
||||
const resizableRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// chart hook
|
||||
const { currentViewData, scrollLeft } = useChart();
|
||||
|
||||
// check if cursor reaches either end while resizing/dragging
|
||||
const checkScrollEnd = (e: MouseEvent): number => {
|
||||
const SCROLL_THRESHOLD = 70;
|
||||
@ -68,7 +60,6 @@ export const ChartDraggable: React.FC<Props> = ({
|
||||
|
||||
return delWidth;
|
||||
};
|
||||
|
||||
// handle block resize from the left end
|
||||
const handleBlockLeftResize = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
if (!currentViewData || !resizableRef.current || !block.position) return;
|
||||
@ -120,7 +111,6 @@ export const ChartDraggable: React.FC<Props> = ({
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
|
||||
// handle block resize from the right end
|
||||
const handleBlockRightResize = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
if (!currentViewData || !resizableRef.current || !block.position) return;
|
||||
@ -163,7 +153,6 @@ export const ChartDraggable: React.FC<Props> = ({
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
|
||||
// handle block x-axis move
|
||||
const handleBlockMove = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
if (!enableBlockMove || !currentViewData || !resizableRef.current || !block.position) return;
|
||||
@ -210,7 +199,6 @@ export const ChartDraggable: React.FC<Props> = ({
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
|
||||
// scroll to a hidden block
|
||||
const handleScrollToBlock = () => {
|
||||
const scrollContainer = document.querySelector("#scroll-container") as HTMLElement;
|
||||
@ -220,7 +208,6 @@ export const ChartDraggable: React.FC<Props> = ({
|
||||
// update container's scroll position to the block's position
|
||||
scrollContainer.scrollLeft = block.position.marginLeft - 4;
|
||||
};
|
||||
|
||||
// update block position from viewport's left end on scroll
|
||||
useEffect(() => {
|
||||
const block = resizableRef.current;
|
||||
@ -229,7 +216,6 @@ export const ChartDraggable: React.FC<Props> = ({
|
||||
|
||||
setPosFromLeft(block.getBoundingClientRect().left);
|
||||
}, [scrollLeft]);
|
||||
|
||||
// check if block is hidden on either side
|
||||
const isBlockHiddenOnLeft =
|
||||
block.position?.marginLeft &&
|
||||
|
@ -1 +1,3 @@
|
||||
export * from "./add-block";
|
||||
export * from "./block-structure";
|
||||
export * from "./draggable";
|
||||
|
@ -19,36 +19,43 @@ type GanttChartRootProps = {
|
||||
enableBlockMove?: boolean;
|
||||
enableReorder?: boolean;
|
||||
bottomSpacing?: boolean;
|
||||
showAllBlocks?: boolean;
|
||||
};
|
||||
|
||||
export const GanttChartRoot: FC<GanttChartRootProps> = ({
|
||||
border = true,
|
||||
title,
|
||||
blocks,
|
||||
loaderTitle = "blocks",
|
||||
blockUpdateHandler,
|
||||
sidebarToRender,
|
||||
blockToRender,
|
||||
enableBlockLeftResize = true,
|
||||
enableBlockRightResize = true,
|
||||
enableBlockMove = true,
|
||||
enableReorder = true,
|
||||
bottomSpacing = false,
|
||||
}) => (
|
||||
<ChartContextProvider>
|
||||
<ChartViewRoot
|
||||
border={border}
|
||||
title={title}
|
||||
blocks={blocks}
|
||||
loaderTitle={loaderTitle}
|
||||
blockUpdateHandler={blockUpdateHandler}
|
||||
sidebarToRender={sidebarToRender}
|
||||
blockToRender={blockToRender}
|
||||
enableBlockLeftResize={enableBlockLeftResize}
|
||||
enableBlockRightResize={enableBlockRightResize}
|
||||
enableBlockMove={enableBlockMove}
|
||||
enableReorder={enableReorder}
|
||||
bottomSpacing={bottomSpacing}
|
||||
/>
|
||||
</ChartContextProvider>
|
||||
);
|
||||
export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
|
||||
const {
|
||||
border = true,
|
||||
title,
|
||||
blocks,
|
||||
loaderTitle = "blocks",
|
||||
blockUpdateHandler,
|
||||
sidebarToRender,
|
||||
blockToRender,
|
||||
enableBlockLeftResize = true,
|
||||
enableBlockRightResize = true,
|
||||
enableBlockMove = true,
|
||||
enableReorder = true,
|
||||
bottomSpacing = false,
|
||||
showAllBlocks = false,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<ChartContextProvider>
|
||||
<ChartViewRoot
|
||||
border={border}
|
||||
title={title}
|
||||
blocks={blocks}
|
||||
loaderTitle={loaderTitle}
|
||||
blockUpdateHandler={blockUpdateHandler}
|
||||
sidebarToRender={sidebarToRender}
|
||||
blockToRender={blockToRender}
|
||||
enableBlockLeftResize={enableBlockLeftResize}
|
||||
enableBlockRightResize={enableBlockRightResize}
|
||||
enableBlockMove={enableBlockMove}
|
||||
enableReorder={enableReorder}
|
||||
bottomSpacing={bottomSpacing}
|
||||
showAllBlocks={showAllBlocks}
|
||||
/>
|
||||
</ChartContextProvider>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
|
||||
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
||||
import { DragDropContext, Draggable, DropResult, Droppable } from "@hello-pangea/dnd";
|
||||
import { MoreVertical } from "lucide-react";
|
||||
// hooks
|
||||
import { useChart } from "components/gantt-chart/hooks";
|
||||
@ -83,7 +82,7 @@ export const CycleGanttSidebar: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={handleOrderChange}>
|
||||
<StrictModeDroppable droppableId="gantt-sidebar">
|
||||
<Droppable droppableId="gantt-sidebar">
|
||||
{(droppableProvided) => (
|
||||
<div
|
||||
id={`gantt-sidebar-${cycleId}`}
|
||||
@ -153,7 +152,7 @@ export const CycleGanttSidebar: React.FC<Props> = (props) => {
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</StrictModeDroppable>
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
|
||||
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
||||
import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea/dnd";
|
||||
import { MoreVertical } from "lucide-react";
|
||||
// hooks
|
||||
import { useChart } from "components/gantt-chart/hooks";
|
||||
@ -83,7 +82,7 @@ export const ModuleGanttSidebar: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={handleOrderChange}>
|
||||
<StrictModeDroppable droppableId="gantt-sidebar">
|
||||
<Droppable droppableId="gantt-sidebar">
|
||||
{(droppableProvided) => (
|
||||
<div
|
||||
id={`gantt-sidebar-${cycleId}`}
|
||||
@ -153,7 +152,7 @@ export const ModuleGanttSidebar: React.FC<Props> = (props) => {
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</StrictModeDroppable>
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
|
||||
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
||||
import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea/dnd";
|
||||
import { MoreVertical } from "lucide-react";
|
||||
// hooks
|
||||
import { useChart } from "components/gantt-chart/hooks";
|
||||
@ -84,7 +83,7 @@ export const ProjectViewGanttSidebar: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={handleOrderChange}>
|
||||
<StrictModeDroppable droppableId="gantt-sidebar">
|
||||
<Droppable droppableId="gantt-sidebar">
|
||||
{(droppableProvided) => (
|
||||
<div
|
||||
id={`gantt-sidebar-${cycleId}`}
|
||||
@ -154,7 +153,7 @@ export const ProjectViewGanttSidebar: React.FC<Props> = (props) => {
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</StrictModeDroppable>
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
|
||||
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
||||
import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea/dnd";
|
||||
import { MoreVertical } from "lucide-react";
|
||||
// hooks
|
||||
import { useChart } from "components/gantt-chart/hooks";
|
||||
@ -27,10 +26,10 @@ type Props = {
|
||||
) => Promise<TIssue | undefined>;
|
||||
viewId?: string;
|
||||
disableIssueCreation?: boolean;
|
||||
showAllBlocks?: boolean;
|
||||
};
|
||||
|
||||
export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const {
|
||||
blockUpdateHandler,
|
||||
blocks,
|
||||
@ -39,6 +38,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
disableIssueCreation,
|
||||
showAllBlocks = false,
|
||||
} = props;
|
||||
|
||||
const router = useRouter();
|
||||
@ -100,7 +100,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={handleOrderChange}>
|
||||
<StrictModeDroppable droppableId="gantt-sidebar">
|
||||
<Droppable droppableId="gantt-sidebar">
|
||||
{(droppableProvided) => (
|
||||
<div
|
||||
id={`gantt-sidebar-${cycleId}`}
|
||||
@ -111,7 +111,15 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
||||
<>
|
||||
{blocks ? (
|
||||
blocks.map((block, index) => {
|
||||
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "");
|
||||
const isBlockVisibleOnSidebar = block.start_date && block.target_date;
|
||||
|
||||
// hide the block if it doesn't have start and target dates and showAllBlocks is false
|
||||
if (!showAllBlocks && !isBlockVisibleOnSidebar) return;
|
||||
|
||||
const duration =
|
||||
!block.start_date || !block.target_date
|
||||
? null
|
||||
: findTotalDaysInRange(block.start_date, block.target_date);
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
@ -149,7 +157,11 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
||||
<IssueGanttSidebarBlock data={block.data} />
|
||||
</div>
|
||||
<div className="flex-shrink-0 text-sm text-custom-text-200">
|
||||
{duration} day{duration > 1 ? "s" : ""}
|
||||
{duration && (
|
||||
<span>
|
||||
{duration} day{duration > 1 ? "s" : ""}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -173,7 +185,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</StrictModeDroppable>
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
};
|
||||
|
@ -13,8 +13,8 @@ export interface IGanttBlock {
|
||||
width: number;
|
||||
};
|
||||
sort_order: number;
|
||||
start_date: Date;
|
||||
target_date: Date;
|
||||
start_date: Date | null;
|
||||
target_date: Date | null;
|
||||
}
|
||||
|
||||
export interface IBlockUpdateData {
|
||||
|
@ -167,6 +167,8 @@ export const getMonthChartItemPositionWidthInMonth = (chartData: ChartDataType,
|
||||
const { startDate } = chartData.data;
|
||||
const { start_date: itemStartDate, target_date: itemTargetDate } = itemData;
|
||||
|
||||
if (!itemStartDate || !itemTargetDate) return null;
|
||||
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
itemStartDate.setHours(0, 0, 0, 0);
|
||||
itemTargetDate.setHours(0, 0, 0, 0);
|
||||
|
@ -12,12 +12,7 @@ import { Breadcrumbs, Button, LayersIcon, PhotoFilterIcon, Tooltip } from "@plan
|
||||
// icons
|
||||
import { List, PlusIcon, Sheet } from "lucide-react";
|
||||
// types
|
||||
import {
|
||||
IIssueDisplayFilterOptions,
|
||||
IIssueDisplayProperties,
|
||||
IIssueFilterOptions,
|
||||
TStaticViewTypes,
|
||||
} from "@plane/types";
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// constants
|
||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ChangeEvent, FC, useCallback, useContext, useEffect, useState } from "react";
|
||||
import { ChangeEvent, FC, useCallback, useEffect, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
// hooks
|
||||
import useReloadConfirmations from "hooks/use-reload-confirmation";
|
||||
|
@ -64,6 +64,7 @@ export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props)
|
||||
{
|
||||
"cursor-not-allowed": disabled,
|
||||
"hover:bg-custom-background-80": !disabled,
|
||||
"bg-custom-background-80": isParentIssueModalOpen,
|
||||
},
|
||||
className
|
||||
)}
|
||||
@ -72,15 +73,20 @@ export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props)
|
||||
>
|
||||
{issue.parent_id && parentIssue ? (
|
||||
<div className="flex items-center gap-1 bg-green-500/20 text-green-700 rounded px-1.5 py-1">
|
||||
<Link
|
||||
href={`/${workspaceSlug}/projects/${projectId}/issues/${parentIssue?.id}`}
|
||||
className="text-xs font-medium"
|
||||
>
|
||||
{parentIssueProjectDetails?.identifier}-{parentIssue.sequence_id}
|
||||
</Link>
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={parentIssue.name}>
|
||||
<Link
|
||||
href={`/${workspaceSlug}/projects/${projectId}/issues/${parentIssue?.id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs font-medium mt-0.5"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{parentIssueProjectDetails?.identifier}-{parentIssue.sequence_id}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
|
||||
{!disabled && (
|
||||
<Tooltip tooltipContent="Remove">
|
||||
<Tooltip tooltipContent="Remove" position="bottom">
|
||||
<span
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -96,7 +102,15 @@ export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props)
|
||||
) : (
|
||||
<span className="text-sm text-custom-text-400">Add parent issue</span>
|
||||
)}
|
||||
{!disabled && <Pencil className="h-4 w-4 flex-shrink-0 hidden group-hover:inline" />}
|
||||
{!disabled && (
|
||||
<span
|
||||
className={cn("p-1 flex-shrink-0 opacity-0 group-hover:opacity-100", {
|
||||
"text-custom-text-400": !issue.parent_id && !parentIssue,
|
||||
})}
|
||||
>
|
||||
<Pencil className="h-2.5 w-2.5 flex-shrink-0" />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { FC } from "react";
|
||||
import useSWR from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { IssueParentSiblingItem } from "./sibling-item";
|
||||
// hooks
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { CircleDot, CopyPlus, Pencil, X, XCircle } from "lucide-react";
|
||||
// hooks
|
||||
@ -101,59 +102,72 @@ export const IssueRelationSelect: React.FC<TIssueRelationSelect> = observer((pro
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"group flex items-center justify-between gap-2 px-2 py-0.5 rounded outline-none",
|
||||
"group flex items-center gap-2 px-2 py-0.5 rounded outline-none",
|
||||
{
|
||||
"cursor-not-allowed": disabled,
|
||||
"hover:bg-custom-background-80": !disabled,
|
||||
"bg-custom-background-80": isRelationModalOpen === relationKey,
|
||||
},
|
||||
className
|
||||
)}
|
||||
onClick={() => toggleRelationModal(relationKey)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{relationIssueIds.length > 0 ? (
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{relationIssueIds.map((relationIssueId) => {
|
||||
const currentIssue = issueMap[relationIssueId];
|
||||
if (!currentIssue) return;
|
||||
<div className="flex items-start justify-between w-full">
|
||||
{relationIssueIds.length > 0 ? (
|
||||
<div className="flex items-center gap-2 py-0.5 flex-wrap">
|
||||
{relationIssueIds.map((relationIssueId) => {
|
||||
const currentIssue = issueMap[relationIssueId];
|
||||
if (!currentIssue) return;
|
||||
|
||||
const projectDetails = getProjectById(currentIssue.project_id);
|
||||
const projectDetails = getProjectById(currentIssue.project_id);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={relationIssueId}
|
||||
className={`group flex items-center gap-1 rounded px-1.5 py-1 ${issueRelationObject[relationKey].className}`}
|
||||
>
|
||||
<a
|
||||
href={`/${workspaceSlug}/projects/${projectDetails?.id}/issues/${currentIssue.id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs font-medium"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
return (
|
||||
<div
|
||||
key={relationIssueId}
|
||||
className={`group flex items-center gap-1 rounded px-1.5 pt-1 pb-1 leading-3 hover:bg-custom-background-90 ${issueRelationObject[relationKey].className}`}
|
||||
>
|
||||
{`${projectDetails?.identifier}-${currentIssue?.sequence_id}`}
|
||||
</a>
|
||||
{!disabled && (
|
||||
<Tooltip tooltipContent="Remove">
|
||||
<span
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
removeRelation(workspaceSlug, projectId, issueId, relationKey, relationIssueId);
|
||||
}}
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={currentIssue.name}>
|
||||
<Link
|
||||
href={`/${workspaceSlug}/projects/${projectDetails?.id}/issues/${currentIssue.id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs font-medium mt-0.5"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<X className="h-2.5 w-2.5 text-custom-text-300 hover:text-red-500" />
|
||||
</span>
|
||||
{`${projectDetails?.identifier}-${currentIssue?.sequence_id}`}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-sm text-custom-text-400">{issueRelationObject[relationKey].placeholder}</span>
|
||||
)}
|
||||
{!disabled && <Pencil className="h-4 w-4 flex-shrink-0 hidden group-hover:inline" />}
|
||||
{!disabled && (
|
||||
<Tooltip tooltipContent="Remove" position="bottom">
|
||||
<span
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
removeRelation(workspaceSlug, projectId, issueId, relationKey, relationIssueId);
|
||||
}}
|
||||
>
|
||||
<X className="h-2.5 w-2.5 text-custom-text-300 hover:text-red-500" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-sm text-custom-text-400">{issueRelationObject[relationKey].placeholder}</span>
|
||||
)}
|
||||
{!disabled && (
|
||||
<span
|
||||
className={cn("p-1 flex-shrink-0 opacity-0 group-hover:opacity-100", {
|
||||
"text-custom-text-400": relationIssueIds.length === 0,
|
||||
})}
|
||||
>
|
||||
<Pencil className="h-2.5 w-2.5 flex-shrink-0" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
|
@ -184,7 +184,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
projectId={projectId?.toString() ?? ""}
|
||||
placeholder="Add assignees"
|
||||
multiple
|
||||
buttonVariant={issue?.assignee_ids?.length > 0 ? "transparent-without-text" : "transparent-with-text"}
|
||||
buttonVariant={issue?.assignee_ids?.length > 1 ? "transparent-without-text" : "transparent-with-text"}
|
||||
className="w-3/5 flex-grow group"
|
||||
buttonContainerClassName="w-full text-left"
|
||||
buttonClassName={`text-sm justify-between ${
|
||||
@ -233,6 +233,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
buttonClassName={`text-sm ${issue?.start_date ? "" : "text-custom-text-400"}`}
|
||||
hideIcon
|
||||
clearIconClassName="h-3 w-3 hidden group-hover:inline"
|
||||
showPlaceholderIcon
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -257,6 +258,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
buttonClassName={`text-sm ${issue?.target_date ? "" : "text-custom-text-400"}`}
|
||||
hideIcon
|
||||
clearIconClassName="h-3 w-3 hidden group-hover:inline"
|
||||
showPlaceholderIcon
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -332,8 +334,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 min-h-8">
|
||||
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<div className="flex gap-2 min-h-8">
|
||||
<div className="flex gap-1 pt-2 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<RelatedIcon className="h-4 w-4 flex-shrink-0" />
|
||||
<span>Relates to</span>
|
||||
</div>
|
||||
@ -347,8 +349,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 min-h-8">
|
||||
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<div className="flex gap-2 min-h-8">
|
||||
<div className="flex gap-1 pt-2 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<XCircle className="h-4 w-4 flex-shrink-0" />
|
||||
<span>Blocking</span>
|
||||
</div>
|
||||
@ -362,8 +364,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 min-h-8">
|
||||
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<div className="flex gap-2 min-h-8">
|
||||
<div className="flex gap-1 pt-2 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<CircleDot className="h-4 w-4 flex-shrink-0" />
|
||||
<span>Blocked by</span>
|
||||
</div>
|
||||
@ -377,8 +379,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 min-h-8">
|
||||
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<div className="flex gap-2 min-h-8">
|
||||
<div className="flex gap-1 pt-2 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<CopyPlus className="h-4 w-4 flex-shrink-0" />
|
||||
<span>Duplicate of</span>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useProject, useWorkspace } from "hooks/store";
|
||||
import { useProject } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useKeypress from "hooks/use-keypress";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
@ -57,14 +57,13 @@ const Inputs = (props: any) => {
|
||||
};
|
||||
|
||||
export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
|
||||
const { formKey, groupId, prePopulatedData, quickAddCallback, viewId, onOpen } = props;
|
||||
const { formKey, prePopulatedData, quickAddCallback, viewId, onOpen } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const { getProjectById } = useProject();
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
// refs
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
// states
|
||||
@ -73,7 +72,6 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
// derived values
|
||||
const workspaceDetail = (workspaceSlug && getWorkspaceBySlug(workspaceSlug.toString())) || null;
|
||||
const projectDetail = projectId ? getProjectById(projectId.toString()) : null;
|
||||
|
||||
const {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/router";
|
||||
import size from "lodash/size";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useIssues, useUser } from "hooks/store";
|
||||
// components
|
||||
@ -26,6 +27,9 @@ export const ProjectArchivedEmptyState: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
currentUser,
|
||||
@ -35,16 +39,13 @@ export const ProjectArchivedEmptyState: React.FC = observer(() => {
|
||||
const userFilters = issuesFilter?.issueFilters?.filters;
|
||||
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
|
||||
|
||||
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath(
|
||||
"empty-filters",
|
||||
activeLayout ?? "list",
|
||||
currentUser?.theme.theme === "light"
|
||||
);
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("archived", "empty-issues", currentUser?.theme.theme === "light");
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode);
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("archived", "empty-issues", isLightMode);
|
||||
|
||||
const issueFilterCount = size(
|
||||
Object.fromEntries(
|
||||
Object.entries(userFilters ?? {}).filter(([key, value]) => value && Array.isArray(value) && value.length > 0)
|
||||
Object.entries(userFilters ?? {}).filter(([, value]) => value && Array.isArray(value) && value.length > 0)
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/router";
|
||||
import size from "lodash/size";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useIssues, useUser } from "hooks/store";
|
||||
// components
|
||||
@ -26,6 +27,9 @@ export const ProjectDraftEmptyState: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
currentUser,
|
||||
@ -35,16 +39,13 @@ export const ProjectDraftEmptyState: React.FC = observer(() => {
|
||||
const userFilters = issuesFilter?.issueFilters?.filters;
|
||||
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
|
||||
|
||||
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath(
|
||||
"empty-filters",
|
||||
activeLayout ?? "list",
|
||||
currentUser?.theme.theme === "light"
|
||||
);
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("draft", "empty-issues", currentUser?.theme.theme === "light");
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode);
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("draft", "empty-issues", isLightMode);
|
||||
|
||||
const issueFilterCount = size(
|
||||
Object.fromEntries(
|
||||
Object.entries(userFilters ?? {}).filter(([key, value]) => value && Array.isArray(value) && value.length > 0)
|
||||
Object.entries(userFilters ?? {}).filter(([, value]) => value && Array.isArray(value) && value.length > 0)
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/router";
|
||||
import size from "lodash/size";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useApplication, useIssues, useUser } from "hooks/store";
|
||||
// components
|
||||
@ -26,6 +27,8 @@ export const ProjectEmptyState: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: commandPaletteStore,
|
||||
@ -40,16 +43,13 @@ export const ProjectEmptyState: React.FC = observer(() => {
|
||||
const userFilters = issuesFilter?.issueFilters?.filters;
|
||||
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
|
||||
|
||||
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath(
|
||||
"empty-filters",
|
||||
activeLayout ?? "list",
|
||||
currentUser?.theme.theme === "light"
|
||||
);
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "issues", currentUser?.theme.theme === "light");
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode);
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "issues", isLightMode);
|
||||
|
||||
const issueFilterCount = size(
|
||||
Object.fromEntries(
|
||||
Object.entries(userFilters ?? {}).filter(([key, value]) => value && Array.isArray(value) && value.length > 0)
|
||||
Object.entries(userFilters ?? {}).filter(([, value]) => value && Array.isArray(value) && value.length > 0)
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -13,11 +13,12 @@ import {
|
||||
} from "components/gantt-chart";
|
||||
// types
|
||||
import { TIssue, TUnGroupedIssues } from "@plane/types";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle";
|
||||
import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module";
|
||||
import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project";
|
||||
import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views";
|
||||
// constants
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { EIssueActions } from "../types";
|
||||
|
||||
interface IBaseGanttRoot {
|
||||
@ -76,12 +77,14 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
||||
viewId={viewId}
|
||||
enableQuickIssueCreate
|
||||
disableIssueCreation={!enableIssueCreation || !isAllowed}
|
||||
showAllBlocks
|
||||
/>
|
||||
)}
|
||||
enableBlockLeftResize={isAllowed}
|
||||
enableBlockRightResize={isAllowed}
|
||||
enableBlockMove={isAllowed}
|
||||
enableReorder={appliedDisplayFilters?.order_by === "sort_order" && isAllowed}
|
||||
showAllBlocks
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { useRouter } from "next/router";
|
||||
// ui
|
||||
import { Tooltip, StateGroupIcon, ControlLink } from "@plane/ui";
|
||||
// helpers
|
||||
|
@ -54,7 +54,7 @@ const defaultValues: Partial<TIssue> = {
|
||||
};
|
||||
|
||||
export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = observer((props) => {
|
||||
const { formKey, groupId, prePopulatedData, quickAddCallback, viewId } = props;
|
||||
const { formKey, prePopulatedData, quickAddCallback, viewId } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
@ -3,6 +3,7 @@ import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useApplication, useGlobalView, useIssues, useProject, useUser } from "hooks/store";
|
||||
import { useWorkspaceIssueProperties } from "hooks/use-workspace-issue-properties";
|
||||
@ -25,6 +26,8 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, globalViewId } = router.query;
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
//swr hook for fetching issue properties
|
||||
useWorkspaceIssueProperties(workspaceSlug);
|
||||
// store
|
||||
@ -46,7 +49,8 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
|
||||
const currentView = isDefaultView ? groupedIssueIds.dataViewId : "custom-view";
|
||||
const currentViewDetails = ALL_ISSUES_EMPTY_STATE_DETAILS[currentView as keyof typeof ALL_ISSUES_EMPTY_STATE_DETAILS];
|
||||
|
||||
const emptyStateImage = getEmptyStateImagePath("all-issues", currentView, currentUser?.theme.theme === "light");
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const emptyStateImage = getEmptyStateImagePath("all-issues", currentView, isLightMode);
|
||||
|
||||
// filter init from the query params
|
||||
|
||||
@ -57,6 +61,7 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
|
||||
["all-issues", "assigned", "created", "subscribed"].includes(globalViewId.toString())
|
||||
) {
|
||||
const routerQueryParams = { ...router.query };
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { ["workspaceSlug"]: _workspaceSlug, ["globalViewId"]: _globalViewId, ...filters } = routerQueryParams;
|
||||
|
||||
let issueFilters: any = {};
|
||||
|
@ -2,7 +2,7 @@ import { FC, useCallback } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useIssues, useUser } from "hooks/store";
|
||||
import { useUser } from "hooks/store";
|
||||
// views
|
||||
import { SpreadsheetView } from "./spreadsheet-view";
|
||||
// types
|
||||
@ -36,7 +36,6 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
// store hooks
|
||||
const { issueMap } = useIssues();
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
@ -55,7 +54,6 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
||||
[canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed]
|
||||
);
|
||||
|
||||
|
||||
const issueIds = (issueStore.groupedIssueIds ?? []) as TUnGroupedIssues;
|
||||
|
||||
const handleIssues = useCallback(
|
||||
|
@ -99,7 +99,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
projectId={projectId}
|
||||
placeholder="Add assignees"
|
||||
multiple
|
||||
buttonVariant={issue?.assignee_ids?.length > 0 ? "transparent-without-text" : "transparent-with-text"}
|
||||
buttonVariant={issue?.assignee_ids?.length > 1 ? "transparent-without-text" : "transparent-with-text"}
|
||||
className="w-3/4 flex-grow group"
|
||||
buttonContainerClassName="w-full text-left"
|
||||
buttonClassName={`text-sm justify-between ${issue?.assignee_ids.length > 0 ? "" : "text-custom-text-400"}`}
|
||||
@ -148,6 +148,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
buttonClassName={`text-sm ${issue?.start_date ? "" : "text-custom-text-400"}`}
|
||||
hideIcon
|
||||
clearIconClassName="h-3 w-3 hidden group-hover:inline"
|
||||
showPlaceholderIcon
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -173,6 +174,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
buttonClassName={`text-sm ${issue?.target_date ? "" : "text-custom-text-400"}`}
|
||||
hideIcon
|
||||
clearIconClassName="h-3 w-3 hidden group-hover:inline"
|
||||
showPlaceholderIcon
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -251,8 +253,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
</div>
|
||||
|
||||
{/* relates to */}
|
||||
<div className="flex items-center gap-3 min-h-8">
|
||||
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<div className="flex gap-3 min-h-8">
|
||||
<div className="flex pt-2 gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<RelatedIcon className="h-4 w-4 flex-shrink-0" />
|
||||
<span>Relates to</span>
|
||||
</div>
|
||||
@ -267,8 +269,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
</div>
|
||||
|
||||
{/* blocking */}
|
||||
<div className="flex items-center gap-3 min-h-8">
|
||||
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<div className="flex gap-3 min-h-8">
|
||||
<div className="flex pt-2 gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<XCircle className="h-4 w-4 flex-shrink-0" />
|
||||
<span>Blocking</span>
|
||||
</div>
|
||||
@ -283,8 +285,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
</div>
|
||||
|
||||
{/* blocked by */}
|
||||
<div className="flex items-center gap-3 min-h-8">
|
||||
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<div className="flex gap-3 min-h-8">
|
||||
<div className="flex pt-2 gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<CircleDot className="h-4 w-4 flex-shrink-0" />
|
||||
<span>Blocked by</span>
|
||||
</div>
|
||||
@ -299,8 +301,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
</div>
|
||||
|
||||
{/* duplicate of */}
|
||||
<div className="flex items-center gap-3 min-h-8">
|
||||
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<div className="flex gap-3 min-h-8">
|
||||
<div className="flex pt-2 gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
|
||||
<CopyPlus className="h-4 w-4 flex-shrink-0" />
|
||||
<span>Duplicate of</span>
|
||||
</div>
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { Fragment, useRef, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
import { usePopper } from "react-popper";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useApplication, useModule, useUser } from "hooks/store";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
@ -15,6 +16,8 @@ export const ModulesListView: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, peekModule } = router.query;
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const { commandPalette: commandPaletteStore } = useApplication();
|
||||
const {
|
||||
@ -25,7 +28,8 @@ export const ModulesListView: React.FC = observer(() => {
|
||||
|
||||
const { storedValue: modulesView } = useLocalStorage("modules_view", "grid");
|
||||
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "modules", currentUser?.theme.theme === "light");
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "modules", isLightMode);
|
||||
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useEffect } from "react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useApplication, useDashboard, useProject, useUser } from "hooks/store";
|
||||
@ -14,6 +15,8 @@ import { Spinner } from "@plane/ui";
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
export const WorkspaceDashboardView = observer(() => {
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: { toggleCreateProjectModal },
|
||||
@ -28,7 +31,8 @@ export const WorkspaceDashboardView = observer(() => {
|
||||
const { homeDashboardId, fetchHomeDashboardWidgets } = useDashboard();
|
||||
const { joinedProjectIds } = useProject();
|
||||
|
||||
const emptyStateImage = getEmptyStateImagePath("onboarding", "dashboard", currentUser?.theme.theme === "light");
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const emptyStateImage = getEmptyStateImagePath("onboarding", "dashboard", isLightMode);
|
||||
|
||||
const handleTourCompleted = () => {
|
||||
updateTourCompleted()
|
||||
|
@ -1,8 +1,6 @@
|
||||
import React, { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// components
|
||||
import { PageForm } from "./page-form";
|
||||
// types
|
||||
@ -25,10 +23,6 @@ export const CreateUpdatePageModal: FC<Props> = (props) => {
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { createPage } = useProjectPages();
|
||||
// store hooks
|
||||
const {
|
||||
eventTracker: { postHogEventTracker },
|
||||
} = useApplication();
|
||||
|
||||
const createProjectPage = async (payload: IPage) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
@ -18,7 +19,9 @@ type IPagesListView = {
|
||||
|
||||
export const PagesListView: FC<IPagesListView> = (props) => {
|
||||
const { pageIds: projectPageIds } = props;
|
||||
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: { toggleCreatePageModal },
|
||||
} = useApplication();
|
||||
@ -36,11 +39,8 @@ export const PagesListView: FC<IPagesListView> = (props) => {
|
||||
? PAGE_EMPTY_STATE_DETAILS[pageTab as keyof typeof PAGE_EMPTY_STATE_DETAILS]
|
||||
: PAGE_EMPTY_STATE_DETAILS["All"];
|
||||
|
||||
const emptyStateImage = getEmptyStateImagePath(
|
||||
"pages",
|
||||
currentPageTabDetails.key,
|
||||
currentUser?.theme.theme === "light"
|
||||
);
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const emptyStateImage = getEmptyStateImagePath("pages", currentPageTabDetails.key, isLightMode);
|
||||
|
||||
const isButtonVisible = currentPageTabDetails.key !== "archived" && currentPageTabDetails.key !== "favorites";
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
import { useProjectPages } from "hooks/store/use-project-specific-pages";
|
||||
@ -14,6 +15,8 @@ import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
|
||||
export const RecentPagesList: FC = observer(() => {
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const { commandPalette: commandPaletteStore } = useApplication();
|
||||
const {
|
||||
@ -22,7 +25,8 @@ export const RecentPagesList: FC = observer(() => {
|
||||
} = useUser();
|
||||
const { recentProjectPages } = useProjectPages();
|
||||
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("pages", "recent", currentUser?.theme.theme === "light");
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("pages", "recent", isLightMode);
|
||||
|
||||
// FIXME: replace any with proper type
|
||||
const isEmpty = recentProjectPages && Object.values(recentProjectPages).every((value: any) => value.length === 0);
|
||||
|
@ -2,6 +2,7 @@ import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useTheme } from "next-themes";
|
||||
// components
|
||||
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
|
||||
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root";
|
||||
@ -27,6 +28,9 @@ export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => {
|
||||
workspaceSlug: string;
|
||||
userId: string;
|
||||
};
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const {
|
||||
membership: { currentWorkspaceRole },
|
||||
currentUser,
|
||||
@ -46,7 +50,8 @@ export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => {
|
||||
}
|
||||
);
|
||||
|
||||
const emptyStateImage = getEmptyStateImagePath("profile", type, currentUser?.theme.theme === "light");
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const emptyStateImage = getEmptyStateImagePath("profile", type, isLightMode);
|
||||
|
||||
const activeLayout = issueFilters?.displayFilters?.layout || undefined;
|
||||
|
||||
|
@ -1,16 +1,17 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useApplication, useProject, useUser } from "hooks/store";
|
||||
// components
|
||||
import { ProjectCard } from "components/project";
|
||||
import { Loader } from "@plane/ui";
|
||||
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
|
||||
// icons
|
||||
import { Plus } from "lucide-react";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
export const ProjectCardList = observer(() => {
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: commandPaletteStore,
|
||||
@ -22,7 +23,8 @@ export const ProjectCardList = observer(() => {
|
||||
} = useUser();
|
||||
const { workspaceProjectIds, searchedProjects, getProjectById } = useProject();
|
||||
|
||||
const emptyStateImage = getEmptyStateImagePath("onboarding", "projects", currentUser?.theme.theme === "light");
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const emptyStateImage = getEmptyStateImagePath("onboarding", "projects", isLightMode);
|
||||
|
||||
const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Search } from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useApplication, useProjectView, useUser } from "hooks/store";
|
||||
// components
|
||||
@ -14,6 +15,8 @@ import { EUserProjectRoles } from "constants/project";
|
||||
export const ProjectViewsList = observer(() => {
|
||||
// states
|
||||
const [query, setQuery] = useState("");
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: { toggleCreateViewModal },
|
||||
@ -43,7 +46,8 @@ export const ProjectViewsList = observer(() => {
|
||||
|
||||
const viewsList = projectViewIds.map((viewId) => getViewById(viewId));
|
||||
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "views", currentUser?.theme.theme === "light");
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "views", isLightMode);
|
||||
|
||||
const filteredViewsList = viewsList.filter((v) => v?.name.toLowerCase().includes(query.toLowerCase()));
|
||||
|
||||
|
@ -13,7 +13,6 @@ const paramsToKey = (params: any) => {
|
||||
start_date,
|
||||
target_date,
|
||||
sub_issue,
|
||||
start_target_date,
|
||||
project,
|
||||
layout,
|
||||
subscriber,
|
||||
@ -28,7 +27,6 @@ const paramsToKey = (params: any) => {
|
||||
let createdByKey = created_by ? created_by.split(",") : [];
|
||||
let labelsKey = labels ? labels.split(",") : [];
|
||||
let subscriberKey = subscriber ? subscriber.split(",") : [];
|
||||
const startTargetDate = start_target_date ? `${start_target_date}`.toUpperCase() : "FALSE";
|
||||
const startDateKey = start_date ?? "";
|
||||
const targetDateKey = target_date ?? "";
|
||||
const type = params.type ? params.type.toUpperCase() : "NULL";
|
||||
@ -47,7 +45,7 @@ const paramsToKey = (params: any) => {
|
||||
labelsKey = labelsKey.sort().join("_");
|
||||
subscriberKey = subscriberKey.sort().join("_");
|
||||
|
||||
return `${layoutKey}_${projectKey}_${stateGroupKey}_${stateKey}_${priorityKey}_${assigneesKey}_${mentionsKey}_${createdByKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${startDateKey}_${targetDateKey}_${sub_issue}_${startTargetDate}_${subscriberKey}`;
|
||||
return `${layoutKey}_${projectKey}_${stateGroupKey}_${stateKey}_${priorityKey}_${assigneesKey}_${mentionsKey}_${createdByKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${startDateKey}_${targetDateKey}_${sub_issue}_${subscriberKey}`;
|
||||
};
|
||||
|
||||
const myIssuesParamsToKey = (params: any) => {
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Fragment, ReactElement } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useApplication, useProject, useUser } from "hooks/store";
|
||||
// layouts
|
||||
@ -16,6 +17,8 @@ import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import { NextPageWithLayout } from "lib/types";
|
||||
|
||||
const AnalyticsPage: NextPageWithLayout = observer(() => {
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: { toggleCreateProjectModal },
|
||||
@ -27,7 +30,8 @@ const AnalyticsPage: NextPageWithLayout = observer(() => {
|
||||
} = useUser();
|
||||
const { workspaceProjectIds } = useProject();
|
||||
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "analytics", currentUser?.theme.theme === "light");
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "analytics", isLightMode);
|
||||
const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
return (
|
||||
|
@ -2,6 +2,7 @@ import { Fragment, useCallback, useState, ReactElement } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useCycle, useUser } from "hooks/store";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
@ -22,6 +23,8 @@ import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
||||
const [createModal, setCreateModal] = useState(false);
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
@ -49,7 +52,9 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
||||
},
|
||||
[handleCurrentLayout, setCycleTab]
|
||||
);
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "cycles", currentUser?.theme.theme === "light");
|
||||
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "cycles", isLightMode);
|
||||
|
||||
const totalCycles = currentProjectCycleIds?.length ?? 0;
|
||||
|
||||
|
@ -4,6 +4,7 @@ import dynamic from "next/dynamic";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import useSWR from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
@ -48,7 +49,9 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => {
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// states
|
||||
const [createUpdatePageModal, setCreateUpdatePageModal] = useState(false);
|
||||
// store
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const {
|
||||
currentUser,
|
||||
currentUserLoader,
|
||||
@ -94,7 +97,8 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => {
|
||||
}
|
||||
};
|
||||
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "pages", currentUser?.theme.theme === "light");
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "pages", isLightMode);
|
||||
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
|
@ -14,7 +14,7 @@ import { RichReadOnlyEditor } from "@plane/rich-text-editor";
|
||||
// icons
|
||||
import { History, MessageSquare } from "lucide-react";
|
||||
// ui
|
||||
import { ExternalLinkIcon, Loader } from "@plane/ui";
|
||||
import { Loader } from "@plane/ui";
|
||||
// fetch-keys
|
||||
import { USER_ACTIVITY } from "constants/fetch-keys";
|
||||
// helper
|
||||
|
@ -89,7 +89,6 @@ export class ArchivedIssuesFilter extends IssueFilterHelperStore implements IArc
|
||||
filteredParams
|
||||
);
|
||||
|
||||
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
|
||||
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
|
||||
|
||||
return filteredRouteParams;
|
||||
|
@ -90,7 +90,6 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
|
||||
filteredParams
|
||||
);
|
||||
|
||||
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
|
||||
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
|
||||
|
||||
return filteredRouteParams;
|
||||
|
@ -89,7 +89,6 @@ export class DraftIssuesFilter extends IssueFilterHelperStore implements IDraftI
|
||||
filteredParams
|
||||
);
|
||||
|
||||
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
|
||||
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
|
||||
|
||||
return filteredRouteParams;
|
||||
|
@ -81,7 +81,6 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
|
||||
// display filters
|
||||
type: displayFilters?.type || undefined,
|
||||
sub_issue: displayFilters?.sub_issue ?? true,
|
||||
start_target_date: displayFilters?.start_target_date ?? true,
|
||||
};
|
||||
|
||||
const issueFiltersParams: Partial<Record<TIssueParams, boolean | string>> = {};
|
||||
@ -170,7 +169,6 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
|
||||
type: filters?.type || null,
|
||||
sub_issue: filters?.sub_issue || false,
|
||||
show_empty_groups: filters?.show_empty_groups || false,
|
||||
start_target_date: filters?.start_target_date || false,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -90,7 +90,6 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
|
||||
filteredParams
|
||||
);
|
||||
|
||||
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
|
||||
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
|
||||
|
||||
return filteredRouteParams;
|
||||
|
@ -93,7 +93,6 @@ export class ProfileIssuesFilter extends IssueFilterHelperStore implements IProf
|
||||
filteredParams
|
||||
);
|
||||
|
||||
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
|
||||
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
|
||||
|
||||
return filteredRouteParams;
|
||||
|
@ -90,7 +90,6 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
|
||||
filteredParams
|
||||
);
|
||||
|
||||
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
|
||||
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
|
||||
|
||||
return filteredRouteParams;
|
||||
|
@ -89,7 +89,6 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj
|
||||
filteredParams
|
||||
);
|
||||
|
||||
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
|
||||
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
|
||||
|
||||
return filteredRouteParams;
|
||||
|
@ -99,7 +99,6 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
|
||||
filteredParams
|
||||
);
|
||||
|
||||
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
|
||||
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
|
||||
|
||||
return filteredRouteParams;
|
||||
|
Loading…
Reference in New Issue
Block a user