mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix: project automation settings flickering (#2680)
* fix: cycle and module sidebar z-index * fix: project automation settings flickering
This commit is contained in:
parent
da799b5a63
commit
df8bdfd5b9
@ -1,7 +1,9 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// component
|
||||
import { CustomSelect, ToggleSwitch } from "@plane/ui";
|
||||
import { CustomSelect, Loader, ToggleSwitch } from "@plane/ui";
|
||||
import { SelectMonthModal } from "components/automation";
|
||||
// icon
|
||||
import { ArchiveRestore } from "lucide-react";
|
||||
@ -11,15 +13,21 @@ import { PROJECT_AUTOMATION_MONTHS } from "constants/project";
|
||||
import { IProject } from "types";
|
||||
|
||||
type Props = {
|
||||
projectDetails: IProject | undefined;
|
||||
handleChange: (formData: Partial<IProject>) => Promise<void>;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const AutoArchiveAutomation: React.FC<Props> = ({ projectDetails, handleChange, disabled = false }) => {
|
||||
const initialValues: Partial<IProject> = { archive_in: 1 };
|
||||
|
||||
export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
|
||||
const { handleChange } = props;
|
||||
// states
|
||||
const [monthModal, setmonthModal] = useState(false);
|
||||
|
||||
const initialValues: Partial<IProject> = { archive_in: 1 };
|
||||
const { user: userStore, project: projectStore } = useMobxStore();
|
||||
|
||||
const projectDetails = projectStore.currentProjectDetails;
|
||||
const userRole = userStore.currentProjectRole;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SelectMonthModal
|
||||
@ -48,46 +56,52 @@ export const AutoArchiveAutomation: React.FC<Props> = ({ projectDetails, handleC
|
||||
projectDetails?.archive_in === 0 ? handleChange({ archive_in: 1 }) : handleChange({ archive_in: 0 })
|
||||
}
|
||||
size="sm"
|
||||
disabled={disabled}
|
||||
disabled={userRole !== 20}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{projectDetails?.archive_in !== 0 && (
|
||||
<div className="ml-12">
|
||||
<div className="flex items-center justify-between rounded px-5 py-4 bg-custom-background-90 border-[0.5px] border-custom-border-200 gap-2 w-full">
|
||||
<div className="w-1/2 text-sm font-medium">Auto-archive issues that are closed for</div>
|
||||
<div className="w-1/2">
|
||||
<CustomSelect
|
||||
value={projectDetails?.archive_in}
|
||||
label={`${projectDetails?.archive_in} ${projectDetails?.archive_in === 1 ? "Month" : "Months"}`}
|
||||
onChange={(val: number) => {
|
||||
handleChange({ archive_in: val });
|
||||
}}
|
||||
input
|
||||
width="w-full"
|
||||
disabled={disabled}
|
||||
>
|
||||
<>
|
||||
{PROJECT_AUTOMATION_MONTHS.map((month) => (
|
||||
<CustomSelect.Option key={month.label} value={month.value}>
|
||||
<span className="text-sm">{month.label}</span>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
{projectDetails ? (
|
||||
projectDetails.archive_in !== 0 && (
|
||||
<div className="ml-12">
|
||||
<div className="flex items-center justify-between rounded px-5 py-4 bg-custom-background-90 border border-custom-border-200 gap-2 w-full">
|
||||
<div className="w-1/2 text-sm font-medium">Auto-archive issues that are closed for</div>
|
||||
<div className="w-1/2">
|
||||
<CustomSelect
|
||||
value={projectDetails?.archive_in}
|
||||
label={`${projectDetails?.archive_in} ${projectDetails?.archive_in === 1 ? "Month" : "Months"}`}
|
||||
onChange={(val: number) => {
|
||||
handleChange({ archive_in: val });
|
||||
}}
|
||||
input
|
||||
width="w-full"
|
||||
disabled={userRole !== 20}
|
||||
>
|
||||
<>
|
||||
{PROJECT_AUTOMATION_MONTHS.map((month) => (
|
||||
<CustomSelect.Option key={month.label} value={month.value}>
|
||||
<span className="text-sm">{month.label}</span>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full text-sm select-none items-center rounded px-1 py-1.5 text-custom-text-200 hover:bg-custom-background-80"
|
||||
onClick={() => setmonthModal(true)}
|
||||
>
|
||||
Customise Time Range
|
||||
</button>
|
||||
</>
|
||||
</CustomSelect>
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full text-sm select-none items-center rounded px-1 py-1.5 text-custom-text-200 hover:bg-custom-background-80"
|
||||
onClick={() => setmonthModal(true)}
|
||||
>
|
||||
Customise Time Range
|
||||
</button>
|
||||
</>
|
||||
</CustomSelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<Loader className="ml-12">
|
||||
<Loader.Item height="50px" />
|
||||
</Loader>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,41 +1,33 @@
|
||||
import React, { useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// component
|
||||
import { SelectMonthModal } from "components/automation";
|
||||
import { CustomSelect, CustomSearchSelect, ToggleSwitch, StateGroupIcon, DoubleCircleIcon } from "@plane/ui";
|
||||
import { CustomSelect, CustomSearchSelect, ToggleSwitch, StateGroupIcon, DoubleCircleIcon, Loader } from "@plane/ui";
|
||||
// icons
|
||||
import { ArchiveX } from "lucide-react";
|
||||
// services
|
||||
import { ProjectStateService } from "services/project";
|
||||
// constants
|
||||
import { PROJECT_AUTOMATION_MONTHS } from "constants/project";
|
||||
import { STATES_LIST } from "constants/fetch-keys";
|
||||
// helpers
|
||||
import { getStatesList } from "helpers/state.helper";
|
||||
// types
|
||||
import { IProject } from "types";
|
||||
// helper
|
||||
import { getStatesList } from "helpers/state.helper";
|
||||
// fetch keys
|
||||
import { PROJECT_AUTOMATION_MONTHS } from "constants/project";
|
||||
|
||||
type Props = {
|
||||
projectDetails: IProject | undefined;
|
||||
handleChange: (formData: Partial<IProject>) => Promise<void>;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const projectStateService = new ProjectStateService();
|
||||
|
||||
export const AutoCloseAutomation: React.FC<Props> = ({ projectDetails, handleChange, disabled = false }) => {
|
||||
export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
const { handleChange } = props;
|
||||
// states
|
||||
const [monthModal, setmonthModal] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
const { user: userStore, project: projectStore } = useMobxStore();
|
||||
|
||||
const { data: stateGroups } = useSWR(
|
||||
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () => projectStateService.getStates(workspaceSlug as string, projectId as string)
|
||||
: null
|
||||
);
|
||||
const userRole = userStore.currentProjectRole;
|
||||
const projectDetails = projectStore.currentProjectDetails;
|
||||
const stateGroups = projectStore.projectStatesByGroups ?? undefined;
|
||||
const states = getStatesList(stateGroups);
|
||||
|
||||
const options = states
|
||||
@ -72,8 +64,7 @@ export const AutoCloseAutomation: React.FC<Props> = ({ projectDetails, handleCha
|
||||
handleClose={() => setmonthModal(false)}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col gap-4 border-b border-custom-border-100 px-4 py-6">
|
||||
<div className="flex flex-col gap-4 border-b border-custom-border-200 px-4 py-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex items-center justify-center p-3 rounded bg-custom-background-90">
|
||||
@ -82,7 +73,7 @@ export const AutoCloseAutomation: React.FC<Props> = ({ projectDetails, handleCha
|
||||
<div className="">
|
||||
<h4 className="text-sm font-medium">Auto-close issues</h4>
|
||||
<p className="text-sm text-custom-text-200 tracking-tight">
|
||||
Plane will automatically close issue that haven’t been completed or cancelled.
|
||||
Plane will automatically close issue that haven{"'"}t been completed or cancelled.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -94,87 +85,93 @@ export const AutoCloseAutomation: React.FC<Props> = ({ projectDetails, handleCha
|
||||
: handleChange({ close_in: 0, default_state: null })
|
||||
}
|
||||
size="sm"
|
||||
disabled={disabled}
|
||||
disabled={userRole !== 20}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{projectDetails?.close_in !== 0 && (
|
||||
<div className="ml-12">
|
||||
<div className="flex flex-col rounded bg-custom-background-90 border-[0.5px] border-custom-border-200 p-2">
|
||||
<div className="flex items-center justify-between px-5 py-4 gap-2 w-full">
|
||||
<div className="w-1/2 text-sm font-medium">Auto-close issues that are inactive for</div>
|
||||
<div className="w-1/2">
|
||||
<CustomSelect
|
||||
value={projectDetails?.close_in}
|
||||
label={`${projectDetails?.close_in} ${projectDetails?.close_in === 1 ? "Month" : "Months"}`}
|
||||
onChange={(val: number) => {
|
||||
handleChange({ close_in: val });
|
||||
}}
|
||||
input
|
||||
width="w-full"
|
||||
disabled={disabled}
|
||||
>
|
||||
<>
|
||||
{PROJECT_AUTOMATION_MONTHS.map((month) => (
|
||||
<CustomSelect.Option key={month.label} value={month.value}>
|
||||
{month.label}
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full select-none items-center rounded px-1 py-1.5 text-custom-text-200 hover:bg-custom-background-80"
|
||||
onClick={() => setmonthModal(true)}
|
||||
>
|
||||
Customise Time Range
|
||||
</button>
|
||||
</>
|
||||
</CustomSelect>
|
||||
{projectDetails ? (
|
||||
projectDetails.close_in !== 0 && (
|
||||
<div className="ml-12">
|
||||
<div className="flex flex-col rounded bg-custom-background-90 border border-custom-border-200">
|
||||
<div className="flex items-center justify-between px-5 py-4 gap-2 w-full">
|
||||
<div className="w-1/2 text-sm font-medium">Auto-close issues that are inactive for</div>
|
||||
<div className="w-1/2">
|
||||
<CustomSelect
|
||||
value={projectDetails?.close_in}
|
||||
label={`${projectDetails?.close_in} ${projectDetails?.close_in === 1 ? "Month" : "Months"}`}
|
||||
onChange={(val: number) => {
|
||||
handleChange({ close_in: val });
|
||||
}}
|
||||
input
|
||||
width="w-full"
|
||||
disabled={userRole !== 20}
|
||||
>
|
||||
<>
|
||||
{PROJECT_AUTOMATION_MONTHS.map((month) => (
|
||||
<CustomSelect.Option key={month.label} value={month.value}>
|
||||
{month.label}
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full select-none items-center rounded px-1 py-1.5 text-custom-text-200 hover:bg-custom-background-80"
|
||||
onClick={() => setmonthModal(true)}
|
||||
>
|
||||
Customise Time Range
|
||||
</button>
|
||||
</>
|
||||
</CustomSelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between px-5 py-4 gap-2 w-full">
|
||||
<div className="w-1/2 text-sm font-medium">Auto-close Status</div>
|
||||
<div className="w-1/2 ">
|
||||
<CustomSearchSelect
|
||||
value={projectDetails?.default_state ? projectDetails?.default_state : defaultState}
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedOption ? (
|
||||
<StateGroupIcon
|
||||
stateGroup={selectedOption.group}
|
||||
color={selectedOption.color}
|
||||
height="16px"
|
||||
width="16px"
|
||||
/>
|
||||
) : currentDefaultState ? (
|
||||
<StateGroupIcon
|
||||
stateGroup={currentDefaultState.group}
|
||||
color={currentDefaultState.color}
|
||||
height="16px"
|
||||
width="16px"
|
||||
/>
|
||||
) : (
|
||||
<DoubleCircleIcon className="h-3.5 w-3.5 text-custom-text-200" />
|
||||
)}
|
||||
{selectedOption?.name
|
||||
? selectedOption.name
|
||||
: currentDefaultState?.name ?? <span className="text-custom-text-200">State</span>}
|
||||
</div>
|
||||
}
|
||||
onChange={(val: string) => {
|
||||
handleChange({ default_state: val });
|
||||
}}
|
||||
options={options}
|
||||
disabled={!multipleOptions}
|
||||
width="w-full"
|
||||
input
|
||||
/>
|
||||
<div className="flex items-center justify-between px-5 py-4 gap-2 w-full">
|
||||
<div className="w-1/2 text-sm font-medium">Auto-close Status</div>
|
||||
<div className="w-1/2 ">
|
||||
<CustomSearchSelect
|
||||
value={projectDetails?.default_state ?? defaultState}
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedOption ? (
|
||||
<StateGroupIcon
|
||||
stateGroup={selectedOption.group}
|
||||
color={selectedOption.color}
|
||||
height="16px"
|
||||
width="16px"
|
||||
/>
|
||||
) : currentDefaultState ? (
|
||||
<StateGroupIcon
|
||||
stateGroup={currentDefaultState.group}
|
||||
color={currentDefaultState.color}
|
||||
height="16px"
|
||||
width="16px"
|
||||
/>
|
||||
) : (
|
||||
<DoubleCircleIcon className="h-3.5 w-3.5 text-custom-text-200" />
|
||||
)}
|
||||
{selectedOption?.name
|
||||
? selectedOption.name
|
||||
: currentDefaultState?.name ?? <span className="text-custom-text-200">State</span>}
|
||||
</div>
|
||||
}
|
||||
onChange={(val: string) => {
|
||||
handleChange({ default_state: val });
|
||||
}}
|
||||
options={options}
|
||||
disabled={!multipleOptions}
|
||||
width="w-full"
|
||||
input
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<Loader className="ml-12">
|
||||
<Loader.Item height="50px" />
|
||||
</Loader>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -41,7 +41,7 @@ export const CyclePeekOverview: React.FC<Props> = observer(({ projectId, workspa
|
||||
{peekCycle && (
|
||||
<div
|
||||
ref={ref}
|
||||
className="flex flex-col gap-3.5 h-full w-[24rem] z-10 overflow-y-auto border-l border-custom-border-100 bg-custom-sidebar-background-100 px-6 py-3.5 duration-300 flex-shrink-0"
|
||||
className="flex flex-col gap-3.5 h-full w-[24rem] overflow-y-auto border-l border-custom-border-100 bg-custom-sidebar-background-100 px-6 py-3.5 duration-300 flex-shrink-0"
|
||||
style={{
|
||||
boxShadow:
|
||||
"0px 1px 4px 0px rgba(0, 0, 0, 0.06), 0px 2px 4px 0px rgba(16, 24, 40, 0.06), 0px 1px 8px -1px rgba(16, 24, 40, 0.06)",
|
||||
|
@ -41,7 +41,7 @@ export const ModulePeekOverview: React.FC<Props> = observer(({ projectId, worksp
|
||||
{peekModule && (
|
||||
<div
|
||||
ref={ref}
|
||||
className="flex flex-col gap-3.5 h-full w-[24rem] z-10 overflow-y-auto border-l border-custom-border-100 bg-custom-sidebar-background-100 px-6 py-3.5 duration-300 flex-shrink-0"
|
||||
className="flex flex-col gap-3.5 h-full w-[24rem] overflow-y-auto border-l border-custom-border-100 bg-custom-sidebar-background-100 px-6 py-3.5 duration-300 flex-shrink-0"
|
||||
style={{
|
||||
boxShadow:
|
||||
"0px 1px 4px 0px rgba(0, 0, 0, 0.06), 0px 2px 4px 0px rgba(16, 24, 40, 0.06), 0px 1px 8px -1px rgba(16, 24, 40, 0.06)",
|
||||
|
@ -58,7 +58,7 @@ const CycleDetailPage: NextPageWithLayout = () => {
|
||||
</div>
|
||||
{cycleId && !isSidebarCollapsed && (
|
||||
<div
|
||||
className="flex flex-col gap-3.5 h-full w-[24rem] z-10 overflow-y-auto border-l border-custom-border-100 bg-custom-sidebar-background-100 px-6 py-3.5 duration-300 flex-shrink-0"
|
||||
className="flex flex-col gap-3.5 h-full w-[24rem] overflow-y-auto border-l border-custom-border-100 bg-custom-sidebar-background-100 px-6 py-3.5 duration-300 flex-shrink-0"
|
||||
style={{
|
||||
boxShadow:
|
||||
"0px 1px 4px 0px rgba(0, 0, 0, 0.06), 0px 2px 4px 0px rgba(16, 24, 40, 0.06), 0px 1px 8px -1px rgba(16, 24, 40, 0.06)",
|
||||
|
@ -58,7 +58,7 @@ const ModuleIssuesPage: NextPageWithLayout = () => {
|
||||
</div>
|
||||
{moduleId && !isSidebarCollapsed && (
|
||||
<div
|
||||
className="flex flex-col gap-3.5 h-full w-[24rem] z-10 overflow-y-auto border-l border-custom-border-100 bg-custom-sidebar-background-100 px-6 py-3.5 duration-300 flex-shrink-0"
|
||||
className="flex flex-col gap-3.5 h-full w-[24rem] overflow-y-auto border-l border-custom-border-100 bg-custom-sidebar-background-100 px-6 py-3.5 duration-300 flex-shrink-0"
|
||||
style={{
|
||||
boxShadow:
|
||||
"0px 1px 4px 0px rgba(0, 0, 0, 0.06), 0px 2px 4px 0px rgba(16, 24, 40, 0.06), 0px 1px 8px -1px rgba(16, 24, 40, 0.06)",
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { ReactElement } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import useSWR from "swr";
|
||||
// services
|
||||
import { ProjectService } from "services/project";
|
||||
// layouts
|
||||
@ -17,7 +17,7 @@ import { ProjectSettingHeader } from "components/headers";
|
||||
import { NextPageWithLayout } from "types/app";
|
||||
import { IProject } from "types";
|
||||
// constant
|
||||
import { PROJECTS_LIST, PROJECT_DETAILS, USER_PROJECT_VIEW } from "constants/fetch-keys";
|
||||
import { USER_PROJECT_VIEW } from "constants/fetch-keys";
|
||||
|
||||
// services
|
||||
const projectService = new ProjectService();
|
||||
@ -41,18 +41,6 @@ const AutomationSettingsPage: NextPageWithLayout = () => {
|
||||
const handleChange = async (formData: Partial<IProject>) => {
|
||||
if (!workspaceSlug || !projectId || !projectDetails) return;
|
||||
|
||||
mutate<IProject>(
|
||||
PROJECT_DETAILS(projectId as string),
|
||||
(prevData) => ({ ...(prevData as IProject), ...formData }),
|
||||
false
|
||||
);
|
||||
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }),
|
||||
(prevData) => (prevData ?? []).map((p) => (p.id === projectDetails.id ? { ...p, ...formData } : p)),
|
||||
false
|
||||
);
|
||||
|
||||
await projectService
|
||||
.updateProject(workspaceSlug as string, projectId as string, formData, user)
|
||||
.then(() => {})
|
||||
@ -72,8 +60,8 @@ const AutomationSettingsPage: NextPageWithLayout = () => {
|
||||
<div className="flex items-center py-3.5 border-b border-custom-border-100">
|
||||
<h3 className="text-xl font-medium">Automations</h3>
|
||||
</div>
|
||||
<AutoArchiveAutomation projectDetails={projectDetails} handleChange={handleChange} disabled={!isAdmin} />
|
||||
<AutoCloseAutomation projectDetails={projectDetails} handleChange={handleChange} disabled={!isAdmin} />
|
||||
<AutoArchiveAutomation handleChange={handleChange} />
|
||||
<AutoCloseAutomation handleChange={handleChange} />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user