diff --git a/web/components/custom-attributes/attribute-display/checkbox.tsx b/web/components/custom-attributes/attribute-display/checkbox.tsx index c4bd663ed..bb998467d 100644 --- a/web/components/custom-attributes/attribute-display/checkbox.tsx +++ b/web/components/custom-attributes/attribute-display/checkbox.tsx @@ -1,17 +1,25 @@ // ui import { ToggleSwitch } from "components/ui"; // types -import { Props } from "./types"; +import { ICustomAttribute } from "types"; + +type Props = { + attributeDetails: ICustomAttribute; + issueId: string; + projectId: string; + value: boolean; + onChange: (val: boolean) => void; +}; export const CustomCheckboxAttribute: React.FC = ({ attributeDetails, onChange, value, }) => { - const handleUpdateCheckbox = (val: boolean | string) => onChange(val.toString()); + const handleUpdateCheckbox = (val: boolean) => onChange(val); return ( -
+
{attributeDetails.extra_settings.representation === "toggle_switch" ? ( ) : ( @@ -23,7 +31,6 @@ export const CustomCheckboxAttribute: React.FC = ({ />
)} - {attributeDetails.display_name}
); }; diff --git a/web/components/custom-attributes/attribute-display/date-time.tsx b/web/components/custom-attributes/attribute-display/date-time.tsx index f47527a7d..b7ea1eaf6 100644 --- a/web/components/custom-attributes/attribute-display/date-time.tsx +++ b/web/components/custom-attributes/attribute-display/date-time.tsx @@ -1,7 +1,15 @@ // react-datepicker import ReactDatePicker from "react-datepicker"; // types -import { Props } from "./types"; +import { ICustomAttribute } from "types"; + +type Props = { + attributeDetails: ICustomAttribute; + issueId: string; + projectId: string; + value: Date | undefined; + onChange: (val: Date | null) => void; +}; const DATE_FORMATS: { [key: string]: string } = { "MM-DD-YYYY": "MM-dd-yyyy", @@ -14,16 +22,12 @@ const TIME_FORMATS: { [key: string]: string } = { "24": "HH:mm", }; -export const CustomDateTimeAttribute: React.FC = ({ - attributeDetails, - onChange, - value, -}) => ( +export const CustomDateTimeAttribute: React.FC = ({ attributeDetails, onChange, value }) => (
); diff --git a/web/components/custom-attributes/attribute-display/email.tsx b/web/components/custom-attributes/attribute-display/email.tsx index 44e8605db..3fc56920b 100644 --- a/web/components/custom-attributes/attribute-display/email.tsx +++ b/web/components/custom-attributes/attribute-display/email.tsx @@ -3,13 +3,17 @@ import { useEffect, useState } from "react"; // react-hook-form import { Controller, useForm } from "react-hook-form"; // types -import { Props } from "./types"; +import { ICustomAttribute } from "types"; -export const CustomEmailAttribute: React.FC = ({ - attributeDetails, - onChange, - value, -}) => { +type Props = { + attributeDetails: ICustomAttribute; + issueId: string; + projectId: string; + value: string | undefined; + onChange: (val: string) => void; +}; + +export const CustomEmailAttribute: React.FC = ({ attributeDetails, onChange, value }) => { const [isEditing, setIsEditing] = useState(false); const { control, handleSubmit, reset, setFocus } = useForm({ defaultValues: { email: "" } }); @@ -45,7 +49,10 @@ export const CustomEmailAttribute: React.FC {!isEditing && ( -
setIsEditing(true)}> +
setIsEditing(true)} + > {value && value !== "" ? value : "Empty"}
)} diff --git a/web/components/custom-attributes/attribute-display/file.tsx b/web/components/custom-attributes/attribute-display/file.tsx index 04a736748..dd43afc95 100644 --- a/web/components/custom-attributes/attribute-display/file.tsx +++ b/web/components/custom-attributes/attribute-display/file.tsx @@ -8,17 +8,26 @@ import { useDropzone } from "react-dropzone"; import fileService from "services/file.service"; // hooks import useToast from "hooks/use-toast"; +import useWorkspaceDetails from "hooks/use-workspace-details"; // icons import { getFileIcon } from "components/icons"; +import { X } from "lucide-react"; // helpers import { getFileExtension, getFileName } from "helpers/attachment.helper"; // types -import { Props } from "./types"; -import useWorkspaceDetails from "hooks/use-workspace-details"; +import { ICustomAttribute } from "types"; + +type Props = { + attributeDetails: ICustomAttribute; + issueId: string; + projectId: string; + value: string | undefined; + onChange: (val: string | undefined) => void; +}; const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB -export const CustomFileAttribute: React.FC = (props) => { +export const CustomFileAttribute: React.FC = (props) => { const { attributeDetails, onChange, value } = props; const [isUploading, setIsUploading] = useState(false); @@ -80,6 +89,13 @@ export const CustomFileAttribute: React.FC { + if (!workspaceDetails || !value || value === "") return; + + onChange(undefined); + fileService.deleteFile(workspaceDetails.id, value); + }; + const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({ onDrop, maxSize: MAX_FILE_SIZE, @@ -95,15 +111,24 @@ export const CustomFileAttribute: React.FC {value && value !== "" && ( - - {getFileIcon(getFileExtension(value))} - {getFileName(value)} - + )}
= ({ - attributeDetails, - onChange, - value, -}) => { +type Props = { + attributeDetails: ICustomAttribute; + issueId: string; + projectId: string; + value: number | undefined; + onChange: (val: number | undefined) => void; +}; + +export const CustomNumberAttribute: React.FC = ({ attributeDetails, onChange, value }) => { const [isEditing, setIsEditing] = useState(false); const { control, handleSubmit, reset, setFocus } = useForm({ defaultValues: { number: "" } }); @@ -19,7 +23,10 @@ export const CustomNumberAttribute: React.FC { setIsEditing(false); - onChange(data.number); + const number = parseInt(data.number, 10); + + if (isNaN(number)) onChange(undefined); + else onChange(number); }; useEffect(() => { @@ -84,10 +91,10 @@ export const CustomNumberAttribute: React.FC ) : (
setIsEditing(true)} > - {value ?? `Enter ${attributeDetails.display_name}`} + {value ?? "Empty"}
)}
diff --git a/web/components/custom-attributes/attribute-display/relation.tsx b/web/components/custom-attributes/attribute-display/relation.tsx index 24c9e72ff..2730be2bd 100644 --- a/web/components/custom-attributes/attribute-display/relation.tsx +++ b/web/components/custom-attributes/attribute-display/relation.tsx @@ -12,11 +12,19 @@ import modulesService from "services/modules.service"; // icons import { Search } from "lucide-react"; // types -import { Props } from "./types"; +import { ICustomAttribute } from "types"; // fetch-keys import { CYCLES_LIST, MODULE_LIST } from "constants/fetch-keys"; -export const CustomRelationAttribute: React.FC = ({ +type Props = { + attributeDetails: ICustomAttribute; + issueId: string; + projectId: string; + value: string | undefined; + onChange: (val: string | undefined) => void; +}; + +export const CustomRelationAttribute: React.FC = ({ attributeDetails, onChange, projectId, diff --git a/web/components/custom-attributes/attribute-display/select.tsx b/web/components/custom-attributes/attribute-display/select.tsx index e893d3deb..1e6644706 100644 --- a/web/components/custom-attributes/attribute-display/select.tsx +++ b/web/components/custom-attributes/attribute-display/select.tsx @@ -7,15 +7,22 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector"; // icons import { Check, Search } from "lucide-react"; // types -import { Props } from "./types"; +import { ICustomAttribute } from "types"; -export const CustomSelectAttribute: React.FC< - Props & - ( - | { multiple?: false; value: string | undefined } - | { multiple?: true; value: string[] | undefined } - ) -> = (props) => { +type Props = { + attributeDetails: ICustomAttribute; + issueId: string; + projectId: string; +} & ( + | { + multiple?: false; + onChange: (val: string | undefined) => void; + value: string | undefined; + } + | { multiple?: true; onChange: (val: string[] | undefined) => void; value: string[] | undefined } +); + +export const CustomSelectAttribute: React.FC = (props) => { const { attributeDetails, multiple = false, onChange, value } = props; const [isOpen, setIsOpen] = useState(false); @@ -62,8 +69,8 @@ export const CustomSelectAttribute: React.FC< }); const comboboxProps: any = { - value, onChange, + value, }; if (multiple) comboboxProps.multiple = true; @@ -101,14 +108,16 @@ export const CustomSelectAttribute: React.FC<
) ) : ( - o.id === value)?.color}40`, - }} - > - {options.find((o) => o.id === value)?.display_name} - +
+ o.id === value)?.color}40`, + }} + > + {options.find((o) => o.id === value)?.display_name} + +
) ) : (
diff --git a/web/components/custom-attributes/attribute-display/text.tsx b/web/components/custom-attributes/attribute-display/text.tsx index 78a7605fe..f8522b794 100644 --- a/web/components/custom-attributes/attribute-display/text.tsx +++ b/web/components/custom-attributes/attribute-display/text.tsx @@ -3,7 +3,15 @@ import { useEffect, useState } from "react"; // react-hook-form import { Controller, useForm } from "react-hook-form"; // types -import { Props } from "./types"; +import { ICustomAttribute } from "types"; + +type Props = { + attributeDetails: ICustomAttribute; + issueId: string; + projectId: string; + value: string | undefined; + onChange: (val: string) => void; +}; export const CustomTextAttribute: React.FC = ({ attributeDetails, @@ -45,7 +53,10 @@ export const CustomTextAttribute: React.FC {!isEditing && ( -
setIsEditing(true)}> +
setIsEditing(true)} + > {value && value !== "" ? value : `Enter ${attributeDetails.display_name}`}
)} diff --git a/web/components/custom-attributes/attribute-display/types.d.ts b/web/components/custom-attributes/attribute-display/types.d.ts deleted file mode 100644 index 2874b69a8..000000000 --- a/web/components/custom-attributes/attribute-display/types.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -// types -import { ICustomAttribute } from "types"; - -export type Props = { - attributeDetails: ICustomAttribute; - issueId: string; - onChange: (value: any) => void; - projectId: string; -}; diff --git a/web/components/custom-attributes/attribute-display/url.tsx b/web/components/custom-attributes/attribute-display/url.tsx index 381fa2da8..17cc7a349 100644 --- a/web/components/custom-attributes/attribute-display/url.tsx +++ b/web/components/custom-attributes/attribute-display/url.tsx @@ -3,7 +3,15 @@ import { useEffect, useState } from "react"; // react-hook-form import { Controller, useForm } from "react-hook-form"; // types -import { Props } from "./types"; +import { ICustomAttribute } from "types"; + +type Props = { + attributeDetails: ICustomAttribute; + issueId: string; + projectId: string; + value: string | undefined; + onChange: (val: string) => void; +}; export const CustomUrlAttribute: React.FC = ({ attributeDetails, @@ -43,8 +51,11 @@ export const CustomUrlAttribute: React.FC return (
{!isEditing && ( -
setIsEditing(true)}> - {value && value !== "" ? value : `Enter ${attributeDetails.display_name}`} +
setIsEditing(true)} + > + {value && value !== "" ? value : "Empty"}
)} {isEditing && ( diff --git a/web/components/custom-attributes/attribute-forms/date-time-attribute-form.tsx b/web/components/custom-attributes/attribute-forms/date-time-attribute-form.tsx index 52e660a02..72dfd5f62 100644 --- a/web/components/custom-attributes/attribute-forms/date-time-attribute-form.tsx +++ b/web/components/custom-attributes/attribute-forms/date-time-attribute-form.tsx @@ -3,11 +3,11 @@ import { Controller } from "react-hook-form"; // components import { FormComponentProps, Input } from "components/custom-attributes"; // ui -import { CustomSelect, ToggleSwitch } from "components/ui"; +import { CustomSelect, ToggleSwitch, Tooltip } from "components/ui"; // constants import { DATE_FORMATS, TIME_FORMATS } from "constants/custom-attributes"; -export const DateTimeAttributeForm: React.FC = ({ control }) => ( +export const DateTimeAttributeForm: React.FC = ({ control, watch }) => (
= ({ control }) name="extra_settings.hide_date" render={({ field: { onChange, value } }) => (
- - Don{"'"}t show date + +
+ + Don{"'"}t show date +
+
)} /> @@ -78,8 +90,20 @@ export const DateTimeAttributeForm: React.FC = ({ control }) name="extra_settings.hide_time" render={({ field: { onChange, value } }) => (
- - Don{"'"}t show time + +
+ + Don{"'"}t show time +
+
)} /> diff --git a/web/components/custom-attributes/attributes-list/issue-modal-attributes-list.tsx b/web/components/custom-attributes/attributes-list/issue-modal-attributes-list.tsx index b96435a6a..eed30fda3 100644 --- a/web/components/custom-attributes/attributes-list/issue-modal-attributes-list.tsx +++ b/web/components/custom-attributes/attributes-list/issue-modal-attributes-list.tsx @@ -63,7 +63,7 @@ export const IssueModalCustomAttributesList: React.FC = observer( onChange(attribute.id, [val])} + onChange={(val) => onChange(attribute.id, [`${val}`])} projectId={projectId} value={values[attribute.id]?.[0] === "true" ? true : false} /> @@ -72,7 +72,7 @@ export const IssueModalCustomAttributesList: React.FC = observer( onChange(attribute.id, [val])} + onChange={(val) => onChange(attribute.id, [val?.toISOString() ?? ""])} projectId={projectId} value={ values[attribute.id]?.[0] ? new Date(values[attribute.id]?.[0]) : undefined @@ -101,7 +101,9 @@ export const IssueModalCustomAttributesList: React.FC = observer( onChange(attribute.id, val)} + onChange={(val) => { + if (val) onChange(attribute.id, val); + }} projectId={projectId} value={values[attribute.id] ?? []} multiple @@ -111,7 +113,9 @@ export const IssueModalCustomAttributesList: React.FC = observer( onChange(attribute.id, [val])} + onChange={(val) => { + if (val) onChange(attribute.id, [val.toString()]); + }} projectId={projectId} value={ values[attribute.id]?.[0] ? parseInt(values[attribute.id]?.[0]) : undefined @@ -122,7 +126,9 @@ export const IssueModalCustomAttributesList: React.FC = observer( onChange(attribute.id, [val])} + onChange={(val) => { + if (val) onChange(attribute.id, [val]); + }} projectId={projectId} value={values[attribute.id]?.[0]} /> @@ -131,16 +137,19 @@ export const IssueModalCustomAttributesList: React.FC = observer( onChange(attribute.id, [val])} + onChange={(val) => { + if (val) onChange(attribute.id, [val]); + }} projectId={projectId} value={attribute.default_value !== "" ? attribute.default_value : undefined} + multiple={false} /> )} {attribute.type === "text" && ( onChange(attribute.id, [val])} + onChange={(val) => onChange(attribute.id, [val])} projectId={projectId} value={attribute.default_value} /> @@ -149,10 +158,7 @@ export const IssueModalCustomAttributesList: React.FC = observer( { - console.log(val); - onChange(attribute.id, [val]); - }} + onChange={(val: string) => onChange(attribute.id, [val])} projectId={projectId} value={values[attribute.id]?.[0]} /> diff --git a/web/components/custom-attributes/attributes-list/sidebar-custom-attributes-list.tsx b/web/components/custom-attributes/attributes-list/sidebar-custom-attributes-list.tsx index 1d2e8b714..c038621af 100644 --- a/web/components/custom-attributes/attributes-list/sidebar-custom-attributes-list.tsx +++ b/web/components/custom-attributes/attributes-list/sidebar-custom-attributes-list.tsx @@ -26,9 +26,10 @@ import { CUSTOM_ATTRIBUTES_LIST } from "constants/custom-attributes"; type Props = { issue: IIssue | undefined; + projectId: string; }; -export const SidebarCustomAttributesList: React.FC = observer(({ issue }) => { +export const SidebarCustomAttributesList: React.FC = observer(({ issue, projectId }) => { const router = useRouter(); const { workspaceSlug } = router.query; @@ -37,14 +38,20 @@ export const SidebarCustomAttributesList: React.FC = observer(({ issue }) customAttributeValues: customAttributeValuesStore, } = useMobxStore(); const { entityAttributes, fetchEntityDetails } = customAttributesStore; - const { issueAttributeValues, fetchIssueAttributeValues } = customAttributeValuesStore; + const { issueAttributeValues, fetchIssueAttributeValues, deleteAttributeValue } = + customAttributeValuesStore; - const handleAttributeUpdate = (attributeId: string, value: string[]) => { + const handleAttributeUpdate = (attributeId: string, value: string | string[] | undefined) => { if (!issue || !workspaceSlug) return; + if (!value) { + deleteAttributeValue(workspaceSlug.toString(), projectId, issue.id, attributeId); + return; + } + const payload: ICustomAttributeValueFormData = { issue_properties: { - [attributeId]: value, + [attributeId]: Array.isArray(value) ? value : [value], }, }; @@ -109,7 +116,7 @@ export const SidebarCustomAttributesList: React.FC = observer(({ issue }) handleAttributeUpdate(attribute.id, [val])} + onChange={(val) => handleAttributeUpdate(attribute.id, [`${val}`])} projectId={issue.project} value={ attributeValue ? (attributeValue?.[0]?.value === "true" ? true : false) : false @@ -120,7 +127,9 @@ export const SidebarCustomAttributesList: React.FC = observer(({ issue }) handleAttributeUpdate(attribute.id, [val.toISOString()])} + onChange={(val: Date | null) => { + handleAttributeUpdate(attribute.id, val ? [val.toISOString()] : undefined); + }} projectId={issue.project} value={attributeValue ? new Date(attributeValue?.[0]?.value ?? "") : undefined} /> @@ -129,7 +138,9 @@ export const SidebarCustomAttributesList: React.FC = observer(({ issue }) handleAttributeUpdate(attribute.id, [val])} + onChange={(val) => { + handleAttributeUpdate(attribute.id, val && val !== "" ? [val] : undefined); + }} projectId={issue.project} value={attributeValue ? attributeValue?.[0]?.value : undefined} /> @@ -138,7 +149,7 @@ export const SidebarCustomAttributesList: React.FC = observer(({ issue }) handleAttributeUpdate(attribute.id, [val])} + onChange={(val) => handleAttributeUpdate(attribute.id, val)} projectId={issue.project} value={attributeValue ? attributeValue?.[0]?.value : undefined} /> @@ -147,7 +158,7 @@ export const SidebarCustomAttributesList: React.FC = observer(({ issue }) handleAttributeUpdate(attribute.id, val)} + onChange={(val) => handleAttributeUpdate(attribute.id, val)} projectId={issue.project} value={Array.isArray(attributeValue) ? attributeValue.map((v) => v.value) : []} multiple @@ -157,7 +168,9 @@ export const SidebarCustomAttributesList: React.FC = observer(({ issue }) handleAttributeUpdate(attribute.id, [val])} + onChange={(val) => { + handleAttributeUpdate(attribute.id, val ? val.toString() : undefined); + }} projectId={issue.project} value={ attributeValue ? parseInt(attributeValue?.[0]?.value ?? "0", 10) : undefined @@ -168,7 +181,7 @@ export const SidebarCustomAttributesList: React.FC = observer(({ issue }) handleAttributeUpdate(attribute.id, [val])} + onChange={(val) => handleAttributeUpdate(attribute.id, val)} projectId={issue.project} value={attributeValue ? attributeValue?.[0]?.value : undefined} /> @@ -177,16 +190,19 @@ export const SidebarCustomAttributesList: React.FC = observer(({ issue }) handleAttributeUpdate(attribute.id, [val])} + onChange={(val) => handleAttributeUpdate(attribute.id, val)} projectId={issue.project} value={attributeValue ? attributeValue?.[0]?.value : undefined} + multiple={false} /> )} {attribute.type === "text" && ( handleAttributeUpdate(attribute.id, [val])} + onChange={(val) => + handleAttributeUpdate(attribute.id, val && val !== "" ? [val] : undefined) + } projectId={issue.project} value={attributeValue ? attributeValue?.[0].value : undefined} /> @@ -195,7 +211,9 @@ export const SidebarCustomAttributesList: React.FC = observer(({ issue }) handleAttributeUpdate(attribute.id, [val])} + onChange={(val) => + handleAttributeUpdate(attribute.id, val && val !== "" ? [val] : undefined) + } projectId={issue.project} value={attributeValue ? attributeValue?.[0]?.value : undefined} /> diff --git a/web/components/issues/sidebar.tsx b/web/components/issues/sidebar.tsx index 6f50edc89..4d8e52cca 100644 --- a/web/components/issues/sidebar.tsx +++ b/web/components/issues/sidebar.tsx @@ -305,7 +305,7 @@ export const IssueDetailsSidebar: React.FC = ({ data={issueDetail ?? null} user={user} /> -
+

{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id} @@ -349,30 +349,9 @@ export const IssueDetailsSidebar: React.FC = ({

-
+
{showFirstSection && (
- {/* {(fieldsToShow.includes("all") || fieldsToShow.includes("entity")) && ( -
-
- -

Object

-
-
- ( - submitChanges({ entity: val })} - projectId={projectId?.toString() ?? ""} - value={value} - /> - )} - /> -
-
- )} */} {(fieldsToShow.includes("all") || fieldsToShow.includes("state")) && (
@@ -663,6 +642,14 @@ export const IssueDetailsSidebar: React.FC = ({ )}
)} + {watchIssue("entity") && ( +
+ +
+ )}
{(fieldsToShow.includes("all") || fieldsToShow.includes("label")) && watchIssue("entity") === null && ( @@ -705,11 +692,6 @@ export const IssueDetailsSidebar: React.FC = ({
)} - {watchIssue("entity") && ( -
- -
- )}
diff --git a/web/services/custom-attributes.service.ts b/web/services/custom-attributes.service.ts index e0ca870a3..fa18e43b9 100644 --- a/web/services/custom-attributes.service.ts +++ b/web/services/custom-attributes.service.ts @@ -91,6 +91,21 @@ class CustomAttributesService extends APIService { throw error?.response?.data; }); } + + async deletePropertyValue( + workspaceSlug: string, + projectId: string, + issueId: string, + propertyId: string + ): Promise { + return this.delete( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/property-values/${propertyId}/` + ) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } } const customAttributesService = new CustomAttributesService(); diff --git a/web/store/custom-attribute-values.ts b/web/store/custom-attribute-values.ts index 84ec0af3a..9bff2c570 100644 --- a/web/store/custom-attribute-values.ts +++ b/web/store/custom-attribute-values.ts @@ -20,6 +20,7 @@ class CustomAttributeValuesStore { issueAttributeValues: observable.ref, fetchIssueAttributeValues: action, createAttributeValue: action, + deleteAttributeValue: action, }); this.rootStore = _rootStore; @@ -94,7 +95,6 @@ class CustomAttributeValuesStore { ...this.issueAttributeValues, [issueId]: response.children, }; - this.fetchIssueAttributeValuesLoader = false; }); } catch (error) { runInAction(() => { @@ -102,6 +102,36 @@ class CustomAttributeValuesStore { }); } }; + + deleteAttributeValue = async ( + workspaceSlug: string, + projectId: string, + issueId: string, + propertyId: string + ) => { + const newChildren = [...(this.issueAttributeValues?.[issueId] ?? [])]; + newChildren.filter((c) => c.id !== propertyId); + + try { + runInAction(() => { + this.issueAttributeValues = { + ...this.issueAttributeValues, + [issueId]: newChildren, + }; + }); + + await customAttributesService.deletePropertyValue( + workspaceSlug, + projectId, + issueId, + propertyId + ); + } catch (error) { + runInAction(() => { + this.error = error; + }); + } + }; } export default CustomAttributeValuesStore;