diff --git a/web/components/core/modals/existing-issues-list-modal.tsx b/web/components/core/modals/existing-issues-list-modal.tsx index 57d721f498..058aae38c5 100644 --- a/web/components/core/modals/existing-issues-list-modal.tsx +++ b/web/components/core/modals/existing-issues-list-modal.tsx @@ -27,8 +27,8 @@ export const ExistingIssuesListModal: React.FC = (props) => { // states const [searchTerm, setSearchTerm] = useState(""); const [issues, setIssues] = useState([]); - const [isSearching, setIsSearching] = useState(false); const [selectedIssues, setSelectedIssues] = useState([]); + const [isSearching, setIsSearching] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false); @@ -72,8 +72,7 @@ export const ExistingIssuesListModal: React.FC = (props) => { useEffect(() => { if (!isOpen || !workspaceSlug || !projectId) return; - - setIsSearching(true); + if (issues.length <= 0) setIsSearching(true); projectService .projectIssuesSearch(workspaceSlug as string, projectId as string, { @@ -83,12 +82,21 @@ export const ExistingIssuesListModal: React.FC = (props) => { }) .then((res) => setIssues(res)) .finally(() => setIsSearching(false)); - }, [debouncedSearchTerm, isOpen, isWorkspaceLevel, projectId, searchParams, workspaceSlug]); + }, [issues, debouncedSearchTerm, isOpen, isWorkspaceLevel, projectId, searchParams, workspaceSlug]); + + useEffect(() => { + setSearchTerm(""); + setIssues([]); + setSelectedIssues([]); + setIsSearching(false); + setIsSubmitting(false); + setIsWorkspaceLevel(false); + }, [isOpen]); return ( <> setSearchTerm("")} appear> - {}}> + = observer((props) => }; return ( -
+
= observer((props) }; return ( -
+
{ hideIcon = false, hideText = false, placeholder, + tooltip, } = props; return ( -
- {!hideIcon && }{" "} - {!hideText && {cycle?.name ?? placeholder}} - {dropdownArrow && ( -
+ +
+ {!hideIcon && }{" "} + {!hideText && {cycle?.name ?? placeholder}} + {dropdownArrow && ( +
+
); }; @@ -78,18 +82,24 @@ const BackgroundButton = (props: ButtonProps) => { hideIcon = false, hideText = false, placeholder, + tooltip, } = props; return ( -
- {!hideIcon && } - {!hideText && {cycle?.name ?? placeholder}} - {dropdownArrow && ( -
+ +
+ {!hideIcon && } + {!hideText && {cycle?.name ?? placeholder}} + {dropdownArrow && ( +
+
); }; @@ -102,21 +112,24 @@ const TransparentButton = (props: ButtonProps) => { hideIcon = false, hideText = false, placeholder, + tooltip, } = props; return ( -
- {!hideIcon && } - {!hideText && {cycle?.name ?? placeholder}} - {dropdownArrow && ( -
+ +
+ {!hideIcon && } + {!hideText && {cycle?.name ?? placeholder}} + {dropdownArrow && ( +
+
); }; @@ -135,8 +148,9 @@ export const CycleDropdown: React.FC = observer((props) => { placeholder = "Cycle", placement, projectId, - value, tabIndex, + tooltip = false, + value, } = props; // states const [query, setQuery] = useState(""); @@ -254,6 +268,7 @@ export const CycleDropdown: React.FC = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "border-without-text" ? ( = observer((props) => { hideIcon={hideIcon} hideText placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "background-with-text" ? ( = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "background-without-text" ? ( = observer((props) => { hideIcon={hideIcon} hideText placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "transparent-with-text" ? ( = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "transparent-without-text" ? ( = observer((props) => { hideIcon={hideIcon} hideText placeholder={placeholder} + tooltip={tooltip} /> ) : null} diff --git a/web/components/dropdowns/date.tsx b/web/components/dropdowns/date.tsx index e128c6ad39..3cbf3449ad 100644 --- a/web/components/dropdowns/date.tsx +++ b/web/components/dropdowns/date.tsx @@ -6,6 +6,8 @@ import { CalendarDays, X } from "lucide-react"; // hooks import { useDropdownKeyDown } from "hooks/use-dropdown-key-down"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; +// ui +import { Tooltip } from "@plane/ui"; // helpers import { renderFormattedDate } from "helpers/date-time.helper"; import { cn } from "helpers/common.helper"; @@ -33,6 +35,7 @@ type ButtonProps = { hideText?: boolean; onClear: () => void; placeholder: string; + tooltip: boolean; }; const BorderButton = (props: ButtonProps) => { @@ -46,27 +49,34 @@ const BorderButton = (props: ButtonProps) => { hideText = false, onClear, placeholder, + tooltip, } = props; return ( -
- {!hideIcon && icon} - {!hideText && {date ? renderFormattedDate(date) : placeholder}} - {isClearable && ( - { - e.stopPropagation(); - onClear(); - }} - /> - )} -
+
+ {!hideIcon && icon} + {!hideText && {date ? renderFormattedDate(date) : placeholder}} + {isClearable && ( + { + e.stopPropagation(); + onClear(); + }} + /> + )} +
+ ); }; @@ -81,24 +91,34 @@ const BackgroundButton = (props: ButtonProps) => { hideText = false, onClear, placeholder, + tooltip, } = props; return ( -
- {!hideIcon && icon} - {!hideText && {date ? renderFormattedDate(date) : placeholder}} - {isClearable && ( - { - e.stopPropagation(); - onClear(); - }} - /> - )} -
+
+ {!hideIcon && icon} + {!hideText && {date ? renderFormattedDate(date) : placeholder}} + {isClearable && ( + { + e.stopPropagation(); + onClear(); + }} + /> + )} +
+ ); }; @@ -113,27 +133,34 @@ const TransparentButton = (props: ButtonProps) => { hideText = false, onClear, placeholder, + tooltip, } = props; return ( -
- {!hideIcon && icon} - {!hideText && {date ? renderFormattedDate(date) : placeholder}} - {isClearable && ( - { - e.stopPropagation(); - onClear(); - }} - /> - )} -
+
+ {!hideIcon && icon} + {!hideText && {date ? renderFormattedDate(date) : placeholder}} + {isClearable && ( + { + e.stopPropagation(); + onClear(); + }} + /> + )} +
+ ); }; @@ -144,6 +171,7 @@ export const DateDropdown: React.FC = (props) => { buttonVariant, className = "", clearIconClassName = "", + closeOnSelect = true, disabled = false, hideIcon = false, icon = , @@ -153,9 +181,9 @@ export const DateDropdown: React.FC = (props) => { onChange, placeholder = "Date", placement, - value, - closeOnSelect = true, tabIndex, + tooltip = false, + value, } = props; const [isOpen, setIsOpen] = useState(false); // refs @@ -218,6 +246,7 @@ export const DateDropdown: React.FC = (props) => { placeholder={placeholder} isClearable={isClearable && isDateSelected} onClear={() => onChange(null)} + tooltip={tooltip} /> ) : buttonVariant === "border-without-text" ? ( = (props) => { placeholder={placeholder} isClearable={isClearable && isDateSelected} onClear={() => onChange(null)} + tooltip={tooltip} hideText /> ) : buttonVariant === "background-with-text" ? ( @@ -241,6 +271,7 @@ export const DateDropdown: React.FC = (props) => { placeholder={placeholder} isClearable={isClearable && isDateSelected} onClear={() => onChange(null)} + tooltip={tooltip} /> ) : buttonVariant === "background-without-text" ? ( = (props) => { placeholder={placeholder} isClearable={isClearable && isDateSelected} onClear={() => onChange(null)} + tooltip={tooltip} hideText /> ) : buttonVariant === "transparent-with-text" ? ( @@ -264,6 +296,7 @@ export const DateDropdown: React.FC = (props) => { placeholder={placeholder} isClearable={isClearable && isDateSelected} onClear={() => onChange(null)} + tooltip={tooltip} /> ) : buttonVariant === "transparent-without-text" ? ( = (props) => { placeholder={placeholder} isClearable={isClearable && isDateSelected} onClear={() => onChange(null)} + tooltip={tooltip} hideText /> ) : null} diff --git a/web/components/dropdowns/estimate.tsx b/web/components/dropdowns/estimate.tsx index 5b7a42d716..18144540da 100644 --- a/web/components/dropdowns/estimate.tsx +++ b/web/components/dropdowns/estimate.tsx @@ -8,6 +8,8 @@ import sortBy from "lodash/sortBy"; import { useApplication, useEstimate } from "hooks/store"; import { useDropdownKeyDown } from "hooks/use-dropdown-key-down"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; +// ui +import { Tooltip } from "@plane/ui"; // helpers import { cn } from "helpers/common.helper"; // types @@ -30,6 +32,7 @@ type ButtonProps = { hideIcon?: boolean; hideText?: boolean; placeholder: string; + tooltip: boolean; }; type DropdownOptions = @@ -49,21 +52,30 @@ const BorderButton = (props: ButtonProps) => { hideIcon = false, hideText = false, placeholder, + tooltip, } = props; return ( -
- {!hideIcon && } - {!hideText && {estimatePoint !== null ? estimatePoint : placeholder}} - {dropdownArrow && ( -
+
+ {!hideIcon && } + {!hideText && ( + {estimatePoint !== null ? estimatePoint : placeholder} + )} + {dropdownArrow && ( +
+ ); }; @@ -76,18 +88,30 @@ const BackgroundButton = (props: ButtonProps) => { hideIcon = false, hideText = false, placeholder, + tooltip, } = props; return ( -
- {!hideIcon && } - {!hideText && {estimatePoint !== null ? estimatePoint : placeholder}} - {dropdownArrow && ( -
+
+ {!hideIcon && } + {!hideText && ( + {estimatePoint !== null ? estimatePoint : placeholder} + )} + {dropdownArrow && ( +
+ ); }; @@ -100,21 +124,30 @@ const TransparentButton = (props: ButtonProps) => { hideIcon = false, hideText = false, placeholder, + tooltip, } = props; return ( -
- {!hideIcon && } - {!hideText && {estimatePoint !== null ? estimatePoint : placeholder}} - {dropdownArrow && ( -
+
+ {!hideIcon && } + {!hideText && ( + {estimatePoint !== null ? estimatePoint : placeholder} + )} + {dropdownArrow && ( +
+ ); }; @@ -133,8 +166,9 @@ export const EstimateDropdown: React.FC = observer((props) => { placeholder = "Estimate", placement, projectId, - value, tabIndex, + tooltip = false, + value, } = props; // states const [query, setQuery] = useState(""); @@ -242,6 +276,7 @@ export const EstimateDropdown: React.FC = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "border-without-text" ? ( = observer((props) => { hideIcon={hideIcon} hideText placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "background-with-text" ? ( = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "background-without-text" ? ( = observer((props) => { hideIcon={hideIcon} hideText placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "transparent-with-text" ? ( = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "transparent-without-text" ? ( = observer((props) => { hideIcon={hideIcon} hideText placeholder={placeholder} + tooltip={tooltip} /> ) : null} diff --git a/web/components/dropdowns/member/buttons.tsx b/web/components/dropdowns/member/buttons.tsx index 3abb9b4cbc..c1ec93c535 100644 --- a/web/components/dropdowns/member/buttons.tsx +++ b/web/components/dropdowns/member/buttons.tsx @@ -3,7 +3,7 @@ import { ChevronDown } from "lucide-react"; // hooks import { useMember } from "hooks/store"; // ui -import { Avatar, AvatarGroup, UserGroupIcon } from "@plane/ui"; +import { Avatar, AvatarGroup, Tooltip, UserGroupIcon } from "@plane/ui"; // helpers import { cn } from "helpers/common.helper"; @@ -14,16 +14,17 @@ type ButtonProps = { placeholder: string; hideIcon?: boolean; hideText?: boolean; + tooltip: boolean; userIds: string | string[] | null; }; -const ButtonAvatars = observer(({ userIds }: { userIds: string | string[] | null }) => { +const ButtonAvatars = observer(({ tooltip, userIds }: { tooltip: boolean; userIds: string | string[] | null }) => { const { getUserDetails } = useMember(); if (Array.isArray(userIds)) { if (userIds.length > 0) return ( - + {userIds.map((userId) => { const userDetails = getUserDetails(userId); @@ -35,7 +36,7 @@ const ButtonAvatars = observer(({ userIds }: { userIds: string | string[] | null } else { if (userIds) { const userDetails = getUserDetails(userIds); - return ; + return ; } } @@ -51,6 +52,7 @@ export const BorderButton = observer((props: ButtonProps) => { hideText = false, placeholder, userIds, + tooltip, } = props; // store hooks const { getUserDetails } = useMember(); @@ -58,22 +60,28 @@ export const BorderButton = observer((props: ButtonProps) => { const isMultiple = Array.isArray(userIds); return ( -
- {!hideIcon && } - {!hideText && ( - - {userIds ? (isMultiple ? placeholder : getUserDetails(userIds)?.display_name) : placeholder} - - )} - {dropdownArrow && ( -
+
+ {!hideIcon && } + {!hideText && ( + + {userIds ? (isMultiple ? placeholder : getUserDetails(userIds)?.display_name) : placeholder} + + )} + {dropdownArrow && ( +
+ ); }); @@ -86,6 +94,7 @@ export const BackgroundButton = observer((props: ButtonProps) => { hideText = false, placeholder, userIds, + tooltip, } = props; // store hooks const { getUserDetails } = useMember(); @@ -93,19 +102,28 @@ export const BackgroundButton = observer((props: ButtonProps) => { const isMultiple = Array.isArray(userIds); return ( -
- {!hideIcon && } - {!hideText && ( - - {userIds ? (isMultiple ? placeholder : getUserDetails(userIds)?.display_name) : placeholder} - - )} - {dropdownArrow && ( -
+
+ {!hideIcon && } + {!hideText && ( + + {userIds ? (isMultiple ? placeholder : getUserDetails(userIds)?.display_name) : placeholder} + + )} + {dropdownArrow && ( +
+ ); }); @@ -118,6 +136,7 @@ export const TransparentButton = observer((props: ButtonProps) => { hideText = false, placeholder, userIds, + tooltip, } = props; // store hooks const { getUserDetails } = useMember(); @@ -125,21 +144,27 @@ export const TransparentButton = observer((props: ButtonProps) => { const isMultiple = Array.isArray(userIds); return ( -
- {!hideIcon && } - {!hideText && ( - - {userIds ? (isMultiple ? placeholder : getUserDetails(userIds)?.display_name) : placeholder} - - )} - {dropdownArrow && ( -
+
+ {!hideIcon && } + {!hideText && ( + + {userIds ? (isMultiple ? placeholder : getUserDetails(userIds)?.display_name) : placeholder} + + )} + {dropdownArrow && ( +
+ ); }); diff --git a/web/components/dropdowns/member/project-member.tsx b/web/components/dropdowns/member/project-member.tsx index 8393c32f0e..7cd8788412 100644 --- a/web/components/dropdowns/member/project-member.tsx +++ b/web/components/dropdowns/member/project-member.tsx @@ -36,8 +36,9 @@ export const ProjectMemberDropdown: React.FC = observer((props) => { placeholder = "Members", placement, projectId, - value, tabIndex, + tooltip = false, + value, } = props; // states const [query, setQuery] = useState(""); @@ -146,6 +147,7 @@ export const ProjectMemberDropdown: React.FC = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "border-without-text" ? ( = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} hideText /> ) : buttonVariant === "background-with-text" ? ( @@ -165,6 +168,7 @@ export const ProjectMemberDropdown: React.FC = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "background-without-text" ? ( = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} hideText /> ) : buttonVariant === "transparent-with-text" ? ( @@ -184,6 +189,7 @@ export const ProjectMemberDropdown: React.FC = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "transparent-without-text" ? ( = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} hideText /> ) : null} diff --git a/web/components/dropdowns/member/workspace-member.tsx b/web/components/dropdowns/member/workspace-member.tsx index 55806c51e1..aaa1d7f7e4 100644 --- a/web/components/dropdowns/member/workspace-member.tsx +++ b/web/components/dropdowns/member/workspace-member.tsx @@ -31,8 +31,9 @@ export const WorkspaceMemberDropdown: React.FC = observer(( onChange, placeholder = "Members", placement, - value, tabIndex, + tooltip = false, + value, } = props; // states const [query, setQuery] = useState(""); @@ -133,6 +134,7 @@ export const WorkspaceMemberDropdown: React.FC = observer(( dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "border-without-text" ? ( = observer(( dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} hideText /> ) : buttonVariant === "background-with-text" ? ( @@ -152,6 +155,7 @@ export const WorkspaceMemberDropdown: React.FC = observer(( dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "background-without-text" ? ( = observer(( dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} hideText /> ) : buttonVariant === "transparent-with-text" ? ( @@ -171,6 +176,7 @@ export const WorkspaceMemberDropdown: React.FC = observer(( dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "transparent-without-text" ? ( = observer(( dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} hideText /> ) : null} diff --git a/web/components/dropdowns/module.tsx b/web/components/dropdowns/module.tsx index 3420dfca10..e415558025 100644 --- a/web/components/dropdowns/module.tsx +++ b/web/components/dropdowns/module.tsx @@ -8,7 +8,7 @@ import { useApplication, useModule } from "hooks/store"; import { useDropdownKeyDown } from "hooks/use-dropdown-key-down"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; // icons -import { DiceIcon } from "@plane/ui"; +import { DiceIcon, Tooltip } from "@plane/ui"; // helpers import { cn } from "helpers/common.helper"; // types @@ -40,6 +40,7 @@ type ButtonProps = { hideText?: boolean; module: IModule | null; placeholder: string; + tooltip: boolean; }; const BorderButton = (props: ButtonProps) => { @@ -51,21 +52,24 @@ const BorderButton = (props: ButtonProps) => { hideText = false, module, placeholder, + tooltip, } = props; return ( -
- {!hideIcon && } - {!hideText && {module?.name ?? placeholder}} - {dropdownArrow && ( -
+ +
+ {!hideIcon && } + {!hideText && {module?.name ?? placeholder}} + {dropdownArrow && ( +
+
); }; @@ -78,18 +82,24 @@ const BackgroundButton = (props: ButtonProps) => { hideText = false, module, placeholder, + tooltip, } = props; return ( -
- {!hideIcon && } - {!hideText && {module?.name ?? placeholder}} - {dropdownArrow && ( -
+ +
+ {!hideIcon && } + {!hideText && {module?.name ?? placeholder}} + {dropdownArrow && ( +
+
); }; @@ -102,21 +112,24 @@ const TransparentButton = (props: ButtonProps) => { hideText = false, module, placeholder, + tooltip, } = props; return ( -
- {!hideIcon && } - {!hideText && {module?.name ?? placeholder}} - {dropdownArrow && ( -
+ +
+ {!hideIcon && } + {!hideText && {module?.name ?? placeholder}} + {dropdownArrow && ( +
+
); }; @@ -135,8 +148,9 @@ export const ModuleDropdown: React.FC = observer((props) => { placeholder = "Module", placement, projectId, - value, tabIndex, + tooltip = false, + value, } = props; // states const [query, setQuery] = useState(""); @@ -253,6 +267,7 @@ export const ModuleDropdown: React.FC = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "border-without-text" ? ( = observer((props) => { hideIcon={hideIcon} hideText placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "background-with-text" ? ( = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "background-without-text" ? ( = observer((props) => { hideIcon={hideIcon} hideText placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "transparent-with-text" ? ( = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "transparent-without-text" ? ( = observer((props) => { hideIcon={hideIcon} hideText placeholder={placeholder} + tooltip={tooltip} /> ) : null} diff --git a/web/components/dropdowns/priority.tsx b/web/components/dropdowns/priority.tsx index fc87559c35..dff1f5aaa4 100644 --- a/web/components/dropdowns/priority.tsx +++ b/web/components/dropdowns/priority.tsx @@ -2,11 +2,12 @@ import { Fragment, ReactNode, useRef, useState } from "react"; import { Combobox } from "@headlessui/react"; import { usePopper } from "react-popper"; import { Check, ChevronDown, Search } from "lucide-react"; +import { useTheme } from "next-themes"; // hooks import { useDropdownKeyDown } from "hooks/use-dropdown-key-down"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; // icons -import { PriorityIcon } from "@plane/ui"; +import { PriorityIcon, Tooltip } from "@plane/ui"; // helpers import { cn } from "helpers/common.helper"; // types @@ -14,7 +15,6 @@ import { TIssuePriorities } from "@plane/types"; import { TDropdownProps } from "./types"; // constants import { ISSUE_PRIORITIES } from "constants/issue"; -import { useTheme } from "next-themes"; type Props = TDropdownProps & { button?: ReactNode; @@ -33,6 +33,7 @@ type ButtonProps = { hideText?: boolean; highlightUrgent: boolean; priority: TIssuePriorities; + tooltip: boolean; }; const BorderButton = (props: ButtonProps) => { @@ -44,6 +45,7 @@ const BorderButton = (props: ButtonProps) => { hideText = false, highlightUrgent, priority, + tooltip, } = props; const priorityDetails = ISSUE_PRIORITIES.find((p) => p.key === priority); @@ -57,47 +59,49 @@ const BorderButton = (props: ButtonProps) => { }; return ( - + ); }; @@ -110,6 +114,7 @@ const BackgroundButton = (props: ButtonProps) => { hideText = false, highlightUrgent, priority, + tooltip, } = props; const priorityDetails = ISSUE_PRIORITIES.find((p) => p.key === priority); @@ -123,47 +128,49 @@ const BackgroundButton = (props: ButtonProps) => { }; return ( - + ); }; @@ -176,6 +183,7 @@ const TransparentButton = (props: ButtonProps) => { hideText = false, highlightUrgent, priority, + tooltip, } = props; const priorityDetails = ISSUE_PRIORITIES.find((p) => p.key === priority); @@ -189,47 +197,49 @@ const TransparentButton = (props: ButtonProps) => { }; return ( - + ); }; @@ -247,8 +257,9 @@ export const PriorityDropdown: React.FC = (props) => { highlightUrgent = true, onChange, placement, - value, tabIndex, + tooltip = false, + value, } = props; // states const [query, setQuery] = useState(""); @@ -341,6 +352,7 @@ export const PriorityDropdown: React.FC = (props) => { dropdownArrow={dropdownArrow && !disabled} dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} + tooltip={tooltip} /> ) : buttonVariant === "border-without-text" ? ( = (props) => { dropdownArrow={dropdownArrow && !disabled} dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} + tooltip={tooltip} hideText /> ) : buttonVariant === "background-with-text" ? ( @@ -364,6 +377,7 @@ export const PriorityDropdown: React.FC = (props) => { dropdownArrow={dropdownArrow && !disabled} dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} + tooltip={tooltip} /> ) : buttonVariant === "background-without-text" ? ( = (props) => { dropdownArrow={dropdownArrow && !disabled} dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} + tooltip={tooltip} hideText /> ) : buttonVariant === "transparent-with-text" ? ( @@ -387,6 +402,7 @@ export const PriorityDropdown: React.FC = (props) => { dropdownArrow={dropdownArrow && !disabled} dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} + tooltip={tooltip} /> ) : buttonVariant === "transparent-without-text" ? ( = (props) => { dropdownArrow={dropdownArrow && !disabled} dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} + tooltip={tooltip} hideText /> ) : null} diff --git a/web/components/dropdowns/project.tsx b/web/components/dropdowns/project.tsx index bbaf3d43f5..cdbe21b087 100644 --- a/web/components/dropdowns/project.tsx +++ b/web/components/dropdowns/project.tsx @@ -7,6 +7,8 @@ import { Check, ChevronDown, Search } from "lucide-react"; import { useProject } from "hooks/store"; import { useDropdownKeyDown } from "hooks/use-dropdown-key-down"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; +// ui +import { Tooltip } from "@plane/ui"; // helpers import { cn } from "helpers/common.helper"; import { renderEmoji } from "helpers/emoji.helper"; @@ -30,6 +32,7 @@ type ButtonProps = { hideText?: boolean; placeholder: string; project: IProject | null; + tooltip: boolean; }; const BorderButton = (props: ButtonProps) => { @@ -41,25 +44,28 @@ const BorderButton = (props: ButtonProps) => { hideText = false, placeholder, project, + tooltip, } = props; return ( -
- {!hideIcon && ( - - {project?.emoji ? renderEmoji(project?.emoji) : project?.icon_prop ? renderEmoji(project?.icon_prop) : null} - - )} - {!hideText && {project?.name ?? placeholder}} - {dropdownArrow && ( -
+ +
+ {!hideIcon && ( + + {project?.emoji ? renderEmoji(project?.emoji) : project?.icon_prop ? renderEmoji(project?.icon_prop) : null} + + )} + {!hideText && {project?.name ?? placeholder}} + {dropdownArrow && ( +
+
); }; @@ -72,22 +78,28 @@ const BackgroundButton = (props: ButtonProps) => { hideText = false, placeholder, project, + tooltip, } = props; return ( -
- {!hideIcon && ( - - {project?.emoji ? renderEmoji(project?.emoji) : project?.icon_prop ? renderEmoji(project?.icon_prop) : null} - - )} - {!hideText && {project?.name ?? placeholder}} - {dropdownArrow && ( -
+ +
+ {!hideIcon && ( + + {project?.emoji ? renderEmoji(project?.emoji) : project?.icon_prop ? renderEmoji(project?.icon_prop) : null} + + )} + {!hideText && {project?.name ?? placeholder}} + {dropdownArrow && ( +
+
); }; @@ -100,25 +112,28 @@ const TransparentButton = (props: ButtonProps) => { hideText = false, placeholder, project, + tooltip, } = props; return ( -
- {!hideIcon && ( - - {project?.emoji ? renderEmoji(project?.emoji) : project?.icon_prop ? renderEmoji(project?.icon_prop) : null} - - )} - {!hideText && {project?.name ?? placeholder}} - {dropdownArrow && ( -
+ +
+ {!hideIcon && ( + + {project?.emoji ? renderEmoji(project?.emoji) : project?.icon_prop ? renderEmoji(project?.icon_prop) : null} + + )} + {!hideText && {project?.name ?? placeholder}} + {dropdownArrow && ( +
+
); }; @@ -136,8 +151,9 @@ export const ProjectDropdown: React.FC = observer((props) => { onChange, placeholder = "Project", placement, - value, tabIndex, + tooltip = false, + value, } = props; // states const [query, setQuery] = useState(""); @@ -239,6 +255,7 @@ export const ProjectDropdown: React.FC = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "border-without-text" ? ( = observer((props) => { hideIcon={hideIcon} hideText placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "background-with-text" ? ( = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "background-without-text" ? ( = observer((props) => { hideIcon={hideIcon} hideText placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "transparent-with-text" ? ( = observer((props) => { dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} placeholder={placeholder} + tooltip={tooltip} /> ) : buttonVariant === "transparent-without-text" ? ( = observer((props) => { hideIcon={hideIcon} hideText placeholder={placeholder} + tooltip={tooltip} /> ) : null} diff --git a/web/components/dropdowns/state.tsx b/web/components/dropdowns/state.tsx index 37ae8ad725..fb8446d234 100644 --- a/web/components/dropdowns/state.tsx +++ b/web/components/dropdowns/state.tsx @@ -8,7 +8,7 @@ import { useApplication, useProjectState } from "hooks/store"; import { useDropdownKeyDown } from "hooks/use-dropdown-key-down"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; // icons -import { StateGroupIcon } from "@plane/ui"; +import { StateGroupIcon, Tooltip } from "@plane/ui"; // helpers import { cn } from "helpers/common.helper"; // types @@ -31,65 +31,111 @@ type ButtonProps = { hideIcon?: boolean; hideText?: boolean; state: IState | undefined; + tooltip: boolean; }; const BorderButton = (props: ButtonProps) => { - const { className, dropdownArrow, dropdownArrowClassName, hideIcon = false, hideText = false, state } = props; + const { + className, + dropdownArrow, + dropdownArrowClassName, + hideIcon = false, + hideText = false, + state, + tooltip, + } = props; return ( -
- {!hideIcon && ( - - )} - {!hideText && {state?.name ?? "State"}} - {dropdownArrow && ( -
+ +
+ {!hideIcon && ( + + )} + {!hideText && {state?.name ?? "State"}} + {dropdownArrow && ( +
+
); }; const BackgroundButton = (props: ButtonProps) => { - const { className, dropdownArrow, dropdownArrowClassName, hideIcon = false, hideText = false, state } = props; + const { + className, + dropdownArrow, + dropdownArrowClassName, + hideIcon = false, + hideText = false, + state, + tooltip, + } = props; return ( -
- {!hideIcon && ( - - )} - {!hideText && {state?.name ?? "State"}} - {dropdownArrow && ( -
+ +
+ {!hideIcon && ( + + )} + {!hideText && {state?.name ?? "State"}} + {dropdownArrow && ( +
+
); }; const TransparentButton = (props: ButtonProps) => { - const { className, dropdownArrow, dropdownArrowClassName, hideIcon = false, hideText = false, state } = props; + const { + className, + dropdownArrow, + dropdownArrowClassName, + hideIcon = false, + hideText = false, + state, + tooltip, + } = props; return ( -
- {!hideIcon && ( - - )} - {!hideText && {state?.name ?? "State"}} - {dropdownArrow && ( -
+ +
+ {!hideIcon && ( + + )} + {!hideText && {state?.name ?? "State"}} + {dropdownArrow && ( +
+
); }; @@ -107,8 +153,9 @@ export const StateDropdown: React.FC = observer((props) => { onChange, placement, projectId, - value, tabIndex, + tooltip = false, + value, } = props; // states const [query, setQuery] = useState(""); @@ -204,6 +251,7 @@ export const StateDropdown: React.FC = observer((props) => { dropdownArrow={dropdownArrow && !disabled} dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} + tooltip={tooltip} /> ) : buttonVariant === "border-without-text" ? ( = observer((props) => { dropdownArrow={dropdownArrow && !disabled} dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} + tooltip={tooltip} hideText /> ) : buttonVariant === "background-with-text" ? ( @@ -221,6 +270,7 @@ export const StateDropdown: React.FC = observer((props) => { dropdownArrow={dropdownArrow && !disabled} dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} + tooltip={tooltip} /> ) : buttonVariant === "background-without-text" ? ( = observer((props) => { dropdownArrow={dropdownArrow && !disabled} dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} + tooltip={tooltip} hideText /> ) : buttonVariant === "transparent-with-text" ? ( @@ -238,6 +289,7 @@ export const StateDropdown: React.FC = observer((props) => { dropdownArrow={dropdownArrow && !disabled} dropdownArrowClassName={dropdownArrowClassName} hideIcon={hideIcon} + tooltip={tooltip} /> ) : buttonVariant === "transparent-without-text" ? ( = observer((props) => { className={buttonClassName} dropdownArrow={dropdownArrow && !disabled} dropdownArrowClassName={dropdownArrowClassName} + hideIcon={hideIcon} + tooltip={tooltip} hideText /> ) : null} diff --git a/web/components/dropdowns/types.d.ts b/web/components/dropdowns/types.d.ts index 5bf3080ea4..b99369022f 100644 --- a/web/components/dropdowns/types.d.ts +++ b/web/components/dropdowns/types.d.ts @@ -18,4 +18,6 @@ export type TDropdownProps = { placeholder?: string; placement?: Placement; tabIndex?: number; + // TODO: rename this prop to showTooltip + tooltip?: boolean; }; diff --git a/web/components/empty-state/empty-state.tsx b/web/components/empty-state/empty-state.tsx index 5416eef431..a74502f4dc 100644 --- a/web/components/empty-state/empty-state.tsx +++ b/web/components/empty-state/empty-state.tsx @@ -1,4 +1,5 @@ import React from "react"; +import Image from "next/image"; // components import { ComicBoxButton } from "./comic-box-button"; // ui @@ -51,8 +52,6 @@ export const EmptyState: React.FC = ({ ); - const imageElement = {primaryButton?.text; - const secondaryButtonElement = secondaryButton && ( diff --git a/web/components/inbox/index.ts b/web/components/inbox/index.ts index ae267f54ca..bc8be5506f 100644 --- a/web/components/inbox/index.ts +++ b/web/components/inbox/index.ts @@ -3,6 +3,8 @@ export * from "./modals"; export * from "./inbox-issue-actions"; export * from "./inbox-issue-status"; +export * from "./content/root"; + export * from "./sidebar/root"; export * from "./sidebar/filter/filter-selection"; diff --git a/web/components/inbox/sidebar/filter/filter-selection.tsx b/web/components/inbox/sidebar/filter/filter-selection.tsx index 62b9f48550..a34cf18719 100644 --- a/web/components/inbox/sidebar/filter/filter-selection.tsx +++ b/web/components/inbox/sidebar/filter/filter-selection.tsx @@ -1,5 +1,6 @@ import { FC } from "react"; import { observer } from "mobx-react-lite"; +import { useRouter } from "next/router"; // mobx store import { useInboxIssues } from "hooks/store"; // ui @@ -16,6 +17,9 @@ type TInboxIssueFilterSelection = { workspaceSlug: string; projectId: string; in export const InboxIssueFilterSelection: FC = observer((props) => { const { workspaceSlug, projectId, inboxId } = props; + // router + const router = useRouter(); + const { inboxIssueId } = router.query; // hooks const { filters: { inboxFilters, updateInboxFilters }, @@ -49,6 +53,12 @@ export const InboxIssueFilterSelection: FC = observe updateInboxFilters(workspaceSlug.toString(), projectId.toString(), inboxId.toString(), { [option.key]: [...currentValue, option.value], }); + + if (inboxIssueId) { + router.push({ + pathname: `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}`, + }); + } }} direction="right" height="rg" diff --git a/web/components/inbox/sidebar/root.tsx b/web/components/inbox/sidebar/root.tsx index cca6b4582d..50bb8a31c6 100644 --- a/web/components/inbox/sidebar/root.tsx +++ b/web/components/inbox/sidebar/root.tsx @@ -1,5 +1,10 @@ import { FC } from "react"; import { Inbox } from "lucide-react"; +import { observer } from "mobx-react"; +// hooks +import { useInboxIssues } from "hooks/store"; +// ui +import { Loader } from "@plane/ui"; // components import { InboxIssueList, InboxIssueFilterSelection, InboxIssueAppliedFilter } from "../"; @@ -9,19 +14,23 @@ type TInboxSidebarRoot = { inboxId: string; }; -export const InboxSidebarRoot: FC = (props) => { +export const InboxSidebarRoot: FC = observer((props) => { const { workspaceSlug, projectId, inboxId } = props; + // store hooks + const { + issues: { loader }, + } = useInboxIssues(); return (
-
+
Inbox
-
+
@@ -30,9 +39,18 @@ export const InboxSidebarRoot: FC = (props) => {
-
- -
+ {loader && ["init-loader", "mutation"].includes(loader) ? ( + + + + + + + ) : ( +
+ +
+ )}
); -}; +}); diff --git a/web/components/issues/activity.tsx b/web/components/issues/activity.tsx deleted file mode 100644 index 6dae8486f7..0000000000 --- a/web/components/issues/activity.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import React from "react"; - -import Link from "next/link"; -import { useRouter } from "next/router"; - -// components -import { ActivityIcon, ActivityMessage } from "components/core"; -import { CommentCard } from "components/issues/comment"; -// ui -import { Loader, Tooltip } from "@plane/ui"; -// helpers -import { renderFormattedTime, renderFormattedDate, calculateTimeAgo } from "helpers/date-time.helper"; -// types -import { IIssueActivity } from "@plane/types"; -import { History } from "lucide-react"; - -type Props = { - activity: IIssueActivity[] | undefined; - handleCommentUpdate: (commentId: string, data: Partial) => Promise; - handleCommentDelete: (commentId: string) => Promise; - showAccessSpecifier?: boolean; -}; - -export const IssueActivitySection: React.FC = ({ - activity, - handleCommentUpdate, - handleCommentDelete, - showAccessSpecifier = false, -}) => { - const router = useRouter(); - const { workspaceSlug } = router.query; - - if (!activity) - return ( - -
- - -
-
- - -
-
- - -
-
- ); - - return ( -
-
    - {activity.map((activityItem, index) => { - // determines what type of action is performed - const message = activityItem.field ? : "created the issue."; - - if ("field" in activityItem && activityItem.field !== "updated_by") { - return ( -
  • -
    - {activity.length > 1 && index !== activity.length - 1 ? ( -
    -
  • - ); - } else if ("comment_json" in activityItem) - return ( -
    - -
    - ); - })} -
-
- ); -}; diff --git a/web/components/issues/comment/add-comment.tsx b/web/components/issues/comment/add-comment.tsx deleted file mode 100644 index e9396ecccb..0000000000 --- a/web/components/issues/comment/add-comment.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import React from "react"; -import { useRouter } from "next/router"; -import { useForm, Controller } from "react-hook-form"; -// hooks -import { useMention, useWorkspace } from "hooks/store"; -// services -import { FileService } from "services/file.service"; -// components -import { LiteTextEditorWithRef } from "@plane/lite-text-editor"; -// ui -import { Button } from "@plane/ui"; -import { Globe2, Lock } from "lucide-react"; -// types -import type { IIssueActivity } from "@plane/types"; - -const defaultValues: Partial = { - access: "INTERNAL", - comment_html: "", -}; - -type Props = { - disabled?: boolean; - onSubmit: (data: IIssueActivity) => Promise; - showAccessSpecifier?: boolean; -}; - -type commentAccessType = { - icon: any; - key: string; - label: "Private" | "Public"; -}; -const commentAccess: commentAccessType[] = [ - { - icon: Lock, - key: "INTERNAL", - label: "Private", - }, - { - icon: Globe2, - key: "EXTERNAL", - label: "Public", - }, -]; - -// services -const fileService = new FileService(); - -export const AddComment: React.FC = ({ disabled = false, onSubmit, showAccessSpecifier = false }) => { - // refs - const editorRef = React.useRef(null); - // router - const router = useRouter(); - const { workspaceSlug } = router.query; - const workspaceStore = useWorkspace(); - const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string; - - // store hooks - const { mentionHighlights, mentionSuggestions } = useMention(); - // form info - const { - control, - formState: { isSubmitting }, - handleSubmit, - reset, - } = useForm({ defaultValues }); - - const handleAddComment = async (formData: IIssueActivity) => { - if (!formData.comment_html || isSubmitting) return; - - await onSubmit(formData).then(() => { - reset(defaultValues); - editorRef.current?.clearEditor(); - }); - }; - - return ( -
-
-
- ( - ( -

" : commentValue} - customClassName="p-2 h-full" - editorContentCustomClassNames="min-h-[35px]" - debouncedUpdatesEnabled={false} - onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)} - commentAccessSpecifier={ - showAccessSpecifier - ? { accessValue: accessValue ?? "INTERNAL", onAccessChange, showAccessSpecifier, commentAccess } - : undefined - } - mentionSuggestions={mentionSuggestions} - mentionHighlights={mentionHighlights} - submitButton={ - - } - /> - )} - /> - )} - /> -
-
-
- ); -}; diff --git a/web/components/issues/comment/comment-card.tsx b/web/components/issues/comment/comment-card.tsx deleted file mode 100644 index 1ee90f99fe..0000000000 --- a/web/components/issues/comment/comment-card.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { observer } from "mobx-react-lite"; -// hooks -import { useMention, useUser, useWorkspace } from "hooks/store"; -// services -import { FileService } from "services/file.service"; -// icons -import { Check, Globe2, Lock, MessageSquare, Pencil, Trash2, X } from "lucide-react"; -// ui -import { CustomMenu } from "@plane/ui"; -import { CommentReaction } from "components/issues"; -import { LiteTextEditorWithRef, LiteReadOnlyEditorWithRef } from "@plane/lite-text-editor"; -// helpers -import { calculateTimeAgo } from "helpers/date-time.helper"; -// types -import type { IIssueActivity } from "@plane/types"; - -// services -const fileService = new FileService(); - -type Props = { - comment: IIssueActivity; - handleCommentDeletion: (comment: string) => void; - onSubmit: (commentId: string, data: Partial) => void; - showAccessSpecifier?: boolean; - workspaceSlug: string; -}; - -export const CommentCard: React.FC = observer((props) => { - const { comment, handleCommentDeletion, onSubmit, showAccessSpecifier = false, workspaceSlug } = props; - const workspaceStore = useWorkspace(); - const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug)?.id as string; - - // states - const [isEditing, setIsEditing] = useState(false); - // refs - const editorRef = React.useRef(null); - const showEditorRef = React.useRef(null); - // store hooks - const { currentUser } = useUser(); - const { mentionHighlights, mentionSuggestions } = useMention(); - // form info - const { - formState: { isSubmitting }, - handleSubmit, - setFocus, - watch, - setValue, - } = useForm({ - defaultValues: comment, - }); - - const onEnter = (formData: Partial) => { - if (isSubmitting) return; - setIsEditing(false); - - onSubmit(comment.id, formData); - - editorRef.current?.setEditorValue(formData.comment_html); - showEditorRef.current?.setEditorValue(formData.comment_html); - }; - - useEffect(() => { - isEditing && setFocus("comment"); - }, [isEditing, setFocus]); - - return ( -
-
- {comment.actor_detail.avatar && comment.actor_detail.avatar !== "" ? ( - { - ) : ( -
- {comment.actor_detail.is_bot - ? comment.actor_detail.first_name.charAt(0) - : comment.actor_detail.display_name.charAt(0)} -
- )} - - - -
- -
-
-
- {comment.actor_detail.is_bot ? comment.actor_detail.first_name + " Bot" : comment.actor_detail.display_name} -
-

commented {calculateTimeAgo(comment.created_at)}

-
-
-
-
- setValue("comment_html", comment_html)} - mentionSuggestions={mentionSuggestions} - mentionHighlights={mentionHighlights} - /> -
-
- - -
-
-
- {showAccessSpecifier && ( -
- {comment.access === "INTERNAL" ? : } -
- )} - - -
-
-
- - {currentUser?.id === comment.actor && ( - - setIsEditing(true)} className="flex items-center gap-1"> - - Edit comment - - {showAccessSpecifier && ( - <> - {comment.access === "INTERNAL" ? ( - onSubmit(comment.id, { access: "EXTERNAL" })} - className="flex items-center gap-1" - > - - Switch to public comment - - ) : ( - onSubmit(comment.id, { access: "INTERNAL" })} - className="flex items-center gap-1" - > - - Switch to private comment - - )} - - )} - { - handleCommentDeletion(comment.id); - }} - className="flex items-center gap-1" - > - - Delete comment - - - )} -
- ); -}); diff --git a/web/components/issues/comment/comment-reaction.tsx b/web/components/issues/comment/comment-reaction.tsx deleted file mode 100644 index a59337575e..0000000000 --- a/web/components/issues/comment/comment-reaction.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { FC } from "react"; -import { useRouter } from "next/router"; -import { observer } from "mobx-react-lite"; -// hooks -import { useUser } from "hooks/store"; -import useCommentReaction from "hooks/use-comment-reaction"; -// ui -// import { ReactionSelector } from "components/core"; -// helper -import { renderEmoji } from "helpers/emoji.helper"; -// types -import { IssueCommentReaction } from "@plane/types"; - -type Props = { - projectId?: string | string[]; - commentId: string; - readonly?: boolean; -}; - -export const CommentReaction: FC = observer((props) => { - const { projectId, commentId, readonly = false } = props; - // router - const router = useRouter(); - const { workspaceSlug } = router.query; - // store hooks - const { currentUser } = useUser(); - - const { commentReactions, groupedReactions, handleReactionCreate, handleReactionDelete } = useCommentReaction( - workspaceSlug, - projectId, - commentId - ); - - const handleReactionClick = (reaction: string) => { - if (!workspaceSlug || !projectId || !commentId) return; - - const isSelected = commentReactions?.some( - (r: IssueCommentReaction) => r.actor === currentUser?.id && r.reaction === reaction - ); - - if (isSelected) { - handleReactionDelete(reaction); - } else { - handleReactionCreate(reaction); - } - }; - - return ( -
- {/* FIXME: have to replace this once the issue details page is ready --issue-detail-- */} - {/* {!readonly && ( - reaction.actor === currentUser?.id) - .map((r: IssueCommentReaction) => r.reaction) || [] - } - onSelect={handleReactionClick} - /> - )} */} - - {Object.keys(groupedReactions || {}).map( - (reaction) => - groupedReactions?.[reaction]?.length && - groupedReactions[reaction].length > 0 && ( - - ) - )} -
- ); -}); diff --git a/web/components/issues/comment/index.ts b/web/components/issues/comment/index.ts deleted file mode 100644 index 61ac899ada..0000000000 --- a/web/components/issues/comment/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./add-comment"; -export * from "./comment-card"; -export * from "./comment-reaction"; diff --git a/web/components/issues/index.ts b/web/components/issues/index.ts index f1c6636cda..3cf88cb7c4 100644 --- a/web/components/issues/index.ts +++ b/web/components/issues/index.ts @@ -1,8 +1,6 @@ export * from "./attachment"; -export * from "./comment"; export * from "./issue-modal"; export * from "./view-select"; -export * from "./activity"; export * from "./delete-issue-modal"; export * from "./description-form"; export * from "./issue-layouts"; diff --git a/web/components/issues/issue-detail/inbox/main-content.tsx b/web/components/issues/issue-detail/inbox/main-content.tsx index 960ef20d70..2e612ad34c 100644 --- a/web/components/issues/issue-detail/inbox/main-content.tsx +++ b/web/components/issues/issue-detail/inbox/main-content.tsx @@ -78,7 +78,9 @@ export const InboxIssueMainContent: React.FC = observer((props) => { )}
- +
+ +
); }); diff --git a/web/components/issues/issue-detail/inbox/root.tsx b/web/components/issues/issue-detail/inbox/root.tsx index f689e42109..b8f12a944c 100644 --- a/web/components/issues/issue-detail/inbox/root.tsx +++ b/web/components/issues/issue-detail/inbox/root.tsx @@ -9,8 +9,6 @@ import useToast from "hooks/use-toast"; // types import { TIssue } from "@plane/types"; import { TIssueOperations } from "../root"; -// ui -import { Loader } from "@plane/ui"; // constants import { EUserProjectRoles } from "constants/project"; @@ -105,46 +103,28 @@ export const InboxIssueDetailRoot: FC = (props) => { // issue details const issue = getIssueById(issueId); + if (!issue) return <>; return ( - <> - {issue ? ( -
-
- -
-
- -
-
- ) : ( - -
- - - - -
-
- - - - -
-
- )} - +
+
+ +
+
+ +
+
); }; diff --git a/web/components/issues/issue-detail/inbox/sidebar.tsx b/web/components/issues/issue-detail/inbox/sidebar.tsx index 0b14e46cb2..e0b2aca28a 100644 --- a/web/components/issues/issue-detail/inbox/sidebar.tsx +++ b/web/components/issues/issue-detail/inbox/sidebar.tsx @@ -1,17 +1,15 @@ import React from "react"; -// import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; -import { CalendarDays, Signal, Tag } from "lucide-react"; +import { CalendarCheck2, Signal, Tag } from "lucide-react"; // hooks -import { useIssueDetail, useProject } from "hooks/store"; +import { useIssueDetail, useProject, useProjectState } from "hooks/store"; // components import { IssueLabel, TIssueOperations } from "components/issues"; -import { PriorityDropdown, ProjectMemberDropdown, StateDropdown } from "components/dropdowns"; -// ui -import { CustomDatePicker } from "components/ui"; +import { DateDropdown, PriorityDropdown, ProjectMemberDropdown, StateDropdown } from "components/dropdowns"; // icons import { DoubleCircleIcon, StateGroupIcon, UserGroupIcon } from "@plane/ui"; -// types +// helper +import { renderFormattedPayloadDate } from "helpers/date-time.helper"; type Props = { workspaceSlug: string; @@ -23,12 +21,9 @@ type Props = { export const InboxIssueDetailsSidebar: React.FC = observer((props) => { const { workspaceSlug, projectId, issueId, issueOperations, is_editable } = props; - // router - // FIXME: Check if we need this. Previously it was used to render Project Identifier conditionally. - // const router = useRouter(); - // const { inboxIssueId } = router.query; // store hooks const { getProjectById } = useProject(); + const { projectStates } = useProjectState(); const { issue: { getIssueById }, } = useIssueDetail(); @@ -41,11 +36,15 @@ export const InboxIssueDetailsSidebar: React.FC = observer((props) => { const minDate = issue.start_date ? new Date(issue.start_date) : null; minDate?.setDate(minDate.getDate()); + const currentIssueState = projectStates?.find((s) => s.id === issue.state_id); + return (
- + {currentIssueState && ( + + )}

{projectDetails?.identifier}-{issue?.sequence_id}

@@ -53,86 +52,103 @@ export const InboxIssueDetailsSidebar: React.FC = observer((props) => {
+
Properties
-
+
{/* State */} -
-
+
+
-

State

-
-
- issueOperations.update(workspaceSlug, projectId, issueId, { state_id: val })} - projectId={projectId?.toString() ?? ""} - disabled={!is_editable} - buttonVariant="background-with-text" - /> + State
+ issueOperations.update(workspaceSlug, projectId, issueId, { state_id: val })} + projectId={projectId?.toString() ?? ""} + disabled={!is_editable} + buttonVariant="transparent-with-text" + className="w-3/5 flex-grow group" + buttonContainerClassName="w-full text-left" + buttonClassName="text-sm" + dropdownArrow + dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline" + />
{/* Assignee */} -
-
+
+
-

Assignees

-
-
- issueOperations.update(workspaceSlug, projectId, issueId, { assignee_ids: val })} - disabled={!is_editable} - projectId={projectId?.toString() ?? ""} - placeholder="Assignees" - multiple - buttonVariant={issue?.assignee_ids?.length > 0 ? "transparent-without-text" : "background-with-text"} - buttonClassName={issue?.assignee_ids?.length > 0 ? "hover:bg-transparent px-0" : ""} - /> + Assignees
+ issueOperations.update(workspaceSlug, projectId, issueId, { assignee_ids: val })} + disabled={!is_editable} + projectId={projectId?.toString() ?? ""} + placeholder="Add assignees" + multiple + buttonVariant={issue?.assignee_ids?.length > 0 ? "transparent-without-text" : "transparent-with-text"} + className="w-3/5 flex-grow group" + buttonContainerClassName="w-full text-left" + buttonClassName={`text-sm justify-between ${ + issue?.assignee_ids.length > 0 ? "" : "text-custom-text-400" + }`} + hideIcon={issue.assignee_ids?.length === 0} + dropdownArrow + dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline" + />
{/* Priority */} -
-
+
+
-

Priority

-
-
- issueOperations.update(workspaceSlug, projectId, issueId, { priority: val })} - disabled={!is_editable} - buttonVariant="background-with-text" - /> + Priority
+ issueOperations.update(workspaceSlug, projectId, issueId, { priority: val })} + disabled={!is_editable} + buttonVariant="border-with-text" + className="w-3/5 flex-grow rounded px-2 hover:bg-custom-background-80" + buttonContainerClassName="w-full text-left" + buttonClassName="w-min h-auto whitespace-nowrap" + />
-
-
+
+
{/* Due Date */} -
-
- -

Due date

-
-
- issueOperations.update(workspaceSlug, projectId, issueId, { target_date: val })} - className="border-none bg-custom-background-80" - minDate={minDate ?? undefined} - disabled={!is_editable} - /> +
+
+ + Due date
+ + issueOperations.update(workspaceSlug, projectId, issueId, { + target_date: val ? renderFormattedPayloadDate(val) : null, + }) + } + minDate={minDate ?? undefined} + disabled={!is_editable} + buttonVariant="transparent-with-text" + className="w-3/5 flex-grow group" + buttonContainerClassName="w-full text-left" + buttonClassName={`text-sm ${issue?.target_date ? "" : "text-custom-text-400"}`} + hideIcon + clearIconClassName="h-3 w-3 hidden group-hover:inline" + />
{/* Labels */} -
-
+
+
-

Label

+ Labels
-
+
= (props) => { hasError={Boolean(errors.name)} placeholder="Title" className="w-full" + disabled={isSubmitting} /> )} /> @@ -150,7 +151,7 @@ export const LabelCreate: FC = (props) => { )} diff --git a/web/components/issues/issue-detail/root.tsx b/web/components/issues/issue-detail/root.tsx index 58a52cc977..6434ce2e93 100644 --- a/web/components/issues/issue-detail/root.tsx +++ b/web/components/issues/issue-detail/root.tsx @@ -120,12 +120,23 @@ export const IssueDetailRoot: FC = (props) => { }, addIssueToCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => { try { - await addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds); - setToastAlert({ - title: "Cycle added to issue successfully", - type: "success", - message: "Issue added to issue successfully", - }); + await addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds) + .then((res) => { + updateIssue(workspaceSlug, projectId, res.id, res); + fetchIssue(workspaceSlug, projectId, res.id); + setToastAlert({ + title: "Cycle added to issue successfully", + type: "success", + message: "Issue added to issue successfully", + }); + }) + .catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "Selected issues could not be added to the cycle. Please try again.", + }); + }); } catch (error) { setToastAlert({ title: "Cycle add to issue failed", @@ -152,12 +163,23 @@ export const IssueDetailRoot: FC = (props) => { }, addIssueToModule: async (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => { try { - await addIssueToModule(workspaceSlug, projectId, moduleId, issueIds); - setToastAlert({ - title: "Module added to issue successfully", - type: "success", - message: "Module added to issue successfully", - }); + await addIssueToModule(workspaceSlug, projectId, moduleId, issueIds) + .then((res) => { + updateIssue(workspaceSlug, projectId, res.id, res); + fetchIssue(workspaceSlug, projectId, res.id); + setToastAlert({ + title: "Module added to issue successfully", + type: "success", + message: "Module added to issue successfully", + }); + }) + .catch(() => + setToastAlert({ + type: "error", + title: "Error!", + message: "Selected issues could not be added to the module. Please try again.", + }) + ); } catch (error) { setToastAlert({ title: "Module add to issue failed", diff --git a/web/components/issues/issue-detail/sidebar.tsx b/web/components/issues/issue-detail/sidebar.tsx index 46b9e6c6c1..f811078f87 100644 --- a/web/components/issues/issue-detail/sidebar.tsx +++ b/web/components/issues/issue-detail/sidebar.tsx @@ -50,38 +50,10 @@ type Props = { issueOperations: TIssueOperations; is_archived: boolean; is_editable: boolean; - fieldsToShow?: ( - | "state" - | "assignee" - | "priority" - | "estimate" - | "parent" - | "blocker" - | "blocked" - | "startDate" - | "dueDate" - | "cycle" - | "module" - | "label" - | "link" - | "delete" - | "all" - | "subscribe" - | "duplicate" - | "relates_to" - )[]; }; export const IssueDetailsSidebar: React.FC = observer((props) => { - const { - workspaceSlug, - projectId, - issueId, - issueOperations, - is_archived, - is_editable, - fieldsToShow = ["all"], - } = props; + const { workspaceSlug, projectId, issueId, issueOperations, is_archived, is_editable } = props; // router const router = useRouter(); const { inboxIssueId } = router.query; @@ -153,21 +125,19 @@ export const IssueDetailsSidebar: React.FC = observer((props) => {
- {currentUser && !is_archived && (fieldsToShow.includes("all") || fieldsToShow.includes("subscribe")) && ( + {currentUser && !is_archived && ( )} - {(fieldsToShow.includes("all") || fieldsToShow.includes("link")) && ( - - )} + - {is_editable && (fieldsToShow.includes("all") || fieldsToShow.includes("delete")) && ( + {is_editable && ( - - -
-
- -
- {showAccessSpecifier && ( -
- {comment.access === "INTERNAL" ? : } -
- )} - - -
- -
-
-
-
- {user?.id === comment.actor && ( - - setIsEditing(true)} className="flex items-center gap-1"> - - Edit comment - - {showAccessSpecifier && ( - <> - {comment.access === "INTERNAL" ? ( - onSubmit({ id: comment.id, access: "EXTERNAL" })} - className="flex items-center gap-1" - > - - Switch to public comment - - ) : ( - onSubmit({ id: comment.id, access: "INTERNAL" })} - className="flex items-center gap-1" - > - - Switch to private comment - - )} - - )} - { - handleCommentDeletion(comment.id); - }} - className="flex items-center gap-1" - > - - Delete comment - - - )} -
- ); -}; diff --git a/web/components/issues/peek-overview/activity/comment-editor.tsx b/web/components/issues/peek-overview/activity/comment-editor.tsx deleted file mode 100644 index 45119e9324..0000000000 --- a/web/components/issues/peek-overview/activity/comment-editor.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React from "react"; -import { useRouter } from "next/router"; -import { useForm, Controller } from "react-hook-form"; -import { Globe2, Lock } from "lucide-react"; -// hooks -import { useMention, useWorkspace } from "hooks/store"; -// services -import { FileService } from "services/file.service"; -// components -import { LiteTextEditorWithRef } from "@plane/lite-text-editor"; -// ui -import { Button } from "@plane/ui"; -// types -import type { IIssueActivity } from "@plane/types"; - -const defaultValues: Partial = { - access: "INTERNAL", - comment_html: "", -}; - -type IIssueCommentEditor = { - disabled?: boolean; - onSubmit: (data: IIssueActivity) => Promise; - showAccessSpecifier?: boolean; -}; - -type commentAccessType = { - icon: any; - key: string; - label: "Private" | "Public"; -}; -const commentAccess: commentAccessType[] = [ - { - icon: Lock, - key: "INTERNAL", - label: "Private", - }, - { - icon: Globe2, - key: "EXTERNAL", - label: "Public", - }, -]; - -// services -const fileService = new FileService(); - -export const IssueCommentEditor: React.FC = (props) => { - const { disabled = false, onSubmit, showAccessSpecifier = false } = props; - // refs - const editorRef = React.useRef(null); - // router - const router = useRouter(); - const { workspaceSlug } = router.query; - const workspaceStore = useWorkspace(); - const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string; - - // store hooks - const { mentionHighlights, mentionSuggestions } = useMention(); - // form info - const { - control, - formState: { isSubmitting }, - handleSubmit, - reset, - } = useForm({ defaultValues }); - - const handleAddComment = async (formData: IIssueActivity) => { - if (!formData.comment_html || isSubmitting) return; - - await onSubmit(formData).then(() => { - reset(defaultValues); - editorRef.current?.clearEditor(); - }); - }; - - return ( -
-
-
- ( - ( -

" : commentValue} - customClassName="p-2 h-full" - editorContentCustomClassNames="min-h-[35px]" - debouncedUpdatesEnabled={false} - mentionSuggestions={mentionSuggestions} - mentionHighlights={mentionHighlights} - onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)} - commentAccessSpecifier={ - showAccessSpecifier - ? { accessValue: accessValue ?? "INTERNAL", onAccessChange, showAccessSpecifier, commentAccess } - : undefined - } - submitButton={ - - } - /> - )} - /> - )} - /> -
-
-
- ); -}; diff --git a/web/components/issues/peek-overview/activity/comment-reaction.tsx b/web/components/issues/peek-overview/activity/comment-reaction.tsx deleted file mode 100644 index 81ce7b4d5e..0000000000 --- a/web/components/issues/peek-overview/activity/comment-reaction.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { FC } from "react"; -import useSWR from "swr"; -import { observer } from "mobx-react-lite"; -// components -import { IssuePeekOverviewReactions } from "components/issues"; -import { useIssueDetail } from "hooks/store"; - -interface IIssueCommentReaction { - workspaceSlug: string; - projectId: string; - issueId: string; - user: any; - - comment: any; - issueCommentReactionCreate: (commentId: string, reaction: string) => void; - issueCommentReactionRemove: (commentId: string, reaction: string) => void; -} - -export const IssueCommentReaction: FC = observer((props) => { - const { workspaceSlug, projectId, issueId, user, comment, issueCommentReactionCreate, issueCommentReactionRemove } = - props; - - const issueDetail = useIssueDetail(); - - const handleCommentReactionCreate = (reaction: string) => { - if (issueCommentReactionCreate && comment?.id) issueCommentReactionCreate(comment?.id, reaction); - }; - - const handleCommentReactionRemove = (reaction: string) => { - if (issueCommentReactionRemove && comment?.id) issueCommentReactionRemove(comment?.id, reaction); - }; - - useSWR( - workspaceSlug && projectId && issueId && comment && comment?.id - ? `ISSUE+PEEK_OVERVIEW_COMMENT_${comment?.id}` - : null, - () => { - if (workspaceSlug && projectId && issueId && comment && comment.id) { - issueDetail.fetchCommentReactions(workspaceSlug, projectId, comment?.id); - } - } - ); - - const issueReactions = issueDetail?.commentReaction.getCommentReactionsByCommentId(comment.id) || null; - - return ( -
- -
- ); -}); diff --git a/web/components/issues/peek-overview/activity/index.ts b/web/components/issues/peek-overview/activity/index.ts deleted file mode 100644 index 705c5a3363..0000000000 --- a/web/components/issues/peek-overview/activity/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./card"; -export * from "./comment-card"; -export * from "./comment-editor"; -export * from "./comment-reaction"; -export * from "./view"; diff --git a/web/components/issues/peek-overview/activity/view.tsx b/web/components/issues/peek-overview/activity/view.tsx deleted file mode 100644 index 4a13abb1ef..0000000000 --- a/web/components/issues/peek-overview/activity/view.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { FC } from "react"; -// components -import { IssueActivityCard, IssueCommentEditor } from "components/issues"; -// types -import { IUser } from "@plane/types"; - -type Props = { - workspaceSlug: string; - projectId: string; - issueId: string; - user: IUser | null; - issueActivity: string[] | undefined; - issueCommentCreate: (comment: any) => void; - issueCommentUpdate: (comment: any) => void; - issueCommentRemove: (commentId: string) => void; - issueCommentReactionCreate: (commentId: string, reaction: string) => void; - issueCommentReactionRemove: (commentId: string, reaction: string) => void; - showCommentAccessSpecifier: boolean; -}; - -export const IssueActivity: FC = (props) => { - const { - workspaceSlug, - projectId, - issueId, - user, - issueActivity, - issueCommentCreate, - issueCommentUpdate, - issueCommentRemove, - issueCommentReactionCreate, - issueCommentReactionRemove, - showCommentAccessSpecifier, - } = props; - - const handleAddComment = async (formData: any) => { - if (!formData.comment_html) return; - await issueCommentCreate(formData); - }; - - return ( -
-
Activity
- -
- - -
-
- ); -}; diff --git a/web/components/issues/peek-overview/index.ts b/web/components/issues/peek-overview/index.ts index 38581dada2..6d602e45b8 100644 --- a/web/components/issues/peek-overview/index.ts +++ b/web/components/issues/peek-overview/index.ts @@ -1,5 +1,3 @@ -export * from "./activity"; -export * from "./reactions"; export * from "./issue-detail"; export * from "./properties"; export * from "./root"; diff --git a/web/components/issues/peek-overview/reactions/index.ts b/web/components/issues/peek-overview/reactions/index.ts deleted file mode 100644 index 579646533e..0000000000 --- a/web/components/issues/peek-overview/reactions/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./preview"; -export * from "./root"; -export * from "./selector"; diff --git a/web/components/issues/peek-overview/reactions/preview.tsx b/web/components/issues/peek-overview/reactions/preview.tsx deleted file mode 100644 index 7dc1f78ec3..0000000000 --- a/web/components/issues/peek-overview/reactions/preview.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { FC } from "react"; -// helpers -import { renderEmoji } from "helpers/emoji.helper"; - -interface IIssueReactionPreview { - issueReactions: any; - user: any; - handleReaction: (reaction: string) => void; -} - -export const IssueReactionPreview: FC = (props) => { - const { issueReactions, user, handleReaction } = props; - - const isUserReacted = (reactions: any) => { - const userReaction = reactions?.find((reaction: any) => reaction.actor === user?.id); - if (userReaction) return true; - return false; - }; - - return ( -
- {Object.keys(issueReactions || {}).map( - (reaction) => - issueReactions[reaction]?.length > 0 && ( - - ) - )} -
- ); -}; diff --git a/web/components/issues/peek-overview/reactions/root.tsx b/web/components/issues/peek-overview/reactions/root.tsx deleted file mode 100644 index d8aa47721f..0000000000 --- a/web/components/issues/peek-overview/reactions/root.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { FC } from "react"; -// components -import { IssueReactionPreview, IssueReactionSelector } from "components/issues"; -// types -import { IUser } from "@plane/types"; - -interface IIssueReaction { - issueReactions: any; - user: IUser | null; - issueReactionCreate: (reaction: string) => void; - issueReactionRemove: (reaction: string) => void; - position?: "top" | "bottom"; -} - -export const IssuePeekOverviewReactions: FC = (props) => { - const { issueReactions, user, issueReactionCreate, issueReactionRemove, position = "bottom" } = props; - - const handleReaction = (reaction: string) => { - const isReactionAvailable = - issueReactions?.[reaction].find((_reaction: any) => _reaction.actor === user?.id) ?? false; - - if (isReactionAvailable) issueReactionRemove(reaction); - else issueReactionCreate(reaction); - }; - - return ( -
- - -
- ); -}; diff --git a/web/components/issues/peek-overview/reactions/selector.tsx b/web/components/issues/peek-overview/reactions/selector.tsx deleted file mode 100644 index 5292cedf31..0000000000 --- a/web/components/issues/peek-overview/reactions/selector.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { FC, Fragment } from "react"; -import { Popover, Transition } from "@headlessui/react"; -// helper -import { renderEmoji } from "helpers/emoji.helper"; -// icons -import { SmilePlus } from "lucide-react"; -// constants -import { issueReactionEmojis } from "constants/issue"; - -interface IIssueReactionSelector { - size?: "sm" | "md" | "lg"; - position?: "top" | "bottom"; - onSelect: (reaction: any) => void; -} - -export const IssueReactionSelector: FC = (props) => { - const { size = "md", position = "top", onSelect } = props; - - return ( - <> - - {({ open, close: closePopover }) => ( - <> - - - - - - - -
- {issueReactionEmojis.map((emoji) => ( - - ))} -
-
-
- - )} -
- - ); -}; diff --git a/web/components/issues/peek-overview/root.tsx b/web/components/issues/peek-overview/root.tsx index 89a659fb3b..a371ef2de9 100644 --- a/web/components/issues/peek-overview/root.tsx +++ b/web/components/issues/peek-overview/root.tsx @@ -113,12 +113,23 @@ export const IssuePeekOverview: FC = observer((props) => { }, addIssueToCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => { try { - await addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds); - setToastAlert({ - title: "Cycle added to issue successfully", - type: "success", - message: "Issue added to issue successfully", - }); + await addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds) + .then((res) => { + updateIssue(workspaceSlug, projectId, res.id, res); + fetchIssue(workspaceSlug, projectId, res.id); + setToastAlert({ + title: "Cycle added to issue successfully", + type: "success", + message: "Issue added to issue successfully", + }); + }) + .catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "Selected issues could not be added to the cycle. Please try again.", + }); + }); } catch (error) { setToastAlert({ title: "Cycle add to issue failed", @@ -145,12 +156,23 @@ export const IssuePeekOverview: FC = observer((props) => { }, addIssueToModule: async (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => { try { - await addIssueToModule(workspaceSlug, projectId, moduleId, issueIds); - setToastAlert({ - title: "Module added to issue successfully", - type: "success", - message: "Module added to issue successfully", - }); + await addIssueToModule(workspaceSlug, projectId, moduleId, issueIds) + .then((res) => { + updateIssue(workspaceSlug, projectId, res.id, res); + fetchIssue(workspaceSlug, projectId, res.id); + setToastAlert({ + title: "Module added to issue successfully", + type: "success", + message: "Module added to issue successfully", + }); + }) + .catch(() => + setToastAlert({ + type: "error", + title: "Error!", + message: "Selected issues could not be added to the module. Please try again.", + }) + ); } catch (error) { setToastAlert({ title: "Module add to issue failed", diff --git a/web/components/page-views/workspace-dashboard.tsx b/web/components/page-views/workspace-dashboard.tsx index 6e185e2d06..fa5f7c2c12 100644 --- a/web/components/page-views/workspace-dashboard.tsx +++ b/web/components/page-views/workspace-dashboard.tsx @@ -55,17 +55,19 @@ export const WorkspaceDashboardView = observer(() => { return ( <> - - {currentUser && !currentUser.is_tour_completed && ( -
- -
- )} {homeDashboardId && joinedProjectIds ? ( -
- {currentUser && } + <> {joinedProjectIds.length > 0 ? ( - +
+ + {currentUser && } + {currentUser && !currentUser.is_tour_completed && ( +
+ +
+ )} + +
) : ( { disabled={!isEditingAllowed} /> )} -
+ ) : (
diff --git a/web/components/workspace/help-section.tsx b/web/components/workspace/help-section.tsx index 353775e820..be92c5cdba 100644 --- a/web/components/workspace/help-section.tsx +++ b/web/components/workspace/help-section.tsx @@ -7,7 +7,7 @@ import { useApplication } from "hooks/store"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; // icons import { FileText, HelpCircle, MessagesSquare, MoveLeft, Zap } from "lucide-react"; -import { DiscordIcon, GithubIcon, Button } from "@plane/ui"; +import { DiscordIcon, GithubIcon, Tooltip, Button } from "@plane/ui"; // assets import packageJson from "package.json"; // components @@ -82,24 +82,29 @@ export const WorkspaceHelpSection: React.FC = observe )}
- - + + + + + + + - + + + +
diff --git a/web/hooks/use-worskspace-issue-properties.ts b/web/hooks/use-workspace-issue-properties.ts similarity index 76% rename from web/hooks/use-worskspace-issue-properties.ts rename to web/hooks/use-workspace-issue-properties.ts index 0accc3ec2a..f6c1c6c2fe 100644 --- a/web/hooks/use-worskspace-issue-properties.ts +++ b/web/hooks/use-workspace-issue-properties.ts @@ -1,12 +1,12 @@ import useSWR from "swr"; import { useEstimate, useLabel, useProjectState } from "./store"; -export const useWorskspaceIssueProperties = (workspaceSlug: string | string[] | undefined) => { +export const useWorkspaceIssueProperties = (workspaceSlug: string | string[] | undefined) => { const { fetchWorkspaceLabels } = useLabel(); const { fetchWorkspaceStates } = useProjectState(); - const { fetchWorskpaceEstimates } = useEstimate(); + const { fetchWorkspaceEstimates } = useEstimate(); // fetch workspace labels useSWR( @@ -23,6 +23,6 @@ export const useWorskspaceIssueProperties = (workspaceSlug: string | string[] | // fetch workspace estimates useSWR( workspaceSlug ? `WORKSPACE_ESTIMATES_${workspaceSlug}` : null, - workspaceSlug ? () => fetchWorskpaceEstimates(workspaceSlug.toString()) : null + workspaceSlug ? () => fetchWorkspaceEstimates(workspaceSlug.toString()) : null ); }; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx index 1d372ace2c..5cd1e6c2c6 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx @@ -2,17 +2,14 @@ import { ReactElement } from "react"; import { useRouter } from "next/router"; import useSWR from "swr"; import { observer } from "mobx-react"; -import { Inbox } from "lucide-react"; // hooks import { useProject, useInboxIssues } from "hooks/store"; // layouts import { AppLayout } from "layouts/app-layout"; -// ui -import { Spinner } from "@plane/ui"; // components import { ProjectInboxHeader } from "components/headers"; -import { InboxSidebarRoot, InboxIssueActionsHeader } from "components/inbox"; -import { InboxIssueDetailRoot } from "components/issues/issue-detail/inbox"; +import { InboxSidebarRoot, InboxContentRoot } from "components/inbox"; + // types import { NextPageWithLayout } from "lib/types"; @@ -20,13 +17,10 @@ const ProjectInboxPage: NextPageWithLayout = observer(() => { const router = useRouter(); const { workspaceSlug, projectId, inboxId, inboxIssueId } = router.query; // store hooks - const { - issues: { getInboxIssuesByInboxId }, - } = useInboxIssues(); const { currentProjectDetails } = useProject(); const { filters: { fetchInboxFilters }, - issues: { loader, fetchInboxIssues }, + issues: { fetchInboxIssues }, } = useInboxIssues(); useSWR( @@ -41,67 +35,25 @@ const ProjectInboxPage: NextPageWithLayout = observer(() => { } ); - // inbox issues list - const inboxIssuesList = inboxId ? getInboxIssuesByInboxId(inboxId?.toString()) : undefined; - - if (!workspaceSlug || !projectId || !inboxId) return <>; + if (!workspaceSlug || !projectId || !inboxId || !currentProjectDetails?.inbox_view) return <>; return ( - <> - {loader === "fetch" ? ( -
- -
- ) : ( -
-
- {workspaceSlug && projectId && inboxId && ( - - )} -
-
- {workspaceSlug && projectId && inboxId && inboxIssueId ? ( -
-
- -
-
- -
-
- ) : ( -
-
-
- - {inboxIssuesList && inboxIssuesList.length > 0 ? ( - - {inboxIssuesList?.length} issues found. Select an issue from the sidebar to view its details. - - ) : ( - No issues found - )} -
-
-
- )} -
-
- )} - +
+
+ +
+
+ +
+
); }); diff --git a/web/public/empty-state/dashboard/light/overdue-issues.svg b/web/public/empty-state/dashboard/light/overdue-issues.svg index 504c76498a..1c66ddd0bb 100644 --- a/web/public/empty-state/dashboard/light/overdue-issues.svg +++ b/web/public/empty-state/dashboard/light/overdue-issues.svg @@ -1,9 +1,9 @@ - - - - - - + + + + + + diff --git a/web/store/estimate.store.ts b/web/store/estimate.store.ts index 33c36ec528..beddd52aba 100644 --- a/web/store/estimate.store.ts +++ b/web/store/estimate.store.ts @@ -23,7 +23,7 @@ export interface IEstimateStore { getProjectActiveEstimateDetails: (projectId: string) => IEstimate | null; // fetch actions fetchProjectEstimates: (workspaceSlug: string, projectId: string) => Promise; - fetchWorskpaceEstimates: (workspaceSlug: string) => Promise; + fetchWorkspaceEstimates: (workspaceSlug: string) => Promise; // crud actions createEstimate: (workspaceSlug: string, projectId: string, data: IEstimateFormData) => Promise; updateEstimate: ( @@ -56,7 +56,7 @@ export class EstimateStore implements IEstimateStore { activeEstimateDetails: computed, // actions fetchProjectEstimates: action, - fetchWorskpaceEstimates: action, + fetchWorkspaceEstimates: action, createEstimate: action, updateEstimate: action, deleteEstimate: action, @@ -158,7 +158,7 @@ export class EstimateStore implements IEstimateStore { * @param workspaceSlug * @param projectId */ - fetchWorskpaceEstimates = async (workspaceSlug: string) => + fetchWorkspaceEstimates = async (workspaceSlug: string) => await this.estimateService.getWorkspaceEstimatesList(workspaceSlug).then((response) => { runInAction(() => { response.forEach((estimate) => { diff --git a/web/store/inbox/inbox_filter.store.ts b/web/store/inbox/inbox_filter.store.ts index 5626c2f9ad..c4566acbe2 100644 --- a/web/store/inbox/inbox_filter.store.ts +++ b/web/store/inbox/inbox_filter.store.ts @@ -115,11 +115,12 @@ export class InboxFilter implements IInboxFilter { }; _filters = { ..._filters, ...filters }; + this.rootStore.inbox.inboxIssue.fetchInboxIssues(workspaceSlug, projectId, inboxId, "mutation"); + const response = await this.rootStore.inbox.inbox.updateInbox(workspaceSlug, projectId, inboxId, { view_props: { filters: _filters }, }); - this.rootStore.inbox.inboxIssue.fetchInboxIssues(workspaceSlug, projectId, inboxId); return response; } catch (error) { throw error; diff --git a/web/store/inbox/inbox_issue.store.ts b/web/store/inbox/inbox_issue.store.ts index 8d1b7137f5..d160ed0378 100644 --- a/web/store/inbox/inbox_issue.store.ts +++ b/web/store/inbox/inbox_issue.store.ts @@ -17,21 +17,24 @@ import type { TInboxDetailedStatus, TIssue, } from "@plane/types"; -// constants -import { INBOX_ISSUE_SOURCE } from "constants/inbox"; -type TInboxIssueLoader = "fetch" | undefined; +type TLoader = "init-loader" | "mutation" | undefined; export interface IInboxIssue { // observables - loader: TInboxIssueLoader; + loader: TLoader; inboxIssues: TInboxIssueDetailIdMap; inboxIssueMap: TInboxIssueDetailMap; // helper methods getInboxIssuesByInboxId: (inboxId: string) => string[] | undefined; getInboxIssueByIssueId: (inboxId: string, issueId: string) => TInboxIssueDetail | undefined; // actions - fetchInboxIssues: (workspaceSlug: string, projectId: string, inboxId: string) => Promise; + fetchInboxIssues: ( + workspaceSlug: string, + projectId: string, + inboxId: string, + loaderType?: TLoader + ) => Promise; fetchInboxIssueById: ( workspaceSlug: string, projectId: string, @@ -63,7 +66,7 @@ export interface IInboxIssue { export class InboxIssue implements IInboxIssue { // observables - loader: TInboxIssueLoader = "fetch"; + loader: TLoader = "init-loader"; inboxIssues: TInboxIssueDetailIdMap = {}; inboxIssueMap: TInboxIssueDetailMap = {}; // root store @@ -104,9 +107,14 @@ export class InboxIssue implements IInboxIssue { }); // actions - fetchInboxIssues = async (workspaceSlug: string, projectId: string, inboxId: string) => { + fetchInboxIssues = async ( + workspaceSlug: string, + projectId: string, + inboxId: string, + loaderType: TLoader = "init-loader" + ) => { try { - this.loader = "fetch"; + this.loader = loaderType; const queryParams = this.rootStore.inbox.inboxFilter.inboxAppliedFilters ?? {}; const response = await this.inboxIssueService.fetchInboxIssues(workspaceSlug, projectId, inboxId, queryParams); diff --git a/web/store/issue/issue-details/issue.store.ts b/web/store/issue/issue-details/issue.store.ts index bc34af0f61..284e5d2011 100644 --- a/web/store/issue/issue-details/issue.store.ts +++ b/web/store/issue/issue-details/issue.store.ts @@ -90,6 +90,7 @@ export class IssueStore implements IIssueStore { this.rootIssueDetailStore.relation.fetchRelations(workspaceSlug, projectId, issueId); // fetching states + // TODO: check if this function is required this.rootIssueDetailStore.rootIssueStore.state.fetchProjectStates(workspaceSlug, projectId); return issue; diff --git a/web/store/issue/issue.store.ts b/web/store/issue/issue.store.ts index f31c8f2f66..8ee689daf3 100644 --- a/web/store/issue/issue.store.ts +++ b/web/store/issue/issue.store.ts @@ -43,7 +43,7 @@ export class IssueStore implements IIssueStore { if (issues && issues.length <= 0) return; runInAction(() => { issues.forEach((issue) => { - set(this.issuesMap, issue.id, issue); + if (!this.issuesMap[issue.id]) set(this.issuesMap, issue.id, issue); }); }); }; diff --git a/web/store/issue/workspace/filter.store.ts b/web/store/issue/workspace/filter.store.ts index a40d2a42ff..df207fee97 100644 --- a/web/store/issue/workspace/filter.store.ts +++ b/web/store/issue/workspace/filter.store.ts @@ -162,14 +162,17 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo ) => { try { if (!viewId) throw new Error("View id is required"); + const issueFilters = this.getIssueFilters(viewId); - if (isEmpty(this.filters) || isEmpty(this.filters[viewId]) || isEmpty(filters)) return; + console.log("issueFilters", issueFilters); + + if (!issueFilters || isEmpty(filters)) return; const _filters = { - filters: this.filters[viewId].filters as IIssueFilterOptions, - displayFilters: this.filters[viewId].displayFilters as IIssueDisplayFilterOptions, - displayProperties: this.filters[viewId].displayProperties as IIssueDisplayProperties, - kanbanFilters: this.filters[viewId].kanbanFilters as TIssueKanbanFilters, + filters: issueFilters.filters as IIssueFilterOptions, + displayFilters: issueFilters.displayFilters as IIssueDisplayFilterOptions, + displayProperties: issueFilters.displayProperties as IIssueDisplayProperties, + kanbanFilters: issueFilters.kanbanFilters as TIssueKanbanFilters, }; switch (type) {