From 33e298606241d7a3be3fafb8b4a30dd5f8c174ed Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 21 Feb 2023 11:23:50 +0530 Subject: [PATCH] feat: global component for links list (#307) --- .../components/core/issues-view-filter.tsx | 1 - apps/app/components/core/issues-view.tsx | 32 ++-- apps/app/components/core/sidebar/index.ts | 1 + .../components/core/sidebar/links-list.tsx | 58 ++++++ apps/app/components/issues/index.ts | 1 - apps/app/components/issues/links-list.tsx | 179 ------------------ apps/app/components/issues/sidebar.tsx | 86 ++++++++- apps/app/components/modules/sidebar.tsx | 61 +++--- .../projects/[projectId]/issues/[issueId].tsx | 2 - .../[projectId]/modules/[moduleId].tsx | 1 + 10 files changed, 180 insertions(+), 242 deletions(-) create mode 100644 apps/app/components/core/sidebar/links-list.tsx delete mode 100644 apps/app/components/issues/links-list.tsx diff --git a/apps/app/components/core/issues-view-filter.tsx b/apps/app/components/core/issues-view-filter.tsx index 4c4a5184a..9962ba96d 100644 --- a/apps/app/components/core/issues-view-filter.tsx +++ b/apps/app/components/core/issues-view-filter.tsx @@ -131,7 +131,6 @@ export const IssuesFilterView: React.FC = ({ issues }) => { { - console.log(option.key); setOrderBy(option.key); }} > diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx index 90c9e582e..0ee896395 100644 --- a/apps/app/components/core/issues-view.tsx +++ b/apps/app/components/core/issues-view.tsx @@ -114,27 +114,31 @@ export const IssuesView: React.FC = ({ if (orderBy === "sort_order") { const destinationGroupArray = groupedByIssues[destination.droppableId]; - if (destination.index === 0) newSortOrder = destinationGroupArray[0].sort_order - 10000; - else if (destination.index === destinationGroupArray.length) - newSortOrder = - destinationGroupArray[destinationGroupArray.length - 1].sort_order + 10000; - else - newSortOrder = - (destinationGroupArray[destination.index - 1].sort_order + - destinationGroupArray[destination.index].sort_order) / - 2; + if (destinationGroupArray.length !== 0) { + if (destination.index === 0) newSortOrder = destinationGroupArray[0].sort_order - 10000; + else if ( + (source.droppableId !== destination.droppableId && + destination.index === destinationGroupArray.length) || + (source.droppableId === destination.droppableId && + destination.index === destinationGroupArray.length - 1) + ) + newSortOrder = + destinationGroupArray[destinationGroupArray.length - 1].sort_order + 10000; + else + newSortOrder = + (destinationGroupArray[destination.index - 1].sort_order + + destinationGroupArray[destination.index].sort_order) / + 2; + } } - if (source.droppableId !== destination.droppableId) { + if (orderBy === "sort_order" || source.droppableId !== destination.droppableId) { const sourceGroup = source.droppableId; // source group id const destinationGroup = destination.droppableId; // destination group id if (!sourceGroup || !destinationGroup) return; if (selectedGroup === "priority") { - // update the removed item for mutation - draggedItem.priority = destinationGroup; - if (cycleId) mutate( CYCLE_ISSUES(cycleId as string), @@ -220,8 +224,6 @@ export const IssuesView: React.FC = ({ // update the removed item for mutation if (!destinationStateId || !destinationState) return; - draggedItem.state = destinationStateId; - draggedItem.state_detail = destinationState; if (cycleId) mutate( diff --git a/apps/app/components/core/sidebar/index.ts b/apps/app/components/core/sidebar/index.ts index 20d186d1e..c5357f576 100644 --- a/apps/app/components/core/sidebar/index.ts +++ b/apps/app/components/core/sidebar/index.ts @@ -1,2 +1,3 @@ +export * from "./links-list"; export * from "./sidebar-progress-stats"; export * from "./single-progress-stats"; diff --git a/apps/app/components/core/sidebar/links-list.tsx b/apps/app/components/core/sidebar/links-list.tsx new file mode 100644 index 000000000..2a30510eb --- /dev/null +++ b/apps/app/components/core/sidebar/links-list.tsx @@ -0,0 +1,58 @@ +import Link from "next/link"; + +// icons +import { LinkIcon, TrashIcon } from "@heroicons/react/24/outline"; +// helpers +import { timeAgo } from "helpers/date-time.helper"; +// types +import { IUserLite, UserAuth } from "types"; + +type Props = { + links: { + id: string; + created_at: Date; + created_by: string; + created_by_detail: IUserLite; + title: string; + url: string; + }[]; + handleDeleteLink: (linkId: string) => void; + userAuth: UserAuth; +}; + +export const LinksList: React.FC = ({ links, handleDeleteLink, userAuth }) => { + const isNotAllowed = userAuth.isGuest || userAuth.isViewer; + + return ( + <> + {links.map((link) => ( +
+ {!isNotAllowed && ( +
+ +
+ )} + + +
+ +
+
+
{link.title}
+ {/*

+ Added {timeAgo(link.created_at)} ago by {link.created_by_detail.email} +

*/} +
+
+ +
+ ))} + + ); +}; diff --git a/apps/app/components/issues/index.ts b/apps/app/components/issues/index.ts index 1c8884993..18253c6d3 100644 --- a/apps/app/components/issues/index.ts +++ b/apps/app/components/issues/index.ts @@ -4,7 +4,6 @@ export * from "./activity"; export * from "./delete-issue-modal"; export * from "./description-form"; export * from "./form"; -export * from "./links-list"; export * from "./modal"; export * from "./my-issues-list-item"; export * from "./parent-issues-list-modal"; diff --git a/apps/app/components/issues/links-list.tsx b/apps/app/components/issues/links-list.tsx deleted file mode 100644 index 752c614b4..000000000 --- a/apps/app/components/issues/links-list.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { FC, useState } from "react"; - -import Link from "next/link"; -import { useRouter } from "next/router"; - -import { mutate } from "swr"; - -// headless ui -import { Disclosure, Transition } from "@headlessui/react"; -// services -import issuesService from "services/issues.service"; -// hooks -import useToast from "hooks/use-toast"; -// components -import { LinkModal } from "components/core"; -// ui -import { CustomMenu } from "components/ui"; -// icons -import { ChevronRightIcon, LinkIcon, PlusIcon } from "@heroicons/react/24/outline"; -// helpers -import { copyTextToClipboard } from "helpers/string.helper"; -import { timeAgo } from "helpers/date-time.helper"; -// types -import { IIssue, IIssueLink, UserAuth } from "types"; -// fetch-keys -import { ISSUE_DETAILS } from "constants/fetch-keys"; - -type Props = { - parentIssue: IIssue; - userAuth: UserAuth; -}; - -export const LinksList: FC = ({ parentIssue, userAuth }) => { - const [linkModal, setLinkModal] = useState(false); - - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - - const { setToastAlert } = useToast(); - - const handleCreateLink = async (formData: IIssueLink) => { - if (!workspaceSlug || !projectId || !parentIssue) return; - - const previousLinks = parentIssue?.issue_link.map((l) => ({ title: l.title, url: l.url })); - - const payload: Partial = { - links_list: [...(previousLinks ?? []), formData], - }; - - await issuesService - .patchIssue(workspaceSlug as string, projectId as string, parentIssue.id, payload) - .then((res) => { - mutate(ISSUE_DETAILS(parentIssue.id as string)); - }) - .catch((err) => { - console.log(err); - }); - }; - - const handleDeleteLink = async (linkId: string) => { - if (!workspaceSlug || !projectId || !parentIssue) return; - - const updatedLinks = parentIssue.issue_link.filter((l) => l.id !== linkId); - - mutate( - ISSUE_DETAILS(parentIssue.id as string), - (prevData) => ({ ...(prevData as IIssue), issue_link: updatedLinks }), - false - ); - - await issuesService - .patchIssue(workspaceSlug as string, projectId as string, parentIssue.id, { - links_list: updatedLinks, - }) - .then((res) => { - mutate(ISSUE_DETAILS(parentIssue.id as string)); - }) - .catch((err) => { - console.log(err); - }); - }; - - const isNotAllowed = userAuth.isGuest || userAuth.isViewer; - - return ( - <> - setLinkModal(false)} - onFormSubmit={handleCreateLink} - /> - {parentIssue.issue_link && parentIssue.issue_link.length > 0 ? ( - - {({ open }) => ( - <> -
- - - Links {parentIssue.issue_link.length} - - {open && !isNotAllowed ? ( -
- -
- ) : null} -
- - - {parentIssue.issue_link.map((link) => ( -
- - - - {link.title} - - {timeAgo(link.created_at)} - - - - {!isNotAllowed && ( -
- - - copyTextToClipboard(link.url).then(() => { - setToastAlert({ - type: "success", - title: "Link copied to clipboard", - }); - }) - } - > - Copy link - - handleDeleteLink(link.id)}> - Remove link - - -
- )} -
- ))} -
-
- - )} -
- ) : ( - !isNotAllowed && ( - - ) - )} - - ); -}; diff --git a/apps/app/components/issues/sidebar.tsx b/apps/app/components/issues/sidebar.tsx index c26ac10eb..24a180bb6 100644 --- a/apps/app/components/issues/sidebar.tsx +++ b/apps/app/components/issues/sidebar.tsx @@ -13,9 +13,10 @@ import { Popover, Listbox, Transition } from "@headlessui/react"; // hooks import useToast from "hooks/use-toast"; // services -import issuesServices from "services/issues.service"; +import issuesService from "services/issues.service"; import modulesService from "services/modules.service"; // components +import { LinkModal, LinksList } from "components/core"; import { DeleteIssueModal, SidebarAssigneeSelect, @@ -43,7 +44,7 @@ import { // helpers import { copyTextToClipboard } from "helpers/string.helper"; // types -import type { ICycle, IIssue, IIssueLabels, IModule, UserAuth } from "types"; +import type { ICycle, IIssue, IIssueLabels, IIssueLink, IModule, UserAuth } from "types"; // fetch-keys import { PROJECT_ISSUE_LABELS, PROJECT_ISSUES_LIST, ISSUE_DETAILS } from "constants/fetch-keys"; @@ -69,6 +70,7 @@ export const IssueDetailsSidebar: React.FC = ({ }) => { const [createLabelForm, setCreateLabelForm] = useState(false); const [deleteIssueModal, setDeleteIssueModal] = useState(false); + const [linkModal, setLinkModal] = useState(false); const router = useRouter(); const { workspaceSlug, projectId, issueId } = router.query; @@ -80,14 +82,14 @@ export const IssueDetailsSidebar: React.FC = ({ ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) : null, workspaceSlug && projectId - ? () => issuesServices.getIssues(workspaceSlug as string, projectId as string) + ? () => issuesService.getIssues(workspaceSlug as string, projectId as string) : null ); const { data: issueLabels, mutate: issueLabelMutate } = useSWR( workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null, workspaceSlug && projectId - ? () => issuesServices.getIssueLabels(workspaceSlug as string, projectId as string) + ? () => issuesService.getIssueLabels(workspaceSlug as string, projectId as string) : null ); @@ -104,7 +106,7 @@ export const IssueDetailsSidebar: React.FC = ({ const handleNewLabel = (formData: any) => { if (!workspaceSlug || !projectId || isSubmitting) return; - issuesServices + issuesService .createIssueLabel(workspaceSlug as string, projectId as string, formData) .then((res) => { reset(defaultValues); @@ -118,7 +120,7 @@ export const IssueDetailsSidebar: React.FC = ({ (cycleDetail: ICycle) => { if (!workspaceSlug || !projectId || !issueDetail) return; - issuesServices + issuesService .addIssueToCycle(workspaceSlug as string, projectId as string, cycleDetail.id, { issues: [issueDetail.id], }) @@ -144,6 +146,48 @@ export const IssueDetailsSidebar: React.FC = ({ [workspaceSlug, projectId, issueId, issueDetail] ); + const handleCreateLink = async (formData: IIssueLink) => { + if (!workspaceSlug || !projectId || !issueDetail) return; + + const previousLinks = issueDetail?.issue_link.map((l) => ({ title: l.title, url: l.url })); + + const payload: Partial = { + links_list: [...(previousLinks ?? []), formData], + }; + + await issuesService + .patchIssue(workspaceSlug as string, projectId as string, issueDetail.id, payload) + .then((res) => { + mutate(ISSUE_DETAILS(issueDetail.id as string)); + }) + .catch((err) => { + console.log(err); + }); + }; + + const handleDeleteLink = async (linkId: string) => { + if (!workspaceSlug || !projectId || !issueDetail) return; + + const updatedLinks = issueDetail.issue_link.filter((l) => l.id !== linkId); + + mutate( + ISSUE_DETAILS(issueDetail.id as string), + (prevData) => ({ ...(prevData as IIssue), issue_link: updatedLinks }), + false + ); + + await issuesService + .patchIssue(workspaceSlug as string, projectId as string, issueDetail.id, { + links_list: updatedLinks, + }) + .then((res) => { + mutate(ISSUE_DETAILS(issueDetail.id as string)); + }) + .catch((err) => { + console.log(err); + }); + }; + useEffect(() => { if (!createLabelForm) return; @@ -154,6 +198,11 @@ export const IssueDetailsSidebar: React.FC = ({ return ( <> + setLinkModal(false)} + onFormSubmit={handleCreateLink} + /> setDeleteIssueModal(false)} isOpen={deleteIssueModal} @@ -297,7 +346,7 @@ export const IssueDetailsSidebar: React.FC = ({ /> -
+
@@ -527,6 +576,29 @@ export const IssueDetailsSidebar: React.FC = ({ )}
+
+
+

Links

+ {!isNotAllowed && ( + + )} +
+
+ {issueDetail?.issue_link && issueDetail.issue_link.length > 0 ? ( + + ) : null} +
+
); diff --git a/apps/app/components/modules/sidebar.tsx b/apps/app/components/modules/sidebar.tsx index f51c11a0b..57ec33a96 100644 --- a/apps/app/components/modules/sidebar.tsx +++ b/apps/app/components/modules/sidebar.tsx @@ -25,7 +25,7 @@ import modulesService from "services/modules.service"; // hooks import useToast from "hooks/use-toast"; // components -import { LinkModal, SidebarProgressStats } from "components/core"; +import { LinkModal, LinksList, SidebarProgressStats } from "components/core"; import { DeleteModuleModal, SidebarLeadSelect, SidebarMembersSelect } from "components/modules"; import ProgressChart from "components/core/sidebar/progress-chart"; @@ -37,7 +37,7 @@ import { renderDateFormat, renderShortNumericDateFormat, timeAgo } from "helpers import { copyTextToClipboard } from "helpers/string.helper"; import { groupBy } from "helpers/array.helper"; // types -import { IIssue, IModule, ModuleIssueResponse, ModuleLink } from "types"; +import { IIssue, IModule, ModuleIssueResponse, ModuleLink, UserAuth } from "types"; // fetch-keys import { MODULE_DETAILS } from "constants/fetch-keys"; // constant @@ -56,9 +56,16 @@ type Props = { module?: IModule; isOpen: boolean; moduleIssues: ModuleIssueResponse[] | undefined; + userAuth: UserAuth; }; -export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen, moduleIssues }) => { +export const ModuleDetailsSidebar: React.FC = ({ + issues, + module, + isOpen, + moduleIssues, + userAuth, +}) => { const [moduleDeleteModal, setModuleDeleteModal] = useState(false); const [moduleLinkModal, setModuleLinkModal] = useState(false); const [startDateRange, setStartDateRange] = useState(new Date()); @@ -128,6 +135,13 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen, }); }; + const handleDeleteLink = (linkId: string) => { + if (!module) return; + + const updatedLinks = module.link_module.filter((l) => l.id !== linkId); + submitChanges({ links_list: updatedLinks }); + }; + useEffect(() => { if (module) reset({ @@ -348,40 +362,13 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen,
- {module.link_module && module.link_module.length > 0 - ? module.link_module.map((link) => ( -
-
- -
- - -
- -
-
-
{link.title}
-

- Added {timeAgo(link.created_at)} ago by{" "} - {link.created_by_detail.email} -

-
-
- -
- )) - : null} + {module.link_module && module.link_module.length > 0 ? ( + + ) : null}
diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index e281c91dd..007a665f6 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -20,7 +20,6 @@ import { IssueDetailsSidebar, IssueActivitySection, AddComment, - LinksList, } from "components/issues"; // ui import { Loader, CustomMenu } from "components/ui"; @@ -196,7 +195,6 @@ const IssueDetailsPage: NextPage = (props) => { />
-
diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx index 561bc6965..262e01b7d 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx @@ -215,6 +215,7 @@ const SingleModule: React.FC = (props) => { module={moduleDetails} isOpen={moduleSidebar} moduleIssues={moduleIssues} + userAuth={props} />