forked from github/plane
chore: set default values on the issue modal
This commit is contained in:
parent
4a13b30874
commit
8ff1e8dd87
@ -26,7 +26,6 @@ const checkboxAttributeRepresentations = [
|
||||
export const CheckboxAttributeForm: React.FC<FormComponentProps> = ({ control }) => (
|
||||
<>
|
||||
<div className="space-y-3">
|
||||
{" "}
|
||||
<Controller
|
||||
control={control}
|
||||
name="display_name"
|
||||
@ -36,30 +35,39 @@ export const CheckboxAttributeForm: React.FC<FormComponentProps> = ({ control })
|
||||
/>
|
||||
<div>
|
||||
<p className="text-xs">Default value</p>
|
||||
<div className="mt-2 flex items-center gap-6 accent-custom-primary-100">
|
||||
<div className="flex items-center gap-1 text-xs">
|
||||
<input
|
||||
type="radio"
|
||||
name="default_value"
|
||||
value="true"
|
||||
id="checked"
|
||||
className="scale-75"
|
||||
defaultChecked
|
||||
/>
|
||||
<label htmlFor="checked">Checked</label>
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="default_value"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<div className="mt-2 flex items-center gap-6 accent-custom-primary-100">
|
||||
<div className="flex items-center gap-1 text-xs">
|
||||
<input
|
||||
type="radio"
|
||||
name="default_value"
|
||||
value="true"
|
||||
id="checked"
|
||||
className="scale-75"
|
||||
defaultChecked={value === "true"}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
<label htmlFor="checked">Checked</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1 text-xs">
|
||||
<input
|
||||
type="radio"
|
||||
name="default_value"
|
||||
value="false"
|
||||
id="unchecked"
|
||||
className="scale-75"
|
||||
/>
|
||||
<label htmlFor="unchecked">Unchecked</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-xs">
|
||||
<input
|
||||
type="radio"
|
||||
name="default_value"
|
||||
value="false"
|
||||
id="unchecked"
|
||||
className="scale-75"
|
||||
defaultChecked={value === "false"}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
<label htmlFor="unchecked">Unchecked</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-3 border-t-[0.5px] border-custom-border-200" />
|
||||
|
@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { CustomCheckboxAttribute } from "components/custom-attributes";
|
||||
// ui
|
||||
import { Loader, Tooltip } from "components/ui";
|
||||
import { Tooltip } from "components/ui";
|
||||
|
||||
type Props = {
|
||||
entityId: string;
|
||||
@ -24,35 +24,23 @@ export const CustomAttributesCheckboxes: React.FC<Props> = observer((props) => {
|
||||
const checkboxFields = Object.values(attributes).filter((a) => a.type === "checkbox");
|
||||
|
||||
return (
|
||||
<>
|
||||
{customAttributes.fetchEntityDetailsLoader ? (
|
||||
<Loader className="space-y-3.5">
|
||||
<Loader.Item height="35px" />
|
||||
<Loader.Item height="35px" />
|
||||
<Loader.Item height="35px" />
|
||||
</Loader>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{Object.entries(checkboxFields).map(([attributeId, attribute]) => (
|
||||
<div key={attributeId} className="flex items-center gap-2">
|
||||
<Tooltip tooltipContent={attribute.display_name} position="top-left">
|
||||
<p className="text-xs text-custom-text-300 w-2/5 truncate">
|
||||
{attribute.display_name}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<div className="w-3/5 flex-shrink-0">
|
||||
<CustomCheckboxAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, [`${val}`])}
|
||||
projectId={projectId}
|
||||
value={values[attribute.id]?.[0] === "true" ? true : false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="space-y-4">
|
||||
{Object.entries(checkboxFields).map(([attributeId, attribute]) => (
|
||||
<div key={attributeId} className="flex items-center gap-2">
|
||||
<Tooltip tooltipContent={attribute.display_name} position="top-left">
|
||||
<p className="text-xs text-custom-text-300 w-2/5 truncate">{attribute.display_name}</p>
|
||||
</Tooltip>
|
||||
<div className="w-3/5 flex-shrink-0">
|
||||
<CustomCheckboxAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, [`${val}`])}
|
||||
projectId={projectId}
|
||||
value={values[attribute.id]?.[0] === "true" ? true : false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ import { observer } from "mobx-react-lite";
|
||||
// headless ui
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
// ui
|
||||
import { Loader, ToggleSwitch } from "components/ui";
|
||||
import { ToggleSwitch } from "components/ui";
|
||||
// icons
|
||||
import { ChevronDown } from "lucide-react";
|
||||
// types
|
||||
@ -36,63 +36,53 @@ export const CustomAttributesDescriptionFields: React.FC<Props> = observer((prop
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{customAttributes.fetchEntityDetailsLoader ? (
|
||||
<Loader className="space-y-3.5">
|
||||
<Loader.Item height="35px" />
|
||||
<Loader.Item height="35px" />
|
||||
<Loader.Item height="35px" />
|
||||
</Loader>
|
||||
) : (
|
||||
<Disclosure defaultOpen>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Disclosure.Button className="font-medium flex items-center gap-2">
|
||||
<ChevronDown
|
||||
className={`transition-all ${open ? "" : "-rotate-90"}`}
|
||||
size={14}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
Form Attributes
|
||||
</Disclosure.Button>
|
||||
<div className={`flex items-center gap-1 ${open ? "" : "hidden"}`}>
|
||||
<span className="text-xs">Hide optional fields</span>
|
||||
<ToggleSwitch
|
||||
value={hideOptionalFields}
|
||||
onChange={() => setHideOptionalFields((prev) => !prev)}
|
||||
/>
|
||||
</div>
|
||||
<Disclosure defaultOpen>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Disclosure.Button className="font-medium flex items-center gap-2">
|
||||
<ChevronDown
|
||||
className={`transition-all ${open ? "" : "-rotate-90"}`}
|
||||
size={14}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
Custom Attributes
|
||||
</Disclosure.Button>
|
||||
<div className={`flex items-center gap-1 ${open ? "" : "hidden"}`}>
|
||||
<span className="text-xs">Hide optional fields</span>
|
||||
<ToggleSwitch
|
||||
value={hideOptionalFields}
|
||||
onChange={() => setHideOptionalFields((prev) => !prev)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Disclosure.Panel className="space-y-3.5 mt-2">
|
||||
{Object.entries(descriptionFields).map(([attributeId, attribute]) => (
|
||||
<div
|
||||
key={attributeId}
|
||||
className={hideOptionalFields && attribute.is_required ? "hidden" : ""}
|
||||
>
|
||||
<input
|
||||
type={attribute.type}
|
||||
className="border border-custom-border-200 rounded w-full px-2 py-1.5 text-xs placeholder:text-custom-text-400 focus:outline-none"
|
||||
placeholder={attribute.display_name}
|
||||
min={attribute.extra_settings.divided_by ? 0 : undefined}
|
||||
max={attribute.extra_settings.divided_by ?? undefined}
|
||||
value={values[attribute.id]?.[0]}
|
||||
onChange={(e) => onChange(attribute.id, e.target.value)}
|
||||
required={attribute.is_required}
|
||||
/>
|
||||
{attribute.type === "number" &&
|
||||
attribute.extra_settings?.representation !== "numerical" && (
|
||||
<span className="text-custom-text-400 text-[10px]">
|
||||
Maximum value: {attribute.extra_settings?.divided_by}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Disclosure.Panel className="space-y-3.5 mt-2">
|
||||
{Object.entries(descriptionFields).map(([attributeId, attribute]) => (
|
||||
<div
|
||||
key={attributeId}
|
||||
className={hideOptionalFields && attribute.is_required ? "hidden" : ""}
|
||||
>
|
||||
<input
|
||||
type={attribute.type}
|
||||
className="border border-custom-border-200 rounded w-full px-2 py-1.5 text-xs placeholder:text-custom-text-400 focus:outline-none"
|
||||
placeholder={attribute.display_name}
|
||||
min={attribute.extra_settings.divided_by ? 0 : undefined}
|
||||
max={attribute.extra_settings.divided_by ?? undefined}
|
||||
value={values[attribute.id]?.[0] ?? attribute.default_value}
|
||||
onChange={(e) => onChange(attribute.id, e.target.value)}
|
||||
required={attribute.is_required}
|
||||
/>
|
||||
{attribute.type === "number" &&
|
||||
attribute.extra_settings?.representation !== "numerical" && (
|
||||
<span className="text-custom-text-400 text-[10px]">
|
||||
Maximum value: {attribute.extra_settings?.divided_by}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
))}
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</Disclosure>
|
||||
);
|
||||
});
|
||||
|
@ -16,7 +16,7 @@ import useWorkspaceDetails from "hooks/use-workspace-details";
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
// ui
|
||||
import { Loader, ToggleSwitch, Tooltip } from "components/ui";
|
||||
import { Loader, Tooltip } from "components/ui";
|
||||
// icons
|
||||
import { ChevronDown, Plus, Trash2 } from "lucide-react";
|
||||
import { getFileIcon } from "components/icons";
|
||||
@ -203,23 +203,14 @@ export const CustomAttributesFileUploads: React.FC<Props> = observer((props) =>
|
||||
<Disclosure defaultOpen>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Disclosure.Button className="font-medium flex items-center gap-2">
|
||||
<ChevronDown
|
||||
className={`transition-all ${open ? "" : "-rotate-90"}`}
|
||||
size={14}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
Attachment attributes
|
||||
</Disclosure.Button>
|
||||
{/* <div className={`flex items-center gap-1 ${open ? "" : "hidden"}`}>
|
||||
<span className="text-xs">Hide optional fields</span>
|
||||
<ToggleSwitch
|
||||
value={hideOptionalFields}
|
||||
onChange={() => setHideOptionalFields((prev) => !prev)}
|
||||
/>
|
||||
</div> */}
|
||||
</div>
|
||||
<Disclosure.Button className="font-medium flex items-center gap-2">
|
||||
<ChevronDown
|
||||
className={`transition-all ${open ? "" : "-rotate-90"}`}
|
||||
size={14}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
Attachment attributes
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel className="mt-2 grid grid-cols-3 gap-4">
|
||||
{Object.entries(fileUploadFields).map(([attributeId, attribute]) => (
|
||||
<div key={attributeId}>
|
||||
|
@ -41,66 +41,62 @@ export const CustomAttributesSelectFields: React.FC<Props> = observer((props) =>
|
||||
<Loader.Item height="27px" width="90px" />
|
||||
</Loader>
|
||||
) : (
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{Object.entries(selectFields).map(([attributeId, attribute]) => (
|
||||
<div key={attributeId}>
|
||||
{attribute.type === "datetime" && (
|
||||
<CustomDateTimeAttribute
|
||||
attributeDetails={attribute}
|
||||
className="bg-transparent border border-custom-border-200 py-1 shadow-custom-shadow-2xs"
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, val ? [val.toISOString()] : undefined)}
|
||||
projectId={projectId}
|
||||
value={
|
||||
values[attribute.id]?.[0] ? new Date(values[attribute.id]?.[0]) : undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "file" && (
|
||||
<CustomFileAttribute
|
||||
attributeDetails={attribute}
|
||||
className="bg-transparent border border-custom-border-200 py-1 shadow-custom-shadow-2xs"
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, val)}
|
||||
projectId={projectId}
|
||||
value={values[attribute.id]?.[0]}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "multi_select" && (
|
||||
<CustomSelectAttribute
|
||||
attributeDetails={attribute}
|
||||
className="bg-transparent border border-custom-border-200 py-1 shadow-custom-shadow-2xs"
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, val)}
|
||||
projectId={projectId}
|
||||
value={values[attribute.id] ?? []}
|
||||
multiple
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "relation" && (
|
||||
<CustomRelationAttribute
|
||||
attributeDetails={attribute}
|
||||
className="bg-transparent border border-custom-border-200 py-1 shadow-custom-shadow-2xs"
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, val)}
|
||||
projectId={projectId}
|
||||
value={values[attribute.id]?.[0]}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "select" && (
|
||||
<CustomSelectAttribute
|
||||
attributeDetails={attribute}
|
||||
className="bg-transparent border border-custom-border-200 py-1 shadow-custom-shadow-2xs"
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, val)}
|
||||
projectId={projectId}
|
||||
value={values[attribute.id]?.[0]}
|
||||
multiple={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
Object.entries(selectFields).map(([attributeId, attribute]) => (
|
||||
<div key={attributeId}>
|
||||
{attribute.type === "datetime" && (
|
||||
<CustomDateTimeAttribute
|
||||
attributeDetails={attribute}
|
||||
className="bg-transparent border border-custom-border-200 py-1 shadow-custom-shadow-2xs"
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, val ? [val.toISOString()] : undefined)}
|
||||
projectId={projectId}
|
||||
value={values[attribute.id]?.[0] ? new Date(values[attribute.id]?.[0]) : undefined}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "file" && (
|
||||
<CustomFileAttribute
|
||||
attributeDetails={attribute}
|
||||
className="bg-transparent border border-custom-border-200 py-1 shadow-custom-shadow-2xs"
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, val)}
|
||||
projectId={projectId}
|
||||
value={values[attribute.id]?.[0]}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "multi_select" && (
|
||||
<CustomSelectAttribute
|
||||
attributeDetails={attribute}
|
||||
className="bg-transparent border border-custom-border-200 py-1 shadow-custom-shadow-2xs"
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, val)}
|
||||
projectId={projectId}
|
||||
value={values[attribute.id] ?? []}
|
||||
multiple
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "relation" && (
|
||||
<CustomRelationAttribute
|
||||
attributeDetails={attribute}
|
||||
className="bg-transparent border border-custom-border-200 py-1 shadow-custom-shadow-2xs"
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, val)}
|
||||
projectId={projectId}
|
||||
value={values[attribute.id]?.[0]}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "select" && (
|
||||
<CustomSelectAttribute
|
||||
attributeDetails={attribute}
|
||||
className="bg-transparent border border-custom-border-200 py-1 shadow-custom-shadow-2xs"
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, val)}
|
||||
projectId={projectId}
|
||||
value={values[attribute.id]?.[0]}
|
||||
multiple={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -33,7 +33,14 @@ import {
|
||||
ObjectsSelect,
|
||||
} from "components/custom-attributes";
|
||||
// ui
|
||||
import { CustomMenu, Input, PrimaryButton, SecondaryButton, ToggleSwitch } from "components/ui";
|
||||
import {
|
||||
CustomMenu,
|
||||
Input,
|
||||
Loader,
|
||||
PrimaryButton,
|
||||
SecondaryButton,
|
||||
ToggleSwitch,
|
||||
} from "components/ui";
|
||||
import { TipTapEditor } from "components/tiptap";
|
||||
// icons
|
||||
import { SparklesIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
@ -253,6 +260,20 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
||||
}
|
||||
}, [customAttributes, entityId, workspaceSlug]);
|
||||
|
||||
// assign default values to attributes
|
||||
useEffect(() => {
|
||||
if (
|
||||
!entityId ||
|
||||
!customAttributes.entityAttributes[entityId] ||
|
||||
Object.keys(customAttributesList).length > 0
|
||||
)
|
||||
return;
|
||||
|
||||
Object.values(customAttributes.entityAttributes[entityId]).forEach((attribute) => {
|
||||
handleCustomAttributesChange(attribute.id, attribute.default_value);
|
||||
});
|
||||
}, [customAttributes, customAttributesList, entityId, handleCustomAttributesChange]);
|
||||
|
||||
// fetch issue attribute values
|
||||
useEffect(() => {
|
||||
if (!initialData || !initialData.id) return;
|
||||
@ -476,29 +497,39 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
||||
</div>
|
||||
)}
|
||||
{entityId !== null && (
|
||||
<div className="space-y-5">
|
||||
<CustomAttributesDescriptionFields
|
||||
entityId={entityId ?? ""}
|
||||
issueId={watch("id") ?? ""}
|
||||
onChange={handleCustomAttributesChange}
|
||||
projectId={projectId}
|
||||
values={customAttributesList}
|
||||
/>
|
||||
<CustomAttributesCheckboxes
|
||||
entityId={entityId ?? ""}
|
||||
issueId={watch("id") ?? ""}
|
||||
onChange={handleCustomAttributesChange}
|
||||
projectId={projectId}
|
||||
values={customAttributesList}
|
||||
/>
|
||||
<CustomAttributesFileUploads
|
||||
entityId={entityId ?? ""}
|
||||
issueId={watch("id") ?? ""}
|
||||
onChange={handleCustomAttributesChange}
|
||||
projectId={projectId}
|
||||
values={customAttributesList}
|
||||
/>
|
||||
</div>
|
||||
<>
|
||||
{customAttributes.fetchEntityDetailsLoader ? (
|
||||
<Loader className="space-y-3.5">
|
||||
<Loader.Item height="35px" />
|
||||
<Loader.Item height="35px" />
|
||||
<Loader.Item height="35px" />
|
||||
</Loader>
|
||||
) : (
|
||||
<div className="space-y-5">
|
||||
<CustomAttributesDescriptionFields
|
||||
entityId={entityId ?? ""}
|
||||
issueId={watch("id") ?? ""}
|
||||
onChange={handleCustomAttributesChange}
|
||||
projectId={projectId}
|
||||
values={customAttributesList}
|
||||
/>
|
||||
<CustomAttributesCheckboxes
|
||||
entityId={entityId ?? ""}
|
||||
issueId={watch("id") ?? ""}
|
||||
onChange={handleCustomAttributesChange}
|
||||
projectId={projectId}
|
||||
values={customAttributesList}
|
||||
/>
|
||||
<CustomAttributesFileUploads
|
||||
entityId={entityId ?? ""}
|
||||
issueId={watch("id") ?? ""}
|
||||
onChange={handleCustomAttributesChange}
|
||||
projectId={projectId}
|
||||
values={customAttributesList}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -33,7 +33,7 @@ export const CUSTOM_ATTRIBUTES_LIST: {
|
||||
},
|
||||
icon: CheckCircle,
|
||||
initialPayload: {
|
||||
default_value: "checked",
|
||||
default_value: "true",
|
||||
extra_settings: {
|
||||
representation: "check",
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user