[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:
Vamsi Krishna 2024-12-17 14:29:56 +05:30 committed by GitHub
parent 4507802aba
commit 8e6d885731
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 82 additions and 33 deletions

View file

@ -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>
);
});

View file

@ -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">&quot;{query}&quot;</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>

View file

@ -26,6 +26,7 @@ export const IssueLabelSelectRoot: FC<TIssueLabelSelectRoot> = (props) => {
issueId={issueId}
values={values}
onSelect={handleLabel}
onAddLabel={labelOperations.createLabel}
/>
);
};

View file

@ -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;

View file

@ -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">&quot;{query}&quot;</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>