mirror of
https://github.com/makeplane/plane.git
synced 2026-02-09 07:38:52 -06:00
[WEB-4300] improvement: add allowedProjectIds to create work item modal (#7195)
This commit is contained in:
@@ -39,6 +39,7 @@ export const IssueLevelModals: FC<TIssueLevelModalsProps> = observer((props) =>
|
||||
toggleDeleteIssueModal,
|
||||
isBulkDeleteIssueModalOpen,
|
||||
toggleBulkDeleteIssueModal,
|
||||
createWorkItemAllowedProjectIds,
|
||||
} = useCommandPalette();
|
||||
// derived values
|
||||
const issueDetails = issueId ? getIssueById(issueId) : undefined;
|
||||
@@ -80,6 +81,7 @@ export const IssueLevelModals: FC<TIssueLevelModalsProps> = observer((props) =>
|
||||
data={getCreateIssueModalData()}
|
||||
isDraft={isDraftIssue}
|
||||
onSubmit={handleCreateIssueSubmit}
|
||||
allowedProjectIds={createWorkItemAllowedProjectIds}
|
||||
/>
|
||||
{workspaceSlug && projectId && issueId && issueDetails && (
|
||||
<DeleteIssueModal
|
||||
|
||||
@@ -4,21 +4,29 @@ import { observer } from "mobx-react-lite";
|
||||
import { ISearchIssueResponse, TIssue } from "@plane/types";
|
||||
// components
|
||||
import { IssueModalContext } from "@/components/issues";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store/user/user-user";
|
||||
|
||||
export type TIssueModalProviderProps = {
|
||||
templateId?: string;
|
||||
dataForPreload?: Partial<TIssue>;
|
||||
allowedProjectIds?: string[];
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const IssueModalProvider = observer((props: TIssueModalProviderProps) => {
|
||||
const { children } = props;
|
||||
const { children, allowedProjectIds } = props;
|
||||
// states
|
||||
const [selectedParentIssue, setSelectedParentIssue] = useState<ISearchIssueResponse | null>(null);
|
||||
// store hooks
|
||||
const { projectsWithCreatePermissions } = useUser();
|
||||
// derived values
|
||||
const projectIdsWithCreatePermissions = Object.keys(projectsWithCreatePermissions ?? {});
|
||||
|
||||
return (
|
||||
<IssueModalContext.Provider
|
||||
value={{
|
||||
allowedProjectIds: allowedProjectIds ?? projectIdsWithCreatePermissions,
|
||||
workItemTemplateId: null,
|
||||
setWorkItemTemplateId: () => {},
|
||||
isApplyingTemplate: false,
|
||||
|
||||
@@ -14,8 +14,9 @@ import { DateRangeDropdown, ProjectDropdown } from "@/components/dropdowns";
|
||||
// constants
|
||||
// helpers
|
||||
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
import { shouldRenderProject } from "@/helpers/project.helper";
|
||||
import { getTabIndex } from "@/helpers/tab-indices.helper";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store/user/user-user";
|
||||
|
||||
type Props = {
|
||||
handleFormSubmit: (values: Partial<ICycle>, dirtyFields: any) => Promise<void>;
|
||||
@@ -36,7 +37,10 @@ const defaultValues: Partial<ICycle> = {
|
||||
|
||||
export const CycleForm: React.FC<Props> = (props) => {
|
||||
const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data, isMobile = false } = props;
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { projectsWithCreatePermissions } = useUser();
|
||||
// form data
|
||||
const {
|
||||
formState: { errors, isSubmitting, dirtyFields },
|
||||
@@ -75,12 +79,14 @@ export const CycleForm: React.FC<Props> = (props) => {
|
||||
<ProjectDropdown
|
||||
value={value}
|
||||
onChange={(val) => {
|
||||
onChange(val);
|
||||
setActiveProject(val);
|
||||
if (!Array.isArray(val)) {
|
||||
onChange(val);
|
||||
setActiveProject(val);
|
||||
}
|
||||
}}
|
||||
multiple={false}
|
||||
buttonVariant="border-with-text"
|
||||
renderCondition={(project) => shouldRenderProject(project)}
|
||||
renderCondition={(project) => !!projectsWithCreatePermissions?.[project.id]}
|
||||
tabIndex={getIndex("cover_image")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,12 @@ import { CreateIssueToastActionItems, IssuesModalProps } from "@/components/issu
|
||||
// constants
|
||||
// hooks
|
||||
import { useIssueModal } from "@/hooks/context/use-issue-modal";
|
||||
import { useEventTracker, useCycle, useIssues, useModule, useIssueDetail, useUser, useProject } from "@/hooks/store";
|
||||
import { useCycle } from "@/hooks/store/use-cycle";
|
||||
import { useEventTracker } from "@/hooks/store/use-event-tracker";
|
||||
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
|
||||
import { useIssues } from "@/hooks/store/use-issues";
|
||||
import { useModule } from "@/hooks/store/use-module";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||
import { useIssuesActions } from "@/hooks/use-issues-actions";
|
||||
// services
|
||||
@@ -59,14 +64,13 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||
const { t } = useTranslation();
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const { workspaceSlug, projectId: routerProjectId, cycleId, moduleId, workItem } = useParams();
|
||||
const { projectsWithCreatePermissions } = useUser();
|
||||
const { fetchCycleDetails } = useCycle();
|
||||
const { fetchModuleDetails } = useModule();
|
||||
const { issues } = useIssues(storeType);
|
||||
const { issues: projectIssues } = useIssues(EIssuesStoreType.PROJECT);
|
||||
const { issues: draftIssues } = useIssues(EIssuesStoreType.WORKSPACE_DRAFT);
|
||||
const { fetchIssue } = useIssueDetail();
|
||||
const { handleCreateUpdatePropertyValues } = useIssueModal();
|
||||
const { allowedProjectIds, handleCreateUpdatePropertyValues } = useIssueModal();
|
||||
const { getProjectByIdentifier } = useProject();
|
||||
// pathname
|
||||
const pathname = usePathname();
|
||||
@@ -76,7 +80,6 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||
const routerProjectIdentifier = workItem?.toString().split("-")[0];
|
||||
const projectIdFromRouter = getProjectByIdentifier(routerProjectIdentifier)?.id;
|
||||
const projectId = data?.project_id ?? routerProjectId?.toString() ?? projectIdFromRouter;
|
||||
const projectIdsWithCreatePermissions = Object.keys(projectsWithCreatePermissions ?? {});
|
||||
|
||||
const fetchIssueDetail = async (issueId: string | undefined) => {
|
||||
setDescription(undefined);
|
||||
@@ -114,10 +117,9 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||
return;
|
||||
}
|
||||
|
||||
// if data is not present, set active project to the project
|
||||
// in the url. This has the least priority.
|
||||
if (projectIdsWithCreatePermissions && projectIdsWithCreatePermissions.length > 0 && !activeProjectId)
|
||||
setActiveProjectId(projectId?.toString() ?? projectIdsWithCreatePermissions?.[0]);
|
||||
// if data is not present, set active project to the first project in the allowedProjectIds array
|
||||
if (allowedProjectIds && allowedProjectIds.length > 0 && !activeProjectId)
|
||||
setActiveProjectId(projectId?.toString() ?? allowedProjectIds?.[0]);
|
||||
|
||||
// clearing up the description state when we leave the component
|
||||
return () => setDescription(undefined);
|
||||
@@ -346,7 +348,7 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||
const handleDuplicateIssueModal = (value: boolean) => setIsDuplicateModalOpen(value);
|
||||
|
||||
// don't open the modal if there are no projects
|
||||
if (!projectIdsWithCreatePermissions || projectIdsWithCreatePermissions.length === 0 || !activeProjectId) return null;
|
||||
if (!allowedProjectIds || allowedProjectIds.length === 0 || !activeProjectId) return null;
|
||||
|
||||
const commonIssueModalProps: IssueFormProps = {
|
||||
issueTitleRef: issueTitleRef,
|
||||
|
||||
@@ -10,10 +10,9 @@ import { TIssue } from "@plane/types";
|
||||
// components
|
||||
import { ProjectDropdown } from "@/components/dropdowns";
|
||||
// helpers
|
||||
import { shouldRenderProject } from "@/helpers/project.helper";
|
||||
import { getTabIndex } from "@/helpers/tab-indices.helper";
|
||||
// store hooks
|
||||
import { useUser } from "@/hooks/store";
|
||||
// hooks
|
||||
import { useIssueModal } from "@/hooks/context/use-issue-modal";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
type TIssueProjectSelectProps = {
|
||||
@@ -25,8 +24,9 @@ type TIssueProjectSelectProps = {
|
||||
export const IssueProjectSelect: React.FC<TIssueProjectSelectProps> = observer((props) => {
|
||||
const { control, disabled = false, handleFormChange } = props;
|
||||
// store hooks
|
||||
const { projectsWithCreatePermissions } = useUser();
|
||||
const { isMobile } = usePlatformOS();
|
||||
// context hooks
|
||||
const { allowedProjectIds } = useIssueModal();
|
||||
|
||||
const { getIndex } = getTabIndex(ETabIndices.ISSUE_FORM, isMobile);
|
||||
|
||||
@@ -37,26 +37,22 @@ export const IssueProjectSelect: React.FC<TIssueProjectSelectProps> = observer((
|
||||
rules={{
|
||||
required: true,
|
||||
}}
|
||||
render={({ field: { value, onChange } }) =>
|
||||
projectsWithCreatePermissions && projectsWithCreatePermissions[value!] ? (
|
||||
<div className="h-7">
|
||||
<ProjectDropdown
|
||||
value={value}
|
||||
onChange={(projectId) => {
|
||||
onChange(projectId);
|
||||
handleFormChange();
|
||||
}}
|
||||
multiple={false}
|
||||
buttonVariant="border-with-text"
|
||||
renderCondition={(project) => shouldRenderProject(project)}
|
||||
tabIndex={getIndex("project_id")}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<div className="h-7">
|
||||
<ProjectDropdown
|
||||
value={value}
|
||||
onChange={(projectId) => {
|
||||
onChange(projectId);
|
||||
handleFormChange();
|
||||
}}
|
||||
multiple={false}
|
||||
buttonVariant="border-with-text"
|
||||
renderCondition={(project) => allowedProjectIds.includes(project.id)}
|
||||
tabIndex={getIndex("project_id")}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -47,6 +47,7 @@ export type THandleParentWorkItemDetailsProps = {
|
||||
};
|
||||
|
||||
export type TIssueModalContext = {
|
||||
allowedProjectIds: string[];
|
||||
workItemTemplateId: string | null;
|
||||
setWorkItemTemplateId: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
isApplyingTemplate: boolean;
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface IssuesModalProps {
|
||||
};
|
||||
isProjectSelectionDisabled?: boolean;
|
||||
templateId?: string;
|
||||
allowedProjectIds?: string[];
|
||||
}
|
||||
|
||||
export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((props) => {
|
||||
@@ -43,7 +44,11 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
||||
|
||||
if (!props.isOpen) return null;
|
||||
return (
|
||||
<IssueModalProvider templateId={props.templateId} dataForPreload={dataForPreload}>
|
||||
<IssueModalProvider
|
||||
templateId={props.templateId}
|
||||
dataForPreload={dataForPreload}
|
||||
allowedProjectIds={props.allowedProjectIds}
|
||||
>
|
||||
<CreateUpdateIssueModalBase {...props} />
|
||||
</IssueModalProvider>
|
||||
);
|
||||
|
||||
@@ -13,9 +13,9 @@ import { DateRangeDropdown, ProjectDropdown, MemberDropdown } from "@/components
|
||||
import { ModuleStatusSelect } from "@/components/modules";
|
||||
// helpers
|
||||
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
import { shouldRenderProject } from "@/helpers/project.helper";
|
||||
import { getTabIndex } from "@/helpers/tab-indices.helper";
|
||||
// types
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store/user/user-user";
|
||||
|
||||
type Props = {
|
||||
handleFormSubmit: (values: Partial<IModule>, dirtyFields: any) => Promise<void>;
|
||||
@@ -37,6 +37,8 @@ const defaultValues: Partial<IModule> = {
|
||||
|
||||
export const ModuleForm: React.FC<Props> = (props) => {
|
||||
const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data, isMobile = false } = props;
|
||||
// store hooks
|
||||
const { projectsWithCreatePermissions } = useUser();
|
||||
// form info
|
||||
const {
|
||||
formState: { errors, isSubmitting, dirtyFields },
|
||||
@@ -93,7 +95,7 @@ export const ModuleForm: React.FC<Props> = (props) => {
|
||||
}}
|
||||
multiple={false}
|
||||
buttonVariant="border-with-text"
|
||||
renderCondition={(project) => shouldRenderProject(project)}
|
||||
renderCondition={(project) => !!projectsWithCreatePermissions?.[project.id]}
|
||||
tabIndex={getIndex("cover_image")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface IBaseCommandPaletteStore {
|
||||
isDeleteIssueModalOpen: boolean;
|
||||
isBulkDeleteIssueModalOpen: boolean;
|
||||
createIssueStoreType: TCreateModalStoreTypes;
|
||||
createWorkItemAllowedProjectIds: string[] | undefined;
|
||||
allStickiesModal: boolean;
|
||||
projectListOpenMap: Record<string, boolean>;
|
||||
getIsProjectListOpen: (projectId: string) => boolean;
|
||||
@@ -36,7 +37,7 @@ export interface IBaseCommandPaletteStore {
|
||||
toggleCreateCycleModal: (value?: boolean) => void;
|
||||
toggleCreateViewModal: (value?: boolean) => void;
|
||||
toggleCreatePageModal: (value?: TCreatePageModal) => void;
|
||||
toggleCreateIssueModal: (value?: boolean, storeType?: TCreateModalStoreTypes) => void;
|
||||
toggleCreateIssueModal: (value?: boolean, storeType?: TCreateModalStoreTypes, allowedProjectIds?: string[]) => void;
|
||||
toggleCreateModuleModal: (value?: boolean) => void;
|
||||
toggleDeleteIssueModal: (value?: boolean) => void;
|
||||
toggleBulkDeleteIssueModal: (value?: boolean) => void;
|
||||
@@ -57,6 +58,7 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
|
||||
isBulkDeleteIssueModalOpen: boolean = false;
|
||||
createPageModal: TCreatePageModal = DEFAULT_CREATE_PAGE_MODAL_DATA;
|
||||
createIssueStoreType: TCreateModalStoreTypes = EIssuesStoreType.PROJECT;
|
||||
createWorkItemAllowedProjectIds: IBaseCommandPaletteStore["createWorkItemAllowedProjectIds"] = undefined;
|
||||
allStickiesModal: boolean = false;
|
||||
projectListOpenMap: Record<string, boolean> = {};
|
||||
|
||||
@@ -74,6 +76,7 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
|
||||
isBulkDeleteIssueModalOpen: observable.ref,
|
||||
createPageModal: observable,
|
||||
createIssueStoreType: observable,
|
||||
createWorkItemAllowedProjectIds: observable,
|
||||
allStickiesModal: observable,
|
||||
projectListOpenMap: observable,
|
||||
// projectPages: computed,
|
||||
@@ -214,13 +217,15 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
|
||||
* @param storeType
|
||||
* @returns
|
||||
*/
|
||||
toggleCreateIssueModal = (value?: boolean, storeType?: TCreateModalStoreTypes) => {
|
||||
toggleCreateIssueModal = (value?: boolean, storeType?: TCreateModalStoreTypes, allowedProjectIds?: string[]) => {
|
||||
if (value !== undefined) {
|
||||
this.isCreateIssueModalOpen = value;
|
||||
this.createIssueStoreType = storeType || EIssuesStoreType.PROJECT;
|
||||
this.createWorkItemAllowedProjectIds = allowedProjectIds ?? undefined;
|
||||
} else {
|
||||
this.isCreateIssueModalOpen = !this.isCreateIssueModalOpen;
|
||||
this.createIssueStoreType = EIssuesStoreType.PROJECT;
|
||||
this.createWorkItemAllowedProjectIds = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import sortBy from "lodash/sortBy";
|
||||
// types
|
||||
import { EUserPermissions } from "@plane/constants";
|
||||
import { TProjectDisplayFilters, TProjectFilters, TProjectOrderByOptions } from "@plane/types";
|
||||
// helpers
|
||||
import { getDate } from "@/helpers/date-time.helper";
|
||||
import { satisfiesDateFilter } from "@/helpers/filter.helper";
|
||||
// plane web constants
|
||||
// types
|
||||
// plane web imports
|
||||
import { TProject } from "@/plane-web/types";
|
||||
|
||||
/**
|
||||
@@ -49,14 +47,6 @@ export const orderJoinedProjects = (
|
||||
export const projectIdentifierSanitizer = (identifier: string): string =>
|
||||
identifier.replace(/[^ÇŞĞIİÖÜA-Za-z0-9]/g, "");
|
||||
|
||||
/**
|
||||
* @description Checks if the project should be rendered or not based on the user role
|
||||
* @param {TProject} project
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const shouldRenderProject = (project: TProject): boolean =>
|
||||
!!project.member_role && project.member_role >= EUserPermissions.MEMBER;
|
||||
|
||||
/**
|
||||
* @description filters projects based on the filter
|
||||
* @param {TProject} project
|
||||
|
||||
Reference in New Issue
Block a user