[WEB-2678]feat: added functionality to add labels directly from dropdown (#6211)
* enhancement:added functionality to add features directly from dropdown * fix: fixed import order * fix: fixed lint errors
This commit is contained in:
parent
4507802aba
commit
8e6d885731
5 changed files with 82 additions and 33 deletions
|
|
@ -7,13 +7,12 @@ import { IIssueLabel, TIssue, TIssueServiceType } from "@plane/types";
|
|||
// components
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// hooks
|
||||
import { useIssueDetail, useLabel, useProjectInbox, useUserPermissions } from "@/hooks/store";
|
||||
import { useIssueDetail, useLabel, useProjectInbox } from "@/hooks/store";
|
||||
// ui
|
||||
// types
|
||||
import { LabelList, LabelCreate, IssueLabelSelectRoot } from "./";
|
||||
import { LabelList, IssueLabelSelectRoot } from "./";
|
||||
// TODO: Fix this import statement, as core should not import from ee
|
||||
// eslint-disable-next-line import/order
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "ee/constants/user-permissions";
|
||||
|
||||
export type TIssueLabel = {
|
||||
workspaceSlug: string;
|
||||
|
|
@ -47,9 +46,7 @@ export const IssueLabel: FC<TIssueLabel> = observer((props) => {
|
|||
issue: { getIssueById },
|
||||
} = useIssueDetail(issueServiceType);
|
||||
const { getIssueInboxByIssueId } = useProjectInbox();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const canCreateLabel = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
const issue = isInboxIssue ? getIssueInboxByIssueId(issueId)?.issue : getIssueById(issueId);
|
||||
|
||||
const labelOperations: TLabelOperations = useMemo(
|
||||
|
|
@ -113,16 +110,6 @@ export const IssueLabel: FC<TIssueLabel> = observer((props) => {
|
|||
labelOperations={labelOperations}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!disabled && canCreateLabel && (
|
||||
<LabelCreate
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
values={issue?.label_ids || []}
|
||||
labelOperations={labelOperations}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,33 +1,40 @@
|
|||
import { Fragment, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePopper } from "react-popper";
|
||||
import { Check, Search, Tag } from "lucide-react";
|
||||
import { Check, Loader, Search, Tag } from "lucide-react";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
// helpers
|
||||
import { IIssueLabel } from "@plane/types";
|
||||
import { getRandomLabelColor } from "@/constants/label";
|
||||
import { getTabIndex } from "@/helpers/tab-indices.helper";
|
||||
// hooks
|
||||
import { useLabel } from "@/hooks/store";
|
||||
import { useLabel, useUserPermissions } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// components
|
||||
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "ee/constants/user-permissions";
|
||||
//constants
|
||||
export interface IIssueLabelSelect {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
issueId: string;
|
||||
values: string[];
|
||||
onSelect: (_labelIds: string[]) => void;
|
||||
onAddLabel: (workspaceSlug: string, projectId: string, data: Partial<IIssueLabel>) => Promise<any>;
|
||||
}
|
||||
|
||||
export const IssueLabelSelect: React.FC<IIssueLabelSelect> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId, values, onSelect } = props;
|
||||
const { workspaceSlug, projectId, issueId, values, onSelect, onAddLabel } = props;
|
||||
// store hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { fetchProjectLabels, getProjectLabels } = useLabel();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// states
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [query, setQuery] = useState("");
|
||||
const [submitting, setSubmitting] = useState<boolean>(false);
|
||||
|
||||
const canCreateLabel = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
|
||||
const projectLabels = getProjectLabels(projectId);
|
||||
|
||||
|
|
@ -83,11 +90,25 @@ export const IssueLabelSelect: React.FC<IIssueLabelSelect> = observer((props) =>
|
|||
</div>
|
||||
);
|
||||
|
||||
const searchInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
const searchInputKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (query !== "" && e.key === "Escape") {
|
||||
e.stopPropagation();
|
||||
setQuery("");
|
||||
}
|
||||
|
||||
if (query !== "" && e.key === "Enter") {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
await handleAddLabel(query);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddLabel = async (labelName: string) => {
|
||||
setSubmitting(true);
|
||||
const label = await onAddLabel(workspaceSlug, projectId, { name: labelName, color: getRandomLabelColor() });
|
||||
onSelect([...values, label.id]);
|
||||
setQuery("");
|
||||
setSubmitting(false);
|
||||
};
|
||||
|
||||
if (!issueId || !values) return <></>;
|
||||
|
|
@ -159,10 +180,19 @@ export const IssueLabelSelect: React.FC<IIssueLabelSelect> = observer((props) =>
|
|||
)}
|
||||
</Combobox.Option>
|
||||
))
|
||||
) : submitting ? (
|
||||
<Loader className="spin h-3.5 w-3.5" />
|
||||
) : canCreateLabel ? (
|
||||
<p
|
||||
onClick={() => {
|
||||
handleAddLabel(query);
|
||||
}}
|
||||
className="text-left text-custom-text-200 cursor-pointer"
|
||||
>
|
||||
+ Add <span className="text-custom-text-100">"{query}"</span> to labels
|
||||
</p>
|
||||
) : (
|
||||
<span className="flex items-center gap-2 p-1">
|
||||
<p className="text-left text-custom-text-200 ">No matching results</p>
|
||||
</span>
|
||||
<p className="text-left text-custom-text-200 ">No matching results.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export const IssueLabelSelectRoot: FC<TIssueLabelSelectRoot> = (props) => {
|
|||
issueId={issueId}
|
||||
values={values}
|
||||
onSelect={handleLabel}
|
||||
onAddLabel={labelOperations.createLabel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-d
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane helpers
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
// types
|
||||
import { TIssue, IIssueDisplayProperties, IIssueMap } from "@plane/types";
|
||||
|
|
@ -26,7 +27,6 @@ import { IssueIdentifier } from "@/plane-web/components/issues";
|
|||
import { TRenderQuickActions } from "../list/list-view-types";
|
||||
import { IssueProperties } from "../properties/all-properties";
|
||||
import { getIssueBlockId } from "../utils";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
|
||||
interface IssueBlockProps {
|
||||
issueId: string;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import { Fragment, useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Placement } from "@popperjs/core";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { usePopper } from "react-popper";
|
||||
import { Check, ChevronDown, Search, Tags } from "lucide-react";
|
||||
import { Check, ChevronDown, Loader, Search, Tags } from "lucide-react";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
// plane helpers
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
|
|
@ -14,9 +14,12 @@ import { IIssueLabel } from "@plane/types";
|
|||
// ui
|
||||
import { ComboDropDown, Tooltip } from "@plane/ui";
|
||||
// hooks
|
||||
import { useLabel } from "@/hooks/store";
|
||||
import { getRandomLabelColor } from "@/constants/label";
|
||||
import { useLabel, useUserPermissions } from "@/hooks/store";
|
||||
import { useDropdownKeyDown } from "@/hooks/use-dropdown-key-down";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "ee/constants/user-permissions";
|
||||
// constants
|
||||
|
||||
export interface IIssuePropertyLabels {
|
||||
projectId: string | null;
|
||||
|
|
@ -62,6 +65,7 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||
// states
|
||||
const [query, setQuery] = useState("");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [submitting, setSubmitting] = useState<boolean>(false);
|
||||
// refs
|
||||
const dropdownRef = useRef<HTMLDivElement | null>(null);
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
|
@ -70,9 +74,12 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
// store hooks
|
||||
const { fetchProjectLabels, getProjectLabels } = useLabel();
|
||||
const { fetchProjectLabels, getProjectLabels, createLabel } = useLabel();
|
||||
const { isMobile } = usePlatformOS();
|
||||
const storeLabels = getProjectLabels(projectId);
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const canCreateLabel = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
|
||||
const onOpen = () => {
|
||||
if (!storeLabels && workspaceSlug && projectId)
|
||||
|
|
@ -102,11 +109,17 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||
|
||||
useOutsideClickDetector(dropdownRef, handleClose);
|
||||
|
||||
const searchInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
const searchInputKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (query !== "" && e.key === "Escape") {
|
||||
e.stopPropagation();
|
||||
setQuery("");
|
||||
}
|
||||
|
||||
if (query !== "" && e.key === "Enter") {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
await handleAddLabel(query);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -249,6 +262,15 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||
</button>
|
||||
);
|
||||
|
||||
const handleAddLabel = async (labelName: string) => {
|
||||
if (!projectId) return;
|
||||
setSubmitting(true);
|
||||
const label = await createLabel(workspaceSlug, projectId, { name: labelName, color: getRandomLabelColor() });
|
||||
onChange([...value, label.id]);
|
||||
setQuery("");
|
||||
setSubmitting(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<ComboDropDown
|
||||
as="div"
|
||||
|
|
@ -314,10 +336,19 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||
)}
|
||||
</Combobox.Option>
|
||||
))
|
||||
) : submitting ? (
|
||||
<Loader className="spin h-3.5 w-3.5" />
|
||||
) : canCreateLabel ? (
|
||||
<p
|
||||
onClick={() => {
|
||||
handleAddLabel(query);
|
||||
}}
|
||||
className="text-left text-custom-text-200 cursor-pointer"
|
||||
>
|
||||
+ Add <span className="text-custom-text-100">"{query}"</span> to labels
|
||||
</p>
|
||||
) : (
|
||||
<span className="flex items-center gap-2 p-1">
|
||||
<p className="text-left text-custom-text-200 ">No matching results</p>
|
||||
</span>
|
||||
<p className="text-left text-custom-text-200 ">No matching results.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue