fix: cycle modal redendent component fix (#2528)

This commit is contained in:
sriram veeraghanta 2023-10-23 18:38:01 +05:30 committed by GitHub
parent 08f7ac6da7
commit 07d548ea43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 66 additions and 350 deletions

View File

@ -8,7 +8,7 @@ import useUser from "hooks/use-user";
// components
import { CommandModal, ShortcutsModal } from "components/command-palette";
import { BulkDeleteIssuesModal } from "components/core";
import { CreateUpdateCycleModal } from "components/cycles";
import { CycleCreateUpdateModal } from "components/cycles";
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
import { CreateUpdateModuleModal } from "components/modules";
import { CreateProjectModal } from "components/project";
@ -180,7 +180,7 @@ export const CommandPalette: FC = observer(() => {
)}
{workspaceSlug && projectId && (
<>
<CreateUpdateCycleModal
<CycleCreateUpdateModal
isOpen={isCreateCycleModalOpen}
handleClose={() => toggleCreateCycleModal(false)}
workspaceSlug={workspaceSlug.toString()}

View File

@ -1,163 +0,0 @@
import { Fragment, useEffect, useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
import { observer } from "mobx-react-lite";
// components
import { CycleForm } from "./form";
// hooks
import useToast from "hooks/use-toast";
import { useMobxStore } from "lib/mobx/store-provider";
// types
import { CycleDateCheckData, ICycle } from "types";
interface ICycleCreateEdit {
cycle?: ICycle | null;
modal: boolean;
modalClose: () => void;
onSubmit?: () => void;
workspaceSlug: string;
projectId: string;
}
export const CycleCreateEditModal: React.FC<ICycleCreateEdit> = observer((props) => {
const { modal, modalClose, cycle = null, onSubmit, workspaceSlug, projectId } = props;
const [activeProject, setActiveProject] = useState<string | null>(null);
const { project: projectStore, cycle: cycleStore } = useMobxStore();
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
const { setToastAlert } = useToast();
const validateCycleDate = async (payload: CycleDateCheckData) => {
let status = false;
await cycleStore.validateDate(workspaceSlug as string, projectId as string, payload).then((res) => {
status = res.status;
});
return status;
};
const formSubmit = async (data: Partial<ICycle>) => {
let isDateValid: boolean = true;
if (data?.start_date && data?.end_date) {
if (cycle?.id && cycle?.start_date && cycle?.end_date)
isDateValid = await validateCycleDate({
start_date: data.start_date,
end_date: data.end_date,
cycle_id: cycle.id,
});
else
isDateValid = await validateCycleDate({
start_date: data.start_date,
end_date: data.end_date,
});
}
if (isDateValid)
if (cycle) {
try {
await cycleStore.updateCycle(workspaceSlug, projectId, cycle.id, data);
if (modalClose) modalClose();
if (onSubmit) onSubmit();
setToastAlert({
type: "success",
title: "Success!",
message: "Cycle updated successfully.",
});
} catch (error) {
console.log("error", error);
setToastAlert({
type: "error",
title: "Warning!",
message: "Something went wrong please try again later.",
});
}
} else {
try {
await cycleStore.createCycle(workspaceSlug, projectId, data);
if (modalClose) modalClose();
if (onSubmit) onSubmit();
setToastAlert({
type: "success",
title: "Success!",
message: "Cycle created successfully.",
});
} catch (error) {
console.log("error", error);
setToastAlert({
type: "error",
title: "Warning!",
message: "Something went wrong please try again later.",
});
}
}
else
setToastAlert({
type: "error",
title: "Error!",
message: "You already have a cycle on the given dates, if you want to create a draft cycle, remove the dates.",
});
};
useEffect(() => {
// if modal is closed, reset active project to null
// and return to avoid activeProject being set to some other project
if (!modal) {
setActiveProject(null);
return;
}
// if data is present, set active project to the project of the
// issue. This has more priority than the project in the url.
if (cycle && cycle.project) {
setActiveProject(cycle.project);
return;
}
// if data is not present, set active project to the project
// in the url. This has the least priority.
if (projects && projects.length > 0 && !activeProject)
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
}, [activeProject, cycle, projectId, projects, modal]);
return (
<Transition.Root show={modal} as={Fragment}>
<Dialog as="div" className="relative z-20" onClose={modalClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl p-5">
<CycleForm
handleFormSubmit={formSubmit}
handleClose={modalClose}
projectId={activeProject ?? ""}
setActiveProject={setActiveProject}
data={cycle}
/>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
});

View File

@ -7,8 +7,7 @@ import { Disclosure, Transition } from "@headlessui/react";
import useToast from "hooks/use-toast";
// components
import { SingleProgressStats } from "components/core";
import { CycleCreateEditModal } from "./cycle-create-edit-modal";
import { CycleDeleteModal } from "./cycle-delete-modal";
import { CycleCreateUpdateModal, CycleDeleteModal } from "components/cycles";
// ui
import { AssigneesList } from "components/ui/avatar";
import { CustomMenu, Tooltip, LinearProgressIndicator, ContrastIcon, RunningIcon } from "@plane/ui";
@ -69,19 +68,14 @@ export interface ICyclesBoardCard {
export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
const { cycle, workspaceSlug, projectId } = props;
const [updateModal, setUpdateModal] = useState(false);
const updateModalCallback = () => {};
const [deleteModal, setDeleteModal] = useState(false);
const deleteModalCallback = () => {};
// store
const { cycle: cycleStore } = useMobxStore();
// toast
const { setToastAlert } = useToast();
// states
const [updateModal, setUpdateModal] = useState(false);
const [deleteModal, setDeleteModal] = useState(false);
// computed
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
const isCompleted = cycleStatus === "completed";
const endDate = new Date(cycle.end_date ?? "");
@ -142,20 +136,18 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
return (
<div>
<CycleCreateEditModal
cycle={cycle}
modal={updateModal}
modalClose={() => setUpdateModal(false)}
onSubmit={updateModalCallback}
<CycleCreateUpdateModal
data={cycle}
isOpen={updateModal}
handleClose={() => setUpdateModal(false)}
workspaceSlug={workspaceSlug}
projectId={projectId}
/>
<CycleDeleteModal
cycle={cycle}
modal={deleteModal}
modalClose={() => setDeleteModal(false)}
onSubmit={deleteModalCallback}
isOpen={deleteModal}
handleClose={() => setDeleteModal(false)}
workspaceSlug={workspaceSlug}
projectId={projectId}
/>

View File

@ -3,8 +3,7 @@ import Link from "next/link";
// hooks
import useToast from "hooks/use-toast";
// components
import { CycleCreateEditModal } from "./cycle-create-edit-modal";
import { CycleDeleteModal } from "./cycle-delete-modal";
import { CycleCreateUpdateModal, CycleDeleteModal } from "components/cycles";
// ui
import { CustomMenu, RadialProgressBar, Tooltip, LinearProgressIndicator, ContrastIcon, RunningIcon } from "@plane/ui";
// icons
@ -66,19 +65,14 @@ const stateGroups = [
export const CyclesListItem: FC<TCyclesListItem> = (props) => {
const { cycle, workspaceSlug, projectId } = props;
const [updateModal, setUpdateModal] = useState(false);
const updateModalCallback = () => {};
const [deleteModal, setDeleteModal] = useState(false);
const deleteModalCallback = () => {};
// store
const { cycle: cycleStore } = useMobxStore();
// toast
const { setToastAlert } = useToast();
// states
const [updateModal, setUpdateModal] = useState(false);
const [deleteModal, setDeleteModal] = useState(false);
// computed
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
const isCompleted = cycleStatus === "completed";
const endDate = new Date(cycle.end_date ?? "");
@ -347,20 +341,18 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
</div>
</div>
<CycleCreateEditModal
cycle={cycle}
modal={updateModal}
modalClose={() => setUpdateModal(false)}
onSubmit={updateModalCallback}
<CycleCreateUpdateModal
data={cycle}
isOpen={updateModal}
handleClose={() => setUpdateModal(false)}
workspaceSlug={workspaceSlug}
projectId={projectId}
/>
<CycleDeleteModal
cycle={cycle}
modal={deleteModal}
modalClose={() => setDeleteModal(false)}
onSubmit={deleteModalCallback}
isOpen={deleteModal}
handleClose={() => setDeleteModal(false)}
workspaceSlug={workspaceSlug}
projectId={projectId}
/>

View File

@ -13,24 +13,23 @@ import { useMobxStore } from "lib/mobx/store-provider";
interface ICycleDelete {
cycle: ICycle;
modal: boolean;
modalClose: () => void;
onSubmit?: () => void;
isOpen: boolean;
handleClose: () => void;
workspaceSlug: string;
projectId: string;
}
export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
const { modal, modalClose, cycle, onSubmit, workspaceSlug, projectId } = props;
const { isOpen, handleClose, cycle, workspaceSlug, projectId } = props;
// store
const { cycle: cycleStore } = useMobxStore();
// toast
const { setToastAlert } = useToast();
// states
const [loader, setLoader] = useState(false);
const formSubmit = async () => {
setLoader(true);
if (cycle?.id)
try {
await cycleStore.removeCycle(workspaceSlug, projectId, cycle?.id);
@ -39,8 +38,7 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
title: "Success!",
message: "Cycle deleted successfully.",
});
if (modalClose) modalClose();
if (onSubmit) onSubmit();
handleClose();
} catch (error) {
setToastAlert({
type: "error",
@ -61,8 +59,8 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
return (
<div>
<div>
<Transition.Root show={modal} as={Fragment}>
<Dialog as="div" className="relative z-20" onClose={modalClose}>
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
@ -103,7 +101,7 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
</p>
</span>
<div className="flex justify-end gap-2">
<SecondaryButton onClick={modalClose}>Cancel</SecondaryButton>
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<DangerButton onClick={formSubmit} loading={loader}>
{loader ? "Deleting..." : "Delete Cycle"}
</DangerButton>

View File

@ -10,7 +10,7 @@ type Props = {
handleFormSubmit: (values: Partial<ICycle>) => Promise<void>;
handleClose: () => void;
projectId: string;
setActiveProject: React.Dispatch<React.SetStateAction<string | null>>;
setActiveProject: (projectId: string) => void;
data?: ICycle | null;
};

View File

@ -16,3 +16,4 @@ export * from "./cycles-list-item";
export * from "./cycles-board";
export * from "./cycles-board-card";
export * from "./cycles-gantt";
export * from "./delete-modal";

View File

@ -1,5 +1,4 @@
import React, { useEffect, useState } from "react";
import { mutate } from "swr";
import React, { useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
// services
import { CycleService } from "services/cycle.service";
@ -8,20 +7,8 @@ import useToast from "hooks/use-toast";
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { CycleForm } from "components/cycles";
// helper
import { getDateRangeStatus } from "helpers/date-time.helper";
// types
import type { CycleDateCheckData, ICycle, IProject, IUser } from "types";
// fetch keys
import {
COMPLETED_CYCLES_LIST,
CURRENT_CYCLE_LIST,
CYCLES_LIST,
DRAFT_CYCLES_LIST,
INCOMPLETE_CYCLES_LIST,
PROJECT_DETAILS,
UPCOMING_CYCLES_LIST,
} from "constants/fetch-keys";
import type { CycleDateCheckData, ICycle } from "types";
type CycleModalProps = {
isOpen: boolean;
@ -34,49 +21,19 @@ type CycleModalProps = {
// services
const cycleService = new CycleService();
export const CreateUpdateCycleModal: React.FC<CycleModalProps> = (props) => {
export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
const { isOpen, handleClose, data, workspaceSlug, projectId } = props;
const [activeProject, setActiveProject] = useState<string | null>(null);
const { project: projectStore } = useMobxStore();
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
// store
const { cycle: cycleStore } = useMobxStore();
// states
const [activeProject, setActiveProject] = useState<string>(projectId);
// toast
const { setToastAlert } = useToast();
const createCycle = async (payload: Partial<ICycle>) => {
await cycleService
.createCycle(workspaceSlug.toString(), projectId.toString(), payload, {} as IUser)
.then((res) => {
switch (getDateRangeStatus(res.start_date, res.end_date)) {
case "completed":
mutate(COMPLETED_CYCLES_LIST(projectId.toString()));
break;
case "current":
mutate(CURRENT_CYCLE_LIST(projectId.toString()));
break;
case "upcoming":
mutate(UPCOMING_CYCLES_LIST(projectId.toString()));
break;
default:
mutate(DRAFT_CYCLES_LIST(projectId.toString()));
}
mutate(INCOMPLETE_CYCLES_LIST(projectId.toString()));
mutate(CYCLES_LIST(projectId.toString()));
// update total cycles count in the project details
mutate<IProject>(
PROJECT_DETAILS(projectId.toString()),
(prevData) => {
if (!prevData) return prevData;
return {
...prevData,
total_cycles: prevData.total_cycles + 1,
};
},
false
);
const createCycle = async (payload: Partial<ICycle>) =>
cycleStore
.createCycle(workspaceSlug, projectId, payload)
.then(() => {
setToastAlert({
type: "success",
title: "Success!",
@ -90,42 +47,11 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = (props) => {
message: "Error in creating cycle. Please try again.",
});
});
};
const updateCycle = async (cycleId: string, payload: Partial<ICycle>) => {
await cycleService
.updateCycle(workspaceSlug.toString(), projectId.toString(), cycleId, payload, {} as IUser)
.then((res) => {
switch (getDateRangeStatus(data?.start_date, data?.end_date)) {
case "completed":
mutate(COMPLETED_CYCLES_LIST(projectId.toString()));
break;
case "current":
mutate(CURRENT_CYCLE_LIST(projectId.toString()));
break;
case "upcoming":
mutate(UPCOMING_CYCLES_LIST(projectId.toString()));
break;
default:
mutate(DRAFT_CYCLES_LIST(projectId.toString()));
}
mutate(CYCLES_LIST(projectId.toString()));
if (getDateRangeStatus(data?.start_date, data?.end_date) != getDateRangeStatus(res.start_date, res.end_date)) {
switch (getDateRangeStatus(res.start_date, res.end_date)) {
case "completed":
mutate(COMPLETED_CYCLES_LIST(projectId.toString()));
break;
case "current":
mutate(CURRENT_CYCLE_LIST(projectId.toString()));
break;
case "upcoming":
mutate(UPCOMING_CYCLES_LIST(projectId.toString()));
break;
default:
mutate(DRAFT_CYCLES_LIST(projectId.toString()));
}
}
const updateCycle = async (cycleId: string, payload: Partial<ICycle>) =>
cycleStore
.updateCycle(workspaceSlug, projectId, cycleId, payload)
.then(() => {
setToastAlert({
type: "success",
title: "Success!",
@ -139,7 +65,6 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = (props) => {
message: "Error in updating cycle. Please try again.",
});
});
};
const dateChecker = async (payload: CycleDateCheckData) => {
let status = false;
@ -186,27 +111,6 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = (props) => {
});
};
useEffect(() => {
// if modal is closed, reset active project to null
// and return to avoid activeProject being set to some other project
if (!isOpen) {
setActiveProject(null);
return;
}
// if data is present, set active project to the project of the
// issue. This has more priority than the project in the url.
if (data && data.project) {
setActiveProject(data.project);
return;
}
// if data is not present, set active project to the project
// in the url. This has the least priority.
if (projects && projects.length > 0 && !activeProject)
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
}, [activeProject, data, projectId, projects, isOpen]);
return (
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
@ -237,7 +141,7 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = (props) => {
<CycleForm
handleFormSubmit={handleFormSubmit}
handleClose={handleClose}
projectId={activeProject ?? ""}
projectId={activeProject}
setActiveProject={setActiveProject}
data={data}
/>

View File

@ -1,7 +1,6 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import useUserAuth from "hooks/use-user-auth";
import { Listbox, Transition } from "@headlessui/react";
// icons
import { ContrastIcon } from "@plane/ui";
@ -9,7 +8,7 @@ import { Plus } from "lucide-react";
// services
import { CycleService } from "services/cycle.service";
// components
import { CreateUpdateCycleModal } from "components/cycles";
import { CycleCreateUpdateModal } from "components/cycles";
// fetch-keys
import { CYCLES_LIST } from "constants/fetch-keys";
@ -29,8 +28,6 @@ export const CycleSelect: React.FC<IssueCycleSelectProps> = ({ projectId, value,
const router = useRouter();
const { workspaceSlug } = router.query;
const { user } = useUserAuth();
const { data: cycles } = useSWR(
workspaceSlug && projectId ? CYCLES_LIST(projectId) : null,
workspaceSlug && projectId
@ -51,7 +48,7 @@ export const CycleSelect: React.FC<IssueCycleSelectProps> = ({ projectId, value,
return (
<>
{workspaceSlug && projectId && (
<CreateUpdateCycleModal
<CycleCreateUpdateModal
isOpen={isCycleModalActive}
handleClose={closeCycleModal}
workspaceSlug={workspaceSlug.toString()}

View File

@ -13,7 +13,7 @@ import useToast from "hooks/use-toast";
// components
import { SidebarProgressStats } from "components/core";
import ProgressChart from "components/core/sidebar/progress-chart";
import { CycleDeleteModal } from "components/cycles/cycle-delete-modal";
import { CycleDeleteModal } from "components/cycles/delete-modal";
// ui
import { CustomRangeDatePicker } from "components/ui";
import { CustomMenu, Loader, ProgressBar } from "@plane/ui";
@ -285,9 +285,8 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
{cycleDetails && workspaceSlug && projectId && (
<CycleDeleteModal
cycle={cycleDetails}
modal={cycleDeleteModal}
modalClose={() => setCycleDeleteModal(false)}
onSubmit={() => {}}
isOpen={cycleDeleteModal}
handleClose={() => setCycleDeleteModal(false)}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
/>

View File

@ -10,8 +10,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { AppLayout } from "layouts/app-layout";
// components
import { CyclesHeader } from "components/headers";
import { CyclesView, ActiveCycleDetails } from "components/cycles";
import { CycleCreateEditModal } from "components/cycles/cycle-create-edit-modal";
import { CyclesView, ActiveCycleDetails, CycleCreateUpdateModal } from "components/cycles";
// ui
import { EmptyState } from "components/common";
// images
@ -26,15 +25,12 @@ import { setLocalStorage, getLocalStorage } from "lib/local-storage";
const ProjectCyclesPage: NextPage = observer(() => {
const [createModal, setCreateModal] = useState(false);
const createOnSubmit = () => {};
// store
const { project: projectStore, cycle: cycleStore } = useMobxStore();
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
// fetching project details
useSWR(
workspaceSlug && projectId ? `PROJECT_DETAILS_${projectId}` : null,
workspaceSlug && projectId ? () => projectStore.fetchProjectDetails(workspaceSlug, projectId) : null
@ -84,14 +80,12 @@ const ProjectCyclesPage: NextPage = observer(() => {
return (
<AppLayout header={<CyclesHeader name={projectDetails?.name} />} withProjectWrapper>
<CycleCreateEditModal
<CycleCreateUpdateModal
workspaceSlug={workspaceSlug}
projectId={projectId}
modal={createModal}
modalClose={() => setCreateModal(false)}
onSubmit={createOnSubmit}
isOpen={createModal}
handleClose={() => setCreateModal(false)}
/>
{projectDetails?.total_cycles === 0 ? (
<div className="h-full grid place-items-center">
<EmptyState

View File

@ -211,12 +211,14 @@ export class CycleStore implements ICycleStore {
createCycle = async (workspaceSlug: string, projectId: string, data: any) => {
try {
console.log("Cycle Creating");
const response = await this.cycleService.createCycle(
workspaceSlug,
projectId,
data,
this.rootStore.user.currentUser
);
console.log("Cycle created");
runInAction(() => {
this.cycle_details = {