From 6c70d3854adf719a7985eddb824fb82a54444361 Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Wed, 28 Feb 2024 15:18:11 +0530 Subject: [PATCH] [WEB-575] chore: safely re-enable SWR (#3805) * safley enable swr and make sure to minimalize re renders * resolve build errors * fix dropdowns updation by adding observer --- .../core/modals/bulk-delete-issues-modal.tsx | 6 +- web/components/dropdowns/cycle.tsx | 277 ------------------ .../dropdowns/cycle/cycle-options.tsx | 162 ++++++++++ web/components/dropdowns/cycle/index.tsx | 149 ++++++++++ web/components/dropdowns/member/index.ts | 2 - web/components/dropdowns/member/index.tsx | 156 ++++++++++ .../dropdowns/member/member-options.tsx | 142 +++++++++ .../dropdowns/member/project-member.tsx | 261 ----------------- .../dropdowns/member/workspace-member.tsx | 238 --------------- .../{module.tsx => module/index.tsx} | 144 +-------- .../dropdowns/module/module-options.tsx | 163 +++++++++++ web/components/issues/draft-issue-form.tsx | 4 +- .../issues/issue-detail/inbox/sidebar.tsx | 4 +- .../issues/issue-detail/sidebar.tsx | 10 +- .../issue-layouts/calendar/issue-blocks.tsx | 4 +- .../issues/issue-layouts/gantt/blocks.tsx | 6 +- .../issues/issue-layouts/kanban/block.tsx | 6 +- .../issue-layouts/kanban/roots/cycle-root.tsx | 17 +- .../issues/issue-layouts/list/block.tsx | 8 +- .../issue-layouts/list/roots/cycle-root.tsx | 17 +- .../properties/all-properties.tsx | 4 +- .../roots/all-issue-layout-root.tsx | 17 +- .../roots/archived-issue-layout-root.tsx | 3 +- .../issue-layouts/roots/cycle-layout-root.tsx | 3 +- .../roots/draft-issue-layout-root.tsx | 3 +- .../roots/module-layout-root.tsx | 3 +- .../roots/project-layout-root.tsx | 24 +- .../roots/project-view-layout-root.tsx | 3 +- .../spreadsheet/columns/assignee-column.tsx | 4 +- .../issue-layouts/spreadsheet/issue-row.tsx | 4 +- .../spreadsheet/roots/cycle-root.tsx | 4 +- web/components/issues/issue-modal/form.tsx | 4 +- web/components/issues/issue-modal/modal.tsx | 1 - .../issues/peek-overview/properties.tsx | 10 +- .../issues/sub-issues/properties.tsx | 4 +- web/components/labels/index.ts | 1 - web/components/labels/labels-list-modal.tsx | 157 ---------- web/components/modules/form.tsx | 6 +- web/components/modules/sidebar.tsx | 6 +- web/components/profile/profile-issues.tsx | 3 +- .../project/create-project-modal.tsx | 4 +- web/constants/swr-config.ts | 4 +- web/hooks/use-workspace-issue-properties.ts | 15 +- web/layouts/app-layout/layout.tsx | 2 +- web/layouts/auth-layout/project-wrapper.tsx | 21 +- web/layouts/auth-layout/workspace-wrapper.tsx | 12 +- web/store/cycle.store.ts | 8 + web/store/module.store.ts | 8 + web/store/project/project.store.ts | 11 + 49 files changed, 952 insertions(+), 1173 deletions(-) delete mode 100644 web/components/dropdowns/cycle.tsx create mode 100644 web/components/dropdowns/cycle/cycle-options.tsx create mode 100644 web/components/dropdowns/cycle/index.tsx delete mode 100644 web/components/dropdowns/member/index.ts create mode 100644 web/components/dropdowns/member/index.tsx create mode 100644 web/components/dropdowns/member/member-options.tsx delete mode 100644 web/components/dropdowns/member/project-member.tsx delete mode 100644 web/components/dropdowns/member/workspace-member.tsx rename web/components/dropdowns/{module.tsx => module/index.tsx} (61%) create mode 100644 web/components/dropdowns/module/module-options.tsx delete mode 100644 web/components/labels/labels-list-modal.tsx diff --git a/web/components/core/modals/bulk-delete-issues-modal.tsx b/web/components/core/modals/bulk-delete-issues-modal.tsx index f5eab83ef..39be2872b 100644 --- a/web/components/core/modals/bulk-delete-issues-modal.tsx +++ b/web/components/core/modals/bulk-delete-issues-modal.tsx @@ -49,8 +49,10 @@ export const BulkDeleteIssuesModal: React.FC = observer((props) => { const [query, setQuery] = useState(""); // fetching project issues. const { data: issues } = useSWR( - workspaceSlug && projectId ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) : null, - workspaceSlug && projectId ? () => issueService.getIssues(workspaceSlug as string, projectId as string) : null + workspaceSlug && projectId && isOpen ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) : null, + workspaceSlug && projectId && isOpen + ? () => issueService.getIssues(workspaceSlug as string, projectId as string) + : null ); const { setToastAlert } = useToast(); diff --git a/web/components/dropdowns/cycle.tsx b/web/components/dropdowns/cycle.tsx deleted file mode 100644 index d1f552a99..000000000 --- a/web/components/dropdowns/cycle.tsx +++ /dev/null @@ -1,277 +0,0 @@ -import { Fragment, ReactNode, useEffect, useRef, useState } from "react"; -import { observer } from "mobx-react-lite"; -import { Combobox } from "@headlessui/react"; -import { usePopper } from "react-popper"; -import { Check, ChevronDown, Search } from "lucide-react"; -// hooks -import { useApplication, useCycle } from "hooks/store"; -import { useDropdownKeyDown } from "hooks/use-dropdown-key-down"; -import useOutsideClickDetector from "hooks/use-outside-click-detector"; -// components -import { DropdownButton } from "./buttons"; -// icons -import { ContrastIcon, CycleGroupIcon } from "@plane/ui"; -// helpers -import { cn } from "helpers/common.helper"; -// types -import { TDropdownProps } from "./types"; -import { TCycleGroups } from "@plane/types"; -// constants -import { BUTTON_VARIANTS_WITH_TEXT } from "./constants"; - -type Props = TDropdownProps & { - button?: ReactNode; - dropdownArrow?: boolean; - dropdownArrowClassName?: string; - onChange: (val: string | null) => void; - onClose?: () => void; - projectId: string; - value: string | null; -}; - -type DropdownOptions = - | { - value: string | null; - query: string; - content: JSX.Element; - }[] - | undefined; - -export const CycleDropdown: React.FC = observer((props) => { - const { - button, - buttonClassName, - buttonContainerClassName, - buttonVariant, - className = "", - disabled = false, - dropdownArrow = false, - dropdownArrowClassName = "", - hideIcon = false, - onChange, - onClose, - placeholder = "Cycle", - placement, - projectId, - showTooltip = false, - tabIndex, - value, - } = props; - // states - const [query, setQuery] = useState(""); - const [isOpen, setIsOpen] = useState(false); - // refs - const dropdownRef = useRef(null); - const inputRef = useRef(null); - // popper-js refs - const [referenceElement, setReferenceElement] = useState(null); - const [popperElement, setPopperElement] = useState(null); - // popper-js init - const { styles, attributes } = usePopper(referenceElement, popperElement, { - placement: placement ?? "bottom-start", - modifiers: [ - { - name: "preventOverflow", - options: { - padding: 12, - }, - }, - ], - }); - // store hooks - const { - router: { workspaceSlug }, - } = useApplication(); - const { getProjectCycleIds, fetchAllCycles, getCycleById } = useCycle(); - - const cycleIds = (getProjectCycleIds(projectId) ?? [])?.filter((cycleId) => { - const cycleDetails = getCycleById(cycleId); - return cycleDetails?.status ? (cycleDetails?.status.toLowerCase() != "completed" ? true : false) : true; - }); - - const options: DropdownOptions = cycleIds?.map((cycleId) => { - const cycleDetails = getCycleById(cycleId); - const cycleStatus = cycleDetails?.status ? (cycleDetails.status.toLocaleLowerCase() as TCycleGroups) : "draft"; - - return { - value: cycleId, - query: `${cycleDetails?.name}`, - content: ( -
- - {cycleDetails?.name} -
- ), - }; - }); - options?.unshift({ - value: null, - query: "No cycle", - content: ( -
- - No cycle -
- ), - }); - - const filteredOptions = - query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase())); - - const selectedCycle = value ? getCycleById(value) : null; - - const onOpen = () => { - if (workspaceSlug && !cycleIds) fetchAllCycles(workspaceSlug, projectId); - }; - - const handleClose = () => { - if (!isOpen) return; - setIsOpen(false); - onClose && onClose(); - }; - - const toggleDropdown = () => { - if (!isOpen) onOpen(); - setIsOpen((prevIsOpen) => !prevIsOpen); - }; - - const dropdownOnChange = (val: string | null) => { - onChange(val); - handleClose(); - }; - - const handleKeyDown = useDropdownKeyDown(toggleDropdown, handleClose); - - const handleOnClick = (e: React.MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - toggleDropdown(); - }; - - const searchInputKeyDown = (e: React.KeyboardEvent) => { - if (query !== "" && e.key === "Escape") { - e.stopPropagation(); - setQuery(""); - } - }; - - useOutsideClickDetector(dropdownRef, handleClose); - - useEffect(() => { - if (isOpen && inputRef.current) { - inputRef.current.focus(); - } - }, [isOpen]); - - return ( - - - {button ? ( - - ) : ( - - )} - - {isOpen && ( - -
-
- - setQuery(e.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - onKeyDown={searchInputKeyDown} - /> -
-
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - `w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none ${ - active ? "bg-custom-background-80" : "" - } ${selected ? "text-custom-text-100" : "text-custom-text-200"}` - } - > - {({ selected }) => ( - <> - {option.content} - {selected && } - - )} - - )) - ) : ( -

No matches found

- ) - ) : ( -

Loading...

- )} -
-
-
- )} -
- ); -}); diff --git a/web/components/dropdowns/cycle/cycle-options.tsx b/web/components/dropdowns/cycle/cycle-options.tsx new file mode 100644 index 000000000..e691569b7 --- /dev/null +++ b/web/components/dropdowns/cycle/cycle-options.tsx @@ -0,0 +1,162 @@ +import { useEffect, useRef, useState } from "react"; +import { Combobox } from "@headlessui/react"; +import { observer } from "mobx-react"; +//components +import { ContrastIcon, CycleGroupIcon } from "@plane/ui"; +//store +import { useApplication, useCycle } from "hooks/store"; +//hooks +import { usePopper } from "react-popper"; +//icon +import { Check, Search } from "lucide-react"; +//types +import { Placement } from "@popperjs/core"; +import { TCycleGroups } from "@plane/types"; + +type DropdownOptions = + | { + value: string | null; + query: string; + content: JSX.Element; + }[] + | undefined; + +interface Props { + projectId: string; + referenceElement: HTMLButtonElement | null; + placement: Placement | undefined; + isOpen: boolean; +} + +export const CycleOptions = observer((props: any) => { + const { projectId, isOpen, referenceElement, placement } = props; + + //state hooks + const [query, setQuery] = useState(""); + const [popperElement, setPopperElement] = useState(null); + const inputRef = useRef(null); + + // store hooks + const { + router: { workspaceSlug }, + } = useApplication(); + const { getProjectCycleIds, fetchAllCycles, getCycleById } = useCycle(); + + useEffect(() => { + if (isOpen) { + onOpen(); + inputRef.current && inputRef.current.focus(); + } + }, [isOpen]); + + // popper-js init + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement: placement ?? "bottom-start", + modifiers: [ + { + name: "preventOverflow", + options: { + padding: 12, + }, + }, + ], + }); + + const cycleIds = (getProjectCycleIds(projectId) ?? [])?.filter((cycleId) => { + const cycleDetails = getCycleById(cycleId); + return cycleDetails?.status ? (cycleDetails?.status.toLowerCase() != "completed" ? true : false) : true; + }); + + const onOpen = () => { + if (workspaceSlug && !cycleIds) fetchAllCycles(workspaceSlug, projectId); + }; + + const searchInputKeyDown = (e: React.KeyboardEvent) => { + if (query !== "" && e.key === "Escape") { + e.stopPropagation(); + setQuery(""); + } + }; + + const options: DropdownOptions = cycleIds?.map((cycleId) => { + const cycleDetails = getCycleById(cycleId); + const cycleStatus = cycleDetails?.status ? (cycleDetails.status.toLocaleLowerCase() as TCycleGroups) : "draft"; + + return { + value: cycleId, + query: `${cycleDetails?.name}`, + content: ( +
+ + {cycleDetails?.name} +
+ ), + }; + }); + options?.unshift({ + value: null, + query: "No cycle", + content: ( +
+ + No cycle +
+ ), + }); + + const filteredOptions = + query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase())); + + return ( + +
+
+ + setQuery(e.target.value)} + placeholder="Search" + displayValue={(assigned: any) => assigned?.name} + onKeyDown={searchInputKeyDown} + /> +
+
+ {filteredOptions ? ( + filteredOptions.length > 0 ? ( + filteredOptions.map((option) => ( + + `w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none ${ + active ? "bg-custom-background-80" : "" + } ${selected ? "text-custom-text-100" : "text-custom-text-200"}` + } + > + {({ selected }) => ( + <> + {option.content} + {selected && } + + )} + + )) + ) : ( +

No matches found

+ ) + ) : ( +

Loading...

+ )} +
+
+
+ ); +}); diff --git a/web/components/dropdowns/cycle/index.tsx b/web/components/dropdowns/cycle/index.tsx new file mode 100644 index 000000000..465eb3e2a --- /dev/null +++ b/web/components/dropdowns/cycle/index.tsx @@ -0,0 +1,149 @@ +import { Fragment, ReactNode, useRef, useState } from "react"; +import { observer } from "mobx-react-lite"; +import { Combobox } from "@headlessui/react"; +import { ChevronDown } from "lucide-react"; +// hooks +import { useCycle } from "hooks/store"; +import { useDropdownKeyDown } from "hooks/use-dropdown-key-down"; +import useOutsideClickDetector from "hooks/use-outside-click-detector"; +// components +import { DropdownButton } from "../buttons"; +// icons +import { ContrastIcon } from "@plane/ui"; +// helpers +import { cn } from "helpers/common.helper"; +// types +import { TDropdownProps } from "../types"; +// constants +import { BUTTON_VARIANTS_WITH_TEXT } from "../constants"; +import { CycleOptions } from "./cycle-options"; + +type Props = TDropdownProps & { + button?: ReactNode; + dropdownArrow?: boolean; + dropdownArrowClassName?: string; + onChange: (val: string | null) => void; + onClose?: () => void; + projectId: string; + value: string | null; +}; + +export const CycleDropdown: React.FC = observer((props) => { + const { + button, + buttonClassName, + buttonContainerClassName, + buttonVariant, + className = "", + disabled = false, + dropdownArrow = false, + dropdownArrowClassName = "", + hideIcon = false, + onChange, + onClose, + placeholder = "Cycle", + placement, + projectId, + showTooltip = false, + tabIndex, + value, + } = props; + // states + + const [isOpen, setIsOpen] = useState(false); + const { getCycleNameById } = useCycle(); + // refs + const dropdownRef = useRef(null); + // popper-js refs + const [referenceElement, setReferenceElement] = useState(null); + + const selectedName = value ? getCycleNameById(value) : null; + + const handleClose = () => { + if (!isOpen) return; + setIsOpen(false); + onClose && onClose(); + }; + + const toggleDropdown = () => { + setIsOpen((prevIsOpen) => !prevIsOpen); + }; + + const dropdownOnChange = (val: string | null) => { + onChange(val); + handleClose(); + }; + + const handleKeyDown = useDropdownKeyDown(toggleDropdown, handleClose); + + const handleOnClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + toggleDropdown(); + }; + + useOutsideClickDetector(dropdownRef, handleClose); + + return ( + + + {button ? ( + + ) : ( + + )} + + {isOpen && ( + + )} + + ); +}); diff --git a/web/components/dropdowns/member/index.ts b/web/components/dropdowns/member/index.ts deleted file mode 100644 index a9f7e09c8..000000000 --- a/web/components/dropdowns/member/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./project-member"; -export * from "./workspace-member"; diff --git a/web/components/dropdowns/member/index.tsx b/web/components/dropdowns/member/index.tsx new file mode 100644 index 000000000..332f2227a --- /dev/null +++ b/web/components/dropdowns/member/index.tsx @@ -0,0 +1,156 @@ +import { Fragment, useRef, useState } from "react"; +import { observer } from "mobx-react-lite"; +import { Combobox } from "@headlessui/react"; +import { ChevronDown } from "lucide-react"; +// hooks +import { useMember } from "hooks/store"; +import { useDropdownKeyDown } from "hooks/use-dropdown-key-down"; +import useOutsideClickDetector from "hooks/use-outside-click-detector"; +// components +import { ButtonAvatars } from "./avatar"; +import { DropdownButton } from "../buttons"; +// helpers +import { cn } from "helpers/common.helper"; +// types +import { MemberDropdownProps } from "./types"; +// constants +import { BUTTON_VARIANTS_WITH_TEXT } from "../constants"; +import { MemberOptions } from "./member-options"; + +type Props = { + projectId?: string; + onClose?: () => void; +} & MemberDropdownProps; + +export const MemberDropdown: React.FC = observer((props) => { + const { + button, + buttonClassName, + buttonContainerClassName, + buttonVariant, + className = "", + disabled = false, + dropdownArrow = false, + dropdownArrowClassName = "", + hideIcon = false, + multiple, + onChange, + onClose, + placeholder = "Members", + placement, + projectId, + showTooltip = false, + tabIndex, + value, + } = props; + // states + const [isOpen, setIsOpen] = useState(false); + // refs + const dropdownRef = useRef(null); + // popper-js refs + const [referenceElement, setReferenceElement] = useState(null); + + const { getUserDetails } = useMember(); + + const comboboxProps: any = { + value, + onChange, + disabled, + }; + if (multiple) comboboxProps.multiple = true; + + const handleClose = () => { + if (!isOpen) return; + setIsOpen(false); + onClose && onClose(); + }; + + const toggleDropdown = () => { + setIsOpen((prevIsOpen) => !prevIsOpen); + }; + + const dropdownOnChange = (val: string & string[]) => { + onChange(val); + if (!multiple) handleClose(); + }; + + const handleKeyDown = useDropdownKeyDown(toggleDropdown, handleClose); + + const handleOnClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + toggleDropdown(); + }; + + useOutsideClickDetector(dropdownRef, handleClose); + + return ( + + + {button ? ( + + ) : ( + + )} + + {isOpen && ( + + )} + + ); +}); diff --git a/web/components/dropdowns/member/member-options.tsx b/web/components/dropdowns/member/member-options.tsx new file mode 100644 index 000000000..46a0b9cba --- /dev/null +++ b/web/components/dropdowns/member/member-options.tsx @@ -0,0 +1,142 @@ +import { useEffect, useRef, useState } from "react"; +import { Combobox } from "@headlessui/react"; +import { observer } from "mobx-react"; +//components +import { Avatar } from "@plane/ui"; +//store +import { useApplication, useMember, useUser } from "hooks/store"; +//hooks +import { usePopper } from "react-popper"; +//icon +import { Check, Search } from "lucide-react"; +//types +import { Placement } from "@popperjs/core"; + +interface Props { + projectId?: string; + referenceElement: HTMLButtonElement | null; + placement: Placement | undefined; + isOpen: boolean; +} + +export const MemberOptions = observer((props: Props) => { + const { projectId, referenceElement, placement, isOpen } = props; + + const [query, setQuery] = useState(""); + const [popperElement, setPopperElement] = useState(null); + const inputRef = useRef(null); + + // store hooks + const { + router: { workspaceSlug }, + } = useApplication(); + const { + getUserDetails, + project: { getProjectMemberIds, fetchProjectMembers }, + workspace: { workspaceMemberIds }, + } = useMember(); + const { currentUser } = useUser(); + + // popper-js init + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement: placement ?? "bottom-start", + modifiers: [ + { + name: "preventOverflow", + options: { + padding: 12, + }, + }, + ], + }); + + useEffect(() => { + if (isOpen) { + onOpen(); + inputRef.current && inputRef.current.focus(); + } + }, [isOpen]); + + const memberIds = projectId ? getProjectMemberIds(projectId) : workspaceMemberIds; + const onOpen = () => { + if (!memberIds && workspaceSlug && projectId) fetchProjectMembers(workspaceSlug, projectId); + }; + + const searchInputKeyDown = (e: React.KeyboardEvent) => { + if (query !== "" && e.key === "Escape") { + e.stopPropagation(); + setQuery(""); + } + }; + + const options = memberIds?.map((userId) => { + const userDetails = getUserDetails(userId); + + return { + value: userId, + query: `${userDetails?.display_name} ${userDetails?.first_name} ${userDetails?.last_name}`, + content: ( +
+ + {currentUser?.id === userId ? "You" : userDetails?.display_name} +
+ ), + }; + }); + + const filteredOptions = + query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase())); + + return ( + +
+
+ + setQuery(e.target.value)} + placeholder="Search" + displayValue={(assigned: any) => assigned?.name} + onKeyDown={searchInputKeyDown} + /> +
+
+ {filteredOptions ? ( + filteredOptions.length > 0 ? ( + filteredOptions.map((option) => ( + + `w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none ${ + active ? "bg-custom-background-80" : "" + } ${selected ? "text-custom-text-100" : "text-custom-text-200"}` + } + > + {({ selected }) => ( + <> + {option.content} + {selected && } + + )} + + )) + ) : ( +

No matching results

+ ) + ) : ( +

Loading...

+ )} +
+
+
+ ); +}); diff --git a/web/components/dropdowns/member/project-member.tsx b/web/components/dropdowns/member/project-member.tsx deleted file mode 100644 index db8702535..000000000 --- a/web/components/dropdowns/member/project-member.tsx +++ /dev/null @@ -1,261 +0,0 @@ -import { Fragment, useEffect, useRef, useState } from "react"; -import { observer } from "mobx-react-lite"; -import { Combobox } from "@headlessui/react"; -import { usePopper } from "react-popper"; -import { Check, ChevronDown, Search } from "lucide-react"; -// hooks -import { useApplication, useMember, useUser } from "hooks/store"; -import { useDropdownKeyDown } from "hooks/use-dropdown-key-down"; -import useOutsideClickDetector from "hooks/use-outside-click-detector"; -// components -import { ButtonAvatars } from "./avatar"; -import { DropdownButton } from "../buttons"; -// icons -import { Avatar } from "@plane/ui"; -// helpers -import { cn } from "helpers/common.helper"; -// types -import { MemberDropdownProps } from "./types"; -// constants -import { BUTTON_VARIANTS_WITH_TEXT } from "../constants"; - -type Props = { - projectId: string; - onClose?: () => void; -} & MemberDropdownProps; - -export const ProjectMemberDropdown: React.FC = observer((props) => { - const { - button, - buttonClassName, - buttonContainerClassName, - buttonVariant, - className = "", - disabled = false, - dropdownArrow = false, - dropdownArrowClassName = "", - hideIcon = false, - multiple, - onChange, - onClose, - placeholder = "Members", - placement, - projectId, - showTooltip = false, - tabIndex, - value, - } = props; - // states - const [query, setQuery] = useState(""); - const [isOpen, setIsOpen] = useState(false); - // refs - const dropdownRef = useRef(null); - const inputRef = useRef(null); - // popper-js refs - const [referenceElement, setReferenceElement] = useState(null); - const [popperElement, setPopperElement] = useState(null); - // popper-js init - const { styles, attributes } = usePopper(referenceElement, popperElement, { - placement: placement ?? "bottom-start", - modifiers: [ - { - name: "preventOverflow", - options: { - padding: 12, - }, - }, - ], - }); - // store hooks - const { - router: { workspaceSlug }, - } = useApplication(); - const { currentUser } = useUser(); - const { - getUserDetails, - project: { getProjectMemberIds, fetchProjectMembers }, - } = useMember(); - const projectMemberIds = getProjectMemberIds(projectId); - - const options = projectMemberIds?.map((userId) => { - const userDetails = getUserDetails(userId); - - return { - value: userId, - query: `${userDetails?.display_name} ${userDetails?.first_name} ${userDetails?.last_name}`, - content: ( -
- - {currentUser?.id === userId ? "You" : userDetails?.display_name} -
- ), - }; - }); - - const filteredOptions = - query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase())); - - const comboboxProps: any = { - value, - onChange, - disabled, - }; - if (multiple) comboboxProps.multiple = true; - - const onOpen = () => { - if (!projectMemberIds && workspaceSlug) fetchProjectMembers(workspaceSlug, projectId); - }; - - const handleClose = () => { - if (!isOpen) return; - setIsOpen(false); - onClose && onClose(); - }; - - const toggleDropdown = () => { - if (!isOpen) onOpen(); - setIsOpen((prevIsOpen) => !prevIsOpen); - }; - - const dropdownOnChange = (val: string & string[]) => { - onChange(val); - if (!multiple) handleClose(); - }; - - const handleKeyDown = useDropdownKeyDown(toggleDropdown, handleClose); - - const handleOnClick = (e: React.MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - toggleDropdown(); - }; - - const searchInputKeyDown = (e: React.KeyboardEvent) => { - if (query !== "" && e.key === "Escape") { - e.stopPropagation(); - setQuery(""); - } - }; - - useOutsideClickDetector(dropdownRef, handleClose); - - useEffect(() => { - if (isOpen && inputRef.current) { - inputRef.current.focus(); - } - }, [isOpen]); - - return ( - - - {button ? ( - - ) : ( - - )} - - {isOpen && ( - -
-
- - setQuery(e.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - onKeyDown={searchInputKeyDown} - /> -
-
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - `w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none ${ - active ? "bg-custom-background-80" : "" - } ${selected ? "text-custom-text-100" : "text-custom-text-200"}` - } - > - {({ selected }) => ( - <> - {option.content} - {selected && } - - )} - - )) - ) : ( -

No matching results

- ) - ) : ( -

Loading...

- )} -
-
-
- )} -
- ); -}); diff --git a/web/components/dropdowns/member/workspace-member.tsx b/web/components/dropdowns/member/workspace-member.tsx deleted file mode 100644 index e7a679750..000000000 --- a/web/components/dropdowns/member/workspace-member.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import { Fragment, useEffect, useRef, useState } from "react"; -import { observer } from "mobx-react-lite"; -import { Combobox } from "@headlessui/react"; -import { usePopper } from "react-popper"; -import { Check, ChevronDown, Search } from "lucide-react"; -// hooks -import { useMember, useUser } from "hooks/store"; -import { useDropdownKeyDown } from "hooks/use-dropdown-key-down"; -import useOutsideClickDetector from "hooks/use-outside-click-detector"; -// components -import { ButtonAvatars } from "./avatar"; -import { DropdownButton } from "../buttons"; -// icons -import { Avatar } from "@plane/ui"; -// helpers -import { cn } from "helpers/common.helper"; -// types -import { MemberDropdownProps } from "./types"; -// constants -import { BUTTON_VARIANTS_WITH_TEXT } from "../constants"; - -export const WorkspaceMemberDropdown: React.FC = observer((props) => { - const { - button, - buttonClassName, - buttonContainerClassName, - buttonVariant, - className = "", - disabled = false, - dropdownArrow = false, - dropdownArrowClassName = "", - hideIcon = false, - multiple, - onChange, - onClose, - placeholder = "Members", - placement, - showTooltip = false, - tabIndex, - value, - } = props; - // states - const [query, setQuery] = useState(""); - const [isOpen, setIsOpen] = useState(false); - // refs - const dropdownRef = useRef(null); - const inputRef = useRef(null); - // popper-js refs - const [referenceElement, setReferenceElement] = useState(null); - const [popperElement, setPopperElement] = useState(null); - // popper-js init - const { styles, attributes } = usePopper(referenceElement, popperElement, { - placement: placement ?? "bottom-start", - modifiers: [ - { - name: "preventOverflow", - options: { - padding: 12, - }, - }, - ], - }); - // store hooks - const { currentUser } = useUser(); - const { - getUserDetails, - workspace: { workspaceMemberIds }, - } = useMember(); - - const options = workspaceMemberIds?.map((userId) => { - const userDetails = getUserDetails(userId); - - return { - value: userId, - query: `${userDetails?.display_name} ${userDetails?.first_name} ${userDetails?.last_name}`, - content: ( -
- - {currentUser?.id === userId ? "You" : userDetails?.display_name} -
- ), - }; - }); - - const filteredOptions = - query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase())); - - const comboboxProps: any = { - value, - onChange, - disabled, - }; - if (multiple) comboboxProps.multiple = true; - - const handleClose = () => { - if (!isOpen) return; - setIsOpen(false); - onClose && onClose(); - }; - - const toggleDropdown = () => { - setIsOpen((prevIsOpen) => !prevIsOpen); - }; - - const dropdownOnChange = (val: string & string[]) => { - onChange(val); - if (!multiple) handleClose(); - }; - - const handleKeyDown = useDropdownKeyDown(toggleDropdown, handleClose); - - const handleOnClick = (e: React.MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - toggleDropdown(); - }; - - useOutsideClickDetector(dropdownRef, handleClose); - - useEffect(() => { - if (isOpen && inputRef.current) { - inputRef.current.focus(); - } - }, [isOpen]); - - return ( - - - {button ? ( - - ) : ( - - )} - - {isOpen && ( - -
-
- - setQuery(e.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - /> -
-
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - `w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none ${ - active ? "bg-custom-background-80" : "" - } ${selected ? "text-custom-text-100" : "text-custom-text-200"}` - } - > - {({ selected }) => ( - <> - {option.content} - {selected && } - - )} - - )) - ) : ( -

No matching results

- ) - ) : ( -

Loading...

- )} -
-
-
- )} -
- ); -}); diff --git a/web/components/dropdowns/module.tsx b/web/components/dropdowns/module/index.tsx similarity index 61% rename from web/components/dropdowns/module.tsx rename to web/components/dropdowns/module/index.tsx index cee0ed9f3..ee2d746d9 100644 --- a/web/components/dropdowns/module.tsx +++ b/web/components/dropdowns/module/index.tsx @@ -1,22 +1,22 @@ import { Fragment, ReactNode, useEffect, useRef, useState } from "react"; import { observer } from "mobx-react-lite"; import { Combobox } from "@headlessui/react"; -import { usePopper } from "react-popper"; -import { Check, ChevronDown, Search, X } from "lucide-react"; +import { ChevronDown, X } from "lucide-react"; // hooks -import { useApplication, useModule } from "hooks/store"; +import { useModule } from "hooks/store"; import { useDropdownKeyDown } from "hooks/use-dropdown-key-down"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; // components -import { DropdownButton } from "./buttons"; +import { DropdownButton } from "../buttons"; // icons import { DiceIcon, Tooltip } from "@plane/ui"; // helpers import { cn } from "helpers/common.helper"; // types -import { TDropdownProps } from "./types"; +import { TDropdownProps } from "../types"; // constants -import { BUTTON_VARIANTS_WITHOUT_TEXT } from "./constants"; +import { BUTTON_VARIANTS_WITHOUT_TEXT } from "../constants"; +import { ModuleOptions } from "./module-options"; type Props = TDropdownProps & { button?: ReactNode; @@ -38,14 +38,6 @@ type Props = TDropdownProps & { } ); -type DropdownOptions = - | { - value: string | null; - query: string; - content: JSX.Element; - }[] - | undefined; - type ButtonContentProps = { disabled: boolean; dropdownArrow: boolean; @@ -166,64 +158,14 @@ export const ModuleDropdown: React.FC = observer((props) => { value, } = props; // states - const [query, setQuery] = useState(""); const [isOpen, setIsOpen] = useState(false); // refs const dropdownRef = useRef(null); const inputRef = useRef(null); // popper-js refs const [referenceElement, setReferenceElement] = useState(null); - const [popperElement, setPopperElement] = useState(null); - // popper-js init - const { styles, attributes } = usePopper(referenceElement, popperElement, { - placement: placement ?? "bottom-start", - modifiers: [ - { - name: "preventOverflow", - options: { - padding: 12, - }, - }, - ], - }); - // store hooks - const { - router: { workspaceSlug }, - } = useApplication(); - const { getProjectModuleIds, fetchModules, getModuleById } = useModule(); - const moduleIds = getProjectModuleIds(projectId); - const options: DropdownOptions = moduleIds?.map((moduleId) => { - const moduleDetails = getModuleById(moduleId); - return { - value: moduleId, - query: `${moduleDetails?.name}`, - content: ( -
- - {moduleDetails?.name} -
- ), - }; - }); - if (!multiple) - options?.unshift({ - value: null, - query: "No module", - content: ( -
- - No module -
- ), - }); - - const filteredOptions = - query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase())); - - const onOpen = () => { - if (!moduleIds && workspaceSlug) fetchModules(workspaceSlug, projectId); - }; + const { getModuleNameById } = useModule(); const handleClose = () => { if (!isOpen) return; @@ -232,7 +174,6 @@ export const ModuleDropdown: React.FC = observer((props) => { }; const toggleDropdown = () => { - if (!isOpen) onOpen(); setIsOpen((prevIsOpen) => !prevIsOpen); }; @@ -249,13 +190,6 @@ export const ModuleDropdown: React.FC = observer((props) => { toggleDropdown(); }; - const searchInputKeyDown = (e: React.KeyboardEvent) => { - if (query !== "" && e.key === "Escape") { - e.stopPropagation(); - setQuery(""); - } - }; - useOutsideClickDetector(dropdownRef, handleClose); const comboboxProps: any = { @@ -314,7 +248,7 @@ export const ModuleDropdown: React.FC = observer((props) => { tooltipContent={ Array.isArray(value) ? `${value - .map((moduleId) => getModuleById(moduleId)?.name) + .map((moduleId) => getModuleNameById(moduleId)) .toString() .replaceAll(",", ", ")}` : "" @@ -339,61 +273,13 @@ export const ModuleDropdown: React.FC = observer((props) => { )} {isOpen && ( - -
-
- - setQuery(e.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - onKeyDown={searchInputKeyDown} - /> -
-
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - cn( - "w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none", - { - "bg-custom-background-80": active, - "text-custom-text-100": selected, - "text-custom-text-200": !selected, - } - ) - } - > - {({ selected }) => ( - <> - {option.content} - {selected && } - - )} - - )) - ) : ( -

No matching results

- ) - ) : ( -

Loading...

- )} -
-
-
+ )} ); diff --git a/web/components/dropdowns/module/module-options.tsx b/web/components/dropdowns/module/module-options.tsx new file mode 100644 index 000000000..e7d205b12 --- /dev/null +++ b/web/components/dropdowns/module/module-options.tsx @@ -0,0 +1,163 @@ +import { useEffect, useRef, useState } from "react"; +import { Combobox } from "@headlessui/react"; +import { observer } from "mobx-react"; +//components +import { DiceIcon } from "@plane/ui"; +//store +import { useApplication, useModule } from "hooks/store"; +//hooks +import { usePopper } from "react-popper"; +import { cn } from "helpers/common.helper"; +//icon +import { Check, Search } from "lucide-react"; +//types +import { Placement } from "@popperjs/core"; + +type DropdownOptions = + | { + value: string | null; + query: string; + content: JSX.Element; + }[] + | undefined; + +interface Props { + projectId: string; + referenceElement: HTMLButtonElement | null; + placement: Placement | undefined; + isOpen: boolean; + multiple: boolean; +} + +export const ModuleOptions = observer((props: Props) => { + const { projectId, isOpen, referenceElement, placement, multiple } = props; + + const [query, setQuery] = useState(""); + const [popperElement, setPopperElement] = useState(null); + const inputRef = useRef(null); + + // store hooks + const { + router: { workspaceSlug }, + } = useApplication(); + const { getProjectModuleIds, fetchModules, getModuleById } = useModule(); + + useEffect(() => { + if (isOpen) { + onOpen(); + inputRef.current && inputRef.current.focus(); + } + }, [isOpen]); + + // popper-js init + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement: placement ?? "bottom-start", + modifiers: [ + { + name: "preventOverflow", + options: { + padding: 12, + }, + }, + ], + }); + + const moduleIds = getProjectModuleIds(projectId); + + const onOpen = () => { + if (workspaceSlug && !moduleIds) fetchModules(workspaceSlug, projectId); + }; + + const searchInputKeyDown = (e: React.KeyboardEvent) => { + if (query !== "" && e.key === "Escape") { + e.stopPropagation(); + setQuery(""); + } + }; + + const options: DropdownOptions = moduleIds?.map((moduleId) => { + const moduleDetails = getModuleById(moduleId); + return { + value: moduleId, + query: `${moduleDetails?.name}`, + content: ( +
+ + {moduleDetails?.name} +
+ ), + }; + }); + if (!multiple) + options?.unshift({ + value: null, + query: "No module", + content: ( +
+ + No module +
+ ), + }); + + const filteredOptions = + query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase())); + + return ( + +
+
+ + setQuery(e.target.value)} + placeholder="Search" + displayValue={(assigned: any) => assigned?.name} + onKeyDown={searchInputKeyDown} + /> +
+
+ {filteredOptions ? ( + filteredOptions.length > 0 ? ( + filteredOptions.map((option) => ( + + cn( + "w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none", + { + "bg-custom-background-80": active, + "text-custom-text-100": selected, + "text-custom-text-200": !selected, + } + ) + } + > + {({ selected }) => ( + <> + {option.content} + {selected && } + + )} + + )) + ) : ( +

No matching results

+ ) + ) : ( +

Loading...

+ )} +
+
+
+ ); +}); diff --git a/web/components/issues/draft-issue-form.tsx b/web/components/issues/draft-issue-form.tsx index cfd6370fa..b08d1d31d 100644 --- a/web/components/issues/draft-issue-form.tsx +++ b/web/components/issues/draft-issue-form.tsx @@ -21,10 +21,10 @@ import { CycleDropdown, DateDropdown, EstimateDropdown, + MemberDropdown, ModuleDropdown, PriorityDropdown, ProjectDropdown, - ProjectMemberDropdown, StateDropdown, } from "components/dropdowns"; // ui @@ -474,7 +474,7 @@ export const DraftIssueForm: FC = observer((props) => { name="assignee_ids" render={({ field: { value, onChange } }) => (
- = observer((props) => { Assignees
- issueOperations.update(workspaceSlug, projectId, issueId, { assignee_ids: val })} disabled={!is_editable} diff --git a/web/components/issues/issue-detail/sidebar.tsx b/web/components/issues/issue-detail/sidebar.tsx index 0db0ed29f..76a14c6db 100644 --- a/web/components/issues/issue-detail/sidebar.tsx +++ b/web/components/issues/issue-detail/sidebar.tsx @@ -27,13 +27,7 @@ import { IssueLabel, } from "components/issues"; import { IssueSubscription } from "./subscription"; -import { - DateDropdown, - EstimateDropdown, - PriorityDropdown, - ProjectMemberDropdown, - StateDropdown, -} from "components/dropdowns"; +import { DateDropdown, EstimateDropdown, PriorityDropdown, MemberDropdown, StateDropdown } from "components/dropdowns"; // icons import { ContrastIcon, DiceIcon, DoubleCircleIcon, RelatedIcon, UserGroupIcon } from "@plane/ui"; // helpers @@ -161,7 +155,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { Assignees - issueOperations.update(workspaceSlug, projectId, issueId, { assignee_ids: val })} disabled={!is_editable} diff --git a/web/components/issues/issue-layouts/calendar/issue-blocks.tsx b/web/components/issues/issue-layouts/calendar/issue-blocks.tsx index 9f3532302..b5d0c4346 100644 --- a/web/components/issues/issue-layouts/calendar/issue-blocks.tsx +++ b/web/components/issues/issue-layouts/calendar/issue-blocks.tsx @@ -26,7 +26,7 @@ export const CalendarIssueBlocks: React.FC = observer((props) => { const { router: { workspaceSlug, projectId }, } = useApplication(); - const { getProjectById } = useProject(); + const { getProjectIdentifierById } = useProject(); const { getProjectStates } = useProjectState(); const { peekIssue, setPeekIssue } = useIssueDetail(); // states @@ -108,7 +108,7 @@ export const CalendarIssueBlocks: React.FC = observer((props) => { }} />
- {getProjectById(issue?.project_id)?.identifier}-{issue.sequence_id} + {getProjectIdentifierById(issue?.project_id)}-{issue.sequence_id}
{issue.name}
diff --git a/web/components/issues/issue-layouts/gantt/blocks.tsx b/web/components/issues/issue-layouts/gantt/blocks.tsx index d668b8a44..209d876ac 100644 --- a/web/components/issues/issue-layouts/gantt/blocks.tsx +++ b/web/components/issues/issue-layouts/gantt/blocks.tsx @@ -66,7 +66,7 @@ export const IssueGanttSidebarBlock: React.FC = observer((props) => { const { issueId } = props; // store hooks const { getStateById } = useProjectState(); - const { getProjectById } = useProject(); + const { getProjectIdentifierById } = useProject(); const { router: { workspaceSlug }, } = useApplication(); @@ -76,7 +76,7 @@ export const IssueGanttSidebarBlock: React.FC = observer((props) => { } = useIssueDetail(); // derived values const issueDetails = getIssueById(issueId); - const projectDetails = issueDetails && getProjectById(issueDetails?.project_id); + const projectIdentifier = issueDetails && getProjectIdentifierById(issueDetails?.project_id); const stateDetails = issueDetails && getStateById(issueDetails?.state_id); const handleIssuePeekOverview = () => @@ -95,7 +95,7 @@ export const IssueGanttSidebarBlock: React.FC = observer((props) => {
{stateDetails && }
- {projectDetails?.identifier} {issueDetails?.sequence_id} + {projectIdentifier} {issueDetails?.sequence_id}
{issueDetails?.name} diff --git a/web/components/issues/issue-layouts/kanban/block.tsx b/web/components/issues/issue-layouts/kanban/block.tsx index 0dc9aa908..8446e7328 100644 --- a/web/components/issues/issue-layouts/kanban/block.tsx +++ b/web/components/issues/issue-layouts/kanban/block.tsx @@ -42,9 +42,9 @@ interface IssueDetailsBlockProps { const KanbanIssueDetailsBlock: React.FC = observer((props: IssueDetailsBlockProps) => { const { issue, handleIssues, quickActions, isReadOnly, displayProperties } = props; // hooks - const { getProjectById } = useProject(); + const { getProjectIdentifierById } = useProject(); const { - router: { workspaceSlug, projectId }, + router: { workspaceSlug }, } = useApplication(); const { setPeekIssue } = useIssueDetail(); @@ -64,7 +64,7 @@ const KanbanIssueDetailsBlock: React.FC = observer((prop
- {getProjectById(issue.project_id)?.identifier}-{issue.sequence_id} + {getProjectIdentifierById(issue.project_id)}-{issue.sequence_id}
{quickActions(issue)}
diff --git a/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx b/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx index 2b311f6eb..f496a5120 100644 --- a/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react"; +import React, { useCallback, useMemo } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; // hooks @@ -46,7 +46,15 @@ export const CycleKanBanLayout: React.FC = observer(() => { const isCompletedCycle = cycleId && currentProjectCompletedCycleIds ? currentProjectCompletedCycleIds.includes(cycleId.toString()) : false; - const canEditIssueProperties = () => !isCompletedCycle; + const canEditIssueProperties = useCallback(() => !isCompletedCycle, [isCompletedCycle]); + + const addIssuesToView = useCallback( + (issueIds: string[]) => { + if (!workspaceSlug || !projectId || !cycleId) throw new Error(); + return issues.addIssueToCycle(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), issueIds); + }, + [issues?.addIssueToCycle, workspaceSlug, projectId, cycleId] + ); return ( { QuickActions={CycleIssueQuickActions} viewId={cycleId?.toString() ?? ""} storeType={EIssuesStoreType.CYCLE} - addIssuesToView={(issueIds: string[]) => { - if (!workspaceSlug || !projectId || !cycleId) throw new Error(); - return issues.addIssueToCycle(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), issueIds); - }} + addIssuesToView={addIssuesToView} canEditPropertiesBasedOnProject={canEditIssueProperties} isCompletedCycle={isCompletedCycle} /> diff --git a/web/components/issues/issue-layouts/list/block.tsx b/web/components/issues/issue-layouts/list/block.tsx index 1bbd574ed..cc04ed716 100644 --- a/web/components/issues/issue-layouts/list/block.tsx +++ b/web/components/issues/issue-layouts/list/block.tsx @@ -24,9 +24,9 @@ export const IssueBlock: React.FC = observer((props: IssueBlock const { issuesMap, issueId, handleIssues, quickActions, displayProperties, canEditProperties } = props; // hooks const { - router: { workspaceSlug, projectId }, + router: { workspaceSlug }, } = useApplication(); - const { getProjectById } = useProject(); + const { getProjectIdentifierById } = useProject(); const { peekIssue, setPeekIssue } = useIssueDetail(); const updateIssue = async (issueToUpdate: TIssue) => { @@ -45,7 +45,7 @@ export const IssueBlock: React.FC = observer((props: IssueBlock if (!issue) return null; const canEditIssueProperties = canEditProperties(issue.project_id); - const projectDetails = getProjectById(issue.project_id); + const projectIdentifier = getProjectIdentifierById(issue.project_id); return (
= observer((props: IssueBlock > {displayProperties && displayProperties?.key && (
- {projectDetails?.identifier}-{issue.sequence_id} + {projectIdentifier}-{issue.sequence_id}
)} diff --git a/web/components/issues/issue-layouts/list/roots/cycle-root.tsx b/web/components/issues/issue-layouts/list/roots/cycle-root.tsx index e30c207b6..d7ea66904 100644 --- a/web/components/issues/issue-layouts/list/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/cycle-root.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react"; +import React, { useCallback, useMemo } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; // hooks @@ -44,7 +44,15 @@ export const CycleListLayout: React.FC = observer(() => { const isCompletedCycle = cycleId && currentProjectCompletedCycleIds ? currentProjectCompletedCycleIds.includes(cycleId.toString()) : false; - const canEditIssueProperties = () => !isCompletedCycle; + const canEditIssueProperties = useCallback(() => !isCompletedCycle, [isCompletedCycle]); + + const addIssuesToView = useCallback( + (issueIds: string[]) => { + if (!workspaceSlug || !projectId || !cycleId) throw new Error(); + return issues.addIssueToCycle(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), issueIds); + }, + [issues?.addIssueToCycle, workspaceSlug, projectId, cycleId] + ); return ( { issueActions={issueActions} viewId={cycleId?.toString()} storeType={EIssuesStoreType.CYCLE} - addIssuesToView={(issueIds: string[]) => { - if (!workspaceSlug || !projectId || !cycleId) throw new Error(); - return issues.addIssueToCycle(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), issueIds); - }} + addIssuesToView={addIssuesToView} canEditPropertiesBasedOnProject={canEditIssueProperties} isCompletedCycle={isCompletedCycle} /> diff --git a/web/components/issues/issue-layouts/properties/all-properties.tsx b/web/components/issues/issue-layouts/properties/all-properties.tsx index 8c16d3e24..ce97f1afa 100644 --- a/web/components/issues/issue-layouts/properties/all-properties.tsx +++ b/web/components/issues/issue-layouts/properties/all-properties.tsx @@ -13,7 +13,7 @@ import { DateDropdown, EstimateDropdown, PriorityDropdown, - ProjectMemberDropdown, + MemberDropdown, ModuleDropdown, CycleDropdown, StateDropdown, @@ -313,7 +313,7 @@ export const IssueProperties: React.FC = observer((props) => { {/* assignee */}
- { } }; - useSWR(workspaceSlug ? `WORKSPACE_GLOBAL_VIEWS${workspaceSlug}` : null, async () => { - if (workspaceSlug) { - await fetchAllGlobalViews(workspaceSlug.toString()); - } - }); + useSWR( + workspaceSlug ? `WORKSPACE_GLOBAL_VIEWS_${workspaceSlug}` : null, + async () => { + if (workspaceSlug) { + await fetchAllGlobalViews(workspaceSlug.toString()); + } + }, + { revalidateIfStale: false, revalidateOnFocus: false } + ); useSWR( workspaceSlug && globalViewId ? `WORKSPACE_GLOBAL_VIEW_ISSUES_${workspaceSlug}_${globalViewId}` : null, @@ -103,7 +107,8 @@ export const AllIssueLayoutRoot: React.FC = observer(() => { await fetchIssues(workspaceSlug.toString(), globalViewId.toString(), issueIds ? "mutation" : "init-loader"); routerFilterParams(); } - } + }, + { revalidateIfStale: false, revalidateOnFocus: false } ); const canEditProperties = useCallback( diff --git a/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx index 5f049d4c3..7db9a1e3b 100644 --- a/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx @@ -33,7 +33,8 @@ export const ArchivedIssueLayoutRoot: React.FC = observer(() => { issues?.groupedIssueIds ? "mutation" : "init-loader" ); } - } + }, + { revalidateIfStale: false, revalidateOnFocus: false } ); if (issues?.loader === "init-loader" || !issues?.groupedIssueIds) { diff --git a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx index bc9c8c397..759495284 100644 --- a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx @@ -47,7 +47,8 @@ export const CycleLayoutRoot: React.FC = observer(() => { cycleId.toString() ); } - } + }, + { revalidateIfStale: false, revalidateOnFocus: false } ); const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout; diff --git a/web/components/issues/issue-layouts/roots/draft-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/draft-issue-layout-root.tsx index 6297ea0cd..02b666ceb 100644 --- a/web/components/issues/issue-layouts/roots/draft-issue-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/draft-issue-layout-root.tsx @@ -33,7 +33,8 @@ export const DraftIssueLayoutRoot: React.FC = observer(() => { issues?.groupedIssueIds ? "mutation" : "init-loader" ); } - } + }, + { revalidateIfStale: false, revalidateOnFocus: false } ); const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout || undefined; diff --git a/web/components/issues/issue-layouts/roots/module-layout-root.tsx b/web/components/issues/issue-layouts/roots/module-layout-root.tsx index b7978c7bc..14505c65a 100644 --- a/web/components/issues/issue-layouts/roots/module-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/module-layout-root.tsx @@ -43,7 +43,8 @@ export const ModuleLayoutRoot: React.FC = observer(() => { moduleId.toString() ); } - } + }, + { revalidateIfStale: false, revalidateOnFocus: false } ); const userFilters = issuesFilter?.issueFilters?.filters; diff --git a/web/components/issues/issue-layouts/roots/project-layout-root.tsx b/web/components/issues/issue-layouts/roots/project-layout-root.tsx index 75bb4bfad..cae73610e 100644 --- a/web/components/issues/issue-layouts/roots/project-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/project-layout-root.tsx @@ -29,16 +29,20 @@ export const ProjectLayoutRoot: FC = observer(() => { // hooks const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT); - useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES_${workspaceSlug}_${projectId}` : null, async () => { - if (workspaceSlug && projectId) { - await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString()); - await issues?.fetchIssues( - workspaceSlug.toString(), - projectId.toString(), - issues?.groupedIssueIds ? "mutation" : "init-loader" - ); - } - }); + useSWR( + workspaceSlug && projectId ? `PROJECT_ISSUES_${workspaceSlug}_${projectId}` : null, + async () => { + if (workspaceSlug && projectId) { + await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString()); + await issues?.fetchIssues( + workspaceSlug.toString(), + projectId.toString(), + issues?.groupedIssueIds ? "mutation" : "init-loader" + ); + } + }, + { revalidateIfStale: false, revalidateOnFocus: false } + ); const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout; diff --git a/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx b/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx index 2329e52df..fa942b7f6 100644 --- a/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx @@ -41,7 +41,8 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => { viewId.toString() ); } - } + }, + { revalidateIfStale: false, revalidateOnFocus: false } ); const issueActions = useMemo( diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx index b9450141b..c4b8ea0ef 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx @@ -1,7 +1,7 @@ import React from "react"; import { observer } from "mobx-react-lite"; // components -import { ProjectMemberDropdown } from "components/dropdowns"; +import { MemberDropdown } from "components/dropdowns"; // types import { TIssue } from "@plane/types"; @@ -17,7 +17,7 @@ export const SpreadsheetAssigneeColumn: React.FC = observer((props: Props return (
- { onChange( diff --git a/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx b/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx index b241a5168..41734a867 100644 --- a/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx @@ -142,7 +142,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => { const router = useRouter(); const { workspaceSlug } = router.query; //hooks - const { getProjectById } = useProject(); + const { getProjectIdentifierById } = useProject(); const { peekIssue, setPeekIssue } = useIssueDetail(); // states const [isMenuActive, setIsMenuActive] = useState(false); @@ -212,7 +212,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => { isMenuActive ? "opacity-0" : "opacity-100" }`} > - {getProjectById(issueDetail.project_id)?.identifier}-{issueDetail.sequence_id} + {getProjectIdentifierById(issueDetail.project_id)}-{issueDetail.sequence_id} {canEditProperties(issueDetail.project_id) && ( diff --git a/web/components/issues/issue-layouts/spreadsheet/roots/cycle-root.tsx b/web/components/issues/issue-layouts/spreadsheet/roots/cycle-root.tsx index 7f92bd74c..0ac5db0aa 100644 --- a/web/components/issues/issue-layouts/spreadsheet/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/roots/cycle-root.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react"; +import React, { useCallback, useMemo } from "react"; import { observer } from "mobx-react-lite"; import { useRouter } from "next/router"; // mobx store @@ -39,7 +39,7 @@ export const CycleSpreadsheetLayout: React.FC = observer(() => { const isCompletedCycle = cycleId && currentProjectCompletedCycleIds ? currentProjectCompletedCycleIds.includes(cycleId.toString()) : false; - const canEditIssueProperties = () => !isCompletedCycle; + const canEditIssueProperties = useCallback(() => !isCompletedCycle, [isCompletedCycle]); return ( = observer((props) => { name="assignee_ids" render={({ field: { value, onChange } }) => (
- { diff --git a/web/components/issues/issue-modal/modal.tsx b/web/components/issues/issue-modal/modal.tsx index 4c2833efa..84a625d20 100644 --- a/web/components/issues/issue-modal/modal.tsx +++ b/web/components/issues/issue-modal/modal.tsx @@ -54,7 +54,6 @@ export const CreateUpdateIssueModal: React.FC = observer((prop const { router: { workspaceSlug, projectId, cycleId, moduleId, viewId: projectViewId }, } = useApplication(); - const { currentWorkspace } = useWorkspace(); const { workspaceProjectIds } = useProject(); const { fetchCycleDetails } = useCycle(); const { fetchModuleDetails } = useModule(); diff --git a/web/components/issues/peek-overview/properties.tsx b/web/components/issues/peek-overview/properties.tsx index 2b428a57b..2588dafec 100644 --- a/web/components/issues/peek-overview/properties.tsx +++ b/web/components/issues/peek-overview/properties.tsx @@ -14,13 +14,7 @@ import { TIssueOperations, IssueRelationSelect, } from "components/issues"; -import { - DateDropdown, - EstimateDropdown, - PriorityDropdown, - ProjectMemberDropdown, - StateDropdown, -} from "components/dropdowns"; +import { DateDropdown, EstimateDropdown, PriorityDropdown, MemberDropdown, StateDropdown } from "components/dropdowns"; // components import { renderFormattedPayloadDate } from "helpers/date-time.helper"; // helpers @@ -87,7 +81,7 @@ export const PeekOverviewProperties: FC = observer((pro Assignees
- issueOperations.update(workspaceSlug, projectId, issueId, { assignee_ids: val })} disabled={disabled} diff --git a/web/components/issues/sub-issues/properties.tsx b/web/components/issues/sub-issues/properties.tsx index 3a205aea2..03c9d8902 100644 --- a/web/components/issues/sub-issues/properties.tsx +++ b/web/components/issues/sub-issues/properties.tsx @@ -2,7 +2,7 @@ import React from "react"; // hooks import { useIssueDetail } from "hooks/store"; // components -import { PriorityDropdown, ProjectMemberDropdown, StateDropdown } from "components/dropdowns"; +import { PriorityDropdown, MemberDropdown, StateDropdown } from "components/dropdowns"; // types import { TSubIssueOperations } from "./root"; @@ -62,7 +62,7 @@ export const IssueProperty: React.FC = (props) => {
- diff --git a/web/components/labels/index.ts b/web/components/labels/index.ts index a22251c64..9195f8649 100644 --- a/web/components/labels/index.ts +++ b/web/components/labels/index.ts @@ -1,7 +1,6 @@ export * from "./create-label-modal"; export * from "./create-update-label-inline"; export * from "./delete-label-modal"; -export * from "./labels-list-modal"; export * from "./project-setting-label-group"; export * from "./project-setting-label-item"; export * from "./project-setting-label-list"; diff --git a/web/components/labels/labels-list-modal.tsx b/web/components/labels/labels-list-modal.tsx deleted file mode 100644 index 82920b9ba..000000000 --- a/web/components/labels/labels-list-modal.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import React, { useState } from "react"; -import { useRouter } from "next/router"; -import { observer } from "mobx-react-lite"; -import useSWR from "swr"; -import { Combobox, Dialog, Transition } from "@headlessui/react"; -import { Search } from "lucide-react"; -// hooks -import { useLabel } from "hooks/store"; -// icons -import { LayerStackIcon } from "@plane/ui"; -// types -import { IIssueLabel } from "@plane/types"; - -type Props = { - isOpen: boolean; - handleClose: () => void; - parent: IIssueLabel | undefined; -}; - -export const LabelsListModal: React.FC = observer((props) => { - const { isOpen, handleClose, parent } = props; - // states - const [query, setQuery] = useState(""); - // router - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - // store hooks - const { projectLabels, fetchProjectLabels, updateLabel } = useLabel(); - - // api call to fetch project details - useSWR( - workspaceSlug && projectId ? "PROJECT_LABELS" : null, - workspaceSlug && projectId ? () => fetchProjectLabels(workspaceSlug.toString(), projectId.toString()) : null - ); - - // derived values - const filteredLabels: IIssueLabel[] = - query === "" - ? projectLabels ?? [] - : projectLabels?.filter((l) => l.name.toLowerCase().includes(query.toLowerCase())) ?? []; - - const handleModalClose = () => { - handleClose(); - setQuery(""); - }; - - const addChildLabel = async (label: IIssueLabel) => { - if (!workspaceSlug || !projectId) return; - - await updateLabel(workspaceSlug.toString(), projectId.toString(), label.id, { - parent: parent?.id!, - }); - }; - - return ( - setQuery("")} appear> - - -
- - -
- - - -
-
- - - {filteredLabels.length > 0 && ( -
  • - {query === "" && ( -

    Labels

    - )} -
      - {filteredLabels.map((label) => { - const children = projectLabels?.filter((l) => l.parent === label.id); - - if ( - (label.parent === "" || label.parent === null) && // issue does not have any other parent - label.id !== parent?.id && // issue is not itself - children?.length === 0 // issue doesn't have any other children - ) - return ( - - `flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-custom-text-200 ${ - active ? "bg-custom-background-80 text-custom-text-100" : "" - }` - } - onClick={() => { - addChildLabel(label); - }} - > - - {label.name} - - ); - })} -
    -
  • - )} -
    - - {query !== "" && filteredLabels.length === 0 && ( -
    -
    - )} -
    -
    -
    -
    -
    -
    - ); -}); diff --git a/web/components/modules/form.tsx b/web/components/modules/form.tsx index 191476357..1dde2b85d 100644 --- a/web/components/modules/form.tsx +++ b/web/components/modules/form.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; // components import { ModuleStatusSelect } from "components/modules"; -import { DateRangeDropdown, ProjectDropdown, ProjectMemberDropdown } from "components/dropdowns"; +import { DateRangeDropdown, ProjectDropdown, MemberDropdown } from "components/dropdowns"; // ui import { Button, Input, TextArea } from "@plane/ui"; // helpers @@ -175,7 +175,7 @@ export const ModuleForm: React.FC = (props) => { name="lead_id" render={({ field: { value, onChange } }) => (
    - = (props) => { name="member_ids" render={({ field: { value, onChange } }) => (
    - = observer((props) => { name="lead_id" render={({ field: { value } }) => (
    - { submitChanges({ lead_id: val }); @@ -409,7 +409,7 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { name="member_ids" render={({ field: { value } }) => (
    - { submitChanges({ member_ids: val }); diff --git a/web/components/profile/profile-issues.tsx b/web/components/profile/profile-issues.tsx index e7b61b4d2..7e501764a 100644 --- a/web/components/profile/profile-issues.tsx +++ b/web/components/profile/profile-issues.tsx @@ -51,7 +51,8 @@ export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => { await fetchFilters(workspaceSlug, userId); await fetchIssues(workspaceSlug, undefined, groupedIssueIds ? "mutation" : "init-loader", userId, type); } - } + }, + { revalidateIfStale: false, revalidateOnFocus: false } ); const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light"; diff --git a/web/components/project/create-project-modal.tsx b/web/components/project/create-project-modal.tsx index 83589795e..49a42a0a3 100644 --- a/web/components/project/create-project-modal.tsx +++ b/web/components/project/create-project-modal.tsx @@ -11,7 +11,7 @@ import { Button, CustomSelect, Input, TextArea } from "@plane/ui"; // components import { ImagePickerPopover } from "components/core"; import EmojiIconPicker from "components/emoji-icon-picker"; -import { WorkspaceMemberDropdown } from "components/dropdowns"; +import { MemberDropdown } from "components/dropdowns"; // helpers import { getRandomEmoji, renderEmoji } from "helpers/emoji.helper"; // constants @@ -383,7 +383,7 @@ export const CreateProjectModal: FC = observer((props) => { control={control} render={({ field: { value, onChange } }) => (
    - fetchWorkspaceModules(workspaceSlug.toString()) : null + workspaceSlug ? () => fetchWorkspaceModules(workspaceSlug.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); // fetch workspace Cycles useSWR( workspaceSlug ? `WORKSPACE_CYCLES_${workspaceSlug}` : null, - workspaceSlug ? () => fetchWorkspaceCycles(workspaceSlug.toString()) : null + workspaceSlug ? () => fetchWorkspaceCycles(workspaceSlug.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); // fetch workspace labels useSWR( workspaceSlug ? `WORKSPACE_LABELS_${workspaceSlug}` : null, - workspaceSlug ? () => fetchWorkspaceLabels(workspaceSlug.toString()) : null + workspaceSlug ? () => fetchWorkspaceLabels(workspaceSlug.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); // fetch workspace states useSWR( workspaceSlug ? `WORKSPACE_STATES_${workspaceSlug}` : null, - workspaceSlug ? () => fetchWorkspaceStates(workspaceSlug.toString()) : null + workspaceSlug ? () => fetchWorkspaceStates(workspaceSlug.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); // fetch workspace estimates useSWR( workspaceSlug ? `WORKSPACE_ESTIMATES_${workspaceSlug}` : null, - workspaceSlug ? () => fetchWorkspaceEstimates(workspaceSlug.toString()) : null + workspaceSlug ? () => fetchWorkspaceEstimates(workspaceSlug.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); }; diff --git a/web/layouts/app-layout/layout.tsx b/web/layouts/app-layout/layout.tsx index d0bd7849a..2a788e761 100644 --- a/web/layouts/app-layout/layout.tsx +++ b/web/layouts/app-layout/layout.tsx @@ -33,7 +33,7 @@ export const AppLayout: FC = observer((props) => { // await issues?.fetchIssues(workspaceSlug, projectId, issues?.groupedIssueIds ? "mutation" : "init-loader"); } }, - { revalidateOnFocus: false, refreshInterval: 600000, revalidateOnMount: true } + { revalidateIfStale: false, revalidateOnFocus: false } ); return ( diff --git a/web/layouts/auth-layout/project-wrapper.tsx b/web/layouts/auth-layout/project-wrapper.tsx index b9fd22f2d..bdd2da8b5 100644 --- a/web/layouts/auth-layout/project-wrapper.tsx +++ b/web/layouts/auth-layout/project-wrapper.tsx @@ -66,37 +66,44 @@ export const ProjectAuthWrapper: FC = observer((props) => { // fetching project labels useSWR( workspaceSlug && projectId ? `PROJECT_LABELS_${workspaceSlug}_${projectId}` : null, - workspaceSlug && projectId ? () => fetchProjectLabels(workspaceSlug.toString(), projectId.toString()) : null + workspaceSlug && projectId ? () => fetchProjectLabels(workspaceSlug.toString(), projectId.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); // fetching project members useSWR( workspaceSlug && projectId ? `PROJECT_MEMBERS_${workspaceSlug}_${projectId}` : null, - workspaceSlug && projectId ? () => fetchProjectMembers(workspaceSlug.toString(), projectId.toString()) : null + workspaceSlug && projectId ? () => fetchProjectMembers(workspaceSlug.toString(), projectId.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); // fetching project states useSWR( workspaceSlug && projectId ? `PROJECT_STATES_${workspaceSlug}_${projectId}` : null, - workspaceSlug && projectId ? () => fetchProjectStates(workspaceSlug.toString(), projectId.toString()) : null + workspaceSlug && projectId ? () => fetchProjectStates(workspaceSlug.toString(), projectId.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); // fetching project estimates useSWR( workspaceSlug && projectId ? `PROJECT_ESTIMATES_${workspaceSlug}_${projectId}` : null, - workspaceSlug && projectId ? () => fetchProjectEstimates(workspaceSlug.toString(), projectId.toString()) : null + workspaceSlug && projectId ? () => fetchProjectEstimates(workspaceSlug.toString(), projectId.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); // fetching project cycles useSWR( workspaceSlug && projectId ? `PROJECT_ALL_CYCLES_${workspaceSlug}_${projectId}` : null, - workspaceSlug && projectId ? () => fetchAllCycles(workspaceSlug.toString(), projectId.toString()) : null + workspaceSlug && projectId ? () => fetchAllCycles(workspaceSlug.toString(), projectId.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); // fetching project modules useSWR( workspaceSlug && projectId ? `PROJECT_MODULES_${workspaceSlug}_${projectId}` : null, - workspaceSlug && projectId ? () => fetchModules(workspaceSlug.toString(), projectId.toString()) : null + workspaceSlug && projectId ? () => fetchModules(workspaceSlug.toString(), projectId.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); // fetching project views useSWR( workspaceSlug && projectId ? `PROJECT_VIEWS_${workspaceSlug}_${projectId}` : null, - workspaceSlug && projectId ? () => fetchViews(workspaceSlug.toString(), projectId.toString()) : null + workspaceSlug && projectId ? () => fetchViews(workspaceSlug.toString(), projectId.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); // fetching project inboxes if inbox is enabled in project settings useSWR( diff --git a/web/layouts/auth-layout/workspace-wrapper.tsx b/web/layouts/auth-layout/workspace-wrapper.tsx index 9f402cb57..ba6498301 100644 --- a/web/layouts/auth-layout/workspace-wrapper.tsx +++ b/web/layouts/auth-layout/workspace-wrapper.tsx @@ -26,22 +26,26 @@ export const WorkspaceAuthWrapper: FC = observer((props) // fetching user workspace information useSWR( workspaceSlug ? `WORKSPACE_MEMBERS_ME_${workspaceSlug}` : null, - workspaceSlug ? () => membership.fetchUserWorkspaceInfo(workspaceSlug.toString()) : null + workspaceSlug ? () => membership.fetchUserWorkspaceInfo(workspaceSlug.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); // fetching workspace projects useSWR( workspaceSlug ? `WORKSPACE_PROJECTS_${workspaceSlug}` : null, - workspaceSlug ? () => fetchProjects(workspaceSlug.toString()) : null + workspaceSlug ? () => fetchProjects(workspaceSlug.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); // fetch workspace members useSWR( workspaceSlug ? `WORKSPACE_MEMBERS_${workspaceSlug}` : null, - workspaceSlug ? () => fetchWorkspaceMembers(workspaceSlug.toString()) : null + workspaceSlug ? () => fetchWorkspaceMembers(workspaceSlug.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); // fetch workspace user projects role useSWR( workspaceSlug ? `WORKSPACE_PROJECTS_ROLE_${workspaceSlug}` : null, - workspaceSlug ? () => membership.fetchUserWorkspaceProjectsRole(workspaceSlug.toString()) : null + workspaceSlug ? () => membership.fetchUserWorkspaceProjectsRole(workspaceSlug.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } ); // while data is being loaded diff --git a/web/store/cycle.store.ts b/web/store/cycle.store.ts index a970eee82..ee4842539 100644 --- a/web/store/cycle.store.ts +++ b/web/store/cycle.store.ts @@ -28,6 +28,7 @@ export interface ICycleStore { currentProjectActiveCycleId: string | null; // computed actions getCycleById: (cycleId: string) => ICycle | null; + getCycleNameById: (cycleId: string) => string | undefined; getActiveCycleById: (cycleId: string) => ICycle | null; getProjectCycleIds: (projectId: string) => string[] | null; // actions @@ -189,6 +190,13 @@ export class CycleStore implements ICycleStore { */ getCycleById = computedFn((cycleId: string): ICycle | null => this.cycleMap?.[cycleId] ?? null); + /** + * @description returns cycle name by cycle id + * @param cycleId + * @returns + */ + getCycleNameById = computedFn((cycleId: string): string => this.cycleMap?.[cycleId]?.name); + /** * @description returns active cycle details by cycle id * @param cycleId diff --git a/web/store/module.store.ts b/web/store/module.store.ts index cd6b7100a..2b4522cd0 100644 --- a/web/store/module.store.ts +++ b/web/store/module.store.ts @@ -19,6 +19,7 @@ export interface IModuleStore { projectModuleIds: string[] | null; // computed actions getModuleById: (moduleId: string) => IModule | null; + getModuleNameById: (moduleId: string) => string; getProjectModuleIds: (projectId: string) => string[] | null; // actions // fetch @@ -114,6 +115,13 @@ export class ModulesStore implements IModuleStore { */ getModuleById = computedFn((moduleId: string) => this.moduleMap?.[moduleId] || null); + /** + * @description get module by id + * @param moduleId + * @returns IModule | null + */ + getModuleNameById = computedFn((moduleId: string) => this.moduleMap?.[moduleId]?.name); + /** * @description returns list of module ids of the project id passed as argument * @param projectId diff --git a/web/store/project/project.store.ts b/web/store/project/project.store.ts index a47d459f8..c9aa826fe 100644 --- a/web/store/project/project.store.ts +++ b/web/store/project/project.store.ts @@ -24,6 +24,7 @@ export interface IProjectStore { // actions setSearchQuery: (query: string) => void; getProjectById: (projectId: string) => IProject | null; + getProjectIdentifierById: (projectId: string) => string; // fetch actions fetchProjects: (workspaceSlug: string) => Promise; fetchProjectDetails: (workspaceSlug: string, projectId: string) => Promise; @@ -210,6 +211,16 @@ export class ProjectStore implements IProjectStore { return projectInfo; }); + /** + * Returns project identifier using project id + * @param projectId + * @returns string + */ + getProjectIdentifierById = computedFn((projectId: string) => { + const projectInfo = this.projectMap?.[projectId]; + return projectInfo?.identifier; + }); + /** * Adds project to favorites and updates project favorite status in the store * @param workspaceSlug