forked from github/plane
Merge branch 'develop' of github.com:makeplane/plane into chore/breadcrumb_component_revamp
This commit is contained in:
commit
ff3b56d916
@ -2,7 +2,7 @@ import * as React from "react";
|
|||||||
|
|
||||||
import { ISvgIcons } from "./type";
|
import { ISvgIcons } from "./type";
|
||||||
|
|
||||||
export const FullScreenPeekIcon: React.FC<ISvgIcons> = ({
|
export const CenterPanelIcon: React.FC<ISvgIcons> = ({
|
||||||
className = "text-current",
|
className = "text-current",
|
||||||
...rest
|
...rest
|
||||||
}) => (
|
}) => (
|
||||||
@ -16,14 +16,18 @@ export const FullScreenPeekIcon: React.FC<ISvgIcons> = ({
|
|||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M19 3H5C3.89543 3 3 3.89543 3 5V19C3 20.1046 3.89543 21 5 21H19C20.1046 21 21 20.1046 21 19V5C21 3.89543 20.1046 3 19 3Z"
|
d="M19 3H5C3.89543 3 3 3.89543 3 5V19C3 20.1046 3.89543 21 5 21H19C20.1046 21 21 20.1046 21 19V5C21 3.89543 20.1046 3 19 3Z"
|
||||||
strokeLinecap="round"
|
stroke="currentColor"
|
||||||
strokeLinejoin="round"
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M20 3H12V5V19V21H20C20.5523 21 21 20.1046 21 19V5C21 3.89543 20.5523 3 20 3Z"
|
d="M15.1111 8.00009H8.8001C8.33334 8.00007 8.00003 8.0001 8.00012 8.88897V15.1111C8.00012 16 8.00012 16 8.8001 16H15.1111C16 16 16 16 16 15.1111V8.88897C16 8.00009 16 8.00009 15.1111 8.00009H15.1111Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
strokeLinecap="round"
|
stroke="currentColor"
|
||||||
strokeLinejoin="round"
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
@ -2,7 +2,7 @@ import * as React from "react";
|
|||||||
|
|
||||||
import { ISvgIcons } from "./type";
|
import { ISvgIcons } from "./type";
|
||||||
|
|
||||||
export const ModalPeekIcon: React.FC<ISvgIcons> = ({
|
export const FullScreenPanelIcon: React.FC<ISvgIcons> = ({
|
||||||
className = "text-current",
|
className = "text-current",
|
||||||
...rest
|
...rest
|
||||||
}) => (
|
}) => (
|
@ -9,9 +9,9 @@ export * from "./subscribe-icon";
|
|||||||
export * from "./external-link-icon";
|
export * from "./external-link-icon";
|
||||||
export * from "./copy-icon";
|
export * from "./copy-icon";
|
||||||
export * from "./layer-stack";
|
export * from "./layer-stack";
|
||||||
export * from "./side-peek-icon";
|
export * from "./side-panel-icon";
|
||||||
export * from "./modal-peek-icon";
|
export * from "./center-panel-icon";
|
||||||
export * from "./panel-center-icon";
|
export * from "./full-screen-panel-icon";
|
||||||
export * from "./priority-icon";
|
export * from "./priority-icon";
|
||||||
export * from "./state";
|
export * from "./state";
|
||||||
export * from "./blocked-icon";
|
export * from "./blocked-icon";
|
||||||
|
@ -2,7 +2,7 @@ import * as React from "react";
|
|||||||
|
|
||||||
import { ISvgIcons } from "./type";
|
import { ISvgIcons } from "./type";
|
||||||
|
|
||||||
export const SidePeekIcon: React.FC<ISvgIcons> = ({
|
export const SidePanelIcon: React.FC<ISvgIcons> = ({
|
||||||
className = "text-current",
|
className = "text-current",
|
||||||
...rest
|
...rest
|
||||||
}) => (
|
}) => (
|
@ -24,7 +24,7 @@ import useToast from "hooks/use-toast";
|
|||||||
import { CustomDatePicker } from "components/ui";
|
import { CustomDatePicker } from "components/ui";
|
||||||
import { LinkModal, LinksList } from "components/core";
|
import { LinkModal, LinksList } from "components/core";
|
||||||
// types
|
// types
|
||||||
import { ICycle, IIssue, IIssueLink, IModule, TIssuePriorities, linkDetails } from "types";
|
import { IIssue, IIssueLink, TIssuePriorities, linkDetails } from "types";
|
||||||
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
||||||
// services
|
// services
|
||||||
import { IssueService } from "services/issue";
|
import { IssueService } from "services/issue";
|
||||||
@ -42,7 +42,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
const [linkModal, setLinkModal] = useState(false);
|
const [linkModal, setLinkModal] = useState(false);
|
||||||
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<linkDetails | null>(null);
|
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<linkDetails | null>(null);
|
||||||
|
|
||||||
const { user: userStore } = useMobxStore();
|
const { user: userStore, cycleIssue: cycleIssueStore, moduleIssue: moduleIssueStore } = useMobxStore();
|
||||||
const userRole = userStore.currentProjectRole;
|
const userRole = userStore.currentProjectRole;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -71,11 +71,15 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
const handleParent = (_parent: string) => {
|
const handleParent = (_parent: string) => {
|
||||||
issueUpdate({ ...issue, parent: _parent });
|
issueUpdate({ ...issue, parent: _parent });
|
||||||
};
|
};
|
||||||
const handleCycle = (_cycle: ICycle) => {
|
const addIssueToCycle = async (cycleId: string) => {
|
||||||
issueUpdate({ ...issue, cycle: _cycle.id });
|
if (!workspaceSlug || !issue || !cycleId) return;
|
||||||
|
cycleIssueStore.addIssueToCycle(workspaceSlug.toString(), issue.project_detail.id, cycleId, issue.id);
|
||||||
};
|
};
|
||||||
const handleModule = (_module: IModule) => {
|
|
||||||
issueUpdate({ ...issue, module: _module.id });
|
const addIssueToModule = async (moduleId: string) => {
|
||||||
|
if (!workspaceSlug || !issue || !moduleId) return;
|
||||||
|
|
||||||
|
moduleIssueStore.addIssueToModule(workspaceSlug.toString(), issue.project_detail.id, moduleId, issue.id);
|
||||||
};
|
};
|
||||||
const handleLabels = (formData: Partial<IIssue>) => {
|
const handleLabels = (formData: Partial<IIssue>) => {
|
||||||
issueUpdate({ ...issue, ...formData });
|
issueUpdate({ ...issue, ...formData });
|
||||||
@ -187,7 +191,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
<div className="flex flex-col gap-5 py-5 w-full">
|
<div className="flex flex-col gap-5 py-5 w-full">
|
||||||
{/* state */}
|
{/* state */}
|
||||||
<div className="flex items-center gap-2 w-full">
|
<div className="flex items-center gap-2 w-full">
|
||||||
<div className="flex items-center gap-2 w-40">
|
<div className="flex items-center gap-2 w-40 text-sm">
|
||||||
<DoubleCircleIcon className="h-4 w-4 flex-shrink-0" />
|
<DoubleCircleIcon className="h-4 w-4 flex-shrink-0" />
|
||||||
<p>State</p>
|
<p>State</p>
|
||||||
</div>
|
</div>
|
||||||
@ -198,7 +202,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
|
|
||||||
{/* assignee */}
|
{/* assignee */}
|
||||||
<div className="flex items-center gap-2 w-full">
|
<div className="flex items-center gap-2 w-full">
|
||||||
<div className="flex items-center gap-2 w-40">
|
<div className="flex items-center gap-2 w-40 text-sm">
|
||||||
<UserGroupIcon className="h-4 w-4 flex-shrink-0" />
|
<UserGroupIcon className="h-4 w-4 flex-shrink-0" />
|
||||||
<p>Assignees</p>
|
<p>Assignees</p>
|
||||||
</div>
|
</div>
|
||||||
@ -209,7 +213,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
|
|
||||||
{/* priority */}
|
{/* priority */}
|
||||||
<div className="flex items-center gap-2 w-full">
|
<div className="flex items-center gap-2 w-full">
|
||||||
<div className="flex items-center gap-2 w-40">
|
<div className="flex items-center gap-2 w-40 text-sm">
|
||||||
<Signal className="h-4 w-4 flex-shrink-0" />
|
<Signal className="h-4 w-4 flex-shrink-0" />
|
||||||
<p>Priority</p>
|
<p>Priority</p>
|
||||||
</div>
|
</div>
|
||||||
@ -220,7 +224,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
|
|
||||||
{/* estimate */}
|
{/* estimate */}
|
||||||
<div className="flex items-center gap-2 w-full">
|
<div className="flex items-center gap-2 w-full">
|
||||||
<div className="flex items-center gap-2 w-40">
|
<div className="flex items-center gap-2 w-40 text-sm">
|
||||||
<Triangle className="h-4 w-4 flex-shrink-0 " />
|
<Triangle className="h-4 w-4 flex-shrink-0 " />
|
||||||
<p>Estimate</p>
|
<p>Estimate</p>
|
||||||
</div>
|
</div>
|
||||||
@ -231,7 +235,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
|
|
||||||
{/* start date */}
|
{/* start date */}
|
||||||
<div className="flex items-center gap-2 w-full">
|
<div className="flex items-center gap-2 w-full">
|
||||||
<div className="flex items-center gap-2 w-40">
|
<div className="flex items-center gap-2 w-40 text-sm">
|
||||||
<CalendarDays className="h-4 w-4 flex-shrink-0" />
|
<CalendarDays className="h-4 w-4 flex-shrink-0" />
|
||||||
<p>Start date</p>
|
<p>Start date</p>
|
||||||
</div>
|
</div>
|
||||||
@ -249,7 +253,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
|
|
||||||
{/* due date */}
|
{/* due date */}
|
||||||
<div className="flex items-center gap-2 w-full">
|
<div className="flex items-center gap-2 w-full">
|
||||||
<div className="flex items-center gap-2 w-40">
|
<div className="flex items-center gap-2 w-40 text-sm">
|
||||||
<CalendarDays className="h-4 w-4 flex-shrink-0" />
|
<CalendarDays className="h-4 w-4 flex-shrink-0" />
|
||||||
<p>Due date</p>
|
<p>Due date</p>
|
||||||
</div>
|
</div>
|
||||||
@ -267,7 +271,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
|
|
||||||
{/* parent */}
|
{/* parent */}
|
||||||
<div className="flex items-center gap-2 w-full">
|
<div className="flex items-center gap-2 w-full">
|
||||||
<div className="flex items-center gap-2 w-40">
|
<div className="flex items-center gap-2 w-40 text-sm">
|
||||||
<User2 className="h-4 w-4 flex-shrink-0" />
|
<User2 className="h-4 w-4 flex-shrink-0" />
|
||||||
<p>Parent</p>
|
<p>Parent</p>
|
||||||
</div>
|
</div>
|
||||||
@ -281,26 +285,26 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
|
|
||||||
<div className="flex flex-col gap-5 py-5 w-full">
|
<div className="flex flex-col gap-5 py-5 w-full">
|
||||||
<div className="flex items-center gap-2 w-80">
|
<div className="flex items-center gap-2 w-80">
|
||||||
<div className="flex items-center gap-2 w-40">
|
<div className="flex items-center gap-2 w-40 text-sm">
|
||||||
<ContrastIcon className="h-4 w-4 flex-shrink-0" />
|
<ContrastIcon className="h-4 w-4 flex-shrink-0" />
|
||||||
<p>Cycle</p>
|
<p>Cycle</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<SidebarCycleSelect issueDetail={issue} handleCycleChange={handleCycle} disabled={isNotAllowed} />
|
<SidebarCycleSelect issueDetail={issue} handleCycleChange={addIssueToCycle} disabled={isNotAllowed} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 w-80">
|
<div className="flex items-center gap-2 w-80">
|
||||||
<div className="flex items-center gap-2 w-40">
|
<div className="flex items-center gap-2 w-40 text-sm">
|
||||||
<DiceIcon className="h-4 w-4 flex-shrink-0" />
|
<DiceIcon className="h-4 w-4 flex-shrink-0" />
|
||||||
<p>Module</p>
|
<p>Module</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<SidebarModuleSelect issueDetail={issue} handleModuleChange={handleModule} disabled={isNotAllowed} />
|
<SidebarModuleSelect issueDetail={issue} handleModuleChange={addIssueToModule} disabled={isNotAllowed} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-2 w-full">
|
<div className="flex items-start gap-2 w-full">
|
||||||
<div className="flex items-center gap-2 w-40 flex-shrink-0">
|
<div className="flex items-center gap-2 w-40 text-sm flex-shrink-0">
|
||||||
<Tag className="h-4 w-4 flex-shrink-0" />
|
<Tag className="h-4 w-4 flex-shrink-0" />
|
||||||
<p>Label</p>
|
<p>Label</p>
|
||||||
</div>
|
</div>
|
||||||
@ -321,8 +325,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
<div className="flex flex-col gap-5 pt-5 w-full">
|
<div className="flex flex-col gap-5 pt-5 w-full">
|
||||||
<div className="flex flex-col gap-2 w-full">
|
<div className="flex flex-col gap-2 w-full">
|
||||||
<div className="flex items-center gap-2 w-80">
|
<div className="flex items-center gap-2 w-80">
|
||||||
<div className="flex items-center gap-2 w-40">
|
<div className="flex items-center gap-2 w-40 text-sm">
|
||||||
<Link2 className="h-4 w-4 rotate-45 flex-shrink-0" />
|
<Link2 className="h-4 w-4 flex-shrink-0" />
|
||||||
<p>Links</p>
|
<p>Links</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -7,7 +7,7 @@ import useSWR from "swr";
|
|||||||
import { PeekOverviewIssueDetails } from "./issue-detail";
|
import { PeekOverviewIssueDetails } from "./issue-detail";
|
||||||
import { PeekOverviewProperties } from "./properties";
|
import { PeekOverviewProperties } from "./properties";
|
||||||
import { IssueComment } from "./activity";
|
import { IssueComment } from "./activity";
|
||||||
import { Button, CustomSelect, FullScreenPeekIcon, ModalPeekIcon, SidePeekIcon } from "@plane/ui";
|
import { Button, CenterPanelIcon, CustomSelect, FullScreenPanelIcon, SidePanelIcon } from "@plane/ui";
|
||||||
import { DeleteIssueModal } from "../delete-issue-modal";
|
import { DeleteIssueModal } from "../delete-issue-modal";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
@ -41,17 +41,17 @@ type TPeekModes = "side-peek" | "modal" | "full-screen";
|
|||||||
const peekOptions: { key: TPeekModes; icon: any; title: string }[] = [
|
const peekOptions: { key: TPeekModes; icon: any; title: string }[] = [
|
||||||
{
|
{
|
||||||
key: "side-peek",
|
key: "side-peek",
|
||||||
icon: SidePeekIcon,
|
icon: SidePanelIcon,
|
||||||
title: "Side Peek",
|
title: "Side Peek",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "modal",
|
key: "modal",
|
||||||
icon: ModalPeekIcon,
|
icon: CenterPanelIcon,
|
||||||
title: "Modal",
|
title: "Modal",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "full-screen",
|
key: "full-screen",
|
||||||
icon: FullScreenPeekIcon,
|
icon: FullScreenPanelIcon,
|
||||||
title: "Full Screen",
|
title: "Full Screen",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -206,7 +206,13 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
|||||||
>
|
>
|
||||||
{peekOptions.map((mode) => (
|
{peekOptions.map((mode) => (
|
||||||
<CustomSelect.Option key={mode.key} value={mode.key}>
|
<CustomSelect.Option key={mode.key} value={mode.key}>
|
||||||
<div className="flex items-center gap-1.5">
|
<div
|
||||||
|
className={`flex items-center gap-1.5 ${
|
||||||
|
currentMode.key === mode.key
|
||||||
|
? "text-custom-text-200"
|
||||||
|
: "text-custom-text-400 hover:text-custom-text-200"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<mode.icon className={`h-4 w-4 flex-shrink-0 -my-1 `} />
|
<mode.icon className={`h-4 w-4 flex-shrink-0 -my-1 `} />
|
||||||
{mode.title}
|
{mode.title}
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,9 +3,9 @@ import Link from "next/link";
|
|||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
import { CustomSelect, FullScreenPeekIcon, ModalPeekIcon, SidePeekIcon } from "@plane/ui";
|
import { CenterPanelIcon, CustomSelect, FullScreenPanelIcon, SidePanelIcon } from "@plane/ui";
|
||||||
// icons
|
// icons
|
||||||
import { LinkIcon, MoveRight, Trash2 } from "lucide-react";
|
import { LinkIcon, MoveDiagonal, MoveRight, Trash2 } from "lucide-react";
|
||||||
// helpers
|
// helpers
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
@ -26,15 +26,15 @@ const peekModes: {
|
|||||||
icon: any;
|
icon: any;
|
||||||
label: string;
|
label: string;
|
||||||
}[] = [
|
}[] = [
|
||||||
{ key: "side", icon: SidePeekIcon, label: "Side Peek" },
|
{ key: "side", icon: SidePanelIcon, label: "Side Peek" },
|
||||||
{
|
{
|
||||||
key: "modal",
|
key: "modal",
|
||||||
icon: ModalPeekIcon,
|
icon: CenterPanelIcon,
|
||||||
label: "Modal Peek",
|
label: "Modal Peek",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "full",
|
key: "full",
|
||||||
icon: FullScreenPeekIcon,
|
icon: FullScreenPanelIcon,
|
||||||
label: "Full Screen Peek",
|
label: "Full Screen Peek",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -73,7 +73,7 @@ export const PeekOverviewHeader: React.FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
<Link href={`/${workspaceSlug}/projects/${issue?.project}/issues/${issue?.id}`}>
|
<Link href={`/${workspaceSlug}/projects/${issue?.project}/issues/${issue?.id}`}>
|
||||||
<a>
|
<a>
|
||||||
<FullScreenPeekIcon className="h-3.5 w-3.5" />
|
<MoveDiagonal className="h-3.5 w-3.5" />
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
|
@ -19,11 +19,12 @@ type Props = {
|
|||||||
value: string[];
|
value: string[];
|
||||||
onChange: (value: string[]) => void;
|
onChange: (value: string[]) => void;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
label?: JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
const issueLabelService = new IssueLabelService();
|
const issueLabelService = new IssueLabelService();
|
||||||
|
|
||||||
export const IssueLabelSelect: React.FC<Props> = ({ setIsOpen, value, onChange, projectId }) => {
|
export const IssueLabelSelect: React.FC<Props> = ({ setIsOpen, value, onChange, projectId, label }) => {
|
||||||
// states
|
// states
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
|
|
||||||
@ -56,7 +57,9 @@ export const IssueLabelSelect: React.FC<Props> = ({ setIsOpen, value, onChange,
|
|||||||
ref={setReferenceElement}
|
ref={setReferenceElement}
|
||||||
className="flex items-center gap-2 cursor-pointer text-xs text-custom-text-200"
|
className="flex items-center gap-2 cursor-pointer text-xs text-custom-text-200"
|
||||||
>
|
>
|
||||||
{value && value.length > 0 ? (
|
{label ? (
|
||||||
|
label
|
||||||
|
) : value && value.length > 0 ? (
|
||||||
<span className="flex items-center justify-center gap-2 text-xs">
|
<span className="flex items-center justify-center gap-2 text-xs">
|
||||||
<IssueLabelsList
|
<IssueLabelsList
|
||||||
labels={value.map((v) => issueLabels?.find((l) => l.id === v)) ?? []}
|
labels={value.map((v) => issueLabels?.find((l) => l.id === v)) ?? []}
|
||||||
|
@ -8,17 +8,15 @@ import useSWR, { mutate } from "swr";
|
|||||||
import { IssueService } from "services/issue";
|
import { IssueService } from "services/issue";
|
||||||
import { CycleService } from "services/cycle.service";
|
import { CycleService } from "services/cycle.service";
|
||||||
// ui
|
// ui
|
||||||
import { CustomSelect, Spinner, Tooltip } from "@plane/ui";
|
import { ContrastIcon, CustomSearchSelect, Tooltip } from "@plane/ui";
|
||||||
// helper
|
|
||||||
import { truncateText } from "helpers/string.helper";
|
|
||||||
// types
|
// types
|
||||||
import { ICycle, IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { CYCLE_ISSUES, INCOMPLETE_CYCLES_LIST, ISSUE_DETAILS } from "constants/fetch-keys";
|
import { CYCLE_ISSUES, INCOMPLETE_CYCLES_LIST, ISSUE_DETAILS } from "constants/fetch-keys";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issueDetail: IIssue | undefined;
|
issueDetail: IIssue | undefined;
|
||||||
handleCycleChange: (cycle: ICycle) => void;
|
handleCycleChange: (cycleId: string) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -52,10 +50,30 @@ export const SidebarCycleSelect: React.FC<Props> = ({ issueDetail, handleCycleCh
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const options = incompleteCycles?.map((cycle) => ({
|
||||||
|
value: cycle.id,
|
||||||
|
query: cycle.name,
|
||||||
|
content: (
|
||||||
|
<div className="flex items-center gap-1.5 truncate">
|
||||||
|
<span className="flex justify-center items-center flex-shrink-0 w-3.5 h-3.5">
|
||||||
|
<ContrastIcon />
|
||||||
|
</span>
|
||||||
|
<span className="truncate flex-grow">{cycle.name}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
const issueCycle = issueDetail?.issue_cycle;
|
const issueCycle = issueDetail?.issue_cycle;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomSelect
|
<CustomSearchSelect
|
||||||
|
value={issueCycle?.cycle_detail.id}
|
||||||
|
onChange={(value: any) => {
|
||||||
|
value === issueCycle?.cycle_detail.id
|
||||||
|
? removeIssueFromCycle(issueCycle?.id ?? "", issueCycle?.cycle ?? "")
|
||||||
|
: handleCycleChange(value);
|
||||||
|
}}
|
||||||
|
options={options}
|
||||||
customButton={
|
customButton={
|
||||||
<div>
|
<div>
|
||||||
<Tooltip position="left" tooltipContent={`${issueCycle ? issueCycle.cycle_detail.name : "No cycle"}`}>
|
<Tooltip position="left" tooltipContent={`${issueCycle ? issueCycle.cycle_detail.name : "No cycle"}`}>
|
||||||
@ -65,41 +83,21 @@ export const SidebarCycleSelect: React.FC<Props> = ({ issueDetail, handleCycleCh
|
|||||||
disabled ? "cursor-not-allowed" : ""
|
disabled ? "cursor-not-allowed" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span className={`truncate ${issueCycle ? "text-custom-text-100" : "text-custom-text-200"}`}>
|
<span
|
||||||
|
className={`flex items-center gap-1.5 truncate ${
|
||||||
|
issueCycle ? "text-custom-text-100" : "text-custom-text-200"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{issueCycle && <ContrastIcon className="h-3.5 w-3.5" />}
|
||||||
{issueCycle ? issueCycle.cycle_detail.name : "No cycle"}
|
{issueCycle ? issueCycle.cycle_detail.name : "No cycle"}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
value={issueCycle ? issueCycle.cycle_detail.id : null}
|
width="max-w-[10rem]"
|
||||||
onChange={(value: any) => {
|
noChevron
|
||||||
!value
|
|
||||||
? removeIssueFromCycle(issueCycle?.id ?? "", issueCycle?.cycle ?? "")
|
|
||||||
: handleCycleChange(incompleteCycles?.find((c) => c.id === value) as ICycle);
|
|
||||||
}}
|
|
||||||
width="w-full"
|
|
||||||
maxHeight="rg"
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
/>
|
||||||
{incompleteCycles ? (
|
|
||||||
incompleteCycles.length > 0 ? (
|
|
||||||
<>
|
|
||||||
{incompleteCycles.map((option) => (
|
|
||||||
<CustomSelect.Option key={option.id} value={option.id}>
|
|
||||||
<Tooltip position="left-bottom" tooltipContent={option.name}>
|
|
||||||
<span className="w-full truncate">{truncateText(option.name, 25)}</span>
|
|
||||||
</Tooltip>
|
|
||||||
</CustomSelect.Option>
|
|
||||||
))}
|
|
||||||
<CustomSelect.Option value={null}>None</CustomSelect.Option>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className="text-center">No cycles found</div>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Spinner />
|
|
||||||
)}
|
|
||||||
</CustomSelect>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -4,15 +4,16 @@ import useSWR from "swr";
|
|||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { TwitterPicker } from "react-color";
|
import { TwitterPicker } from "react-color";
|
||||||
// headless ui
|
// headless ui
|
||||||
import { Listbox, Popover, Transition } from "@headlessui/react";
|
import { Popover, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import { IssueLabelService } from "services/issue";
|
import { IssueLabelService } from "services/issue";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
// ui
|
// ui
|
||||||
import { Input, Spinner } from "@plane/ui";
|
import { Input } from "@plane/ui";
|
||||||
|
import { IssueLabelSelect } from "../select";
|
||||||
// icons
|
// icons
|
||||||
import { Component, Plus, X } from "lucide-react";
|
import { Plus, X } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { IIssue, IIssueLabels } from "types";
|
import { IIssue, IIssueLabels } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
@ -117,102 +118,21 @@ export const SidebarLabelSelect: React.FC<Props> = ({
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<Listbox
|
<IssueLabelSelect
|
||||||
as="div"
|
setIsOpen={setCreateLabelForm}
|
||||||
value={issueDetails?.labels ?? []}
|
value={issueDetails?.labels ?? []}
|
||||||
onChange={(val: any) => submitChanges({ labels: val })}
|
onChange={(val: any) => submitChanges({ labels: val })}
|
||||||
className="flex-shrink-0"
|
projectId={issueDetails?.project_detail.id ?? ""}
|
||||||
multiple
|
label={
|
||||||
disabled={isNotAllowed || uneditable}
|
<span
|
||||||
>
|
className={`flex ${
|
||||||
{({ open }) => (
|
isNotAllowed || uneditable ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-90"
|
||||||
<div className="relative">
|
} items-center gap-2 rounded-2xl border border-custom-border-100 px-2 py-0.5 text-xs hover:text-custom-text-200 text-custom-text-300`}
|
||||||
<Listbox.Button
|
>
|
||||||
className={`flex ${
|
Select Label
|
||||||
isNotAllowed || uneditable ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-90"
|
</span>
|
||||||
} items-center gap-2 rounded-2xl border border-custom-border-100 px-2 py-0.5 text-xs hover:text-custom-text-200 text-custom-text-300`}
|
}
|
||||||
>
|
/>
|
||||||
Select Label
|
|
||||||
</Listbox.Button>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
show={open}
|
|
||||||
as={React.Fragment}
|
|
||||||
leave="transition ease-in duration-100"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<Listbox.Options className="absolute right-0 z-10 mt-1 max-h-28 w-40 overflow-auto rounded-md bg-custom-background-80 py-1 text-xs shadow-lg border border-custom-border-100 focus:outline-none">
|
|
||||||
<div className="py-1">
|
|
||||||
{issueLabels ? (
|
|
||||||
issueLabels.length > 0 ? (
|
|
||||||
issueLabels.map((label: IIssueLabels) => {
|
|
||||||
const children = issueLabels?.filter((l) => l.parent === label.id);
|
|
||||||
|
|
||||||
if (children.length === 0) {
|
|
||||||
if (!label.parent)
|
|
||||||
return (
|
|
||||||
<Listbox.Option
|
|
||||||
key={label.id}
|
|
||||||
className={({ active, selected }) =>
|
|
||||||
`${active || selected ? "bg-custom-background-90" : ""} ${
|
|
||||||
selected ? "" : "text-custom-text-200"
|
|
||||||
} flex cursor-pointer select-none items-center gap-2 truncate p-2`
|
|
||||||
}
|
|
||||||
value={label.id}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="h-2 w-2 flex-shrink-0 rounded-full"
|
|
||||||
style={{
|
|
||||||
backgroundColor: label.color && label.color !== "" ? label.color : "#000",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{label.name}
|
|
||||||
</Listbox.Option>
|
|
||||||
);
|
|
||||||
} else
|
|
||||||
return (
|
|
||||||
<div className="border-y border-custom-border-100 bg-custom-background-90">
|
|
||||||
<div className="flex select-none items-center gap-2 truncate p-2 font-medium text-custom-text-100">
|
|
||||||
<Component className="h-3 w-3" />
|
|
||||||
{label.name}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{children.map((child) => (
|
|
||||||
<Listbox.Option
|
|
||||||
key={child.id}
|
|
||||||
className={({ active, selected }) =>
|
|
||||||
`${active || selected ? "bg-custom-background-100" : ""} ${
|
|
||||||
selected ? "" : "text-custom-text-200"
|
|
||||||
} flex cursor-pointer select-none items-center gap-2 truncate p-2`
|
|
||||||
}
|
|
||||||
value={child.id}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="h-2 w-2 flex-shrink-0 rounded-full"
|
|
||||||
style={{
|
|
||||||
backgroundColor: child?.color ?? "black",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{child.name}
|
|
||||||
</Listbox.Option>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<div className="text-center">No labels found</div>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Spinner />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Listbox.Options>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Listbox>
|
|
||||||
{!isNotAllowed && (
|
{!isNotAllowed && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -4,17 +4,15 @@ import useSWR, { mutate } from "swr";
|
|||||||
// services
|
// services
|
||||||
import { ModuleService } from "services/module.service";
|
import { ModuleService } from "services/module.service";
|
||||||
// ui
|
// ui
|
||||||
import { CustomSelect, Spinner, Tooltip } from "@plane/ui";
|
import { CustomSearchSelect, DiceIcon, Tooltip } from "@plane/ui";
|
||||||
// helper
|
|
||||||
import { truncateText } from "helpers/string.helper";
|
|
||||||
// types
|
// types
|
||||||
import { IIssue, IModule } from "types";
|
import { IIssue } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ISSUE_DETAILS, MODULE_ISSUES, MODULE_LIST } from "constants/fetch-keys";
|
import { ISSUE_DETAILS, MODULE_ISSUES, MODULE_LIST } from "constants/fetch-keys";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issueDetail: IIssue | undefined;
|
issueDetail: IIssue | undefined;
|
||||||
handleModuleChange: (module: IModule) => void;
|
handleModuleChange: (moduleId: string) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,10 +42,30 @@ export const SidebarModuleSelect: React.FC<Props> = ({ issueDetail, handleModule
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const options = modules?.map((module) => ({
|
||||||
|
value: module.id,
|
||||||
|
query: module.name,
|
||||||
|
content: (
|
||||||
|
<div className="flex items-center gap-1.5 truncate">
|
||||||
|
<span className="flex justify-center items-center flex-shrink-0 w-3.5 h-3.5">
|
||||||
|
<DiceIcon />
|
||||||
|
</span>
|
||||||
|
<span className="truncate flex-grow">{module.name}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
const issueModule = issueDetail?.issue_module;
|
const issueModule = issueDetail?.issue_module;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomSelect
|
<CustomSearchSelect
|
||||||
|
value={issueModule?.module_detail.id}
|
||||||
|
onChange={(value: any) => {
|
||||||
|
value === issueModule?.module_detail.id
|
||||||
|
? removeIssueFromModule(issueModule?.id ?? "", issueModule?.module ?? "")
|
||||||
|
: handleModuleChange(value);
|
||||||
|
}}
|
||||||
|
options={options}
|
||||||
customButton={
|
customButton={
|
||||||
<div>
|
<div>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@ -60,41 +78,21 @@ export const SidebarModuleSelect: React.FC<Props> = ({ issueDetail, handleModule
|
|||||||
disabled ? "cursor-not-allowed" : ""
|
disabled ? "cursor-not-allowed" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span className={`truncate ${issueModule ? "text-custom-text-100" : "text-custom-text-200"}`}>
|
<span
|
||||||
|
className={`flex items-center gap-1.5 truncate ${
|
||||||
|
issueModule ? "text-custom-text-100" : "text-custom-text-200"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{issueModule && <DiceIcon className="h-3.5 w-3.5" />}
|
||||||
{modules?.find((m) => m.id === issueModule?.module)?.name ?? "No module"}
|
{modules?.find((m) => m.id === issueModule?.module)?.name ?? "No module"}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
value={issueModule ? issueModule.module_detail?.id : null}
|
width="max-w-[10rem]"
|
||||||
onChange={(value: any) => {
|
noChevron
|
||||||
!value
|
|
||||||
? removeIssueFromModule(issueModule?.id ?? "", issueModule?.module ?? "")
|
|
||||||
: handleModuleChange(modules?.find((m) => m.id === value) as IModule);
|
|
||||||
}}
|
|
||||||
width="w-full"
|
|
||||||
maxHeight="rg"
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
/>
|
||||||
{modules ? (
|
|
||||||
modules.length > 0 ? (
|
|
||||||
<>
|
|
||||||
{modules.map((option) => (
|
|
||||||
<CustomSelect.Option key={option.id} value={option.id}>
|
|
||||||
<Tooltip position="left-bottom" tooltipContent={option.name}>
|
|
||||||
<span className="w-full truncate">{truncateText(option.name, 25)}</span>
|
|
||||||
</Tooltip>
|
|
||||||
</CustomSelect.Option>
|
|
||||||
))}
|
|
||||||
<CustomSelect.Option value={null}>None</CustomSelect.Option>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className="text-center">No modules found</div>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Spinner />
|
|
||||||
)}
|
|
||||||
</CustomSelect>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ import useSWR from "swr";
|
|||||||
// services
|
// services
|
||||||
import { ProjectStateService } from "services/project";
|
import { ProjectStateService } from "services/project";
|
||||||
// ui
|
// ui
|
||||||
import { CustomSelect, Spinner, StateGroupIcon } from "@plane/ui";
|
import { CustomSearchSelect, StateGroupIcon } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { getStatesList } from "helpers/state.helper";
|
import { getStatesList } from "helpers/state.helper";
|
||||||
import { addSpaceIfCamelCase } from "helpers/string.helper";
|
import { addSpaceIfCamelCase } from "helpers/string.helper";
|
||||||
@ -33,16 +33,30 @@ export const SidebarStateSelect: React.FC<Props> = ({ value, onChange, disabled
|
|||||||
);
|
);
|
||||||
const states = getStatesList(stateGroups);
|
const states = getStatesList(stateGroups);
|
||||||
|
|
||||||
const selectedState = states?.find((s) => s.id === value);
|
const selectedOption = states?.find((s) => s.id === value);
|
||||||
|
|
||||||
|
const options = states?.map((state) => ({
|
||||||
|
value: state.id,
|
||||||
|
query: state.name,
|
||||||
|
content: (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<StateGroupIcon stateGroup={state.group} color={state.color} />
|
||||||
|
{state.name}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomSelect
|
<CustomSearchSelect
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
options={options}
|
||||||
customButton={
|
customButton={
|
||||||
<div className="bg-custom-background-80 text-xs rounded px-2.5 py-0.5">
|
<div className="bg-custom-background-80 text-xs rounded px-2.5 py-0.5">
|
||||||
{selectedState ? (
|
{selectedOption ? (
|
||||||
<div className="flex items-center gap-1.5 text-left text-custom-text-100">
|
<div className="flex items-center gap-1.5 text-left text-custom-text-100">
|
||||||
<StateGroupIcon stateGroup={selectedState.group} color={selectedState.color} />
|
<StateGroupIcon stateGroup={selectedOption.group} color={selectedOption.color} />
|
||||||
{addSpaceIfCamelCase(selectedState?.name ?? "")}
|
{addSpaceIfCamelCase(selectedOption?.name ?? "")}
|
||||||
</div>
|
</div>
|
||||||
) : inboxIssueId ? (
|
) : inboxIssueId ? (
|
||||||
<div className="flex items-center gap-1.5 text-left text-custom-text-100">
|
<div className="flex items-center gap-1.5 text-left text-custom-text-100">
|
||||||
@ -54,27 +68,9 @@ export const SidebarStateSelect: React.FC<Props> = ({ value, onChange, disabled
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
value={value}
|
width="min-w-[10rem]"
|
||||||
onChange={onChange}
|
noChevron
|
||||||
optionsClassName="w-min"
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
/>
|
||||||
{states ? (
|
|
||||||
states.length > 0 ? (
|
|
||||||
states.map((state) => (
|
|
||||||
<CustomSelect.Option key={state.id} value={state.id}>
|
|
||||||
<>
|
|
||||||
<StateGroupIcon stateGroup={state.group} color={state.color} />
|
|
||||||
{state.name}
|
|
||||||
</>
|
|
||||||
</CustomSelect.Option>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="text-center">No states found</div>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Spinner />
|
|
||||||
)}
|
|
||||||
</CustomSelect>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -37,7 +37,7 @@ import { ContrastIcon, DiceIcon, DoubleCircleIcon, UserGroupIcon } from "@plane/
|
|||||||
// helpers
|
// helpers
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import type { ICycle, IIssue, IIssueLink, linkDetails, IModule } from "types";
|
import type { IIssue, IIssueLink, linkDetails } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
|
import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
|
||||||
|
|
||||||
@ -97,14 +97,14 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const handleCycleChange = useCallback(
|
const handleCycleChange = useCallback(
|
||||||
(cycleDetails: ICycle) => {
|
(cycleId: string) => {
|
||||||
if (!workspaceSlug || !projectId || !issueDetail || !user) return;
|
if (!workspaceSlug || !projectId || !issueDetail || !user) return;
|
||||||
|
|
||||||
issueService
|
issueService
|
||||||
.addIssueToCycle(
|
.addIssueToCycle(
|
||||||
workspaceSlug as string,
|
workspaceSlug as string,
|
||||||
projectId as string,
|
projectId as string,
|
||||||
cycleDetails.id,
|
cycleId,
|
||||||
{
|
{
|
||||||
issues: [issueDetail.id],
|
issues: [issueDetail.id],
|
||||||
},
|
},
|
||||||
@ -118,14 +118,14 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleModuleChange = useCallback(
|
const handleModuleChange = useCallback(
|
||||||
(moduleDetail: IModule) => {
|
(moduleId: string) => {
|
||||||
if (!workspaceSlug || !projectId || !issueDetail || !user) return;
|
if (!workspaceSlug || !projectId || !issueDetail || !user) return;
|
||||||
|
|
||||||
moduleService
|
moduleService
|
||||||
.addIssuesToModule(
|
.addIssuesToModule(
|
||||||
workspaceSlug as string,
|
workspaceSlug as string,
|
||||||
projectId as string,
|
projectId as string,
|
||||||
moduleDetail.id,
|
moduleId,
|
||||||
{
|
{
|
||||||
issues: [issueDetail.id],
|
issues: [issueDetail.id],
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user