From e6b0012fe2f413469de6054d1e67d4d05d504854 Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Sat, 18 Mar 2023 11:34:29 +0530 Subject: [PATCH] fix: mutation on issue create (#473) * refractor: added params to fetch key * feat: create views directly from views list page fix: selected filter not showing up in multi-level dropdown, refactor: arranged imports * fix: mutation on project create --- apps/app/components/issues/modal.tsx | 14 +- .../components/ui/multi-level-dropdown.tsx | 11 +- apps/app/components/views/form.tsx | 184 +++++++++++++++++- .../projects/[projectId]/views/index.tsx | 16 +- apps/app/types/views.d.ts | 1 + 5 files changed, 214 insertions(+), 12 deletions(-) diff --git a/apps/app/components/issues/modal.tsx b/apps/app/components/issues/modal.tsx index c87ff2e66..53f31bf1d 100644 --- a/apps/app/components/issues/modal.tsx +++ b/apps/app/components/issues/modal.tsx @@ -15,6 +15,8 @@ import useUser from "hooks/use-user"; import useToast from "hooks/use-toast"; // components import { IssueForm } from "components/issues"; +// hooks +import useIssuesView from "hooks/use-issues-view"; // types import type { IIssue } from "types"; // fetch keys @@ -26,6 +28,8 @@ import { PROJECTS_LIST, MODULE_ISSUES, SUB_ISSUES, + PROJECT_ISSUES_LIST_WITH_PARAMS, + CYCLE_ISSUES_WITH_PARAMS, } from "constants/fetch-keys"; export interface IssuesModalProps { @@ -50,6 +54,8 @@ export const CreateUpdateIssueModal: React.FC = ({ const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId } = router.query; + const { params } = useIssuesView(); + if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string }; if (moduleId) prePopulateData = { ...prePopulateData, module: moduleId as string }; @@ -96,7 +102,7 @@ export const CreateUpdateIssueModal: React.FC = ({ issues: [issueId], }) .then((res) => { - mutate(CYCLE_ISSUES(cycleId)); + mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId, params)); if (isUpdatingSingleIssue) { mutate( PROJECT_ISSUES_DETAILS, @@ -105,7 +111,7 @@ export const CreateUpdateIssueModal: React.FC = ({ ); } else mutate( - PROJECT_ISSUES_LIST(workspaceSlug as string, activeProject ?? ""), + PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params), (prevData) => (prevData ?? []).map((i) => { if (i.id === res.id) return { ...i, sprints: cycleId }; @@ -137,7 +143,7 @@ export const CreateUpdateIssueModal: React.FC = ({ await issuesService .createIssues(workspaceSlug as string, activeProject ?? "", payload) .then((res) => { - mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, activeProject ?? "")); + mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params)); if (payload.cycle && payload.cycle !== "") addIssueToCycle(res.id, payload.cycle); if (payload.module && payload.module !== "") addIssueToModule(res.id, payload.module); @@ -171,7 +177,7 @@ export const CreateUpdateIssueModal: React.FC = ({ mutate(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false); } else { mutate( - PROJECT_ISSUES_LIST(workspaceSlug as string, activeProject ?? ""), + PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params), (prevData) => (prevData ?? []).map((i) => { if (i.id === res.id) return { ...i, ...res }; diff --git a/apps/app/components/ui/multi-level-dropdown.tsx b/apps/app/components/ui/multi-level-dropdown.tsx index 8e24cb3a5..b4fa897b0 100644 --- a/apps/app/components/ui/multi-level-dropdown.tsx +++ b/apps/app/components/ui/multi-level-dropdown.tsx @@ -1,7 +1,10 @@ -import { Menu, Transition } from "@headlessui/react"; import { Fragment, useState } from "react"; -import { ChevronRightIcon, ChevronLeftIcon } from "@heroicons/react/20/solid"; + +// headless ui +import { Menu, Transition } from "@headlessui/react"; +// icons import { ChevronDownIcon } from "@heroicons/react/24/outline"; +import { ChevronRightIcon, ChevronLeftIcon } from "@heroicons/react/20/solid"; type MultiLevelDropdownProps = { label: string; @@ -107,7 +110,7 @@ export const MultiLevelDropdown: React.FC = ({ : "left-full translate-x-1" }`} > -
+
{option.children.map((child) => ( = ({ }} className={({ active }) => `${ - active || option.selected ? "bg-gray-100" : "text-gray-900" + active || child.selected ? "bg-gray-100" : "text-gray-900" } flex w-full items-center rounded px-1 py-1.5 capitalize` } > diff --git a/apps/app/components/views/form.tsx b/apps/app/components/views/form.tsx index 69212928f..ed2e0933e 100644 --- a/apps/app/components/views/form.tsx +++ b/apps/app/components/views/form.tsx @@ -1,11 +1,33 @@ import { useEffect } from "react"; -// react-hook-form +import { useRouter } from "next/router"; + +import useSWR from "swr"; + import { useForm } from "react-hook-form"; // ui -import { Input, PrimaryButton, SecondaryButton, TextArea } from "components/ui"; +import { + Avatar, + Input, + MultiLevelDropdown, + PrimaryButton, + SecondaryButton, + TextArea, +} from "components/ui"; // types import { IView } from "types"; +// constant +import { PROJECT_MEMBERS, STATE_LIST } from "constants/fetch-keys"; +// helpers +import { getStatesList } from "helpers/state.helper"; +// services +import stateService from "services/state.service"; +import projectService from "services/project.service"; +// icons +import { getStateGroupIcon } from "components/icons"; +import { getPriorityIcon } from "components/icons/priority-icon"; +// components +import { PRIORITIES } from "constants/project"; type Props = { handleFormSubmit: (values: IView) => Promise; @@ -27,11 +49,31 @@ export const ViewForm: React.FC = ({ data, preLoadedData, }) => { + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { data: states } = useSWR( + workspaceSlug && projectId ? STATE_LIST(projectId as string) : null, + workspaceSlug && projectId + ? () => stateService.getStates(workspaceSlug as string, projectId as string) + : null + ); + const statesList = getStatesList(states ?? {}); + + const { data: members } = useSWR( + projectId ? PROJECT_MEMBERS(projectId as string) : null, + workspaceSlug && projectId + ? () => projectService.projectMembers(workspaceSlug as string, projectId as string) + : null + ); + const { register, formState: { errors, isSubmitting }, handleSubmit, reset, + watch, + setValue, } = useForm({ defaultValues, }); @@ -58,6 +100,8 @@ export const ViewForm: React.FC = ({ }); }, [preLoadedData, reset]); + const filters = watch("query"); + return (
@@ -94,6 +138,142 @@ export const ViewForm: React.FC = ({ register={register} />
+
+ { + const key = option.key as keyof typeof filters; + + if (!filters?.[key]?.includes(option.value)) + setValue("query", { + ...filters, + [key]: [...((filters?.[key] as any[]) ?? []), option.value], + }); + else { + setValue("query", { + ...filters, + [key]: (filters?.[key] as any[])?.filter((item) => item !== option.value), + }); + } + }} + direction="right" + options={[ + { + id: "priority", + label: "Priority", + value: PRIORITIES, + children: [ + ...PRIORITIES.map((priority) => ({ + id: priority ?? "none", + label: ( +
+ {getPriorityIcon(priority)} {priority ?? "None"} +
+ ), + value: { + key: "priority", + value: priority, + }, + selected: filters?.priority?.includes(priority ?? "none"), + })), + ], + }, + { + id: "state", + label: "State", + value: statesList, + children: [ + ...statesList.map((state) => ({ + id: state.id, + label: ( +
+ {getStateGroupIcon(state.group, "16", "16", state.color)} {state.name} +
+ ), + value: { + key: "state", + value: state.id, + }, + selected: filters?.state?.includes(state.id), + })), + ], + }, + { + id: "assignee", + label: "Assignee", + value: members, + children: [ + ...(members?.map((member) => ({ + id: member.member.id, + label: ( +
+ + {member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + : member.member.email} +
+ ), + value: { + key: "assignees", + value: member.member.id, + }, + selected: filters?.assignees?.includes(member.member.id), + })) ?? []), + ], + }, + ]} + /> +
+
+
+ {Object.keys(filters ?? {}).map((key) => { + const queryKey = key as keyof typeof filters; + if (queryKey === "state") + return ( +
+ {filters.state?.map((stateID) => { + const state = statesList.find((state) => state.id === stateID); + if (!state) return null; + return ( +
+ {getStateGroupIcon(state?.group, "16", "16", state?.color)} + {state?.name} +
+ ); + })} +
+ ); + else if (queryKey === "priority") + return ( +
+ {filters.priority?.map((priority) => ( +
+ {getPriorityIcon(priority)} + {priority} +
+ ))} +
+ ); + else if (queryKey === "assignees") + return ( +
+ {filters.assignees?.map((assigneeID) => { + const member = members?.find((member) => member.member.id === assigneeID); + if (!member) return null; + return ( +
+ + {member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + : member.member.email} +
+ ); + })} +
+ ); + })} +
+
diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx index b755d5bef..f38f324e8 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx @@ -21,13 +21,14 @@ import { TrashIcon } from "@heroicons/react/20/solid"; // fetching keys import { PROJECT_DETAILS, VIEWS_LIST } from "constants/fetch-keys"; // components -import { CustomMenu, Spinner } from "components/ui"; -import { DeleteViewModal } from "components/views"; +import { CustomMenu, Spinner, PrimaryButton } from "components/ui"; +import { DeleteViewModal, CreateUpdateViewModal } from "components/views"; // types import { IView } from "types"; import type { NextPage, GetServerSidePropsContext } from "next"; const ProjectViews: NextPage = () => { + const [isCreateViewModalOpen, setIsCreateViewModalOpen] = useState(false); const [selectedView, setSelectedView] = useState(null); const { @@ -59,7 +60,18 @@ const ProjectViews: NextPage = () => { } + right={ +
+ setIsCreateViewModalOpen(true)}> + Create View + +
+ } > + setIsCreateViewModalOpen(false)} + />