From 1d3c2ce996956e20a78071f621ef8abe35ae14ac Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Mon, 12 Aug 2024 20:55:36 +0530 Subject: [PATCH] Issue types properties improvement (#816) * chore: fix properties overflow in peek overview full screnn. * style: improve issue type enable/ disable UI and UX: * chore: merge create and update issue type modal. * chore: remove unwanted toast messages. * chore: handle options integrity error while creating/ updating property. * chore: empty options error validation. --- .../plane/ee/views/app/issue_property/base.py | 42 +++++++--- .../components/issues/peek-overview/view.tsx | 2 +- .../issue-types/common/issue-type-logo.tsx | 5 +- .../issue-types/create-update/index.ts | 3 +- .../{create-modal.tsx => modal.tsx} | 76 +++++++++++++----- .../create-update/update-modal.tsx | 79 ------------------- .../issue-types/issue-type-list-item.tsx | 36 ++++++--- .../issue-types/issue-types-list.tsx | 8 +- .../properties/attributes/dropdown.tsx | 6 +- .../attributes/options/create-option-item.tsx | 4 +- .../properties/attributes/options/option.tsx | 9 ++- .../properties/attributes/options/root.tsx | 5 +- .../dropdowns/property-attributes.tsx | 8 +- .../selected-attribute-properties.tsx | 5 +- .../properties/property-list-item.tsx | 14 +++- .../issue-types/properties/root.tsx | 1 - .../components/issue-types/quick-actions.tsx | 23 +++--- web/ee/components/issue-types/root.tsx | 19 ++++- .../components/issue-types/values/update.tsx | 32 +++----- .../components/issues/issue-modal/modal.tsx | 6 -- 20 files changed, 192 insertions(+), 191 deletions(-) rename web/ee/components/issue-types/create-update/{create-modal.tsx => modal.tsx} (55%) delete mode 100644 web/ee/components/issue-types/create-update/update-modal.tsx diff --git a/apiserver/plane/ee/views/app/issue_property/base.py b/apiserver/plane/ee/views/app/issue_property/base.py index 835a50be65..dd3d7d767d 100644 --- a/apiserver/plane/ee/views/app/issue_property/base.py +++ b/apiserver/plane/ee/views/app/issue_property/base.py @@ -194,11 +194,20 @@ class IssuePropertyEndpoint(BaseAPIView): # Check if the property type is option and create the options if issue_property.property_type == "OPTION": - self.create_options(issue_property, options) - self.update_property_default_options(issue_property) - # Reset the default options if the property is required - if issue_property.is_required: - self.reset_options_default(issue_property) + try: + self.create_options(issue_property, options) + self.update_property_default_options(issue_property) + # Reset the default options if the property is required + if issue_property.is_required: + self.reset_options_default(issue_property) + + except IntegrityError: + return Response( + { + "error": "An option with the same name already exists in this property", + }, + status=status.HTTP_409_CONFLICT, + ) serializer = IssuePropertySerializer(issue_property) # generate the response with the new data and options @@ -282,13 +291,22 @@ class IssuePropertyEndpoint(BaseAPIView): serializer.save() if issue_property.property_type == "OPTION": - self.handle_options_create_update( - issue_property, options, slug, project_id - ) - self.update_property_default_options(issue_property) - # Reset the default options if the property is required - if issue_property.is_required: - self.reset_options_default(issue_property) + try: + self.handle_options_create_update( + issue_property, options, slug, project_id + ) + self.update_property_default_options(issue_property) + # Reset the default options if the property is required + if issue_property.is_required: + self.reset_options_default(issue_property) + + except IntegrityError: + return Response( + { + "error": "An option with the same name already exists in this property", + }, + status=status.HTTP_409_CONFLICT, + ) response = { **serializer.data, diff --git a/web/core/components/issues/peek-overview/view.tsx b/web/core/components/issues/peek-overview/view.tsx index 617c36dfa1..b11f82cd00 100644 --- a/web/core/components/issues/peek-overview/view.tsx +++ b/web/core/components/issues/peek-overview/view.tsx @@ -241,7 +241,7 @@ export const IssueView: FC = observer((props) => {
diff --git a/web/ee/components/issue-types/common/issue-type-logo.tsx b/web/ee/components/issue-types/common/issue-type-logo.tsx index 317f9ec982..38c75e795b 100644 --- a/web/ee/components/issue-types/common/issue-type-logo.tsx +++ b/web/ee/components/issue-types/common/issue-type-logo.tsx @@ -3,6 +3,7 @@ import React, { FC } from "react"; import { TLogoProps } from "@plane/types"; // ui import { LayersIcon, LUCIDE_ICONS_LIST } from "@plane/ui"; +// helpers import { cn } from "@/helpers/common.helper"; type Props = { @@ -37,7 +38,7 @@ export const IssueTypeLogo: FC = (props) => { width={size} height={size} style={{ - color: icon_props?.color, + color: icon_props?.color ?? "#ffffff", }} /> ) : ( @@ -45,7 +46,7 @@ export const IssueTypeLogo: FC = (props) => { style={{ height: size, width: size, - color: icon_props?.color, + color: icon_props?.color ?? "#ffffff", }} /> )} diff --git a/web/ee/components/issue-types/create-update/index.ts b/web/ee/components/issue-types/create-update/index.ts index c4b2dc4015..843778182f 100644 --- a/web/ee/components/issue-types/create-update/index.ts +++ b/web/ee/components/issue-types/create-update/index.ts @@ -1,3 +1,2 @@ export * from "./form"; -export * from "./create-modal"; -export * from "./update-modal"; +export * from "./modal"; diff --git a/web/ee/components/issue-types/create-update/create-modal.tsx b/web/ee/components/issue-types/create-update/modal.tsx similarity index 55% rename from web/ee/components/issue-types/create-update/create-modal.tsx rename to web/ee/components/issue-types/create-update/modal.tsx index 78ca24faa7..458fd9cf86 100644 --- a/web/ee/components/issue-types/create-update/create-modal.tsx +++ b/web/ee/components/issue-types/create-update/modal.tsx @@ -5,47 +5,54 @@ import { EModalPosition, EModalWidth, ModalCore, setToast, TOAST_TYPE } from "@p import { getRandomIconName } from "@/helpers/emoji.helper"; // plane web components import { CreateOrUpdateIssueTypeForm } from "@/plane-web/components/issue-types/"; -// hooks +// plane web helpers import { getRandomBackgroundColor } from "@/plane-web/helpers/issue-type.helper"; -import { useIssueTypes } from "@/plane-web/hooks/store"; +// plane web +import { useIssueType, useIssueTypes } from "@/plane-web/hooks/store"; // plane web types import { TIssueType } from "@/plane-web/types"; -// plane web helpers type Props = { + issueTypeId: string | null; isModalOpen: boolean; handleModalClose: () => void; }; -export const defaultIssueTypeData: Partial = { +const defaultIssueTypeData: Partial = { id: undefined, name: "", description: "", }; -export const CreateIssueTypeModal: FC = (props) => { - const { isModalOpen, handleModalClose } = props; +export const CreateOrUpdateIssueTypeModal: FC = (props) => { + const { issueTypeId, isModalOpen, handleModalClose } = props; // states const [isSubmitting, setIsSubmitting] = useState(false); - const [issueTypeFormData, setIssueTypeFormData] = useState>(defaultIssueTypeData); + const [issueTypeFormData, setIssueTypeFormData] = useState | undefined>(undefined); // store hooks const { createType } = useIssueTypes(); + const issueType = useIssueType(issueTypeId); + // derived values + const issueTypeDetail = issueType?.asJSON; useEffect(() => { if (isModalOpen) { - setIssueTypeFormData({ - ...defaultIssueTypeData, - logo_props: { - in_use: "icon", - icon: { - name: getRandomIconName(), - color: "#ffffff", - background_color: getRandomBackgroundColor(), + if (issueTypeDetail) { + setIssueTypeFormData(issueTypeDetail); + } else { + setIssueTypeFormData({ + ...defaultIssueTypeData, + logo_props: { + in_use: "icon", + icon: { + name: getRandomIconName(), + background_color: getRandomBackgroundColor(), + }, }, - }, - }); + }); + } } - }, [isModalOpen]); + }, [issueTypeDetail, isModalOpen]); // handlers const handleFormDataChange = (key: T, value: TIssueType[T]) => @@ -56,7 +63,8 @@ export const CreateIssueTypeModal: FC = (props) => { handleModalClose(); }; - const handleFormSubmit = async () => { + const handleCreateIssueType = async () => { + if (!issueTypeFormData) return; setIsSubmitting(true); await createType(issueTypeFormData) .then(() => { @@ -79,6 +87,32 @@ export const CreateIssueTypeModal: FC = (props) => { }); }; + const handleUpdateIssueType = async () => { + if (!issueTypeFormData) return; + + setIsSubmitting(true); + await issueType + ?.updateType(issueTypeFormData) + .then(() => { + handleModalClearAndClose(); + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Success!", + message: `Issue type ${issueTypeFormData?.name} updated successfully.`, + }); + }) + .catch(() => { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: `Failed to update issue type. Please try again!`, + }); + }) + .finally(() => { + setIsSubmitting(false); + }); + }; + if (!isModalOpen) return null; return ( @@ -89,11 +123,11 @@ export const CreateIssueTypeModal: FC = (props) => { width={EModalWidth.XXL} > ); diff --git a/web/ee/components/issue-types/create-update/update-modal.tsx b/web/ee/components/issue-types/create-update/update-modal.tsx deleted file mode 100644 index 6a93463d62..0000000000 --- a/web/ee/components/issue-types/create-update/update-modal.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { FC, useEffect, useState } from "react"; -// ui -import { EModalPosition, EModalWidth, ModalCore, setToast, TOAST_TYPE } from "@plane/ui"; -// plane web components -import { CreateOrUpdateIssueTypeForm, defaultIssueTypeData } from "@/plane-web/components/issue-types/"; -// hooks -import { useIssueType } from "@/plane-web/hooks/store"; -// plane web types -import { TIssueType } from "@/plane-web/types"; - -type Props = { - data: Partial; - isModalOpen: boolean; - handleModalClose: () => void; -}; - -export const UpdateIssueTypeModal: FC = (props) => { - const { data, isModalOpen, handleModalClose } = props; - // states - const [isSubmitting, setIsSubmitting] = useState(false); - const [issueTypeFormData, setIssueTypeFormData] = useState | undefined>(undefined); - // store hooks - const issueType = useIssueType(data?.id); - - useEffect(() => { - if (isModalOpen) { - setIssueTypeFormData(data); - } - }, [data, isModalOpen]); - - // handlers - const handleFormDataChange = (key: T, value: TIssueType[T]) => - setIssueTypeFormData((prev) => ({ ...prev, [key]: value })); - - const handleFormSubmit = async () => { - if (!issueTypeFormData) return; - - setIsSubmitting(true); - await issueType - ?.updateType(issueTypeFormData) - .then(() => { - handleModalClose(); - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: `Issue type ${issueTypeFormData?.name} updated successfully.`, - }); - }) - .catch(() => { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: `Failed to update issue type. Please try again!`, - }); - }) - .finally(() => { - setIsSubmitting(false); - }); - }; - - if (!isModalOpen) return null; - - return ( - - - - ); -}; diff --git a/web/ee/components/issue-types/issue-type-list-item.tsx b/web/ee/components/issue-types/issue-type-list-item.tsx index 4aa92efb1d..88724295bb 100644 --- a/web/ee/components/issue-types/issue-type-list-item.tsx +++ b/web/ee/components/issue-types/issue-type-list-item.tsx @@ -13,10 +13,11 @@ type TIssueTypeListItem = { issueTypeId: string; isOpen: boolean; onToggle: (issueTypeId: string) => void; + onEditIssueTypeIdChange: (issueTypeId: string) => void; }; export const IssueTypeListItem = observer((props: TIssueTypeListItem) => { - const { issueTypeId, isOpen, onToggle } = props; + const { issueTypeId, isOpen, onToggle, onEditIssueTypeIdChange } = props; // store hooks const issueType = useIssueType(issueTypeId); // derived values @@ -62,18 +63,27 @@ export const IssueTypeListItem = observer((props: TIssueTypeListItem) => {
-
- {issueTypeDetail?.is_default && ( -
- Default -
- )} - {!issueTypeDetail?.is_active && ( -
- Disabled -
- )} - {!issueTypeDetail?.is_default && } +
+
+ {issueTypeDetail?.is_default && ( +
+ Default +
+ )} + {!issueTypeDetail?.is_active && ( +
+ Disabled +
+ )} +
+
+ {!issueTypeDetail?.is_default && ( + + )} +
} diff --git a/web/ee/components/issue-types/issue-types-list.tsx b/web/ee/components/issue-types/issue-types-list.tsx index 5f3ff13030..e04290aaee 100644 --- a/web/ee/components/issue-types/issue-types-list.tsx +++ b/web/ee/components/issue-types/issue-types-list.tsx @@ -8,7 +8,12 @@ import { IssueTypeListItem } from "@/plane-web/components/issue-types"; // plane web hooks import { useIssueTypes } from "@/plane-web/hooks/store"; -export const IssueTypesList = observer(() => { +type TIssueTypesList = { + onEditIssueTypeIdChange: (issueTypeId: string) => void; +}; + +export const IssueTypesList = observer((props: TIssueTypesList) => { + const { onEditIssueTypeIdChange } = props; // router const { projectId } = useParams(); // states @@ -48,6 +53,7 @@ export const IssueTypesList = observer(() => { (issueTypeId === currentProjectDefaultIssueType?.id && currentProjectIssueTypeIds.length === 1) } onToggle={handleIssueTypeListToggle} + onEditIssueTypeIdChange={onEditIssueTypeIdChange} /> ))} diff --git a/web/ee/components/issue-types/properties/attributes/dropdown.tsx b/web/ee/components/issue-types/properties/attributes/dropdown.tsx index b2b6a6b209..ffd19715be 100644 --- a/web/ee/components/issue-types/properties/attributes/dropdown.tsx +++ b/web/ee/components/issue-types/properties/attributes/dropdown.tsx @@ -5,6 +5,7 @@ import { IssuePropertyOptionsRoot, PropertyMultiSelect, PropertySettingsConfiguration, + TIssuePropertyFormError, } from "@/plane-web/components/issue-types/properties"; // plane web constants import { ISSUE_PROPERTY_SETTINGS_CONFIGURATIONS } from "@/plane-web/constants/issue-properties"; @@ -22,10 +23,11 @@ type TDropdownAttributesProps = { value: TIssueProperty[K], shouldSync?: boolean ) => void; + error?: TIssuePropertyFormError; }; export const DropdownAttributes = observer((props: TDropdownAttributesProps) => { - const { issueTypeId, dropdownPropertyDetail, currentOperationMode, onDropdownDetailChange } = props; + const { issueTypeId, dropdownPropertyDetail, currentOperationMode, onDropdownDetailChange, error } = props; // store hooks const issueType = useIssueType(issueTypeId); // derived values @@ -65,7 +67,7 @@ export const DropdownAttributes = observer((props: TDropdownAttributesProps) => ))} )} - +
Default • Optional
diff --git a/web/ee/components/issue-types/properties/attributes/options/create-option-item.tsx b/web/ee/components/issue-types/properties/attributes/options/create-option-item.tsx index 1219ccb4b4..e6b4131564 100644 --- a/web/ee/components/issue-types/properties/attributes/options/create-option-item.tsx +++ b/web/ee/components/issue-types/properties/attributes/options/create-option-item.tsx @@ -8,6 +8,7 @@ import { TIssuePropertyOptionCreateUpdateData } from "@/plane-web/types"; type TIssuePropertyCreateOptionItem = { propertyOptionCreateListData: TIssuePropertyOptionCreateUpdateData; updateCreateListData: (value: TIssuePropertyOptionCreateUpdateData) => void; + error?: string; }; export const IssuePropertyCreateOptionItem = observer( @@ -15,13 +16,14 @@ export const IssuePropertyCreateOptionItem = observer( props: TIssuePropertyCreateOptionItem, ref: React.Ref ) { - const { propertyOptionCreateListData, updateCreateListData } = props; + const { propertyOptionCreateListData, updateCreateListData, error} = props; return (
); diff --git a/web/ee/components/issue-types/properties/attributes/options/option.tsx b/web/ee/components/issue-types/properties/attributes/options/option.tsx index 8922f96adc..474e6bab0a 100644 --- a/web/ee/components/issue-types/properties/attributes/options/option.tsx +++ b/web/ee/components/issue-types/properties/attributes/options/option.tsx @@ -11,20 +11,21 @@ type TIssuePropertyOptionItem = { optionId?: string; propertyOptionData: TIssuePropertyOptionCreateUpdateData; updateOptionData: (value: TIssuePropertyOptionCreateUpdateData) => void; + error?: string; }; export const IssuePropertyOptionItem: FC = observer((props) => { - const { optionId, propertyOptionData, updateOptionData } = props; + const { optionId, propertyOptionData, updateOptionData, error: optionsError } = props; // derived values const { key, ...propertyOptionCreateData } = propertyOptionData; // states - const [error, setError] = useState(undefined); + const [error, setError] = useState(optionsError); const [optionData, setOptionData] = useState>(propertyOptionCreateData); useEffect(() => { if (optionId && !optionData.name) setError("Option name is required."); - else setError(undefined); - }, [optionId, optionData]); + else setError(optionsError ?? undefined); + }, [optionId, optionData, optionsError]); // handle create/ update operation const handleCreateUpdate = async () => { diff --git a/web/ee/components/issue-types/properties/attributes/options/root.tsx b/web/ee/components/issue-types/properties/attributes/options/root.tsx index f0228880bd..1a70b78c3e 100644 --- a/web/ee/components/issue-types/properties/attributes/options/root.tsx +++ b/web/ee/components/issue-types/properties/attributes/options/root.tsx @@ -3,7 +3,6 @@ import { observer } from "mobx-react"; // ui import { GripVertical } from "lucide-react"; import { Sortable, Tooltip } from "@plane/ui"; -// plane web components import { IssuePropertyCreateOptionItem, IssuePropertyOptionItem, @@ -15,10 +14,11 @@ import { TIssuePropertyOptionCreateUpdateData } from "@/plane-web/types"; type TIssuePropertyOptionsRoot = { issuePropertyId: string | undefined; + error?: string; }; export const IssuePropertyOptionsRoot: FC = observer((props) => { - const { issuePropertyId } = props; + const { issuePropertyId, error } = props; // store hooks const { propertyOptions, handlePropertyOptionsList } = usePropertyOptions(); // derived values @@ -116,6 +116,7 @@ export const IssuePropertyOptionsRoot: FC = observer( scrollIntoElementView(); }, 0); }} + error={index === 0 ? error : undefined} /> ))}
diff --git a/web/ee/components/issue-types/properties/dropdowns/property-attributes.tsx b/web/ee/components/issue-types/properties/dropdowns/property-attributes.tsx index 0621880989..597ced9c1e 100644 --- a/web/ee/components/issue-types/properties/dropdowns/property-attributes.tsx +++ b/web/ee/components/issue-types/properties/dropdowns/property-attributes.tsx @@ -9,7 +9,7 @@ import { Tooltip } from "@plane/ui"; // helpers import { cn } from "@/helpers/common.helper"; // plane web components -import { SelectedAttributeProperties } from "@/plane-web/components/issue-types/properties"; +import { SelectedAttributeProperties, TIssuePropertyFormError } from "@/plane-web/components/issue-types/properties"; // plane web helpers import { getIssuePropertyAttributeDisplayName } from "@/plane-web/helpers/issue-properties.helper"; // plane web types @@ -25,14 +25,16 @@ type TPropertyAttributesDropdownProps = { shouldSync?: boolean ) => void; disabled?: boolean; + error?: TIssuePropertyFormError; }; export const PropertyAttributesDropdown = observer((props: TPropertyAttributesDropdownProps) => { - const { issueTypeId, propertyDetail, currentOperationMode, onPropertyDetailChange, disabled } = props; + const { issueTypeId, propertyDetail, currentOperationMode, onPropertyDetailChange, disabled, error } = props; // states const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); // derived values + const doesAttributeHasError = Boolean(error?.is_multi) || Boolean(error?.options); const attributeDisplayName = getIssuePropertyAttributeDisplayName(propertyDetail); // list of property types that should not be allowed to change attributes const DISABLE_ATTRIBUTE_CHANGE_LIST = [EIssuePropertyType.BOOLEAN, EIssuePropertyType.DATETIME]; @@ -63,6 +65,7 @@ export const PropertyAttributesDropdown = observer((props: TPropertyAttributesDr { "bg-custom-background-90 cursor-not-allowed": disabled, "py-2": !attributeDisplayName, + "border-red-500": doesAttributeHasError, } )} disabled={disabled} @@ -86,6 +89,7 @@ export const PropertyAttributesDropdown = observer((props: TPropertyAttributesDr propertyDetail={propertyDetail} currentOperationMode={currentOperationMode} onPropertyDetailChange={onPropertyDetailChange} + error={error} /> , diff --git a/web/ee/components/issue-types/properties/dropdowns/selected-attribute-properties.tsx b/web/ee/components/issue-types/properties/dropdowns/selected-attribute-properties.tsx index d2e3b9d50c..d69f910a7c 100644 --- a/web/ee/components/issue-types/properties/dropdowns/selected-attribute-properties.tsx +++ b/web/ee/components/issue-types/properties/dropdowns/selected-attribute-properties.tsx @@ -7,6 +7,7 @@ import { MemberPickerAttributes, NumberAttributes, TextAttributes, + TIssuePropertyFormError, } from "@/plane-web/components/issue-types/properties"; // plane web helpers import { getIssuePropertyTypeKey } from "@/plane-web/helpers/issue-properties.helper"; @@ -23,10 +24,11 @@ type TSelectedPropertyAttributesProps = { shouldSync?: boolean ) => void; disabled?: boolean; + error?: TIssuePropertyFormError; }; export const SelectedAttributeProperties = observer((props: TSelectedPropertyAttributesProps) => { - const { issueTypeId, propertyDetail, currentOperationMode, onPropertyDetailChange } = props; + const { issueTypeId, propertyDetail, currentOperationMode, onPropertyDetailChange, error } = props; const ISSUE_PROPERTY_ATTRIBUTE_DETAILS: Partial> = { TEXT: ( @@ -51,6 +53,7 @@ export const SelectedAttributeProperties = observer((props: TSelectedPropertyAtt dropdownPropertyDetail={propertyDetail as Partial>} currentOperationMode={currentOperationMode} onDropdownDetailChange={onPropertyDetailChange} + error={error} /> ), BOOLEAN: ( diff --git a/web/ee/components/issue-types/properties/property-list-item.tsx b/web/ee/components/issue-types/properties/property-list-item.tsx index b2e2bbde4d..0650d43477 100644 --- a/web/ee/components/issue-types/properties/property-list-item.tsx +++ b/web/ee/components/issue-types/properties/property-list-item.tsx @@ -36,11 +36,13 @@ type TIssuePropertyListItem = { handleIssuePropertyCreateList: (mode: TCreationListModes, value: TIssuePropertyCreateList) => void; }; -export type TIssuePropertyError = { +export type TIssuePropertyFormError = { [key in keyof TIssueProperty]?: string; +} & { + options?: string; }; -const defaultIssuePropertyError: TIssuePropertyError = { +const defaultIssuePropertyError: TIssuePropertyFormError = { display_name: "", property_type: "", }; @@ -69,7 +71,7 @@ export const IssuePropertyListItem = observer((props: TIssuePropertyListItem) => ); const [issuePropertyData, setIssuePropertyData] = useState>>(issuePropertyDetail); - const [issuePropertyError, setIssuePropertyError] = useState(defaultIssuePropertyError); + const [issuePropertyError, setIssuePropertyError] = useState(defaultIssuePropertyError); // derived values // check if mandatory field is disabled for the property const isMandatoryFieldDisabled = @@ -103,6 +105,11 @@ export const IssuePropertyListItem = observer((props: TIssuePropertyListItem) => error.property_type = "You must select a property type."; hasError = true; } + const nonEmptyPropertyOptions = propertyOptions.filter((option) => !!option.name); + if (issuePropertyData.property_type === EIssuePropertyType.OPTION && nonEmptyPropertyOptions.length === 0) { + error.options = "You must add at least one option."; + hasError = true; + } setIssuePropertyError(error); return hasError; }; @@ -336,6 +343,7 @@ export const IssuePropertyListItem = observer((props: TIssuePropertyListItem) => currentOperationMode={issuePropertyOperationMode} onPropertyDetailChange={handlePropertyDataChange} disabled={!issuePropertyData.property_type} + error={issuePropertyError} />
diff --git a/web/ee/components/issue-types/properties/root.tsx b/web/ee/components/issue-types/properties/root.tsx index e7449089c5..1db8be7350 100644 --- a/web/ee/components/issue-types/properties/root.tsx +++ b/web/ee/components/issue-types/properties/root.tsx @@ -107,7 +107,6 @@ export const IssuePropertiesRoot = observer((props: TIssuePropertiesRoot) => { ...defaultIssueProperty, }) } - disabled={!issueType?.is_active} > Add new property diff --git a/web/ee/components/issue-types/quick-actions.tsx b/web/ee/components/issue-types/quick-actions.tsx index 25fad646ee..dc27e3de19 100644 --- a/web/ee/components/issue-types/quick-actions.tsx +++ b/web/ee/components/issue-types/quick-actions.tsx @@ -7,20 +7,18 @@ import { Pencil } from "lucide-react"; import { setPromiseToast, ToggleSwitch, Tooltip } from "@plane/ui"; // helpers import { cn } from "@/helpers/common.helper"; -// plane web components -import { UpdateIssueTypeModal } from "@/plane-web/components/issue-types"; // plane web hooks import { useIssueType } from "@/plane-web/hooks/store"; type Props = { issueTypeId: string; + onEditIssueTypeIdChange: (issueTypeId: string) => void; }; export const IssueTypeQuickActions: React.FC = observer((props) => { - const { issueTypeId } = props; + const { issueTypeId, onEditIssueTypeIdChange } = props; // states const [isLoading, setIsLoading] = useState(false); - const [isCreateUpdateModalOpen, setIsCreateUpdateModalOpen] = useState(false); // store hooks const issueType = useIssueType(issueTypeId); // derived values @@ -56,13 +54,11 @@ export const IssueTypeQuickActions: React.FC = observer((props) => { return ( <> - setIsCreateUpdateModalOpen(false)} - /> -
- {issueType?.is_active && ( +
+
+ +
+
- )} - +
); diff --git a/web/ee/components/issue-types/root.tsx b/web/ee/components/issue-types/root.tsx index 0fd87c5b97..dffce8b846 100644 --- a/web/ee/components/issue-types/root.tsx +++ b/web/ee/components/issue-types/root.tsx @@ -7,7 +7,7 @@ import { Button } from "@plane/ui"; // hooks import { useProject } from "@/hooks/store"; // plane web components -import { CreateIssueTypeModal, IssueTypeEmptyState, IssueTypesList } from "@/plane-web/components/issue-types"; +import { IssueTypeEmptyState, IssueTypesList, CreateOrUpdateIssueTypeModal } from "@/plane-web/components/issue-types"; // plane web hooks import { useFlag } from "@/plane-web/hooks/store"; @@ -16,11 +16,17 @@ export const IssueTypesRoot = observer(() => { const { workspaceSlug, projectId } = useParams(); // states const [isModalOpen, setIsModalOpen] = useState(false); + const [editIssueTypeId, setEditIssueTypeId] = useState(null); // store hooks const { currentProjectDetails } = useProject(); // derived values const isIssueTypeSettingsEnabled = useFlag(workspaceSlug?.toString(), "ISSUE_TYPE_SETTINGS"); + const handleEditIssueTypeIdChange = (issueTypeId: string) => { + setEditIssueTypeId(issueTypeId); + setIsModalOpen(true); + }; + return (
@@ -33,13 +39,20 @@ export const IssueTypesRoot = observer(() => {
{isIssueTypeSettingsEnabled && currentProjectDetails?.is_issue_type_enabled ? ( - + ) : ( )}
{/* Modal */} - setIsModalOpen(false)} /> + { + setEditIssueTypeId(null); + setIsModalOpen(false); + }} + />
); }); diff --git a/web/ee/components/issue-types/values/update.tsx b/web/ee/components/issue-types/values/update.tsx index e22192771f..7e41885a45 100644 --- a/web/ee/components/issue-types/values/update.tsx +++ b/web/ee/components/issue-types/values/update.tsx @@ -66,28 +66,18 @@ export const IssueAdditionalPropertyValuesUpdate: React.FC { - // TODO: remove - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Property update successfully.", - }); - }) - .catch((error) => { - // revert the value if update fails - setIssuePropertyValues((prev) => ({ - ...prev, - [propertyId]: beforeUpdateValue, - })); - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: error?.error ?? "Property could not be update. Please try again.", - }); + await issuePropertyValuesService.update(workspaceSlug, projectId, issueId, propertyId, value).catch((error) => { + // revert the value if update fails + setIssuePropertyValues((prev) => ({ + ...prev, + [propertyId]: beforeUpdateValue, + })); + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: error?.error ?? "Property could not be update. Please try again.", }); + }); }; // if issue types are not enabled, return null diff --git a/web/ee/components/issues/issue-modal/modal.tsx b/web/ee/components/issues/issue-modal/modal.tsx index 0b4b471f3d..bb0a6f5de2 100644 --- a/web/ee/components/issues/issue-modal/modal.tsx +++ b/web/ee/components/issues/issue-modal/modal.tsx @@ -289,12 +289,6 @@ export const CreateUpdateIssueModal: React.FC = observer((prop .then(() => { // reset issue property values setIssuePropertyValues({}); - // TODO: remove - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Custom properties created successfully.", - }); }) .catch(() => { setToast({