diff --git a/apps/app/components/automation/auto-archive-automation.tsx b/apps/app/components/automation/auto-archive-automation.tsx new file mode 100644 index 000000000..8a78fc543 --- /dev/null +++ b/apps/app/components/automation/auto-archive-automation.tsx @@ -0,0 +1,96 @@ +import React, { useState } from "react"; + +// component +import { CustomSelect, ToggleSwitch } from "components/ui"; +import { SelectMonthModal } from "components/automation"; +// icons +import { ChevronDownIcon } from "@heroicons/react/24/outline"; +// constants +import { PROJECT_AUTOMATION_MONTHS } from "constants/project"; +// types +import { IProject } from "types"; + +type Props = { + projectDetails: IProject | undefined; + handleChange: (formData: Partial) => Promise; +}; + +export const AutoArchiveAutomation: React.FC = ({ projectDetails, handleChange }) => { + const [monthModal, setmonthModal] = useState(false); + + const initialValues: Partial = { archive_in: 1 }; + return ( + <> + setmonthModal(false)} + handleChange={handleChange} + /> +
+
+
+

Auto-archive closed issues

+

+ Plane will automatically archive issues that have been completed or canceled for the + configured time period +

+
+ { + if (projectDetails?.archive_in === 0) { + handleChange({ archive_in: 1 }); + } else { + handleChange({ archive_in: 0 }); + } + }} + size="sm" + /> +
+ {projectDetails?.archive_in !== 0 && ( +
+
+ Auto-archive issues that are closed for +
+
+ + {`${projectDetails?.archive_in} Months`} + +
+
+ )} +
+ + ); +}; diff --git a/apps/app/components/automation/auto-close-automation.tsx b/apps/app/components/automation/auto-close-automation.tsx new file mode 100644 index 000000000..11451d045 --- /dev/null +++ b/apps/app/components/automation/auto-close-automation.tsx @@ -0,0 +1,193 @@ +import React, { useState } from "react"; + +import useSWR from "swr"; + +import { useRouter } from "next/router"; + +// component +import { CustomSearchSelect, CustomSelect, ToggleSwitch } from "components/ui"; +import { SelectMonthModal } from "components/automation"; +// icons +import { ChevronDownIcon, Squares2X2Icon } from "@heroicons/react/24/outline"; +import { getStateGroupIcon } from "components/icons"; +// services +import stateService from "services/state.service"; +// constants +import { PROJECT_AUTOMATION_MONTHS } from "constants/project"; +import { STATES_LIST } from "constants/fetch-keys"; +// types +import { IProject } from "types"; +// helper +import { getStatesList } from "helpers/state.helper"; + +type Props = { + projectDetails: IProject | undefined; + handleChange: (formData: Partial) => Promise; +}; + +export const AutoCloseAutomation: React.FC = ({ projectDetails, handleChange }) => { + const [monthModal, setmonthModal] = useState(false); + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { data: stateGroups } = useSWR( + workspaceSlug && projectId ? STATES_LIST(projectId as string) : null, + workspaceSlug && projectId + ? () => stateService.getStates(workspaceSlug as string, projectId as string) + : null + ); + + const states = getStatesList(stateGroups ?? {}); + + const options = states + ?.filter((state) => state.group === "cancelled") + .map((state) => ({ + value: state.id, + query: state.name, + content: ( +
+ {getStateGroupIcon(state.group, "16", "16", state.color)} + {state.name} +
+ ), + })); + + const multipleOptions = options.length > 1; + + const defaultState = stateGroups && stateGroups.cancelled ? stateGroups.cancelled[0].id : null; + + const selectedOption = states?.find( + (s) => s.id === projectDetails?.default_state ?? defaultState + ); + const currentDefaultState = states.find((s) => s.id === defaultState); + + const initialValues: Partial = { + close_in: 1, + default_state: defaultState, + }; + + return ( + <> + setmonthModal(false)} + handleChange={handleChange} + /> + +
+
+
+

Auto-close inactive issues

+

+ Plane will automatically close the issues that have not been updated for the + configured time period. +

+
+ { + if (projectDetails?.close_in === 0) { + handleChange({ close_in: 1, default_state: defaultState }); + } else { + handleChange({ close_in: 0, default_state: null }); + } + }} + size="sm" + /> +
+ {projectDetails?.close_in !== 0 && ( +
+
+
+ Auto-close issues that are inactive for +
+
+ + {`${projectDetails?.close_in} Months`} + +
+
+
+
Auto-close Status
+
+ +
+ {selectedOption ? ( + getStateGroupIcon(selectedOption.group, "16", "16", selectedOption.color) + ) : currentDefaultState ? ( + getStateGroupIcon( + currentDefaultState.group, + "16", + "16", + currentDefaultState.color + ) + ) : ( + + )} + {selectedOption?.name + ? selectedOption.name + : currentDefaultState?.name ?? ( + State + )} +
+ {multipleOptions && ( +
+
+
+ )} +
+ + ); +}; diff --git a/apps/app/components/automation/index.ts b/apps/app/components/automation/index.ts new file mode 100644 index 000000000..73decae11 --- /dev/null +++ b/apps/app/components/automation/index.ts @@ -0,0 +1,3 @@ +export * from "./auto-close-automation"; +export * from "./auto-archive-automation"; +export * from "./select-month-modal"; diff --git a/apps/app/components/automation/select-month-modal.tsx b/apps/app/components/automation/select-month-modal.tsx new file mode 100644 index 000000000..ceb7ffa1a --- /dev/null +++ b/apps/app/components/automation/select-month-modal.tsx @@ -0,0 +1,147 @@ +import React from "react"; + +import { useRouter } from "next/router"; + +// react-hook-form +import { useForm } from "react-hook-form"; +// headless ui +import { Dialog, Transition } from "@headlessui/react"; +// ui +import { Input, PrimaryButton, SecondaryButton } from "components/ui"; +// types +import type { IProject } from "types"; + +// types +type Props = { + isOpen: boolean; + type: "auto-close" | "auto-archive"; + initialValues: Partial; + handleClose: () => void; + handleChange: (formData: Partial) => Promise; +}; + +export const SelectMonthModal: React.FC = ({ + type, + initialValues, + isOpen, + handleClose, + handleChange, +}) => { + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { + register, + formState: { errors, isSubmitting }, + handleSubmit, + reset, + } = useForm({ + defaultValues: initialValues, + }); + + const onClose = () => { + handleClose(); + reset(initialValues); + }; + + const onSubmit = (formData: Partial) => { + if (!workspaceSlug && !projectId) return; + handleChange(formData); + onClose(); + }; + + const inputSection = (name: string) => ( +
+ + Months +
+ ); + + return ( + + + +
+ + +
+
+ + +
+
+ + Customize Time Range + +
+
+ {type === "auto-close" ? ( + <> + {inputSection("close_in")} + {errors.close_in && ( + + Select a month between 1 and 12. + + )} + + ) : ( + <> + {inputSection("archive_in")} + {errors.archive_in && ( + + Select a month between 1 and 12. + + )} + + )} +
+
+
+
+ Cancel + + {isSubmitting ? "Submitting..." : "Submit"} + +
+
+
+
+
+
+
+
+ ); +}; diff --git a/apps/app/components/core/feeds.tsx b/apps/app/components/core/feeds.tsx index bc915d294..27be9ba31 100644 --- a/apps/app/components/core/feeds.tsx +++ b/apps/app/components/core/feeds.tsx @@ -23,6 +23,7 @@ import { renderShortDateWithYearFormat, timeAgo } from "helpers/date-time.helper import { addSpaceIfCamelCase } from "helpers/string.helper"; // types import RemirrorRichTextEditor from "components/rich-text-editor"; +import { Icon } from "components/ui"; const activityDetails: { [key: string]: { @@ -105,6 +106,10 @@ const activityDetails: { message: "updated the attachment", icon: