[WEB-2442] feat: Revamp Timeline Layout (#5915)
* chore: added issue relations in issue listing * chore: added pagination for issue detail endpoint * chore: bulk date update endpoint * chore: appended the target date * chore: issue relation new types defined * fix: order by and issue filters * fix: passed order by in pagination * chore: changed the key for issue dates * Revamp Timeline Layout * fix block dragging * minor ui fixes * improve auto scroll UX * remove unused import * fix timeline layout heights * modify base timeline store * Segregate issue relation types --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
parent
f986bd83fd
commit
a88a39fb1e
112 changed files with 2918 additions and 2641 deletions
|
|
@ -24,17 +24,17 @@ export const IssueDetailWidgetCollapsibles: FC<Props> = observer((props) => {
|
|||
const {
|
||||
issue: { getIssueById },
|
||||
subIssues: { subIssuesByIssueId },
|
||||
relation: { getRelationsByIssueId },
|
||||
relation: { getRelationCountByIssueId },
|
||||
} = useIssueDetail();
|
||||
|
||||
// derived values
|
||||
const issue = getIssueById(issueId);
|
||||
const subIssues = subIssuesByIssueId(issueId);
|
||||
const issueRelations = getRelationsByIssueId(issueId);
|
||||
const issueRelationsCount = getRelationCountByIssueId(issueId);
|
||||
|
||||
// render conditions
|
||||
const shouldRenderSubIssues = !!subIssues && subIssues.length > 0;
|
||||
const shouldRenderRelations = Object.values(issueRelations ?? {}).some((relation) => relation.length > 0);
|
||||
const shouldRenderRelations = issueRelationsCount > 0;
|
||||
const shouldRenderLinks = !!issue?.link_count && issue?.link_count > 0;
|
||||
const shouldRenderAttachments = !!issue?.attachment_count && issue?.attachment_count > 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
"use client";
|
||||
import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { CircleDot, CopyPlus, XCircle } from "lucide-react";
|
||||
import { TIssue, TIssueRelationIdMap } from "@plane/types";
|
||||
import { Collapsible, RelatedIcon } from "@plane/ui";
|
||||
import { Collapsible } from "@plane/ui";
|
||||
// components
|
||||
import { RelationIssueList } from "@/components/issues";
|
||||
import { DeleteIssueModal } from "@/components/issues/delete-issue-modal";
|
||||
import { CreateUpdateIssueModal } from "@/components/issues/issue-modal";
|
||||
// hooks
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
// Plane-web
|
||||
import { ISSUE_RELATION_OPTIONS } from "@/plane-web/components/relations";
|
||||
import { TIssueRelationTypes } from "@/plane-web/types";
|
||||
// helper
|
||||
import { useRelationOperations } from "./helper";
|
||||
|
||||
|
|
@ -20,35 +22,16 @@ type Props = {
|
|||
disabled: boolean;
|
||||
};
|
||||
|
||||
const ISSUE_RELATION_OPTIONS = [
|
||||
{
|
||||
key: "blocked_by",
|
||||
label: "Blocked by",
|
||||
icon: (size: number) => <CircleDot size={size} />,
|
||||
className: "bg-red-500/20 text-red-700",
|
||||
},
|
||||
{
|
||||
key: "blocking",
|
||||
label: "Blocking",
|
||||
icon: (size: number) => <XCircle size={size} />,
|
||||
className: "bg-yellow-500/20 text-yellow-700",
|
||||
},
|
||||
{
|
||||
key: "relates_to",
|
||||
label: "Relates to",
|
||||
icon: (size: number) => <RelatedIcon height={size} width={size} />,
|
||||
className: "bg-custom-background-80 text-custom-text-200",
|
||||
},
|
||||
{
|
||||
key: "duplicate",
|
||||
label: "Duplicate of",
|
||||
icon: (size: number) => <CopyPlus size={size} />,
|
||||
className: "bg-custom-background-80 text-custom-text-200",
|
||||
},
|
||||
];
|
||||
|
||||
type TIssueCrudState = { toggle: boolean; issueId: string | undefined; issue: TIssue | undefined };
|
||||
|
||||
export type TRelationObject = {
|
||||
key: TIssueRelationTypes;
|
||||
label: string;
|
||||
className: string;
|
||||
icon: (size: number) => React.ReactElement;
|
||||
placeholder: string;
|
||||
};
|
||||
|
||||
export const RelationsCollapsibleContent: FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId, disabled = false } = props;
|
||||
// state
|
||||
|
|
@ -96,17 +79,19 @@ export const RelationsCollapsibleContent: FC<Props> = observer((props) => {
|
|||
if (!relations) return null;
|
||||
|
||||
// map relations to array
|
||||
const relationsArray = Object.keys(relations).map((relationKey) => {
|
||||
const issueIds = relations[relationKey as keyof TIssueRelationIdMap];
|
||||
const issueRelationOption = ISSUE_RELATION_OPTIONS.find((option) => option.key === relationKey);
|
||||
return {
|
||||
relationKey: relationKey as keyof TIssueRelationIdMap,
|
||||
issueIds: issueIds,
|
||||
icon: issueRelationOption?.icon,
|
||||
label: issueRelationOption?.label,
|
||||
className: issueRelationOption?.className,
|
||||
};
|
||||
});
|
||||
const relationsArray = (Object.keys(relations) as TIssueRelationTypes[])
|
||||
.filter((relationKey) => !!ISSUE_RELATION_OPTIONS[relationKey])
|
||||
.map((relationKey) => {
|
||||
const issueIds = relations[relationKey];
|
||||
const issueRelationOption = ISSUE_RELATION_OPTIONS[relationKey];
|
||||
return {
|
||||
relationKey: relationKey,
|
||||
issueIds: issueIds,
|
||||
icon: issueRelationOption?.icon,
|
||||
label: issueRelationOption?.label,
|
||||
className: issueRelationOption?.className,
|
||||
};
|
||||
});
|
||||
|
||||
// filter out relations with no issues
|
||||
const filteredRelationsArray = relationsArray.filter((relation) => relation.issueIds.length > 0);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
"use client";
|
||||
import { useMemo } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { CircleDot, CopyPlus, XCircle } from "lucide-react";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { RelatedIcon, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
import { ISSUE_DELETED, ISSUE_UPDATED } from "@/constants/event-tracker";
|
||||
// helper
|
||||
|
|
@ -91,30 +90,3 @@ export const useRelationOperations = (): TRelationIssueOperations => {
|
|||
|
||||
return issueOperations;
|
||||
};
|
||||
|
||||
export const ISSUE_RELATION_OPTIONS = [
|
||||
{
|
||||
key: "blocked_by",
|
||||
label: "Blocked by",
|
||||
icon: (size: number) => <CircleDot size={size} />,
|
||||
className: "bg-red-500/20 text-red-700",
|
||||
},
|
||||
{
|
||||
key: "blocking",
|
||||
label: "Blocking",
|
||||
icon: (size: number) => <XCircle size={size} />,
|
||||
className: "bg-yellow-500/20 text-yellow-700",
|
||||
},
|
||||
{
|
||||
key: "relates_to",
|
||||
label: "Relates to",
|
||||
icon: (size: number) => <RelatedIcon height={size} width={size} />,
|
||||
className: "bg-custom-background-80 text-custom-text-200",
|
||||
},
|
||||
{
|
||||
key: "duplicate",
|
||||
label: "Duplicate of",
|
||||
icon: (size: number) => <CopyPlus size={size} />,
|
||||
className: "bg-custom-background-80 text-custom-text-200",
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Plus } from "lucide-react";
|
||||
import { TIssueRelationTypes } from "@plane/types";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// hooks
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
// helper
|
||||
import { ISSUE_RELATION_OPTIONS } from "./helper";
|
||||
// Plane-web
|
||||
import { ISSUE_RELATION_OPTIONS } from "@/plane-web/components/relations";
|
||||
import { TIssueRelationTypes } from "@/plane-web/types";
|
||||
|
||||
type Props = {
|
||||
issueId: string;
|
||||
|
|
@ -30,8 +30,14 @@ export const RelationActionButton: FC<Props> = observer((props) => {
|
|||
const customButtonElement = customButton ? <>{customButton}</> : <Plus className="h-4 w-4" />;
|
||||
|
||||
return (
|
||||
<CustomMenu customButton={customButtonElement} placement="bottom-start" disabled={disabled} closeOnSelect>
|
||||
{ISSUE_RELATION_OPTIONS.map((item, index) => (
|
||||
<CustomMenu
|
||||
customButton={customButtonElement}
|
||||
placement="bottom-start"
|
||||
disabled={disabled}
|
||||
maxHeight="lg"
|
||||
closeOnSelect
|
||||
>
|
||||
{Object.values(ISSUE_RELATION_OPTIONS).map((item, index) => (
|
||||
<CustomMenu.MenuItem
|
||||
key={index}
|
||||
onClick={(e) => {
|
||||
|
|
|
|||
|
|
@ -17,12 +17,11 @@ export const RelationsCollapsibleTitle: FC<Props> = observer((props) => {
|
|||
const { isOpen, issueId, disabled } = props;
|
||||
// store hook
|
||||
const {
|
||||
relation: { getRelationsByIssueId },
|
||||
relation: { getRelationCountByIssueId },
|
||||
} = useIssueDetail();
|
||||
|
||||
// derived values
|
||||
const issueRelations = getRelationsByIssueId(issueId);
|
||||
const relationsCount = Object.values(issueRelations ?? {}).reduce((acc, curr) => acc + curr.length, 0);
|
||||
const relationsCount = getRelationCountByIssueId(issueId);
|
||||
|
||||
// indicator element
|
||||
const indicatorElement = useMemo(
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { TIssueRelationTypes } from "@plane/types";
|
||||
// hooks
|
||||
import { issueRelationObject } from "@/components/issues/issue-detail/relation-select";
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
// components
|
||||
// Plane-web
|
||||
import { ISSUE_RELATION_OPTIONS } from "@/plane-web/components/relations";
|
||||
import { TIssueRelationTypes } from "@/plane-web/types";
|
||||
//
|
||||
import { IssueActivityBlockComponent } from "./";
|
||||
// component helpers
|
||||
// types
|
||||
|
||||
type TIssueRelationActivity = { activityId: string; ends: "top" | "bottom" | undefined };
|
||||
|
||||
|
|
@ -23,7 +22,7 @@ export const IssueRelationActivity: FC<TIssueRelationActivity> = observer((props
|
|||
if (!activity) return <></>;
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={activity.field ? issueRelationObject[activity.field as TIssueRelationTypes].icon(14) : <></>}
|
||||
icon={activity.field ? ISSUE_RELATION_OPTIONS[activity.field as TIssueRelationTypes].icon(14) : <></>}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -4,42 +4,19 @@ import React from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { CircleDot, CopyPlus, Pencil, X, XCircle } from "lucide-react";
|
||||
import { TIssueRelationTypes, ISearchIssueResponse } from "@plane/types";
|
||||
// hooks
|
||||
// Plane
|
||||
import { ISearchIssueResponse } from "@plane/types";
|
||||
import { RelatedIcon, Tooltip, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { ExistingIssuesListModal } from "@/components/core";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useIssueDetail, useIssues, useProject } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// components
|
||||
// ui
|
||||
// helpers
|
||||
// types
|
||||
|
||||
export type TRelationObject = { className: string; icon: (size: number) => React.ReactElement; placeholder: string };
|
||||
|
||||
export const issueRelationObject: Record<TIssueRelationTypes, TRelationObject> = {
|
||||
relates_to: {
|
||||
className: "bg-custom-background-80 text-custom-text-200",
|
||||
icon: (size) => <RelatedIcon height={size} width={size} className="text-custom-text-200" />,
|
||||
placeholder: "Add related issues",
|
||||
},
|
||||
blocking: {
|
||||
className: "bg-yellow-500/20 text-yellow-700",
|
||||
icon: (size) => <XCircle size={size} className="text-custom-text-200" />,
|
||||
placeholder: "None",
|
||||
},
|
||||
blocked_by: {
|
||||
className: "bg-red-500/20 text-red-700",
|
||||
icon: (size) => <CircleDot size={size} className="text-custom-text-200" />,
|
||||
placeholder: "None",
|
||||
},
|
||||
duplicate: {
|
||||
className: "bg-custom-background-80 text-custom-text-200",
|
||||
icon: (size) => <CopyPlus size={size} className="text-custom-text-200" />,
|
||||
placeholder: "None",
|
||||
},
|
||||
};
|
||||
// Plane-web
|
||||
import { ISSUE_RELATION_OPTIONS } from "@/plane-web/components/relations";
|
||||
import { TIssueRelationTypes } from "@/plane-web/types";
|
||||
|
||||
type TIssueRelationSelect = {
|
||||
className?: string;
|
||||
|
|
@ -129,7 +106,7 @@ export const IssueRelationSelect: React.FC<TIssueRelationSelect> = observer((pro
|
|||
return (
|
||||
<div
|
||||
key={relationIssueId}
|
||||
className={`group flex items-center gap-1 rounded px-1.5 pb-1 pt-1 leading-3 hover:bg-custom-background-90 ${issueRelationObject[relationKey].className}`}
|
||||
className={`group flex items-center gap-1 rounded px-1.5 pb-1 pt-1 leading-3 hover:bg-custom-background-90 ${ISSUE_RELATION_OPTIONS[relationKey].className}`}
|
||||
>
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={currentIssue.name} isMobile={isMobile}>
|
||||
<Link
|
||||
|
|
@ -160,7 +137,7 @@ export const IssueRelationSelect: React.FC<TIssueRelationSelect> = observer((pro
|
|||
})}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-sm text-custom-text-400">{issueRelationObject[relationKey].placeholder}</span>
|
||||
<span className="text-sm text-custom-text-400">{ISSUE_RELATION_OPTIONS[relationKey].placeholder}</span>
|
||||
)}
|
||||
{!disabled && (
|
||||
<span
|
||||
|
|
|
|||
|
|
@ -4,19 +4,20 @@ import { useParams } from "next/navigation";
|
|||
// plane constants
|
||||
import { ALL_ISSUES } from "@plane/constants";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { setToast, TOAST_TYPE } from "@plane/ui";
|
||||
// hooks
|
||||
import { ChartDataType, GanttChartRoot, IBlockUpdateData, IssueGanttSidebar } from "@/components/gantt-chart";
|
||||
import { getMonthChartItemPositionWidthInMonth } from "@/components/gantt-chart/views";
|
||||
import { GanttChartRoot, IBlockUpdateData, IssueGanttSidebar } from "@/components/gantt-chart";
|
||||
import { ETimeLineTypeType, TimeLineTypeContext } from "@/components/gantt-chart/contexts";
|
||||
import { QuickAddIssueRoot, IssueGanttBlock, GanttQuickAddIssueButton } from "@/components/issues";
|
||||
//constants
|
||||
import { EIssueLayoutTypes, EIssuesStoreType } from "@/constants/issue";
|
||||
// helpers
|
||||
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
import { getIssueBlocksStructure } from "@/helpers/issue.helper";
|
||||
//hooks
|
||||
import { useIssues, useUserPermissions } from "@/hooks/store";
|
||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||
import { useIssuesActions } from "@/hooks/use-issues-actions";
|
||||
import { useTimeLineChart } from "@/hooks/use-timeline-chart";
|
||||
// plane web hooks
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
import { useBulkOperationStatus } from "@/plane-web/hooks/use-bulk-operation-status";
|
||||
|
|
@ -37,11 +38,12 @@ export type GanttStoreType =
|
|||
export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGanttRoot) => {
|
||||
const { viewId, isCompletedCycle = false } = props;
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
|
||||
const storeType = useIssueStoreType() as GanttStoreType;
|
||||
const { issues, issuesFilter, issueMap } = useIssues(storeType);
|
||||
const { issues, issuesFilter } = useIssues(storeType);
|
||||
const { fetchIssues, fetchNextIssues, updateIssue, quickAddIssue } = useIssuesActions(storeType);
|
||||
const { initGantt } = useTimeLineChart(ETimeLineTypeType.ISSUE);
|
||||
// store hooks
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
|
|
@ -56,6 +58,10 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
|||
fetchIssues("init-loader", { canGroup: false, perPageCount: 100 }, viewId);
|
||||
}, [fetchIssues, storeType, viewId]);
|
||||
|
||||
useEffect(() => {
|
||||
initGantt();
|
||||
}, []);
|
||||
|
||||
const issuesIds = (issues.groupedIssueIds?.[ALL_ISSUES] as string[]) ?? [];
|
||||
const nextPageResults = issues.getPaginationData(undefined, undefined)?.nextPageResults;
|
||||
|
||||
|
|
@ -65,21 +71,6 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
|||
fetchNextIssues();
|
||||
}, [fetchNextIssues]);
|
||||
|
||||
const getBlockById = useCallback(
|
||||
(id: string, currentViewData?: ChartDataType | undefined) => {
|
||||
const issue = issueMap[id];
|
||||
const block = getIssueBlocksStructure(issue);
|
||||
if (currentViewData) {
|
||||
return {
|
||||
...block,
|
||||
position: getMonthChartItemPositionWidthInMonth(currentViewData, block),
|
||||
};
|
||||
}
|
||||
return block;
|
||||
},
|
||||
[issueMap]
|
||||
);
|
||||
|
||||
const updateIssueBlockStructure = async (issue: TIssue, data: IBlockUpdateData) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
|
|
@ -90,6 +81,23 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
|||
};
|
||||
|
||||
const isAllowed = allowPermissions([EUserPermissions.ADMIN, EUserPermissions.MEMBER], EUserPermissionsLevel.PROJECT);
|
||||
const updateBlockDates = useCallback(
|
||||
(
|
||||
updates: {
|
||||
id: string;
|
||||
start_date?: string;
|
||||
target_date?: string;
|
||||
}[]
|
||||
) =>
|
||||
issues.updateIssueDates(workspaceSlug.toString(), projectId.toString(), updates).catch(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "Error while updating Issue Dates, Please try again Later",
|
||||
});
|
||||
}),
|
||||
[issues]
|
||||
);
|
||||
|
||||
const quickAdd =
|
||||
enableIssueCreation && isAllowed && !isCompletedCycle ? (
|
||||
|
|
@ -107,28 +115,30 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
|||
|
||||
return (
|
||||
<IssueLayoutHOC layout={EIssueLayoutTypes.GANTT}>
|
||||
<div className="h-full w-full">
|
||||
<GanttChartRoot
|
||||
border={false}
|
||||
title="Issues"
|
||||
loaderTitle="Issues"
|
||||
blockIds={issuesIds}
|
||||
getBlockById={getBlockById}
|
||||
blockUpdateHandler={updateIssueBlockStructure}
|
||||
blockToRender={(data: TIssue) => <IssueGanttBlock issueId={data.id} />}
|
||||
sidebarToRender={(props) => <IssueGanttSidebar {...props} showAllBlocks />}
|
||||
enableBlockLeftResize={isAllowed}
|
||||
enableBlockRightResize={isAllowed}
|
||||
enableBlockMove={isAllowed}
|
||||
enableReorder={appliedDisplayFilters?.order_by === "sort_order" && isAllowed}
|
||||
enableAddBlock={isAllowed}
|
||||
enableSelection={isBulkOperationsEnabled && isAllowed}
|
||||
quickAdd={quickAdd}
|
||||
loadMoreBlocks={loadMoreIssues}
|
||||
canLoadMoreBlocks={nextPageResults}
|
||||
showAllBlocks
|
||||
/>
|
||||
</div>
|
||||
<TimeLineTypeContext.Provider value={ETimeLineTypeType.ISSUE}>
|
||||
<div className="h-full w-full">
|
||||
<GanttChartRoot
|
||||
border={false}
|
||||
title="Issues"
|
||||
loaderTitle="Issues"
|
||||
blockIds={issuesIds}
|
||||
blockUpdateHandler={updateIssueBlockStructure}
|
||||
blockToRender={(data: TIssue) => <IssueGanttBlock issueId={data.id} />}
|
||||
sidebarToRender={(props) => <IssueGanttSidebar {...props} showAllBlocks />}
|
||||
enableBlockLeftResize={isAllowed}
|
||||
enableBlockRightResize={isAllowed}
|
||||
enableBlockMove={isAllowed}
|
||||
enableReorder={appliedDisplayFilters?.order_by === "sort_order" && isAllowed}
|
||||
enableAddBlock={isAllowed}
|
||||
enableSelection={isBulkOperationsEnabled && isAllowed}
|
||||
quickAdd={quickAdd}
|
||||
loadMoreBlocks={loadMoreIssues}
|
||||
canLoadMoreBlocks={nextPageResults}
|
||||
updateBlockDates={updateBlockDates}
|
||||
showAllBlocks
|
||||
/>
|
||||
</div>
|
||||
</TimeLineTypeContext.Provider>
|
||||
</IssueLayoutHOC>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { X, Pencil, Trash, Link as LinkIcon } from "lucide-react";
|
||||
import { TIssue, TIssueRelationTypes } from "@plane/types";
|
||||
// Plane
|
||||
import { TIssue } from "@plane/types";
|
||||
import { ControlLink, CustomMenu, Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import { RelationIssueProperty } from "@/components/issues/relations";
|
||||
|
|
@ -13,7 +14,8 @@ import useIssuePeekOverviewRedirection from "@/hooks/use-issue-peek-overview-red
|
|||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
// types
|
||||
import { TIssueRelationTypes } from "@/plane-web/types";
|
||||
//
|
||||
import { TRelationIssueOperations } from "../issue-detail-widgets/relations/helper";
|
||||
|
||||
type Props = {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
"use client";
|
||||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { TIssue, TIssueRelationTypes } from "@plane/types";
|
||||
// Plane
|
||||
import { TIssue } from "@plane/types";
|
||||
// components
|
||||
import { RelationIssueListItem } from "@/components/issues/relations";
|
||||
// types
|
||||
// Plane-web
|
||||
import { TIssueRelationTypes } from "@/plane-web/types";
|
||||
//
|
||||
import { TRelationIssueOperations } from "../issue-detail-widgets/relations/helper";
|
||||
|
||||
type Props = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue