forked from github/plane
chore: custom attributes on the create issue modal
This commit is contained in:
parent
57f4941ee2
commit
ef77ca6524
@ -84,7 +84,11 @@ export const CustomSelectAttribute: React.FC<
|
||||
backgroundColor: `${selectedOption?.color}40`,
|
||||
}}
|
||||
>
|
||||
{selectedOption?.display_name ?? "Select"}
|
||||
{Array.isArray(value)
|
||||
? value.length > 0
|
||||
? value.map((v) => options.find((o) => o.id === v)?.display_name).join(", ")
|
||||
: `Select ${attributeDetails.display_name}`
|
||||
: value}
|
||||
</Combobox.Button>
|
||||
<Transition
|
||||
as={React.Fragment}
|
||||
@ -98,7 +102,7 @@ export const CustomSelectAttribute: React.FC<
|
||||
<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"
|
||||
className="fixed 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} />
|
||||
|
@ -21,9 +21,7 @@ export const CustomUrlAttribute: React.FC<Props & { value: string | undefined }>
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing) {
|
||||
setFocus("url");
|
||||
}
|
||||
if (isEditing) setFocus("url");
|
||||
}, [isEditing, setFocus]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -8,9 +8,14 @@ import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import {
|
||||
CustomCheckboxAttribute,
|
||||
CustomDateTimeAttribute,
|
||||
CustomEmailAttribute,
|
||||
CustomFileAttribute,
|
||||
CustomNumberAttribute,
|
||||
CustomRelationAttribute,
|
||||
CustomSelectAttribute,
|
||||
CustomTextAttribute,
|
||||
CustomUrlAttribute,
|
||||
} from "components/custom-attributes";
|
||||
// ui
|
||||
import { Loader } from "components/ui";
|
||||
@ -18,12 +23,13 @@ import { Loader } from "components/ui";
|
||||
type Props = {
|
||||
entityId: string;
|
||||
issueId: string;
|
||||
onSubmit: () => Promise<void>;
|
||||
onChange: (attributeId: string, val: string[]) => Promise<void>;
|
||||
projectId: string;
|
||||
values: { [key: string]: string[] };
|
||||
};
|
||||
|
||||
export const IssueModalCustomAttributesList: React.FC<Props> = observer(
|
||||
({ entityId, issueId, onSubmit, projectId }) => {
|
||||
({ entityId, issueId, onChange, projectId, values }) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
@ -42,7 +48,7 @@ export const IssueModalCustomAttributesList: React.FC<Props> = observer(
|
||||
}, [entityAttributes, entityId, fetchEntityDetails, workspaceSlug]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
{fetchEntityDetailsLoader ? (
|
||||
<Loader className="flex items-center gap-2">
|
||||
<Loader.Item height="27px" width="90px" />
|
||||
@ -50,50 +56,114 @@ export const IssueModalCustomAttributesList: React.FC<Props> = observer(
|
||||
<Loader.Item height="27px" width="90px" />
|
||||
</Loader>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<>
|
||||
{Object.entries(attributes).map(([attributeId, attribute]) => (
|
||||
<div key={attributeId}>
|
||||
{attribute.type === "text" && (
|
||||
<CustomTextAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issueId}
|
||||
onChange={() => {}}
|
||||
projectId={projectId}
|
||||
value={attribute.default_value ?? ""}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "select" && (
|
||||
<CustomSelectAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issueId}
|
||||
onChange={() => {}}
|
||||
projectId={projectId}
|
||||
value={attribute.default_value ?? ""}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "checkbox" && (
|
||||
<CustomCheckboxAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issueId}
|
||||
onChange={() => {}}
|
||||
onChange={(val: string) => onChange(attribute.id, [val])}
|
||||
projectId={projectId}
|
||||
value={attribute.default_value === "checked" ? true : false}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "datetime" && (
|
||||
<CustomDateTimeAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issueId}
|
||||
onChange={(val: string) => onChange(attribute.id, [val])}
|
||||
projectId={projectId}
|
||||
value={
|
||||
attribute.default_value !== "" ? new Date(attribute.default_value) : undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "email" && (
|
||||
<CustomEmailAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issueId}
|
||||
onChange={(val: string) => onChange(attribute.id, [val])}
|
||||
projectId={projectId}
|
||||
value={attribute.default_value}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "file" && (
|
||||
<CustomFileAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issueId}
|
||||
onChange={() => {}}
|
||||
onChange={(val: string) => onChange(attribute.id, [val])}
|
||||
projectId={projectId}
|
||||
value={null}
|
||||
value={undefined}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "multi_select" && (
|
||||
<CustomSelectAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issueId}
|
||||
onChange={(val: string[]) => onChange(attribute.id, val)}
|
||||
projectId={projectId}
|
||||
value={[]}
|
||||
multiple
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "number" && (
|
||||
<CustomNumberAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issueId}
|
||||
onChange={(val: string) => onChange(attribute.id, [val])}
|
||||
projectId={projectId}
|
||||
value={
|
||||
attribute.default_value !== ""
|
||||
? parseInt(attribute.default_value, 10)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "relation" && (
|
||||
<CustomRelationAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issueId}
|
||||
onChange={(val: string) => onChange(attribute.id, [val])}
|
||||
projectId={projectId}
|
||||
value={attribute.default_value !== "" ? attribute.default_value : undefined}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "select" && (
|
||||
<CustomSelectAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issueId}
|
||||
onChange={(val: string) => onChange(attribute.id, [val])}
|
||||
projectId={projectId}
|
||||
value={attribute.default_value !== "" ? attribute.default_value : undefined}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "text" && (
|
||||
<CustomTextAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issueId}
|
||||
onChange={(val: string) => onChange(attribute.id, [val])}
|
||||
projectId={projectId}
|
||||
value={attribute.default_value}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "url" && (
|
||||
<CustomUrlAttribute
|
||||
attributeDetails={attribute}
|
||||
issueId={issueId}
|
||||
onChange={(val: string) => {
|
||||
console.log(val);
|
||||
onChange(attribute.id, [val]);
|
||||
}}
|
||||
projectId={projectId}
|
||||
value={values[attribute.id]?.[0]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -2,6 +2,9 @@ import React, { FC, useState, useEffect, useRef } from "react";
|
||||
|
||||
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
|
||||
@ -75,18 +78,20 @@ export interface IssueFormProps {
|
||||
)[];
|
||||
}
|
||||
|
||||
export const IssueForm: FC<IssueFormProps> = ({
|
||||
handleFormSubmit,
|
||||
initialData,
|
||||
projectId,
|
||||
setActiveProject,
|
||||
createMore,
|
||||
setCreateMore,
|
||||
handleClose,
|
||||
status,
|
||||
user,
|
||||
fieldsToShow,
|
||||
}) => {
|
||||
export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
||||
const {
|
||||
handleFormSubmit,
|
||||
initialData,
|
||||
projectId,
|
||||
setActiveProject,
|
||||
createMore,
|
||||
setCreateMore,
|
||||
handleClose,
|
||||
status,
|
||||
user,
|
||||
fieldsToShow,
|
||||
} = props;
|
||||
|
||||
const [stateModal, setStateModal] = useState(false);
|
||||
const [labelModal, setLabelModal] = useState(false);
|
||||
const [parentIssueListModalOpen, setParentIssueListModalOpen] = useState(false);
|
||||
@ -95,6 +100,8 @@ export const IssueForm: FC<IssueFormProps> = ({
|
||||
const [gptAssistantModal, setGptAssistantModal] = useState(false);
|
||||
const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false);
|
||||
|
||||
const [attributesList, setAttributesList] = useState<{ [key: string]: string[] }>({});
|
||||
|
||||
const editorRef = useRef<any>(null);
|
||||
|
||||
const router = useRouter();
|
||||
@ -102,6 +109,9 @@ export const IssueForm: FC<IssueFormProps> = ({
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { customAttributeValues: customAttributeValuesStore } = useMobxStore();
|
||||
const { createAttributeValue } = customAttributeValuesStore;
|
||||
|
||||
const {
|
||||
register,
|
||||
formState: { errors, isSubmitting },
|
||||
@ -122,19 +132,17 @@ export const IssueForm: FC<IssueFormProps> = ({
|
||||
const handleCreateUpdateIssue = async (formData: Partial<IIssue>) => {
|
||||
await handleFormSubmit(formData);
|
||||
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await createAttributeValue(workspaceSlug.toString(), projectId, watch("id"), {
|
||||
issue_properties: attributesList,
|
||||
});
|
||||
|
||||
setGptAssistantModal(false);
|
||||
|
||||
reset({
|
||||
...defaultValues,
|
||||
project: projectId,
|
||||
description: {
|
||||
type: "doc",
|
||||
content: [
|
||||
{
|
||||
type: "paragraph",
|
||||
},
|
||||
],
|
||||
},
|
||||
description_html: "<p></p>",
|
||||
});
|
||||
editorRef?.current?.clearEditor();
|
||||
@ -220,6 +228,8 @@ export const IssueForm: FC<IssueFormProps> = ({
|
||||
const maxDate = targetDate ? new Date(targetDate) : null;
|
||||
maxDate?.setDate(maxDate.getDate());
|
||||
|
||||
console.log("attributesList", attributesList);
|
||||
|
||||
return (
|
||||
<>
|
||||
{projectId && (
|
||||
@ -545,9 +555,17 @@ export const IssueForm: FC<IssueFormProps> = ({
|
||||
) : (
|
||||
<IssueModalCustomAttributesList
|
||||
entityId={watch("entity") ?? ""}
|
||||
issueId=""
|
||||
onSubmit={async () => {}}
|
||||
issueId={watch("id") ?? ""}
|
||||
onChange={async (attributeId: string, val: string[]) => {
|
||||
console.log(val);
|
||||
|
||||
setAttributesList((prev) => ({
|
||||
...prev,
|
||||
[attributeId]: val,
|
||||
}));
|
||||
}}
|
||||
projectId={projectId}
|
||||
values={attributesList}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@ -578,4 +596,4 @@ export const IssueForm: FC<IssueFormProps> = ({
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -25,7 +25,7 @@ class ProjectIssuesServices extends APIService {
|
||||
projectId: string,
|
||||
data: any,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
): Promise<IIssue> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/`, data)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackIssueEvent(response.data, "ISSUE_CREATE", user);
|
||||
@ -404,7 +404,7 @@ class ProjectIssuesServices extends APIService {
|
||||
issueId: string,
|
||||
data: Partial<IIssue>,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
): Promise<IIssue> {
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/`,
|
||||
data
|
||||
|
Loading…
Reference in New Issue
Block a user