forked from github/plane
refactor: attribute form component
This commit is contained in:
parent
e713db48b3
commit
57f4941ee2
@ -1,19 +1,29 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
|
||||
// headless ui
|
||||
import { Combobox, Transition } from "@headlessui/react";
|
||||
// hooks
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// icons
|
||||
import { Search } from "lucide-react";
|
||||
import { Check, Search } from "lucide-react";
|
||||
// types
|
||||
import { Props } from "./types";
|
||||
|
||||
export const CustomSelectAttribute: React.FC<Props & { value: string | undefined }> = ({
|
||||
attributeDetails,
|
||||
onChange,
|
||||
value,
|
||||
}) => {
|
||||
export const CustomSelectAttribute: React.FC<
|
||||
Props &
|
||||
(
|
||||
| { multiple?: false; value: string | undefined }
|
||||
| { multiple?: true; value: string[] | undefined }
|
||||
)
|
||||
> = (props) => {
|
||||
const { attributeDetails, multiple = false, onChange, value } = props;
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
const dropdownButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const dropdownOptionsRef = useRef<HTMLUListElement>(null);
|
||||
|
||||
const selectedOption =
|
||||
attributeDetails.children.find((option) => option.id === value) ??
|
||||
attributeDetails.children.find((option) => option.is_default);
|
||||
@ -22,66 +32,115 @@ export const CustomSelectAttribute: React.FC<Props & { value: string | undefined
|
||||
option.display_name.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
|
||||
const handleOnOpen = () => {
|
||||
const dropdownButton = dropdownButtonRef.current;
|
||||
const dropdownOptions = dropdownOptionsRef.current;
|
||||
|
||||
if (!dropdownButton || !dropdownOptions) return;
|
||||
|
||||
const dropdownWidth = dropdownOptions.clientWidth;
|
||||
const dropdownHeight = dropdownOptions.clientHeight;
|
||||
|
||||
const dropdownBtnX = dropdownButton.getBoundingClientRect().x;
|
||||
const dropdownBtnY = dropdownButton.getBoundingClientRect().y;
|
||||
const dropdownBtnHeight = dropdownButton.clientHeight;
|
||||
|
||||
let top = dropdownBtnY + dropdownBtnHeight;
|
||||
if (dropdownBtnY + dropdownHeight > window.innerHeight)
|
||||
top = dropdownBtnY - dropdownHeight - 10;
|
||||
else top = top + 4;
|
||||
|
||||
let left = dropdownBtnX;
|
||||
if (dropdownBtnX + dropdownWidth > window.innerWidth) left = dropdownBtnX - dropdownWidth;
|
||||
|
||||
dropdownOptions.style.top = `${Math.round(top)}px`;
|
||||
dropdownOptions.style.left = `${Math.round(left)}px`;
|
||||
};
|
||||
|
||||
useOutsideClickDetector(dropdownOptionsRef, () => {
|
||||
if (isOpen) setIsOpen(false);
|
||||
});
|
||||
|
||||
const comboboxProps: any = {
|
||||
value,
|
||||
onChange,
|
||||
};
|
||||
|
||||
if (multiple) comboboxProps.multiple = true;
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
as="div"
|
||||
value={value}
|
||||
onChange={(val) => onChange(val)}
|
||||
className="relative flex-shrink-0 text-left"
|
||||
>
|
||||
{({ open }: { open: boolean }) => (
|
||||
<>
|
||||
<Combobox.Button
|
||||
className={`flex items-center text-xs rounded px-2.5 py-0.5 truncate w-min max-w-full text-left ${
|
||||
selectedOption ? "" : "bg-custom-background-80"
|
||||
}`}
|
||||
style={{
|
||||
backgroundColor: `${selectedOption?.color}40`,
|
||||
}}
|
||||
>
|
||||
{selectedOption?.display_name ?? "Select"}
|
||||
</Combobox.Button>
|
||||
<Transition
|
||||
show={open}
|
||||
as={React.Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0 translate-y-1"
|
||||
enterTo="opacity-100 translate-y-0"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
>
|
||||
<Combobox.Options className="fixed z-10 mb-2 border-[0.5px] border-custom-border-300 p-1 min-w-[10rem] max-h-60 max-w-[10rem] rounded bg-custom-background-100 text-xs shadow-custom-shadow-rg focus:outline-none mt-1 flex flex-col overflow-hidden">
|
||||
<div className="flex w-full items-center justify-start rounded-sm border-[0.6px] border-custom-border-200 bg-custom-background-90 px-2 mb-1">
|
||||
<Search className="text-custom-text-400" size={12} strokeWidth={1.5} />
|
||||
<Combobox.Input
|
||||
className="w-full bg-transparent py-1 px-2 text-xs text-custom-text-200 placeholder:text-custom-text-400 focus:outline-none"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Type to search..."
|
||||
displayValue={(assigned: any) => assigned?.name}
|
||||
/>
|
||||
<Combobox as="div" className="flex-shrink-0 text-left" {...comboboxProps}>
|
||||
{({ open }: { open: boolean }) => {
|
||||
if (open) handleOnOpen();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Combobox.Button
|
||||
ref={dropdownButtonRef}
|
||||
className={`flex items-center text-xs rounded px-2.5 py-0.5 truncate w-min max-w-full text-left ${
|
||||
selectedOption ? "" : "bg-custom-background-80"
|
||||
}`}
|
||||
style={{
|
||||
backgroundColor: `${selectedOption?.color}40`,
|
||||
}}
|
||||
>
|
||||
{selectedOption?.display_name ?? "Select"}
|
||||
</Combobox.Button>
|
||||
<Transition
|
||||
as={React.Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0 translate-y-1"
|
||||
enterTo="opacity-100 translate-y-0"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
>
|
||||
<div className="fixed z-10 top-0 left-0 h-full w-full cursor-auto">
|
||||
<Combobox.Options
|
||||
ref={dropdownOptionsRef}
|
||||
className="absolute z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-lg focus:outline-none w-48 whitespace-nowrap"
|
||||
>
|
||||
<div className="flex w-full items-center justify-start rounded-sm border-[0.6px] border-custom-border-200 bg-custom-background-90 px-2 mb-1">
|
||||
<Search className="text-custom-text-400" size={12} strokeWidth={1.5} />
|
||||
<Combobox.Input
|
||||
className="w-full bg-transparent py-1 px-2 text-xs text-custom-text-200 placeholder:text-custom-text-400 focus:outline-none"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Type to search..."
|
||||
displayValue={(assigned: any) => assigned?.name}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-1 overflow-y-auto">
|
||||
{(options ?? []).map((option) => (
|
||||
<Combobox.Option
|
||||
key={option.id}
|
||||
value={option.id}
|
||||
className={({ active }) =>
|
||||
`flex items-center justify-between gap-1 cursor-pointer select-none truncate rounded px-1 py-1.5 w-full ${
|
||||
active ? "bg-custom-background-80" : ""
|
||||
}`
|
||||
}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<span
|
||||
className="px-1 rounded-sm truncate"
|
||||
style={{ backgroundColor: `${option.color}40` }}
|
||||
>
|
||||
{option.display_name}
|
||||
</span>
|
||||
{selected && <Check size={14} strokeWidth={1.5} />}
|
||||
</>
|
||||
)}
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</div>
|
||||
</Combobox.Options>
|
||||
</div>
|
||||
<div className="mt-1 overflow-y-auto">
|
||||
{(options ?? []).map((option) => (
|
||||
<Combobox.Option
|
||||
key={option.id}
|
||||
value={option.id}
|
||||
className="flex items-center gap-1 cursor-pointer select-none truncate rounded px-1 py-1.5 hover:bg-custom-background-80 w-full"
|
||||
>
|
||||
<span
|
||||
className="px-1 rounded-sm truncate"
|
||||
style={{ backgroundColor: `${option.color}40` }}
|
||||
>
|
||||
{option.display_name}
|
||||
</span>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</div>
|
||||
</Combobox.Options>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Combobox>
|
||||
);
|
||||
};
|
||||
|
@ -1,5 +1,10 @@
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// headless ui
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
// react-hook-form
|
||||
@ -17,7 +22,7 @@ import {
|
||||
UrlAttributeForm,
|
||||
} from "components/custom-attributes";
|
||||
// ui
|
||||
import { PrimaryButton, ToggleSwitch } from "components/ui";
|
||||
import { PrimaryButton, SecondaryButton, ToggleSwitch } from "components/ui";
|
||||
// icons
|
||||
import { ChevronDown } from "lucide-react";
|
||||
// types
|
||||
@ -26,9 +31,7 @@ import { ICustomAttribute, TCustomAttributeTypes } from "types";
|
||||
import { CUSTOM_ATTRIBUTES_LIST } from "constants/custom-attributes";
|
||||
|
||||
type Props = {
|
||||
data: Partial<ICustomAttribute>;
|
||||
handleDeleteAttribute: () => void;
|
||||
handleUpdateAttribute: (data: Partial<ICustomAttribute>) => Promise<void>;
|
||||
attributeDetails: Partial<ICustomAttribute>;
|
||||
objectId: string;
|
||||
type: TCustomAttributeTypes;
|
||||
};
|
||||
@ -56,7 +59,9 @@ const RenderForm: React.FC<{ type: TCustomAttributeTypes } & FormComponentProps>
|
||||
else if (type === "file")
|
||||
FormToRender = <FileAttributeForm control={control} objectId={objectId} watch={watch} />;
|
||||
else if (type === "multi_select")
|
||||
FormToRender = <SelectAttributeForm control={control} objectId={objectId} watch={watch} />;
|
||||
FormToRender = (
|
||||
<SelectAttributeForm control={control} objectId={objectId} watch={watch} multiple />
|
||||
);
|
||||
else if (type === "number")
|
||||
FormToRender = <NumberAttributeForm control={control} objectId={objectId} watch={watch} />;
|
||||
else if (type === "relation")
|
||||
@ -71,15 +76,17 @@ const RenderForm: React.FC<{ type: TCustomAttributeTypes } & FormComponentProps>
|
||||
return FormToRender;
|
||||
};
|
||||
|
||||
export const AttributeForm: React.FC<Props> = ({
|
||||
data,
|
||||
handleDeleteAttribute,
|
||||
handleUpdateAttribute,
|
||||
objectId,
|
||||
type,
|
||||
}) => {
|
||||
export const AttributeForm: React.FC<Props> = observer(({ attributeDetails, objectId, type }) => {
|
||||
const [isRemoving, setIsRemoving] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const typeMetaData = CUSTOM_ATTRIBUTES_LIST[type];
|
||||
|
||||
const { customAttributes: customAttributesStore } = useMobxStore();
|
||||
const { deleteEntityAttribute, updateEntityAttribute } = customAttributesStore;
|
||||
|
||||
const {
|
||||
control,
|
||||
formState: { isSubmitting },
|
||||
@ -88,18 +95,30 @@ export const AttributeForm: React.FC<Props> = ({
|
||||
watch,
|
||||
} = useForm({ defaultValues: typeMetaData.defaultFormValues });
|
||||
|
||||
const handleFormSubmit = async (data: Partial<ICustomAttribute>) => {
|
||||
await handleUpdateAttribute(data);
|
||||
const handleUpdateAttribute = async (data: Partial<ICustomAttribute>) => {
|
||||
if (!workspaceSlug || !attributeDetails.id || !objectId) return;
|
||||
|
||||
await updateEntityAttribute(workspaceSlug.toString(), objectId, attributeDetails.id, data);
|
||||
};
|
||||
|
||||
const handleDeleteAttribute = async () => {
|
||||
if (!workspaceSlug || !attributeDetails.id || !objectId) return;
|
||||
|
||||
setIsRemoving(true);
|
||||
|
||||
await deleteEntityAttribute(workspaceSlug.toString(), objectId, attributeDetails.id).finally(
|
||||
() => setIsRemoving(false)
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) return;
|
||||
if (!attributeDetails) return;
|
||||
|
||||
reset({
|
||||
...typeMetaData.defaultFormValues,
|
||||
...data,
|
||||
...attributeDetails,
|
||||
});
|
||||
}, [data, reset, typeMetaData.defaultFormValues]);
|
||||
}, [attributeDetails, reset, typeMetaData.defaultFormValues]);
|
||||
|
||||
return (
|
||||
<Disclosure
|
||||
@ -111,20 +130,25 @@ export const AttributeForm: React.FC<Props> = ({
|
||||
<Disclosure.Button className="p-3 flex items-center justify-between gap-1 w-full">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<typeMetaData.icon size={14} strokeWidth={1.5} />
|
||||
<h6 className="text-sm">{data.display_name ?? typeMetaData.label}</h6>
|
||||
<h6 className="text-sm">{attributeDetails.display_name ?? typeMetaData.label}</h6>
|
||||
</div>
|
||||
<div className={`${open ? "-rotate-180" : ""} transition-all`}>
|
||||
<ChevronDown size={16} strokeWidth={1.5} rotate="180deg" />
|
||||
</div>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel>
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)} className="p-3 pl-9 pt-0">
|
||||
{data.type && (
|
||||
<RenderForm type={data.type} control={control} objectId={objectId} watch={watch} />
|
||||
<form onSubmit={handleSubmit(handleUpdateAttribute)} className="p-3 pl-9 pt-0">
|
||||
{attributeDetails.type && (
|
||||
<RenderForm
|
||||
type={attributeDetails.type}
|
||||
control={control}
|
||||
objectId={objectId}
|
||||
watch={watch}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-8 flex items-center justify-between">
|
||||
<div className="flex-shrink-0 flex items-center gap-2">
|
||||
{data.type !== "checkbox" && (
|
||||
{attributeDetails.type !== "checkbox" && (
|
||||
<>
|
||||
<Controller
|
||||
control={control}
|
||||
@ -138,13 +162,13 @@ export const AttributeForm: React.FC<Props> = ({
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
<SecondaryButton
|
||||
type="button"
|
||||
onClick={handleDeleteAttribute}
|
||||
className="text-xs font-medium px-3 py-2 rounded bg-custom-background-100 border border-custom-border-200"
|
||||
loading={isRemoving}
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
{isRemoving ? "Removing..." : "Remove"}
|
||||
</SecondaryButton>
|
||||
<PrimaryButton type="submit" loading={isSubmitting}>
|
||||
{isSubmitting ? "Saving..." : "Save"}
|
||||
</PrimaryButton>
|
||||
@ -156,4 +180,4 @@ export const AttributeForm: React.FC<Props> = ({
|
||||
)}
|
||||
</Disclosure>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -40,7 +40,7 @@ export const RelationAttributeForm: React.FC<FormComponentProps> = ({ control })
|
||||
</CustomSelect>
|
||||
)}
|
||||
/>
|
||||
<div>
|
||||
{/* <div>
|
||||
<p className="text-xs">Selection type</p>
|
||||
<div className="mt-2 flex items-center gap-6 accent-custom-primary-100">
|
||||
<div className="flex items-center gap-1 text-xs">
|
||||
@ -60,6 +60,6 @@ export const RelationAttributeForm: React.FC<FormComponentProps> = ({ control })
|
||||
<label htmlFor="multiSelect">Multi select</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
|
@ -39,7 +39,7 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
|
||||
const { entityAttributes, fetchEntityDetails } = customAttributesStore;
|
||||
const { issueAttributeValues, fetchIssueAttributeValues } = customAttributeValuesStore;
|
||||
|
||||
const handleAttributeUpdate = (attributeId: string, value: string) => {
|
||||
const handleAttributeUpdate = (attributeId: string, value: string[]) => {
|
||||
if (!issue || !workspaceSlug) return;
|
||||
|
||||
const payload: ICustomAttributeValueFormData = {
|
||||
@ -109,7 +109,7 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
|
||||
<CustomCheckboxAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issue.id}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, val)}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])}
|
||||
projectId={issue.project}
|
||||
value={
|
||||
attributeValue ? (attributeValue?.[0]?.value === "true" ? true : false) : false
|
||||
@ -120,7 +120,7 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
|
||||
<CustomDateTimeAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issue.id}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, val)}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])}
|
||||
projectId={issue.project}
|
||||
value={attributeValue ? new Date(attributeValue?.[0]?.value ?? "") : undefined}
|
||||
/>
|
||||
@ -129,7 +129,7 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
|
||||
<CustomEmailAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issue.id}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, val)}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])}
|
||||
projectId={issue.project}
|
||||
value={attributeValue ? attributeValue?.[0]?.value : undefined}
|
||||
/>
|
||||
@ -138,16 +138,26 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
|
||||
<CustomFileAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issue.id}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, val)}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])}
|
||||
projectId={issue.project}
|
||||
value={attributeValue ? attributeValue?.[0]?.value : undefined}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "multi_select" && (
|
||||
<CustomSelectAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issue.id}
|
||||
onChange={(val: string[]) => handleAttributeUpdate(attribute.id, val)}
|
||||
projectId={issue.project}
|
||||
value={Array.isArray(attributeValue) ? attributeValue.map((v) => v.value) : []}
|
||||
multiple
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "number" && (
|
||||
<CustomNumberAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issue.id}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, val)}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])}
|
||||
projectId={issue.project}
|
||||
value={
|
||||
attributeValue ? parseInt(attributeValue?.[0]?.value ?? "0", 10) : undefined
|
||||
@ -158,7 +168,7 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
|
||||
<CustomRelationAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issue.id}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, val)}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])}
|
||||
projectId={issue.project}
|
||||
value={attributeValue ? attributeValue?.[0]?.value : undefined}
|
||||
/>
|
||||
@ -167,7 +177,7 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
|
||||
<CustomSelectAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issue.id}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, val)}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])}
|
||||
projectId={issue.project}
|
||||
value={attributeValue ? attributeValue?.[0]?.value : undefined}
|
||||
/>
|
||||
@ -176,7 +186,7 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
|
||||
<CustomTextAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issue.id}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, val)}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])}
|
||||
projectId={issue.project}
|
||||
value={attributeValue ? attributeValue?.[0].value : undefined}
|
||||
/>
|
||||
@ -185,7 +195,7 @@ export const SidebarCustomAttributesList: React.FC<Props> = observer(({ issue })
|
||||
<CustomUrlAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issue.id}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, val)}
|
||||
onChange={(val: string) => handleAttributeUpdate(attribute.id, [val])}
|
||||
projectId={issue.project}
|
||||
value={attributeValue ? attributeValue?.[0]?.value : undefined}
|
||||
/>
|
||||
|
@ -39,11 +39,9 @@ export const ObjectModal: React.FC<Props> = observer(
|
||||
createEntity,
|
||||
createEntityAttribute,
|
||||
createEntityAttributeLoader,
|
||||
deleteEntityAttribute,
|
||||
entityAttributes,
|
||||
fetchEntityDetails,
|
||||
fetchEntityDetailsLoader,
|
||||
updateEntityAttribute,
|
||||
} = customAttributesStore;
|
||||
|
||||
const handleClose = () => {
|
||||
@ -88,18 +86,6 @@ export const ObjectModal: React.FC<Props> = observer(
|
||||
await createEntityAttribute(workspaceSlug.toString(), { ...payload, parent: object.id });
|
||||
};
|
||||
|
||||
const handleUpdateAttribute = async (attributeId: string, data: Partial<ICustomAttribute>) => {
|
||||
if (!workspaceSlug || !object || !object.id) return;
|
||||
|
||||
await updateEntityAttribute(workspaceSlug.toString(), object.id, attributeId, data);
|
||||
};
|
||||
|
||||
const handleDeleteAttribute = async (attributeId: string) => {
|
||||
if (!workspaceSlug || !object || !object.id) return;
|
||||
|
||||
await deleteEntityAttribute(workspaceSlug.toString(), object.id, attributeId);
|
||||
};
|
||||
|
||||
// fetch the object details if object state has id
|
||||
useEffect(() => {
|
||||
if (!object.id || object.id === "") return;
|
||||
@ -193,11 +179,7 @@ export const ObjectModal: React.FC<Props> = observer(
|
||||
return (
|
||||
<AttributeForm
|
||||
key={attributeId}
|
||||
data={attribute}
|
||||
handleDeleteAttribute={() => handleDeleteAttribute(attributeId)}
|
||||
handleUpdateAttribute={async (data) =>
|
||||
await handleUpdateAttribute(attributeId, data)
|
||||
}
|
||||
attributeDetails={attribute}
|
||||
objectId={object.id ?? ""}
|
||||
type={attribute.type}
|
||||
/>
|
||||
|
@ -84,8 +84,9 @@ export const CUSTOM_ATTRIBUTES_LIST: {
|
||||
},
|
||||
},
|
||||
multi_select: {
|
||||
defaultFormValues: { default_value: "", display_name: "", is_required: false },
|
||||
defaultFormValues: { default_value: "", display_name: "", is_multi: true, is_required: false },
|
||||
icon: Disc,
|
||||
initialPayload: { is_multi: true },
|
||||
label: "Multi Select",
|
||||
},
|
||||
number: {
|
||||
|
@ -63,7 +63,10 @@ class CustomAttributeValuesStore {
|
||||
|
||||
newChildren.map((child) => {
|
||||
if (attributesToUpdate.includes(child.id) && child)
|
||||
child.prop_value = [{ type: 0, value: data.issue_properties[child.id] }];
|
||||
child.prop_value = data.issue_properties[child.id].map((value) => ({
|
||||
type: 0,
|
||||
value,
|
||||
}));
|
||||
|
||||
return child;
|
||||
});
|
||||
|
2
web/types/custom-attributes.d.ts
vendored
2
web/types/custom-attributes.d.ts
vendored
@ -81,7 +81,7 @@ export interface ICustomAttributeValue {
|
||||
|
||||
export interface ICustomAttributeValueFormData {
|
||||
issue_properties: {
|
||||
[key: string]: string;
|
||||
[key: string]: string[];
|
||||
};
|
||||
a_epoch?: number;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user