Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/constants/src/issue/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./common";
export * from "./filter";
export * from "./layout";
export * from "./modal";
19 changes: 19 additions & 0 deletions packages/constants/src/issue/modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// plane imports
import { TIssue } from "@plane/types";

export const DEFAULT_WORK_ITEM_FORM_VALUES: Partial<TIssue> = {
project_id: "",
type_id: null,
name: "",
description_html: "",
estimate_point: null,
state_id: "",
parent_id: null,
priority: "none",
assignee_ids: [],
label_ids: [],
cycle_id: null,
module_ids: null,
start_date: null,
target_date: null,
};
1 change: 1 addition & 0 deletions packages/types/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ export * from "./epics";
export * from "./charts";
export * from "./home";
export * from "./stickies";
export * from "./utils";
5 changes: 5 additions & 0 deletions packages/types/src/utils.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type PartialDeep<K> = {
[attr in keyof K]?: K[attr] extends object ? PartialDeep<K[attr]> : K[attr];
};

export type CompleteOrEmpty<T> = T | Record<string, never>;
34 changes: 34 additions & 0 deletions packages/utils/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,37 @@ import { twMerge } from "tailwind-merge";
export const getSupportEmail = (defaultEmail: string = ""): string => defaultEmail;

export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));

/**
* Extracts IDs from an array of objects with ID property
*/
export const extractIds = <T extends { id: string }>(items: T[]): string[] => items.map((item) => item.id);

/**
* Checks if an ID exists and is valid within the provided list
*/
export const isValidId = (id: string | null | undefined, validIds: string[]): boolean => !!id && validIds.includes(id);

/**
* Filters an array to only include valid IDs
*/
export const filterValidIds = (ids: string[], validIds: string[]): string[] =>
ids.filter((id) => validIds.includes(id));

/**
* Filters an array to include only valid IDs, returning both valid and invalid IDs
*/
export const partitionValidIds = (ids: string[], validIds: string[]): { valid: string[]; invalid: string[] } => {
const valid: string[] = [];
const invalid: string[] = [];

ids.forEach((id) => {
if (validIds.includes(id)) {
valid.push(id);
} else {
invalid.push(id);
}
});

return { valid, invalid };
};
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from "./state";
export * from "./string";
export * from "./theme";
export * from "./workspace";
export * from "./work-item";
1 change: 1 addition & 0 deletions packages/utils/src/work-item/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./modal";
33 changes: 33 additions & 0 deletions packages/utils/src/work-item/modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// plane imports
import { DEFAULT_WORK_ITEM_FORM_VALUES } from "@plane/constants";
import { IPartialProject, ISearchIssueResponse, IState, TIssue } from "@plane/types";

export const getUpdateFormDataForReset = (projectId: string | null | undefined, formData: Partial<TIssue>) => ({
...DEFAULT_WORK_ITEM_FORM_VALUES,
project_id: projectId,
name: formData.name,
description_html: formData.description_html,
priority: formData.priority,
start_date: formData.start_date,
target_date: formData.target_date,
});

export const convertWorkItemDataToSearchResponse = (
workspaceSlug: string,
workItem: TIssue,
project: IPartialProject | undefined,
state: IState | undefined
): ISearchIssueResponse => ({
id: workItem.id,
name: workItem.name,
project_id: workItem.project_id ?? "",
project__identifier: project?.identifier ?? "",
project__name: project?.name ?? "",
sequence_id: workItem.sequence_id,
type_id: workItem.type_id ?? "",
state__color: state?.color ?? "",
start_date: workItem.start_date,
state__group: state?.group ?? "backlog",
state__name: state?.name ?? "",
workspace__slug: workspaceSlug,
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FC, ReactNode } from "react";
// components
import { AppHeader } from "@/components/core";
// local components
import { ProjectSettingHeader } from "./header";
import { ProjectSettingHeader } from "../header";
import { ProjectSettingsSidebar } from "./sidebar";

export interface IProjectSettingLayout {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { AppHeader } from "@/components/core";
import { useUserPermissions } from "@/hooks/store";
// plane web constants
// local components
import { WorkspaceSettingHeader } from "./header";
import { WorkspaceSettingHeader } from "../header";
import { MobileWorkspaceSettingsTabs } from "./mobile-header-tabs";
import { WorkspaceSettingsSidebar } from "./sidebar";

Expand Down
2 changes: 2 additions & 0 deletions web/ce/components/issues/issue-modal/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from "./provider";
export * from "./issue-type-select";
export * from "./additional-properties";
export * from "./template-select";

3 changes: 3 additions & 0 deletions web/ce/components/issues/issue-modal/issue-type-select.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Control } from "react-hook-form";
// plane imports
import { EditorRefApi } from "@plane/editor";
// types
import { TBulkIssueProperties, TIssue } from "@plane/types";

Expand All @@ -9,6 +11,7 @@ export type TIssueTypeDropdownVariant = "xs" | "sm";
export type TIssueTypeSelectProps<T extends Partial<TIssueFields>> = {
control: Control<T>;
projectId: string | null;
editorRef?: React.MutableRefObject<EditorRefApi | null>;
disabled?: boolean;
variant?: TIssueTypeDropdownVariant;
placeholder?: string;
Expand Down
19 changes: 17 additions & 2 deletions web/ce/components/issues/issue-modal/provider.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import React from "react";
import React, { useState } from "react";
import { observer } from "mobx-react-lite";
// plane imports
import { ISearchIssueResponse } from "@plane/types";
// components
import { IssueModalContext } from "@/components/issues";

type TIssueModalProviderProps = {
export type TIssueModalProviderProps = {
templateId?: string;
children: React.ReactNode;
};

export const IssueModalProvider = observer((props: TIssueModalProviderProps) => {
const { children } = props;
// states
const [selectedParentIssue, setSelectedParentIssue] = useState<ISearchIssueResponse | null>(null);

return (
<IssueModalContext.Provider
value={{
workItemTemplateId: null,
setWorkItemTemplateId: () => {},
isApplyingTemplate: false,
setIsApplyingTemplate: () => {},
selectedParentIssue,
setSelectedParentIssue,
issuePropertyValues: {},
setIssuePropertyValues: () => {},
issuePropertyValueErrors: {},
Expand All @@ -20,6 +32,9 @@ export const IssueModalProvider = observer((props: TIssueModalProviderProps) =>
getActiveAdditionalPropertiesLength: () => 0,
handlePropertyValuesValidation: () => true,
handleCreateUpdatePropertyValues: () => Promise.resolve(),
handleParentWorkItemDetails: () => Promise.resolve(undefined),
handleProjectEntitiesFetch: () => Promise.resolve(),
handleTemplateChange: () => Promise.resolve(),
Comment on lines +35 to +37
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Empty promise-returning handler methods

The handler methods return resolved promises but don't implement any functionality. These should be properly implemented to support the component's intended behavior.

-        handleParentWorkItemDetails: () => Promise.resolve(undefined),
-        handleProjectEntitiesFetch: () => Promise.resolve(),
-        handleTemplateChange: () => Promise.resolve(),
+        handleParentWorkItemDetails: async (workspaceSlug, projectId, issueId) => {
+          // Implement parent work item details fetching logic
+          return undefined;
+        },
+        handleProjectEntitiesFetch: async (workspaceSlug, projectId) => {
+          // Implement project entities fetching logic
+        },
+        handleTemplateChange: async (templateId) => {
+          // Implement template change logic
+        },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
handleParentWorkItemDetails: () => Promise.resolve(undefined),
handleProjectEntitiesFetch: () => Promise.resolve(),
handleTemplateChange: () => Promise.resolve(),
handleParentWorkItemDetails: async (workspaceSlug, projectId, issueId) => {
// Implement parent work item details fetching logic
return undefined;
},
handleProjectEntitiesFetch: async (workspaceSlug, projectId) => {
// Implement project entities fetching logic
},
handleTemplateChange: async (templateId) => {
// Implement template change logic
},

}}
>
{children}
Expand Down
15 changes: 15 additions & 0 deletions web/ce/components/issues/issue-modal/template-select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export type TWorkItemTemplateDropdownSize = "xs" | "sm";

export type TWorkItemTemplateSelect = {
projectId: string | null;
typeId: string | null;
disabled?: boolean;
size?: TWorkItemTemplateDropdownSize;
placeholder?: string;
renderChevron?: boolean;
dropDownContainerClassName?: string;
handleFormChange?: () => void;
};

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const WorkItemTemplateSelect = (props: TWorkItemTemplateSelect) => <></>;
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const ChangeIssueAssignee: React.FC<Props> = observer((props) => {
} = useMember();
// derived values
const projectId = issue?.project_id ?? "";
const projectMemberIds = getProjectMemberIds(projectId);
const projectMemberIds = getProjectMemberIds(projectId, false);

const options =
projectMemberIds
Expand Down
6 changes: 5 additions & 1 deletion web/core/components/dropdowns/member/member-options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ export const MemberOptions: React.FC<Props> = observer((props: Props) => {
}
}, [isOpen, isMobile]);

const memberIds = propsMemberIds ? propsMemberIds : projectId ? getProjectMemberIds(projectId) : workspaceMemberIds;
const memberIds = propsMemberIds
? propsMemberIds
: projectId
? getProjectMemberIds(projectId, true)
: workspaceMemberIds;
const onOpen = () => {
if (!memberIds && workspaceSlug && projectId) fetchProjectMembers(workspaceSlug.toString(), projectId);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Props = {
secondaryButton?: ButtonConfig;
customPrimaryButton?: React.ReactNode;
customSecondaryButton?: React.ReactNode;
className?: string;
};

const sizeClasses = {
Expand Down Expand Up @@ -66,12 +67,18 @@ export const DetailedEmptyState: React.FC<Props> = observer((props) => {
customPrimaryButton,
customSecondaryButton,
assetPath,
className,
} = props;

const hasButtons = primaryButton || secondaryButton || customPrimaryButton || customSecondaryButton;

return (
<div className="flex items-center justify-center min-h-full min-w-full overflow-y-auto py-10 md:px-20 px-5">
<div
className={cn(
"flex items-center justify-center min-h-full min-w-full overflow-y-auto py-10 md:px-20 px-5",
className
)}
>
<div className={cn("flex flex-col gap-5", sizeClasses[size])}>
<div className="flex flex-col gap-1.5 flex-shrink">
<h3 className={cn("text-xl font-semibold", { "font-medium": !description })}>{title}</h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import React from "react";
import { observer } from "mobx-react";
import { Control, Controller, FieldErrors } from "react-hook-form";
import { Control, Controller, FormState } from "react-hook-form";
// plane imports
import { ETabIndices } from "@plane/constants";
// types
Expand All @@ -18,12 +18,17 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
type TIssueTitleInputProps = {
control: Control<TIssue>;
issueTitleRef: React.MutableRefObject<HTMLInputElement | null>;
errors: FieldErrors<TIssue>;
formState: FormState<TIssue>;
handleFormChange: () => void;
};

export const IssueTitleInput: React.FC<TIssueTitleInputProps> = observer((props) => {
const { control, issueTitleRef, errors, handleFormChange } = props;
const {
control,
issueTitleRef,
formState: { errors },
handleFormChange,
} = props;
// store hooks
const { isMobile } = usePlatformOS();
const { t } = useTranslation();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import React, { createContext } from "react";
import { UseFormWatch } from "react-hook-form";
// types
import { TIssue } from "@plane/types";
// plane web types
import { TIssuePropertyValueErrors, TIssuePropertyValues } from "@/plane-web/types";
import { createContext } from "react";
// ce imports
import { TIssueFields } from "ce/components/issues";
// react-hook-form
import { UseFormReset, UseFormWatch } from "react-hook-form";
// plane imports
import { EditorRefApi } from "@plane/editor";
import { ISearchIssueResponse, TIssue } from "@plane/types";
import { TIssuePropertyValues, TIssuePropertyValueErrors } from "@/plane-web/types/issue-types";

export type TPropertyValuesValidationProps = {
projectId: string | null;
workspaceSlug: string;
watch: UseFormWatch<TIssue>;
watch: UseFormWatch<TIssueFields>;
};

export type TActiveAdditionalPropertiesProps = {
projectId: string | null;
workspaceSlug: string;
watch: UseFormWatch<TIssue>;
watch: UseFormWatch<TIssueFields>;
};

export type TCreateUpdatePropertyValuesProps = {
Expand All @@ -25,7 +28,31 @@ export type TCreateUpdatePropertyValuesProps = {
isDraft?: boolean;
};

export type THandleTemplateChangeProps = {
workspaceSlug: string;
reset: UseFormReset<TIssue>;
editorRef: React.MutableRefObject<EditorRefApi | null>;
};

export type THandleProjectEntitiesFetchProps = {
workspaceSlug: string;
templateId: string;
};

export type THandleParentWorkItemDetailsProps = {
workspaceSlug: string;
parentId: string | undefined;
parentProjectId: string | undefined;
isParentEpic: boolean;
};

export type TIssueModalContext = {
workItemTemplateId: string | null;
setWorkItemTemplateId: React.Dispatch<React.SetStateAction<string | null>>;
isApplyingTemplate: boolean;
setIsApplyingTemplate: React.Dispatch<React.SetStateAction<boolean>>;
selectedParentIssue: ISearchIssueResponse | null;
setSelectedParentIssue: React.Dispatch<React.SetStateAction<ISearchIssueResponse | null>>;
issuePropertyValues: TIssuePropertyValues;
setIssuePropertyValues: React.Dispatch<React.SetStateAction<TIssuePropertyValues>>;
issuePropertyValueErrors: TIssuePropertyValueErrors;
Expand All @@ -34,6 +61,9 @@ export type TIssueModalContext = {
getActiveAdditionalPropertiesLength: (props: TActiveAdditionalPropertiesProps) => number;
handlePropertyValuesValidation: (props: TPropertyValuesValidationProps) => boolean;
handleCreateUpdatePropertyValues: (props: TCreateUpdatePropertyValuesProps) => Promise<void>;
handleParentWorkItemDetails: (props: THandleParentWorkItemDetailsProps) => Promise<ISearchIssueResponse | undefined>;
handleProjectEntitiesFetch: (props: THandleProjectEntitiesFetchProps) => Promise<void>;
handleTemplateChange: (props: THandleTemplateChangeProps) => Promise<void>;
};

export const IssueModalContext = createContext<TIssueModalContext | undefined>(undefined);
Loading