refactor: issue modal custom attributes

This commit is contained in:
Aaryan Khandelwal 2023-09-20 12:47:48 +05:30
parent d04eac30b0
commit 4842fc8e58
11 changed files with 535 additions and 383 deletions

View File

@ -1,3 +1,3 @@
export * from "./issue-modal-attributes-list";
export * from "./issue-modal";
export * from "./peek-overview-custom-attributes-list";
export * from "./sidebar-custom-attributes-list";

View File

@ -1,197 +0,0 @@
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
// mobx
import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite";
// headless ui
import { Disclosure } from "@headlessui/react";
// components
import {
CustomCheckboxAttribute,
CustomDateTimeAttribute,
CustomFileAttribute,
CustomRelationAttribute,
CustomSelectAttribute,
} from "components/custom-attributes";
// ui
import { Loader, ToggleSwitch } from "components/ui";
// icons
import { ChevronDown } from "lucide-react";
// types
import { TCustomAttributeTypes } from "types";
type Props = {
entityId: string;
issueId: string;
onChange: (attributeId: string, val: string | string[] | undefined) => void;
projectId: string;
values: { [key: string]: string[] };
};
const DESCRIPTION_FIELDS: TCustomAttributeTypes[] = ["email", "number", "text", "url"];
export const IssueModalCustomAttributesList: React.FC<Props> = observer((props) => {
const { entityId, issueId, onChange, projectId, values } = props;
const [hideOptionalFields, setHideOptionalFields] = useState(false);
const router = useRouter();
const { workspaceSlug } = router.query;
const { customAttributes: customAttributesStore } = useMobxStore();
const { entityAttributes, fetchEntityDetails, fetchEntityDetailsLoader } = customAttributesStore;
const attributes = entityAttributes[entityId] ?? {};
// fetch entity details
useEffect(() => {
if (!entityAttributes[entityId]) {
if (!workspaceSlug) return;
fetchEntityDetails(workspaceSlug.toString(), entityId);
}
}, [entityAttributes, entityId, fetchEntityDetails, workspaceSlug]);
const descriptionFields = Object.values(attributes).filter((a) =>
DESCRIPTION_FIELDS.includes(a.type)
);
const nonDescriptionFields = Object.values(attributes).filter(
(a) => !DESCRIPTION_FIELDS.includes(a.type)
);
return (
<>
{fetchEntityDetailsLoader ? (
<Loader className="flex items-center gap-2">
<Loader.Item height="27px" width="90px" />
<Loader.Item height="27px" width="90px" />
<Loader.Item height="27px" width="90px" />
</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}
/>
Description Fields
</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] ?? 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>
<div className="flex items-center gap-2 flex-wrap mt-3.5">
{Object.entries(nonDescriptionFields).map(([attributeId, attribute]) => (
<div key={attributeId}>
{attribute.type === "checkbox" && (
<CustomCheckboxAttribute
attributeDetails={attribute}
issueId={issueId}
onChange={(val) => onChange(attribute.id, [`${val}`])}
projectId={projectId}
value={values[attribute.id]?.[0] === "true" ? true : false}
/>
)}
{attribute.type === "datetime" && (
<CustomDateTimeAttribute
attributeDetails={attribute}
className="bg-transparent border border-custom-border-200 py-1"
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"
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"
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"
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"
issueId={issueId}
onChange={(val) => onChange(attribute.id, val)}
projectId={projectId}
value={values[attribute.id]?.[0]}
multiple={false}
/>
)}
</div>
))}
</div>
</>
)}
</>
);
});

View File

@ -0,0 +1,80 @@
import { useEffect } from "react";
import { useRouter } from "next/router";
// mobx
import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite";
// components
import { CustomCheckboxAttribute } from "components/custom-attributes";
// ui
import { Loader, Tooltip } from "components/ui";
type Props = {
entityId: string;
issueId: string;
onChange: (attributeId: string, val: string | string[] | undefined) => void;
projectId: string;
values: { [key: string]: string[] };
};
export const CustomAttributesCheckboxes: React.FC<Props> = observer((props) => {
const { entityId, issueId, onChange, projectId, values } = props;
const router = useRouter();
const { workspaceSlug } = router.query;
const { customAttributes: customAttributesStore } = useMobxStore();
const { entityAttributes, fetchEntityDetails, fetchEntityDetailsLoader } = customAttributesStore;
const attributes = entityAttributes[entityId] ?? {};
// fetch entity details
useEffect(() => {
if (!entityAttributes[entityId]) {
if (!workspaceSlug) return;
fetchEntityDetails(workspaceSlug.toString(), entityId);
}
}, [entityAttributes, entityId, fetchEntityDetails, workspaceSlug]);
const checkboxFields = Object.values(attributes).filter((a) => a.type === "checkbox");
return (
<>
{fetchEntityDetailsLoader ? (
<Loader className="flex items-center gap-2">
<Loader.Item height="27px" width="90px" />
<Loader.Item height="27px" width="90px" />
<Loader.Item height="27px" width="90px" />
</Loader>
) : (
<div>
<h5 className="text-sm">Checkboxes</h5>
<div className="mt-3.5 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-1/3 truncate">
{attribute.display_name}
</p>
</Tooltip>
<div className="w-2/3 flex-shrink-0">
{attribute.type === "checkbox" && (
<CustomCheckboxAttribute
attributeDetails={attribute}
issueId={issueId}
onChange={(val) => onChange(attribute.id, [`${val}`])}
projectId={projectId}
value={values[attribute.id]?.[0] === "true" ? true : false}
/>
)}
</div>
</div>
))}
</div>
</div>
)}
</>
);
});

View File

@ -0,0 +1,113 @@
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
// mobx
import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite";
// headless ui
import { Disclosure } from "@headlessui/react";
// ui
import { Loader, ToggleSwitch } from "components/ui";
// icons
import { ChevronDown } from "lucide-react";
// types
import { TCustomAttributeTypes } from "types";
type Props = {
entityId: string;
issueId: string;
onChange: (attributeId: string, val: string | string[] | undefined) => void;
projectId: string;
values: { [key: string]: string[] };
};
const DESCRIPTION_FIELDS: TCustomAttributeTypes[] = ["email", "number", "text", "url"];
export const CustomAttributesDescriptionFields: React.FC<Props> = observer((props) => {
const { entityId, issueId, onChange, projectId, values } = props;
const [hideOptionalFields, setHideOptionalFields] = useState(false);
const router = useRouter();
const { workspaceSlug } = router.query;
const { customAttributes: customAttributesStore } = useMobxStore();
const { entityAttributes, fetchEntityDetails, fetchEntityDetailsLoader } = customAttributesStore;
const attributes = entityAttributes[entityId] ?? {};
// fetch entity details
useEffect(() => {
if (!entityAttributes[entityId]) {
if (!workspaceSlug) return;
fetchEntityDetails(workspaceSlug.toString(), entityId);
}
}, [entityAttributes, entityId, fetchEntityDetails, workspaceSlug]);
const descriptionFields = Object.values(attributes).filter((a) =>
DESCRIPTION_FIELDS.includes(a.type)
);
return (
<>
{fetchEntityDetailsLoader ? (
<Loader className="flex items-center gap-2">
<Loader.Item height="27px" width="90px" />
<Loader.Item height="27px" width="90px" />
<Loader.Item height="27px" width="90px" />
</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}
/>
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] ?? 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>
)}
</>
);
});

View File

@ -0,0 +1,3 @@
export * from "./checkboxes";
export * from "./description-fields";
export * from "./select-fields";

View File

@ -0,0 +1,124 @@
import { useEffect } from "react";
import { useRouter } from "next/router";
// mobx
import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite";
// components
import {
CustomDateTimeAttribute,
CustomFileAttribute,
CustomRelationAttribute,
CustomSelectAttribute,
} from "components/custom-attributes";
// ui
import { Loader } from "components/ui";
// types
import { TCustomAttributeTypes } from "types";
type Props = {
entityId: string;
issueId: string;
onChange: (attributeId: string, val: string | string[] | undefined) => void;
projectId: string;
values: { [key: string]: string[] };
};
const SELECT_FIELDS: TCustomAttributeTypes[] = ["datetime", "multi_select", "relation", "select"];
export const CustomAttributesSelectFields: React.FC<Props> = observer((props) => {
const { entityId, issueId, onChange, projectId, values } = props;
const router = useRouter();
const { workspaceSlug } = router.query;
const { customAttributes: customAttributesStore } = useMobxStore();
const { entityAttributes, fetchEntityDetails, fetchEntityDetailsLoader } = customAttributesStore;
const attributes = entityAttributes[entityId] ?? {};
// fetch entity details
useEffect(() => {
if (!entityAttributes[entityId]) {
if (!workspaceSlug) return;
fetchEntityDetails(workspaceSlug.toString(), entityId);
}
}, [entityAttributes, entityId, fetchEntityDetails, workspaceSlug]);
const selectFields = Object.values(attributes).filter((a) => SELECT_FIELDS.includes(a.type));
return (
<>
{fetchEntityDetailsLoader ? (
<Loader className="flex items-center gap-2">
<Loader.Item height="27px" width="90px" />
<Loader.Item height="27px" width="90px" />
<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>
)}
</>
);
});

View File

@ -226,19 +226,27 @@ export const ObjectModal: React.FC<Props> = observer(
</Loader>
)}
</div>
<div className="mt-3">
<TypesDropdown onClick={handleCreateEntityAttribute} />
</div>
</div>
)}
</div>
<div className="flex items-center justify-end gap-3 px-6 py-5 border-t border-custom-border-200">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
{!object.id && (
<PrimaryButton onClick={handleCreateObject} loading={isCreatingObject}>
{isCreatingObject ? "Creating..." : "Create Object"}
</PrimaryButton>
<div
className={`flex items-center gap-3 px-6 py-5 border-t border-custom-border-200 ${
object.id ? "justify-between" : "justify-end"
}`}
>
{object.id && (
<div className="flex-shrink-0">
<TypesDropdown onClick={handleCreateEntityAttribute} />
</div>
)}
<div className="flex items-center gap-3">
<SecondaryButton onClick={handleClose}>Close</SecondaryButton>
{!object.id && (
<PrimaryButton onClick={handleCreateObject} loading={isCreatingObject}>
{isCreatingObject ? "Creating..." : "Create Object"}
</PrimaryButton>
)}
</div>
</div>
</Dialog.Panel>
</Transition.Child>

View File

@ -4,7 +4,6 @@ import { useRouter } from "next/router";
// mobx
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// react-hook-form
import { Controller, useForm } from "react-hook-form";
// services
@ -25,7 +24,12 @@ import {
} from "components/issues/select";
import { CreateStateModal } from "components/states";
import { CreateLabelModal } from "components/labels";
import { IssueModalCustomAttributesList, ObjectsSelect } from "components/custom-attributes";
import {
CustomAttributesCheckboxes,
CustomAttributesDescriptionFields,
CustomAttributesSelectFields,
ObjectsSelect,
} from "components/custom-attributes";
// ui
import { CustomMenu, Input, PrimaryButton, SecondaryButton, ToggleSwitch } from "components/ui";
import { TipTapEditor } from "components/tiptap";
@ -253,7 +257,7 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
</>
)}
<form onSubmit={handleSubmit(handleCreateUpdateIssue)}>
<div className="space-y-5">
<div className="space-y-5 p-5">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-x-2">
{(fieldsToShow.includes("all") || fieldsToShow.includes("project")) && (
@ -410,153 +414,16 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
/>
</div>
)}
<div className="flex flex-wrap items-center gap-2">
{(fieldsToShow.includes("all") || fieldsToShow.includes("state")) && (
<Controller
control={control}
name="state"
render={({ field: { value, onChange } }) => (
<IssueStateSelect
setIsOpen={setStateModal}
value={value}
onChange={onChange}
projectId={projectId}
/>
)}
/>
)}
{/* default object properties */}
{watch("entity") === null && (
<>
{(fieldsToShow.includes("all") || fieldsToShow.includes("priority")) && (
<Controller
control={control}
name="priority"
render={({ field: { value, onChange } }) => (
<IssuePrioritySelect value={value} onChange={onChange} />
)}
/>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("assignee")) && (
<Controller
control={control}
name="assignees"
render={({ field: { value, onChange } }) => (
<IssueAssigneeSelect
projectId={projectId}
value={value}
onChange={onChange}
/>
)}
/>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("label")) && (
<Controller
control={control}
name="labels"
render={({ field: { value, onChange } }) => (
<IssueLabelSelect
setIsOpen={setLabelModal}
value={value}
onChange={onChange}
projectId={projectId}
/>
)}
/>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("startDate")) && (
<div>
<Controller
control={control}
name="start_date"
render={({ field: { value, onChange } }) => (
<IssueDateSelect
label="Start date"
maxDate={maxDate ?? undefined}
onChange={onChange}
value={value}
/>
)}
/>
</div>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("dueDate")) && (
<div>
<Controller
control={control}
name="target_date"
render={({ field: { value, onChange } }) => (
<IssueDateSelect
label="Due date"
minDate={minDate ?? undefined}
onChange={onChange}
value={value}
/>
)}
/>
</div>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("estimate")) && (
<div>
<Controller
control={control}
name="estimate_point"
render={({ field: { value, onChange } }) => (
<IssueEstimateSelect value={value} onChange={onChange} />
)}
/>
</div>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && (
<Controller
control={control}
name="parent"
render={({ field: { onChange } }) => (
<ParentIssuesListModal
isOpen={parentIssueListModalOpen}
handleClose={() => setParentIssueListModalOpen(false)}
onChange={(issue) => {
onChange(issue.id);
setSelectedParentIssue(issue);
}}
projectId={projectId}
/>
)}
/>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && (
<CustomMenu ellipsis>
{watch("parent") ? (
<>
<CustomMenu.MenuItem
renderAs="button"
onClick={() => setParentIssueListModalOpen(true)}
>
Change parent issue
</CustomMenu.MenuItem>
<CustomMenu.MenuItem
renderAs="button"
onClick={() => setValue("parent", null)}
>
Remove parent issue
</CustomMenu.MenuItem>
</>
) : (
<CustomMenu.MenuItem
renderAs="button"
onClick={() => setParentIssueListModalOpen(true)}
>
Select Parent Issue
</CustomMenu.MenuItem>
)}
</CustomMenu>
)}
</>
)}
</div>
{watch("entity") !== null && (
<div>
<IssueModalCustomAttributesList
<div className="space-y-5">
<CustomAttributesDescriptionFields
entityId={watch("entity") ?? ""}
issueId={watch("id") ?? ""}
onChange={handleCustomAttributesChange}
projectId={projectId}
values={customAttributesList}
/>
<CustomAttributesCheckboxes
entityId={watch("entity") ?? ""}
issueId={watch("id") ?? ""}
onChange={handleCustomAttributesChange}
@ -568,31 +435,185 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
</div>
</div>
</div>
<div className="-mx-5 mt-5 flex items-center justify-between gap-2 border-t border-custom-border-200 px-5 pt-5">
<div
className="flex cursor-pointer items-center gap-1"
onClick={() => setCreateMore((prevData) => !prevData)}
>
<span className="text-xs">Create more</span>
<ToggleSwitch value={createMore} onChange={() => {}} size="sm" />
<div className="space-y-4 px-5 py-4 border-t border-custom-border-200">
<div className="flex items-center gap-2 flex-wrap">
{(fieldsToShow.includes("all") || fieldsToShow.includes("state")) && (
<Controller
control={control}
name="state"
render={({ field: { value, onChange } }) => (
<IssueStateSelect
setIsOpen={setStateModal}
value={value}
onChange={onChange}
projectId={projectId}
/>
)}
/>
)}
{/* default object properties */}
{watch("entity") === null ? (
<>
{(fieldsToShow.includes("all") || fieldsToShow.includes("priority")) && (
<Controller
control={control}
name="priority"
render={({ field: { value, onChange } }) => (
<IssuePrioritySelect value={value} onChange={onChange} />
)}
/>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("assignee")) && (
<Controller
control={control}
name="assignees"
render={({ field: { value, onChange } }) => (
<IssueAssigneeSelect
projectId={projectId}
value={value}
onChange={onChange}
/>
)}
/>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("label")) && (
<Controller
control={control}
name="labels"
render={({ field: { value, onChange } }) => (
<IssueLabelSelect
setIsOpen={setLabelModal}
value={value}
onChange={onChange}
projectId={projectId}
/>
)}
/>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("startDate")) && (
<div>
<Controller
control={control}
name="start_date"
render={({ field: { value, onChange } }) => (
<IssueDateSelect
label="Start date"
maxDate={maxDate ?? undefined}
onChange={onChange}
value={value}
/>
)}
/>
</div>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("dueDate")) && (
<div>
<Controller
control={control}
name="target_date"
render={({ field: { value, onChange } }) => (
<IssueDateSelect
label="Due date"
minDate={minDate ?? undefined}
onChange={onChange}
value={value}
/>
)}
/>
</div>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("estimate")) && (
<div>
<Controller
control={control}
name="estimate_point"
render={({ field: { value, onChange } }) => (
<IssueEstimateSelect value={value} onChange={onChange} />
)}
/>
</div>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && (
<Controller
control={control}
name="parent"
render={({ field: { onChange } }) => (
<ParentIssuesListModal
isOpen={parentIssueListModalOpen}
handleClose={() => setParentIssueListModalOpen(false)}
onChange={(issue) => {
onChange(issue.id);
setSelectedParentIssue(issue);
}}
projectId={projectId}
/>
)}
/>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && (
<CustomMenu ellipsis>
{watch("parent") ? (
<>
<CustomMenu.MenuItem
renderAs="button"
onClick={() => setParentIssueListModalOpen(true)}
>
Change parent issue
</CustomMenu.MenuItem>
<CustomMenu.MenuItem
renderAs="button"
onClick={() => setValue("parent", null)}
>
Remove parent issue
</CustomMenu.MenuItem>
</>
) : (
<CustomMenu.MenuItem
renderAs="button"
onClick={() => setParentIssueListModalOpen(true)}
>
Select Parent Issue
</CustomMenu.MenuItem>
)}
</CustomMenu>
)}
</>
) : (
<CustomAttributesSelectFields
entityId={watch("entity") ?? ""}
issueId={watch("id") ?? ""}
onChange={handleCustomAttributesChange}
projectId={projectId}
values={customAttributesList}
/>
)}
</div>
<div className="flex items-center gap-2">
<SecondaryButton
onClick={() => {
handleDiscardClose();
}}
<div className="flex items-center justify-between gap-2">
<div
className="flex cursor-pointer items-center gap-1"
onClick={() => setCreateMore((prevData) => !prevData)}
>
Discard
</SecondaryButton>
<PrimaryButton type="submit" loading={isSubmitting}>
{status
? isSubmitting
? "Updating Issue..."
: "Update Issue"
: isSubmitting
? "Adding Issue..."
: "Add Issue"}
</PrimaryButton>
<span className="text-xs">Create more</span>
<ToggleSwitch value={createMore} onChange={() => {}} size="sm" />
</div>
<div className="flex items-center gap-2">
<SecondaryButton
onClick={() => {
handleDiscardClose();
}}
>
Discard
</SecondaryButton>
<PrimaryButton type="submit" loading={isSubmitting}>
{status
? isSubmitting
? "Updating Issue..."
: "Update Issue"
: isSubmitting
? "Adding Issue..."
: "Add Issue"}
</PrimaryButton>
</div>
</div>
</div>
</form>

View File

@ -510,7 +510,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer(
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="my-10 flex items-center justify-center p-4 text-center sm:p-0 md:my-20">
<Transition.Child
as={React.Fragment}
@ -521,7 +521,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer(
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 p-5 text-left shadow-xl transition-all sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:w-full sm:max-w-2xl">
<IssueForm
handleFormSubmit={handleFormSubmit}
initialData={data ?? prePopulateData}

View File

@ -78,7 +78,7 @@ export const CustomSearchSelect = ({
) : (
<Combobox.Button
type="button"
className={`flex items-center justify-between gap-1 w-full rounded shadow-sm border border-custom-border-200 duration-300 focus:outline-none ${
className={`flex items-center justify-between gap-1 w-full rounded shadow-custom-shadow-2xs border border-custom-border-200 duration-300 focus:outline-none ${
input ? "px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
} ${
disabled

View File

@ -45,7 +45,7 @@ const CustomSelect = ({
) : (
<Listbox.Button
type="button"
className={`flex items-center justify-between gap-1 w-full rounded border border-custom-border-200 shadow-sm duration-300 focus:outline-none ${
className={`flex items-center justify-between gap-1 w-full rounded border border-custom-border-200 shadow-custom-shadow-2xs duration-300 focus:outline-none ${
input ? "px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
} ${
disabled