mirror of
https://github.com/makeplane/plane.git
synced 2025-12-23 07:09:34 +01:00
chore: components restructure and improvements (#5383)
* chore: update issue identifier component. * fix: browser tab closed on closing emoji picker issue fixed. * chore: revert back changes in logo props. * chore: update sortable. * chore: minor componenets restructuring. * minor ui update. * fix: issue identifier display in command palette search. * style: issue activity icons consistency.
This commit is contained in:
1
packages/types/src/common.d.ts
vendored
1
packages/types/src/common.d.ts
vendored
@@ -19,5 +19,6 @@ export type TLogoProps = {
|
||||
icon?: {
|
||||
name?: string;
|
||||
color?: string;
|
||||
background_color?: string;
|
||||
};
|
||||
};
|
||||
|
||||
1
packages/types/src/issues.d.ts
vendored
1
packages/types/src/issues.d.ts
vendored
@@ -140,6 +140,7 @@ export interface IIssueActivity {
|
||||
name: string;
|
||||
priority: string | null;
|
||||
sequence_id: string;
|
||||
type_id: string;
|
||||
} | null;
|
||||
new_identifier: string | null;
|
||||
new_value: string | null;
|
||||
|
||||
5
packages/types/src/issues/activity/base.d.ts
vendored
5
packages/types/src/issues/activity/base.d.ts
vendored
@@ -60,4 +60,9 @@ export type TIssueActivityComment =
|
||||
id: string;
|
||||
activity_type: "WORKLOG";
|
||||
created_at?: string;
|
||||
}
|
||||
| {
|
||||
id: string;
|
||||
activity_type: "ISSUE_ADDITIONAL_PROPERTIES_ACTIVITY";
|
||||
created_at?: string;
|
||||
};
|
||||
|
||||
1
packages/types/src/project/projects.d.ts
vendored
1
packages/types/src/project/projects.d.ts
vendored
@@ -144,4 +144,5 @@ export interface ISearchIssueResponse {
|
||||
state__group: TStateGroups;
|
||||
state__name: string;
|
||||
workspace__slug: string;
|
||||
type_id: string;
|
||||
}
|
||||
|
||||
1
packages/types/src/workspace.d.ts
vendored
1
packages/types/src/workspace.d.ts
vendored
@@ -118,6 +118,7 @@ export interface IWorkspaceIssueSearchResult {
|
||||
project_id: string;
|
||||
sequence_id: number;
|
||||
workspace__slug: string;
|
||||
type_id: string;
|
||||
}
|
||||
|
||||
export interface IWorkspacePageSearchResult {
|
||||
|
||||
@@ -101,7 +101,7 @@ export const EmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
|
||||
type: EmojiIconPickerTypes.EMOJI,
|
||||
value: val,
|
||||
});
|
||||
if (closeOnSelect) close();
|
||||
if (closeOnSelect) handleToggle(false);
|
||||
}}
|
||||
height="20rem"
|
||||
width="100%"
|
||||
@@ -120,7 +120,7 @@ export const EmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
|
||||
type: EmojiIconPickerTypes.ICON,
|
||||
value: val,
|
||||
});
|
||||
if (closeOnSelect) close();
|
||||
if (closeOnSelect) handleToggle(false);
|
||||
}}
|
||||
/>
|
||||
</Tab.Panel>
|
||||
|
||||
@@ -5,7 +5,6 @@ import useFontFaceObserver from "use-font-face-observer";
|
||||
import { LUCIDE_ICONS_LIST } from "./icons";
|
||||
// helpers
|
||||
import { emojiCodeToUnicode } from "./helpers";
|
||||
import { cn } from "../../helpers";
|
||||
|
||||
type TLogoProps = {
|
||||
in_use: "emoji" | "icon";
|
||||
@@ -23,11 +22,10 @@ type Props = {
|
||||
logo: TLogoProps;
|
||||
size?: number;
|
||||
type?: "lucide" | "material";
|
||||
customColor?: string;
|
||||
};
|
||||
|
||||
export const Logo: FC<Props> = (props) => {
|
||||
const { logo, size = 16, customColor, type = "material" } = props;
|
||||
const { logo, size = 16, type = "material" } = props;
|
||||
|
||||
// destructuring the logo object
|
||||
const { in_use, emoji, icon } = logo;
|
||||
@@ -74,20 +72,19 @@ export const Logo: FC<Props> = (props) => {
|
||||
{lucideIcon && (
|
||||
<lucideIcon.element
|
||||
style={{
|
||||
color: !customColor ? color : undefined,
|
||||
color: color,
|
||||
height: size,
|
||||
width: size,
|
||||
}}
|
||||
className={cn(customColor)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<span
|
||||
className={cn("material-symbols-rounded", customColor)}
|
||||
className="material-symbols-rounded"
|
||||
style={{
|
||||
fontSize: size,
|
||||
color: !customColor ? color : undefined,
|
||||
color: color,
|
||||
scale: "115%",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -2,8 +2,10 @@ import React, { Fragment, useEffect, useMemo } from "react";
|
||||
import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
||||
import { Draggable } from "./draggable";
|
||||
|
||||
type TEnhancedData<T> = T & { __uuid__?: string };
|
||||
|
||||
type Props<T> = {
|
||||
data: T[];
|
||||
data: TEnhancedData<T>[];
|
||||
render: (item: T, index: number) => React.ReactNode;
|
||||
onChange: (data: T[], movedItem?: T) => void;
|
||||
keyExtractor: (item: T, index: number) => string;
|
||||
@@ -12,9 +14,9 @@ type Props<T> = {
|
||||
};
|
||||
|
||||
const moveItem = <T,>(
|
||||
data: T[],
|
||||
source: T,
|
||||
destination: T & Record<symbol, string>,
|
||||
data: TEnhancedData<T>[],
|
||||
source: TEnhancedData<T>,
|
||||
destination: TEnhancedData<T> & Record<symbol, string>,
|
||||
keyExtractor: (item: T, index: number) => string
|
||||
): {
|
||||
newData: T[];
|
||||
@@ -44,7 +46,16 @@ const moveItem = <T,>(
|
||||
|
||||
newData.splice(adjustedDestinationIndex, 0, movedItem);
|
||||
|
||||
return { newData, movedItem };
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { __uuid__: movedItemId, ...movedItemData } = movedItem;
|
||||
return {
|
||||
newData: newData.map((item) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { __uuid__: uuid, ...rest } = item;
|
||||
return rest as T;
|
||||
}),
|
||||
movedItem: movedItemData as T,
|
||||
};
|
||||
};
|
||||
|
||||
export const Sortable = <T,>({ data, render, onChange, keyExtractor, containerClassName, id }: Props<T>) => {
|
||||
@@ -55,8 +66,8 @@ export const Sortable = <T,>({ data, render, onChange, keyExtractor, containerCl
|
||||
if (!destination) return;
|
||||
const { newData, movedItem } = moveItem(
|
||||
data,
|
||||
source.data as T,
|
||||
destination.data as T & { closestEdge: string },
|
||||
source.data as TEnhancedData<T>,
|
||||
destination.data as TEnhancedData<T> & { closestEdge: string },
|
||||
keyExtractor
|
||||
);
|
||||
onChange(newData, movedItem);
|
||||
@@ -76,8 +87,12 @@ export const Sortable = <T,>({ data, render, onChange, keyExtractor, containerCl
|
||||
|
||||
return (
|
||||
<>
|
||||
{enhancedData.map((item, index) => (
|
||||
<Draggable key={keyExtractor(item, index)} data={item} className={containerClassName}>
|
||||
{data.map((item, index) => (
|
||||
<Draggable
|
||||
key={keyExtractor(enhancedData[index], index)}
|
||||
data={enhancedData[index]}
|
||||
className={containerClassName}
|
||||
>
|
||||
<Fragment>{render(item, index)}</Fragment>
|
||||
</Draggable>
|
||||
))}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./issue-identifier";
|
||||
export * from "./issue-properties-activity";
|
||||
|
||||
@@ -1,27 +1,44 @@
|
||||
import { observer } from "mobx-react";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useIssueDetail, useProject } from "@/hooks/store";
|
||||
|
||||
type TIssueIdentifierProps = {
|
||||
issueId: string;
|
||||
type TIssueIdentifierBaseProps = {
|
||||
projectId: string;
|
||||
size?: "xs" | "sm" | "md" | "lg";
|
||||
textContainerClassName?: string;
|
||||
};
|
||||
|
||||
type TIssueIdentifierFromStore = TIssueIdentifierBaseProps & {
|
||||
issueId: string;
|
||||
};
|
||||
|
||||
type TIssueIdentifierWithDetails = TIssueIdentifierBaseProps & {
|
||||
issueTypeId?: string | null;
|
||||
projectIdentifier: string;
|
||||
issueSequenceId: string | number;
|
||||
};
|
||||
|
||||
type TIssueIdentifierProps = TIssueIdentifierFromStore | TIssueIdentifierWithDetails;
|
||||
export const IssueIdentifier: React.FC<TIssueIdentifierProps> = observer((props) => {
|
||||
const { issueId, projectId } = props;
|
||||
const { projectId, textContainerClassName } = props;
|
||||
// store hooks
|
||||
const { getProjectById } = useProject();
|
||||
const { getProjectIdentifierById } = useProject();
|
||||
const {
|
||||
issue: { getIssueById },
|
||||
} = useIssueDetail();
|
||||
// Determine if the component is using store data or not
|
||||
const isUsingStoreData = "issueId" in props;
|
||||
// derived values
|
||||
const issue = getIssueById(issueId);
|
||||
const projectDetails = getProjectById(projectId);
|
||||
const issue = isUsingStoreData ? getIssueById(props.issueId) : null;
|
||||
const projectIdentifier = isUsingStoreData ? getProjectIdentifierById(projectId) : props.projectIdentifier;
|
||||
const issueSequenceId = isUsingStoreData ? issue?.sequence_id : props.issueSequenceId;
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-base font-medium text-custom-text-300">
|
||||
{projectDetails?.identifier}-{issue?.sequence_id}
|
||||
<span className={cn("text-base font-medium text-custom-text-300", textContainerClassName)}>
|
||||
{projectIdentifier}-{issueSequenceId}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./root";
|
||||
@@ -0,0 +1,8 @@
|
||||
import { FC } from "react";
|
||||
|
||||
type TIssueAdditionalPropertiesActivity = {
|
||||
activityId: string;
|
||||
ends: "top" | "bottom" | undefined;
|
||||
};
|
||||
|
||||
export const IssueAdditionalPropertiesActivity: FC<TIssueAdditionalPropertiesActivity> = () => <></>;
|
||||
@@ -7,10 +7,11 @@ import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
import { FolderPlus, Search, Settings } from "lucide-react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// icons
|
||||
// types
|
||||
import { IWorkspaceSearchResults } from "@plane/types";
|
||||
// hooks
|
||||
// ui
|
||||
import { LayersIcon, Loader, ToggleSwitch, Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import {
|
||||
ChangeIssueAssignee,
|
||||
ChangeIssuePriority,
|
||||
@@ -23,28 +24,28 @@ import {
|
||||
CommandPaletteWorkspaceSettingsActions,
|
||||
} from "@/components/command-palette";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// fetch-keys
|
||||
import { ISSUE_DETAILS } from "@/constants/fetch-keys";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import useDebounce from "@/hooks/use-debounce";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// services
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
// plane web services
|
||||
import { WorkspaceService } from "@/plane-web/services";
|
||||
// services
|
||||
import { IssueService } from "@/services/issue";
|
||||
|
||||
// ui
|
||||
// components
|
||||
// types
|
||||
// fetch-keys
|
||||
// constants
|
||||
|
||||
const workspaceService = new WorkspaceService();
|
||||
const issueService = new IssueService();
|
||||
|
||||
export const CommandModal: React.FC = observer(() => {
|
||||
// hooks
|
||||
const { getProjectById, workspaceProjectIds } = useProject();
|
||||
const { workspaceProjectIds } = useProject();
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { canPerformAnyCreateAction } = useUser();
|
||||
// states
|
||||
@@ -141,8 +142,6 @@ export const CommandModal: React.FC = observer(() => {
|
||||
[debouncedSearchTerm, isWorkspaceLevel, projectId, workspaceSlug] // Only call effect if debounced search term changes
|
||||
);
|
||||
|
||||
const projectDetails = getProjectById(issueDetails?.project_id ?? "");
|
||||
|
||||
return (
|
||||
<Transition.Root show={isCommandPaletteOpen} afterLeave={() => setSearchTerm("")} as={React.Fragment}>
|
||||
<Dialog as="div" className="relative z-30" onClose={() => closePalette()}>
|
||||
@@ -198,8 +197,15 @@ export const CommandModal: React.FC = observer(() => {
|
||||
}`}
|
||||
>
|
||||
{issueDetails && (
|
||||
<div className="overflow-hidden truncate rounded-md bg-custom-background-80 p-2 text-xs font-medium text-custom-text-200">
|
||||
{projectDetails?.identifier}-{issueDetails.sequence_id} {issueDetails.name}
|
||||
<div className="flex gap-2 items-center overflow-hidden truncate rounded-md bg-custom-background-80 p-2 text-xs font-medium text-custom-text-200">
|
||||
{issueDetails.project_id && (
|
||||
<IssueIdentifier
|
||||
issueId={issueDetails.id}
|
||||
projectId={issueDetails.project_id}
|
||||
textContainerClassName="text-xs font-medium text-custom-text-200"
|
||||
/>
|
||||
)}
|
||||
{issueDetails.name}
|
||||
</div>
|
||||
)}
|
||||
{projectId && (
|
||||
|
||||
@@ -9,11 +9,14 @@ import {
|
||||
IWorkspaceProjectSearchResult,
|
||||
IWorkspaceSearchResult,
|
||||
} from "@plane/types";
|
||||
import { ContrastIcon, DiceIcon, LayersIcon } from "@plane/ui";
|
||||
// ui
|
||||
import { ContrastIcon, DiceIcon } from "@plane/ui";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
|
||||
export const commandGroups: {
|
||||
[key: string]: {
|
||||
icon: JSX.Element;
|
||||
icon: JSX.Element | null;
|
||||
itemName: (item: any) => React.ReactNode;
|
||||
path: (item: any, projectId: string | undefined) => string;
|
||||
title: string;
|
||||
@@ -31,14 +34,18 @@ export const commandGroups: {
|
||||
title: "Cycles",
|
||||
},
|
||||
issue: {
|
||||
icon: <LayersIcon className="h-3 w-3" />,
|
||||
icon: null,
|
||||
itemName: (issue: IWorkspaceIssueSearchResult) => (
|
||||
<h6>
|
||||
<span className="text-xs text-custom-text-300">
|
||||
{issue.project__identifier}-{issue.sequence_id}
|
||||
</span>{" "}
|
||||
<div className="flex gap-2">
|
||||
<IssueIdentifier
|
||||
projectId={issue.project_id}
|
||||
issueTypeId={issue.type_id}
|
||||
projectIdentifier={issue.project__identifier}
|
||||
issueSequenceId={issue.sequence_id}
|
||||
textContainerClassName="text-xs"
|
||||
/>{" "}
|
||||
{issue.name}
|
||||
</h6>
|
||||
</div>
|
||||
),
|
||||
path: (issue: IWorkspaceIssueSearchResult) =>
|
||||
`/${issue?.workspace__slug}/projects/${issue?.project_id}/issues/${issue?.id}`,
|
||||
|
||||
@@ -165,7 +165,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Users2Icon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
icon: <Users2Icon size={12} className="text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
archived_at: {
|
||||
message: (activity) => {
|
||||
@@ -182,7 +182,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <ArchiveIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
icon: <ArchiveIcon size={12} className="text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
attachment: {
|
||||
message: (activity, showIssue) => {
|
||||
@@ -219,7 +219,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <PaperclipIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
icon: <PaperclipIcon size={12} className="text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
description: {
|
||||
message: (activity, showIssue) => (
|
||||
@@ -233,7 +233,7 @@ const activityDetails: {
|
||||
)}
|
||||
</>
|
||||
),
|
||||
icon: <MessageSquareIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
icon: <MessageSquareIcon size={12} className="text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
estimate_point: {
|
||||
message: (activity, showIssue) => {
|
||||
@@ -262,7 +262,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <TriangleIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
icon: <TriangleIcon size={12} className="text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
issue: {
|
||||
message: (activity) => {
|
||||
@@ -279,7 +279,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <LayersIcon width={12} height={12} color="#6b7280" aria-hidden="true" />,
|
||||
icon: <LayersIcon width={12} height={12} className="text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
labels: {
|
||||
message: (activity, showIssue, workspaceSlug) => {
|
||||
@@ -320,7 +320,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <TagIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
icon: <TagIcon size={12} className="text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
link: {
|
||||
message: (activity, showIssue) => {
|
||||
@@ -385,7 +385,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Link2Icon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
icon: <Link2Icon size={12} className="text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
cycles: {
|
||||
message: (activity, showIssue, workspaceSlug) => {
|
||||
@@ -435,7 +435,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <ContrastIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
icon: <ContrastIcon size={12} className="text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
modules: {
|
||||
message: (activity, showIssue, workspaceSlug) => {
|
||||
@@ -482,7 +482,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <DiceIcon className="h-3 w-3 !text-[#6b7280]" aria-hidden="true" />,
|
||||
icon: <DiceIcon className="h-3 w-3 !text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
name: {
|
||||
message: (activity, showIssue) => (
|
||||
@@ -496,7 +496,7 @@ const activityDetails: {
|
||||
)}
|
||||
</>
|
||||
),
|
||||
icon: <MessageSquareIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
icon: <MessageSquareIcon size={12} className="text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
parent: {
|
||||
message: (activity, showIssue) => {
|
||||
@@ -527,7 +527,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <UsersIcon className="h-3 w-3 !text-[#6b7280]" aria-hidden="true" />,
|
||||
icon: <UsersIcon className="h-3 w-3 !text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
priority: {
|
||||
message: (activity, showIssue) => (
|
||||
@@ -544,7 +544,7 @@ const activityDetails: {
|
||||
)}
|
||||
</>
|
||||
),
|
||||
icon: <SignalMediumIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
icon: <SignalMediumIcon size={12} className="text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
relates_to: {
|
||||
message: (activity, showIssue) => {
|
||||
@@ -563,7 +563,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <RelatedIcon height="12" width="12" color="#6b7280" />,
|
||||
icon: <RelatedIcon height="12" width="12" className="text-custom-text-200" />,
|
||||
},
|
||||
blocking: {
|
||||
message: (activity, showIssue) => {
|
||||
@@ -582,7 +582,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <BlockerIcon height="12" width="12" color="#6b7280" />,
|
||||
icon: <BlockerIcon height="12" width="12" className="text-custom-text-200" />,
|
||||
},
|
||||
blocked_by: {
|
||||
message: (activity, showIssue) => {
|
||||
@@ -601,7 +601,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <BlockedIcon height="12" width="12" color="#6b7280" />,
|
||||
icon: <BlockedIcon height="12" width="12" className="text-custom-text-200" />,
|
||||
},
|
||||
duplicate: {
|
||||
message: (activity, showIssue) => {
|
||||
@@ -620,7 +620,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <CopyPlus size={12} color="#6b7280" />,
|
||||
icon: <CopyPlus size={12} className="text-custom-text-200" />,
|
||||
},
|
||||
state: {
|
||||
message: (activity, showIssue) => (
|
||||
@@ -634,7 +634,7 @@ const activityDetails: {
|
||||
)}
|
||||
</>
|
||||
),
|
||||
icon: <LayoutGridIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
icon: <LayoutGridIcon size={12} className="text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
start_date: {
|
||||
message: (activity, showIssue) => {
|
||||
@@ -666,7 +666,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Calendar size={12} color="#6b7280" aria-hidden="true" />,
|
||||
icon: <Calendar size={12} className="text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
target_date: {
|
||||
message: (activity, showIssue) => {
|
||||
@@ -697,7 +697,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Calendar size={12} color="#6b7280" aria-hidden="true" />,
|
||||
icon: <Calendar size={12} className="text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
inbox: {
|
||||
message: (activity, showIssue) => (
|
||||
@@ -712,7 +712,7 @@ const activityDetails: {
|
||||
{activity.verb === "2" && ` from intake by marking a duplicate issue.`}
|
||||
</>
|
||||
),
|
||||
icon: <Intake className="size-3" color="#6b7280" aria-hidden="true" />,
|
||||
icon: <Intake className="size-3 text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -734,7 +734,7 @@ export const ActivityMessage = ({ activity, showIssue = false }: ActivityMessage
|
||||
{activityDetails[activity.field as keyof typeof activityDetails]?.message(
|
||||
activity,
|
||||
showIssue,
|
||||
workspaceSlug ? workspaceSlug.toString() : activity.workspace_detail?.slug ?? ""
|
||||
workspaceSlug ? workspaceSlug.toString() : (activity.workspace_detail?.slug ?? "")
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -2,15 +2,16 @@ import { observer } from "mobx-react";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
// hooks
|
||||
import { ISearchIssueResponse } from "@plane/types";
|
||||
// plane web hooks
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
|
||||
interface Props {
|
||||
issue: ISearchIssueResponse;
|
||||
canDeleteIssueIds: boolean;
|
||||
identifier: string | undefined;
|
||||
}
|
||||
|
||||
export const BulkDeleteIssuesModalItem: React.FC<Props> = observer((props: Props) => {
|
||||
const { issue, canDeleteIssueIds, identifier } = props;
|
||||
const { issue, canDeleteIssueIds } = props;
|
||||
|
||||
const color = issue.state__color;
|
||||
|
||||
@@ -20,7 +21,7 @@ export const BulkDeleteIssuesModalItem: React.FC<Props> = observer((props: Props
|
||||
as="div"
|
||||
value={issue.id}
|
||||
className={({ active }) =>
|
||||
`flex cursor-pointer select-none items-center justify-between rounded-md px-3 py-2 ${
|
||||
`flex cursor-pointer select-none items-center justify-between rounded-md px-3 py-2 my-0.5 ${
|
||||
active ? "bg-custom-background-80 text-custom-text-100" : ""
|
||||
}`
|
||||
}
|
||||
@@ -33,9 +34,7 @@ export const BulkDeleteIssuesModalItem: React.FC<Props> = observer((props: Props
|
||||
backgroundColor: color,
|
||||
}}
|
||||
/>
|
||||
<span className="flex-shrink-0 text-xs">
|
||||
{identifier}-{issue.sequence_id}
|
||||
</span>
|
||||
<IssueIdentifier issueId={issue.id} projectId={issue.project_id} textContainerClassName="text-xs" />
|
||||
<span>{issue.name}</span>
|
||||
</div>
|
||||
</Combobox.Option>
|
||||
|
||||
@@ -6,8 +6,9 @@ import { useParams } from "next/navigation";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
import { Search } from "lucide-react";
|
||||
import { Combobox, Dialog, Transition } from "@headlessui/react";
|
||||
//plane
|
||||
// types
|
||||
import { ISearchIssueResponse, IUser } from "@plane/types";
|
||||
// ui
|
||||
import { Button, Loader, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
@@ -15,13 +16,11 @@ import { EmptyState } from "@/components/empty-state";
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { EIssuesStoreType } from "@/constants/issue";
|
||||
// hooks
|
||||
import { useIssues, useProject } from "@/hooks/store";
|
||||
import { useIssues } from "@/hooks/store";
|
||||
import useDebounce from "@/hooks/use-debounce";
|
||||
// services
|
||||
import { ProjectService } from "@/services/project";
|
||||
// ui
|
||||
// icons
|
||||
// components
|
||||
// local components
|
||||
import { BulkDeleteIssuesModalItem } from "./bulk-delete-issues-modal-item";
|
||||
|
||||
type FormInput = {
|
||||
@@ -41,7 +40,6 @@ export const BulkDeleteIssuesModal: React.FC<Props> = observer((props) => {
|
||||
// router params
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// hooks
|
||||
const { getProjectById } = useProject();
|
||||
const {
|
||||
issues: { removeBulkIssues },
|
||||
} = useIssues(EIssuesStoreType.PROJECT);
|
||||
@@ -115,8 +113,6 @@ export const BulkDeleteIssuesModal: React.FC<Props> = observer((props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const projectDetails = getProjectById(projectId as string);
|
||||
|
||||
const issueList =
|
||||
issues.length > 0 ? (
|
||||
<li className="p-2">
|
||||
@@ -127,7 +123,6 @@ export const BulkDeleteIssuesModal: React.FC<Props> = observer((props) => {
|
||||
{issues.map((issue) => (
|
||||
<BulkDeleteIssuesModalItem
|
||||
issue={issue}
|
||||
identifier={projectDetails?.identifier}
|
||||
canDeleteIssueIds={watch("delete_issue_ids").includes(issue.id)}
|
||||
key={issue.id}
|
||||
/>
|
||||
|
||||
@@ -10,6 +10,8 @@ import { Button, Loader, ToggleSwitch, Tooltip, TOAST_TYPE, setToast } from "@pl
|
||||
// hooks
|
||||
import useDebounce from "@/hooks/use-debounce";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
// services
|
||||
import { ProjectService } from "@/services/project";
|
||||
// components
|
||||
@@ -149,7 +151,13 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
|
||||
key={issue.id}
|
||||
className="flex items-center gap-1 whitespace-nowrap rounded-md border border-custom-border-200 bg-custom-background-80 py-1 pl-2 text-xs text-custom-text-100"
|
||||
>
|
||||
{issue.project__identifier}-{issue.sequence_id}
|
||||
<IssueIdentifier
|
||||
projectId={issue.project_id}
|
||||
issueTypeId={issue.type_id}
|
||||
projectIdentifier={issue.project__identifier}
|
||||
issueSequenceId={issue.sequence_id}
|
||||
textContainerClassName="text-xs text-custom-text-200"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="group p-1"
|
||||
@@ -232,7 +240,7 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
|
||||
htmlFor={`issue-${issue.id}`}
|
||||
value={issue}
|
||||
className={({ active }) =>
|
||||
`group flex w-full cursor-pointer select-none items-center justify-between gap-2 rounded-md px-3 py-2 text-custom-text-200 ${
|
||||
`group flex w-full cursor-pointer select-none items-center justify-between gap-2 rounded-md px-3 py-2 my-0.5 text-custom-text-200 ${
|
||||
active ? "bg-custom-background-80 text-custom-text-100" : ""
|
||||
} ${selected ? "text-custom-text-100" : ""}`
|
||||
}
|
||||
@@ -245,8 +253,14 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
|
||||
backgroundColor: issue.state__color,
|
||||
}}
|
||||
/>
|
||||
<span className="flex-shrink-0 text-xs">
|
||||
{issue.project__identifier}-{issue.sequence_id}
|
||||
<span className="flex-shrink-0">
|
||||
<IssueIdentifier
|
||||
projectId={issue.project_id}
|
||||
issueTypeId={issue.type_id}
|
||||
projectIdentifier={issue.project__identifier}
|
||||
issueSequenceId={issue.sequence_id}
|
||||
textContainerClassName="text-xs text-custom-text-200"
|
||||
/>
|
||||
</span>
|
||||
{issue.name}
|
||||
</div>
|
||||
|
||||
@@ -22,9 +22,11 @@ import { EIssuesStoreType } from "@/constants/issue";
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { renderFormattedDate, renderFormattedDateWithoutYear } from "@/helpers/date-time.helper";
|
||||
// hooks
|
||||
import { useIssueDetail, useIssues, useProject } from "@/hooks/store";
|
||||
import { useIssueDetail, useIssues } from "@/hooks/store";
|
||||
import { useIntersectionObserver } from "@/hooks/use-intersection-observer";
|
||||
import useLocalStorage from "@/hooks/use-local-storage";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
|
||||
export type ActiveCycleStatsProps = {
|
||||
workspaceSlug: string;
|
||||
@@ -62,8 +64,6 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
|
||||
setPeekIssue,
|
||||
} = useIssueDetail();
|
||||
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && projectId && cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId, { priority: "urgent,high" }) : null,
|
||||
workspaceSlug && projectId && cycleId ? () => fetchActiveCycleIssues(workspaceSlug, projectId, 30, cycleId) : null,
|
||||
@@ -183,20 +183,16 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-1.5 flex-grow w-full min-w-24 truncate">
|
||||
<PriorityIcon priority={issue.priority} withContainer size={12} />
|
||||
|
||||
<Tooltip
|
||||
tooltipHeading="Issue ID"
|
||||
tooltipContent={`${currentProjectDetails?.identifier}-${issue.sequence_id}`}
|
||||
>
|
||||
<span className="flex-shrink-0 text-xs text-custom-text-200">
|
||||
{currentProjectDetails?.identifier}-{issue.sequence_id}
|
||||
</span>
|
||||
</Tooltip>
|
||||
<IssueIdentifier
|
||||
issueId={issue.id}
|
||||
projectId={projectId}
|
||||
textContainerClassName="text-xs text-custom-text-200"
|
||||
/>
|
||||
<Tooltip position="top-left" tooltipHeading="Title" tooltipContent={issue.name}>
|
||||
<span className="text-[0.825rem] text-custom-text-100 truncate">{issue.name}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<PriorityIcon priority={issue.priority} withContainer size={12} />
|
||||
<div className="flex items-center gap-1.5 flex-shrink-0">
|
||||
<StateDropdown
|
||||
value={issue.state_id}
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
|
||||
import isToday from "date-fns/isToday";
|
||||
import { observer } from "mobx-react";
|
||||
// types
|
||||
import { TIssue, TWidgetIssue } from "@plane/types";
|
||||
// hooks
|
||||
// ui
|
||||
import { Avatar, AvatarGroup, ControlLink, PriorityIcon } from "@plane/ui";
|
||||
// helpers
|
||||
import { findTotalDaysInRange, getDate, renderFormattedDate } from "@/helpers/date-time.helper";
|
||||
// hooks
|
||||
import { useIssueDetail, useMember, useProject } from "@/hooks/store";
|
||||
// types
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
|
||||
export type IssueListItemProps = {
|
||||
issueId: string;
|
||||
@@ -42,23 +44,35 @@ export const AssignedUpcomingIssueListItem: React.FC<IssueListItemProps> = obser
|
||||
<ControlLink
|
||||
href={`/${workspaceSlug}/projects/${issueDetails.project_id}/issues/${issueDetails.id}`}
|
||||
onClick={() => onClick(issueDetails)}
|
||||
className="grid grid-cols-6 gap-1 rounded px-3 py-2 hover:bg-custom-background-80"
|
||||
className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80"
|
||||
>
|
||||
<div className="col-span-4 flex items-center gap-3">
|
||||
<PriorityIcon priority={issueDetails.priority} withContainer />
|
||||
<span className="flex-shrink-0 text-xs font-medium">
|
||||
{projectDetails?.identifier} {issueDetails.sequence_id}
|
||||
</span>
|
||||
<div className="col-span-7 flex items-center gap-3">
|
||||
{projectDetails && (
|
||||
<IssueIdentifier
|
||||
issueId={issueDetails.id}
|
||||
projectId={projectDetails?.id}
|
||||
textContainerClassName="text-xs text-custom-text-200 font-medium"
|
||||
/>
|
||||
)}
|
||||
<h6 className="flex-grow truncate text-sm">{issueDetails.name}</h6>
|
||||
</div>
|
||||
<div className="text-center text-xs">
|
||||
<div className="flex justify-center col-span-1">
|
||||
<PriorityIcon priority={issueDetails.priority} size={12} withContainer />
|
||||
</div>
|
||||
<div className="text-center text-xs col-span-2">
|
||||
{targetDate ? (isToday(targetDate) ? "Today" : renderFormattedDate(targetDate)) : "-"}
|
||||
</div>
|
||||
<div className="text-center text-xs">
|
||||
<div className="flex justify-center text-xs col-span-2">
|
||||
{blockedByIssues.length > 0
|
||||
? blockedByIssues.length > 1
|
||||
? `${blockedByIssues.length} blockers`
|
||||
: `${blockedByIssueProjectDetails?.identifier} ${blockedByIssues[0]?.sequence_id}`
|
||||
: blockedByIssueProjectDetails && (
|
||||
<IssueIdentifier
|
||||
issueId={blockedByIssues[0]?.id}
|
||||
projectId={blockedByIssueProjectDetails?.id}
|
||||
textContainerClassName="text-xs text-custom-text-200 font-medium"
|
||||
/>
|
||||
)
|
||||
: "-"}
|
||||
</div>
|
||||
</ControlLink>
|
||||
@@ -89,23 +103,35 @@ export const AssignedOverdueIssueListItem: React.FC<IssueListItemProps> = observ
|
||||
<ControlLink
|
||||
href={`/${workspaceSlug}/projects/${issueDetails.project_id}/issues/${issueDetails.id}`}
|
||||
onClick={() => onClick(issueDetails)}
|
||||
className="grid grid-cols-6 gap-1 rounded px-3 py-2 hover:bg-custom-background-80"
|
||||
className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80"
|
||||
>
|
||||
<div className="col-span-4 flex items-center gap-3">
|
||||
<PriorityIcon priority={issueDetails.priority} withContainer />
|
||||
<span className="flex-shrink-0 text-xs font-medium">
|
||||
{projectDetails?.identifier} {issueDetails.sequence_id}
|
||||
</span>
|
||||
<div className="col-span-7 flex items-center gap-3">
|
||||
{projectDetails && (
|
||||
<IssueIdentifier
|
||||
issueId={issueDetails.id}
|
||||
projectId={projectDetails?.id}
|
||||
textContainerClassName="text-xs text-custom-text-200 font-medium"
|
||||
/>
|
||||
)}
|
||||
<h6 className="flex-grow truncate text-sm">{issueDetails.name}</h6>
|
||||
</div>
|
||||
<div className="text-center text-xs">
|
||||
<div className="flex justify-center col-span-1">
|
||||
<PriorityIcon priority={issueDetails.priority} size={12} withContainer />
|
||||
</div>
|
||||
<div className="text-center text-xs col-span-2">
|
||||
{dueBy} {`day${dueBy > 1 ? "s" : ""}`}
|
||||
</div>
|
||||
<div className="text-center text-xs">
|
||||
<div className="flex justify-center text-xs col-span-2">
|
||||
{blockedByIssues.length > 0
|
||||
? blockedByIssues.length > 1
|
||||
? `${blockedByIssues.length} blockers`
|
||||
: `${blockedByIssueProjectDetails?.identifier} ${blockedByIssues[0]?.sequence_id}`
|
||||
: blockedByIssueProjectDetails && (
|
||||
<IssueIdentifier
|
||||
issueId={blockedByIssues[0]?.id}
|
||||
projectId={blockedByIssueProjectDetails?.id}
|
||||
textContainerClassName="text-xs text-custom-text-200 font-medium"
|
||||
/>
|
||||
)
|
||||
: "-"}
|
||||
</div>
|
||||
</ControlLink>
|
||||
@@ -130,15 +156,21 @@ export const AssignedCompletedIssueListItem: React.FC<IssueListItemProps> = obse
|
||||
<ControlLink
|
||||
href={`/${workspaceSlug}/projects/${issueDetails.project_id}/issues/${issueDetails.id}`}
|
||||
onClick={() => onClick(issueDetails)}
|
||||
className="grid grid-cols-6 gap-1 rounded px-3 py-2 hover:bg-custom-background-80"
|
||||
className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80"
|
||||
>
|
||||
<div className="col-span-6 flex items-center gap-3">
|
||||
<PriorityIcon priority={issueDetails.priority} withContainer />
|
||||
<span className="flex-shrink-0 text-xs font-medium">
|
||||
{projectDetails?.identifier} {issueDetails.sequence_id}
|
||||
</span>
|
||||
<div className="col-span-11 flex items-center gap-3">
|
||||
{projectDetails && (
|
||||
<IssueIdentifier
|
||||
issueId={issueDetails.id}
|
||||
projectId={projectDetails?.id}
|
||||
textContainerClassName="text-xs text-custom-text-200 font-medium"
|
||||
/>
|
||||
)}
|
||||
<h6 className="flex-grow truncate text-sm">{issueDetails.name}</h6>
|
||||
</div>
|
||||
<div className="flex justify-center col-span-1">
|
||||
<PriorityIcon priority={issueDetails.priority} size={12} withContainer />
|
||||
</div>
|
||||
</ControlLink>
|
||||
);
|
||||
});
|
||||
@@ -163,19 +195,25 @@ export const CreatedUpcomingIssueListItem: React.FC<IssueListItemProps> = observ
|
||||
<ControlLink
|
||||
href={`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`}
|
||||
onClick={() => onClick(issue)}
|
||||
className="grid grid-cols-6 gap-1 rounded px-3 py-2 hover:bg-custom-background-80"
|
||||
className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80"
|
||||
>
|
||||
<div className="col-span-4 flex items-center gap-3">
|
||||
<PriorityIcon priority={issue.priority} withContainer />
|
||||
<span className="flex-shrink-0 text-xs font-medium">
|
||||
{projectDetails?.identifier} {issue.sequence_id}
|
||||
</span>
|
||||
<div className="col-span-7 flex items-center gap-3">
|
||||
{projectDetails && (
|
||||
<IssueIdentifier
|
||||
issueId={issue.id}
|
||||
projectId={projectDetails?.id}
|
||||
textContainerClassName="text-xs text-custom-text-200 font-medium"
|
||||
/>
|
||||
)}
|
||||
<h6 className="flex-grow truncate text-sm">{issue.name}</h6>
|
||||
</div>
|
||||
<div className="text-center text-xs">
|
||||
<div className="flex justify-center col-span-1">
|
||||
<PriorityIcon priority={issue.priority} size={12} withContainer />
|
||||
</div>
|
||||
<div className="text-center text-xs col-span-2">
|
||||
{targetDate ? (isToday(targetDate) ? "Today" : renderFormattedDate(targetDate)) : "-"}
|
||||
</div>
|
||||
<div className="flex justify-center text-xs">
|
||||
<div className="flex justify-center text-xs col-span-2">
|
||||
{issue.assignee_ids && issue.assignee_ids?.length > 0 ? (
|
||||
<AvatarGroup>
|
||||
{issue.assignee_ids?.map((assigneeId) => {
|
||||
@@ -215,19 +253,25 @@ export const CreatedOverdueIssueListItem: React.FC<IssueListItemProps> = observe
|
||||
<ControlLink
|
||||
href={`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`}
|
||||
onClick={() => onClick(issue)}
|
||||
className="grid grid-cols-6 gap-1 rounded px-3 py-2 hover:bg-custom-background-80"
|
||||
className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80"
|
||||
>
|
||||
<div className="col-span-4 flex items-center gap-3">
|
||||
<PriorityIcon priority={issue.priority} withContainer />
|
||||
<span className="flex-shrink-0 text-xs font-medium">
|
||||
{projectDetails?.identifier} {issue.sequence_id}
|
||||
</span>
|
||||
<div className="col-span-7 flex items-center gap-3">
|
||||
{projectDetails && (
|
||||
<IssueIdentifier
|
||||
issueId={issue.id}
|
||||
projectId={projectDetails?.id}
|
||||
textContainerClassName="text-xs text-custom-text-200 font-medium"
|
||||
/>
|
||||
)}
|
||||
<h6 className="flex-grow truncate text-sm">{issue.name}</h6>
|
||||
</div>
|
||||
<div className="text-center text-xs">
|
||||
<div className="flex justify-center col-span-1">
|
||||
<PriorityIcon priority={issue.priority} size={12} withContainer />
|
||||
</div>
|
||||
<div className="text-center text-xs col-span-2">
|
||||
{dueBy} {`day${dueBy > 1 ? "s" : ""}`}
|
||||
</div>
|
||||
<div className="flex justify-center text-xs">
|
||||
<div className="flex justify-center text-xs col-span-2">
|
||||
{issue.assignee_ids.length > 0 ? (
|
||||
<AvatarGroup>
|
||||
{issue.assignee_ids?.map((assigneeId) => {
|
||||
@@ -265,16 +309,22 @@ export const CreatedCompletedIssueListItem: React.FC<IssueListItemProps> = obser
|
||||
<ControlLink
|
||||
href={`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`}
|
||||
onClick={() => onClick(issue)}
|
||||
className="grid grid-cols-6 gap-1 rounded px-3 py-2 hover:bg-custom-background-80"
|
||||
className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80"
|
||||
>
|
||||
<div className="col-span-5 flex items-center gap-3">
|
||||
<PriorityIcon priority={issue.priority} withContainer />
|
||||
<span className="flex-shrink-0 text-xs font-medium">
|
||||
{projectDetails?.identifier} {issue.sequence_id}
|
||||
</span>
|
||||
<div className="col-span-9 flex items-center gap-3">
|
||||
{projectDetails && (
|
||||
<IssueIdentifier
|
||||
issueId={issue.id}
|
||||
projectId={projectDetails?.id}
|
||||
textContainerClassName="text-xs text-custom-text-200 font-medium"
|
||||
/>
|
||||
)}
|
||||
<h6 className="flex-grow truncate text-sm">{issue.name}</h6>
|
||||
</div>
|
||||
<div className="flex justify-center text-xs">
|
||||
<div className="flex justify-center col-span-1">
|
||||
<PriorityIcon priority={issue.priority} size={12} withContainer />
|
||||
</div>
|
||||
<div className="flex justify-center text-xs col-span-2">
|
||||
{issue.assignee_ids.length > 0 ? (
|
||||
<AvatarGroup>
|
||||
{issue.assignee_ids?.map((assigneeId) => {
|
||||
|
||||
@@ -74,22 +74,23 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
|
||||
</Loader>
|
||||
) : issuesList.length > 0 ? (
|
||||
<>
|
||||
<div className="mt-7 mx-6 border-b-[0.5px] border-custom-border-200 grid grid-cols-6 gap-1 text-xs text-custom-text-300 pb-1">
|
||||
<div className="mt-7 mx-6 border-b-[0.5px] border-custom-border-200 grid grid-cols-12 gap-1 text-xs text-custom-text-300 pb-1">
|
||||
<h6
|
||||
className={cn("pl-1 flex items-center gap-1 col-span-4", {
|
||||
"col-span-6": type === "assigned" && tab === "completed",
|
||||
"col-span-5": type === "created" && tab === "completed",
|
||||
className={cn("pl-1 flex items-center gap-1 col-span-7", {
|
||||
"col-span-11": type === "assigned" && tab === "completed",
|
||||
"col-span-9": type === "created" && tab === "completed",
|
||||
})}
|
||||
>
|
||||
Issues
|
||||
<span className="flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-medium rounded-xl px-3 flex items-center text-center justify-center">
|
||||
<span className="flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-medium rounded-xl px-2 flex items-center text-center justify-center">
|
||||
{widgetStats.count}
|
||||
</span>
|
||||
</h6>
|
||||
{["upcoming", "pending"].includes(tab) && <h6 className="text-center">Due date</h6>}
|
||||
{tab === "overdue" && <h6 className="text-center">Due by</h6>}
|
||||
{type === "assigned" && tab !== "completed" && <h6 className="text-center">Blocked by</h6>}
|
||||
{type === "created" && <h6 className="text-center">Assigned to</h6>}
|
||||
<h6 className="text-center col-span-1">Priority</h6>
|
||||
{["upcoming", "pending"].includes(tab) && <h6 className="text-center col-span-2">Due date</h6>}
|
||||
{tab === "overdue" && <h6 className="text-center col-span-2">Due by</h6>}
|
||||
{type === "assigned" && tab !== "completed" && <h6 className="text-center col-span-2">Blocked by</h6>}
|
||||
{type === "created" && <h6 className="text-center col-span-2">Assigned to</h6>}
|
||||
</div>
|
||||
<div className="px-4 pb-3 mt-2">
|
||||
{issuesList.map((issue) => {
|
||||
|
||||
@@ -2,7 +2,8 @@ import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// hooks
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
// plane wev components
|
||||
// plane web components
|
||||
import { IssueAdditionalPropertiesActivity } from "@/plane-web/components/issues";
|
||||
import { IssueActivityWorklog } from "@/plane-web/components/issues/worklog/activity/root";
|
||||
// plane web constants
|
||||
import { TActivityFilters, filterActivityOnSelectedFilters } from "@/plane-web/constants/issues";
|
||||
@@ -56,6 +57,11 @@ export const IssueActivityCommentRoot: FC<TIssueActivityCommentRoot> = observer(
|
||||
activityId={activityComment.id}
|
||||
ends={index === 0 ? "top" : index === filteredActivityComments.length - 1 ? "bottom" : undefined}
|
||||
/>
|
||||
) : activityComment.activity_type === "ISSUE_ADDITIONAL_PROPERTIES_ACTIVITY" ? (
|
||||
<IssueAdditionalPropertiesActivity
|
||||
activityId={activityComment.id}
|
||||
ends={index === 0 ? "top" : index === filteredActivityComments.length - 1 ? "bottom" : undefined}
|
||||
/>
|
||||
) : activityComment.activity_type === "WORKLOG" ? (
|
||||
<IssueActivityWorklog
|
||||
workspaceSlug={workspaceSlug}
|
||||
|
||||
@@ -27,9 +27,9 @@ export const IssueArchivedAtActivity: FC<TIssueArchivedAtActivity> = observer((p
|
||||
<IssueActivityBlockComponent
|
||||
icon={
|
||||
activity.new_value === "restore" ? (
|
||||
<RotateCcw className="h-3.5 w-3.5" color="#6b7280" aria-hidden="true" />
|
||||
<RotateCcw className="h-3.5 w-3.5 text-custom-text-200" aria-hidden="true" />
|
||||
) : (
|
||||
<ArchiveIcon className="h-3.5 w-3.5" color="#6b7280" aria-hidden="true" />
|
||||
<ArchiveIcon className="h-3.5 w-3.5 text-custom-text-200" aria-hidden="true" />
|
||||
)
|
||||
}
|
||||
activityId={activityId}
|
||||
|
||||
@@ -21,7 +21,7 @@ export const IssueAssigneeActivity: FC<TIssueAssigneeActivity> = observer((props
|
||||
if (!activity) return <></>;
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={<Users className="h-3 w-3 flex-shrink-0" />}
|
||||
icon={<Users className="h-3.5 w-3.5 flex-shrink-0 text-custom-text-200" />}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
||||
@@ -20,7 +20,7 @@ export const IssueAttachmentActivity: FC<TIssueAttachmentActivity> = observer((p
|
||||
if (!activity) return <></>;
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={<Paperclip size={14} color="#6b7280" aria-hidden="true" />}
|
||||
icon={<Paperclip size={14} className="text-custom-text-200" aria-hidden="true" />}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
||||
@@ -23,7 +23,7 @@ export const IssueCycleActivity: FC<TIssueCycleActivity> = observer((props) => {
|
||||
if (!activity) return <></>;
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={<ContrastIcon className="h-4 w-4 flex-shrink-0 text-[#6b7280]" />}
|
||||
icon={<ContrastIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200" />}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
||||
@@ -24,7 +24,7 @@ export const IssueDefaultActivity: FC<TIssueDefaultActivity> = observer((props)
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
activityId={activityId}
|
||||
icon={<LayersIcon width={12} height={12} color="#6b7280" aria-hidden="true" />}
|
||||
icon={<LayersIcon width={14} height={14} className="text-custom-text-200" aria-hidden="true" />}
|
||||
ends={ends}
|
||||
>
|
||||
<>{activity.verb === "created" ? " created the issue." : " deleted an issue."}</>
|
||||
|
||||
@@ -20,7 +20,7 @@ export const IssueDescriptionActivity: FC<TIssueDescriptionActivity> = observer(
|
||||
if (!activity) return <></>;
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={<MessageSquare size={14} color="#6b7280" aria-hidden="true" />}
|
||||
icon={<MessageSquare size={14} className="text-custom-text-200" aria-hidden="true" />}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
||||
@@ -21,7 +21,7 @@ export const IssueEstimateActivity: FC<TIssueEstimateActivity> = observer((props
|
||||
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={<Triangle size={14} color="#6b7280" aria-hidden="true" />}
|
||||
icon={<Triangle size={14} className="text-custom-text-200" aria-hidden="true" />}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
||||
@@ -36,7 +36,7 @@ export const IssueInboxActivity: FC<TIssueInboxActivity> = observer((props) => {
|
||||
if (!activity) return <></>;
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={<Intake className="h-4 w-4 flex-shrink-0" />}
|
||||
icon={<Intake className="h-4 w-4 flex-shrink-0 text-custom-text-200" />}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
||||
@@ -21,7 +21,7 @@ export const IssueLabelActivity: FC<TIssueLabelActivity> = observer((props) => {
|
||||
if (!activity) return <></>;
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={<Tag size={14} color="#6b7280" aria-hidden="true" />}
|
||||
icon={<Tag size={14} className="text-custom-text-200" aria-hidden="true" />}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
||||
@@ -20,7 +20,7 @@ export const IssueLinkActivity: FC<TIssueLinkActivity> = observer((props) => {
|
||||
if (!activity) return <></>;
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={<MessageSquare size={14} color="#6b7280" aria-hidden="true" />}
|
||||
icon={<MessageSquare size={14} className="text-custom-text-200" aria-hidden="true" />}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
||||
@@ -23,7 +23,7 @@ export const IssueModuleActivity: FC<TIssueModuleActivity> = observer((props) =>
|
||||
if (!activity) return <></>;
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={<DiceIcon className="h-4 w-4 flex-shrink-0 text-[#6b7280]" />}
|
||||
icon={<DiceIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200" />}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
||||
@@ -20,7 +20,7 @@ export const IssueNameActivity: FC<TIssueNameActivity> = observer((props) => {
|
||||
if (!activity) return <></>;
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={<MessageSquare size={14} color="#6b7280" aria-hidden="true" />}
|
||||
icon={<MessageSquare size={14} className="text-custom-text-200" aria-hidden="true" />}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
||||
@@ -20,7 +20,7 @@ export const IssueParentActivity: FC<TIssueParentActivity> = observer((props) =>
|
||||
if (!activity) return <></>;
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={<LayoutPanelTop size={14} color="#6b7280" aria-hidden="true" />}
|
||||
icon={<LayoutPanelTop size={14} className="text-custom-text-200" aria-hidden="true" />}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
||||
@@ -20,7 +20,7 @@ export const IssuePriorityActivity: FC<TIssuePriorityActivity> = observer((props
|
||||
if (!activity) return <></>;
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={<Signal size={14} color="#6b7280" aria-hidden="true" />}
|
||||
icon={<Signal size={14} className="text-custom-text-200" aria-hidden="true" />}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
||||
@@ -22,7 +22,7 @@ export const IssueStartDateActivity: FC<TIssueStartDateActivity> = observer((pro
|
||||
if (!activity) return <></>;
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={<CalendarDays size={14} color="#6b7280" aria-hidden="true" />}
|
||||
icon={<CalendarDays size={14} className="text-custom-text-200" aria-hidden="true" />}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
||||
@@ -23,7 +23,7 @@ export const IssueStateActivity: FC<TIssueStateActivity> = observer((props) => {
|
||||
if (!activity) return <></>;
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={<DoubleCircleIcon className="h-4 w-4 flex-shrink-0" />}
|
||||
icon={<DoubleCircleIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200" />}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
||||
@@ -22,7 +22,7 @@ export const IssueTargetDateActivity: FC<TIssueTargetDateActivity> = observer((p
|
||||
if (!activity) return <></>;
|
||||
return (
|
||||
<IssueActivityBlockComponent
|
||||
icon={<CalendarDays size={14} color="#6b7280" aria-hidden="true" />}
|
||||
icon={<CalendarDays size={14} className="text-custom-text-200" aria-hidden="true" />}
|
||||
activityId={activityId}
|
||||
ends={ends}
|
||||
>
|
||||
|
||||
@@ -45,7 +45,7 @@ export const IssueCommentBlock: FC<TIssueCommentBlock> = observer((props) => {
|
||||
</>
|
||||
)}
|
||||
<div className="absolute top-2 left-4 w-5 h-5 rounded-full overflow-hidden flex justify-center items-center bg-custom-background-80">
|
||||
<MessageCircle className="w-3 h-3" color="#6b7280" />
|
||||
<MessageCircle className="w-3 h-3 text-custom-text-200" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full truncate relative flex ">
|
||||
|
||||
@@ -66,8 +66,8 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="mb-2.5 flex items-center">
|
||||
<IssueIdentifier issueId={issueId} projectId={issue.project_id} />
|
||||
<div className="mb-2.5 flex items-center gap-4">
|
||||
<IssueIdentifier issueId={issueId} projectId={issue.project_id} size="md" />
|
||||
<IssueUpdateStatus isSubmitting={isSubmitting} />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,15 +4,17 @@ import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { Pencil, X } from "lucide-react";
|
||||
// hooks
|
||||
// components
|
||||
import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
||||
import { ParentIssuesListModal } from "@/components/issues";
|
||||
// ui
|
||||
import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { ParentIssuesListModal } from "@/components/issues";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useIssueDetail, useProject } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
// types
|
||||
import { TIssueOperations } from "./root";
|
||||
|
||||
@@ -102,16 +104,23 @@ export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props)
|
||||
disabled={disabled}
|
||||
>
|
||||
{issue.parent_id && parentIssue ? (
|
||||
<div className="flex items-center gap-1 bg-green-500/20 text-green-700 rounded px-1.5 py-1">
|
||||
<div className="flex items-center gap-1 bg-green-500/20 rounded px-1.5 py-1">
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={parentIssue.name} isMobile={isMobile}>
|
||||
<Link
|
||||
href={`/${workspaceSlug}/projects/${parentIssue.project_id}/issues/${parentIssue?.id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs font-medium"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{parentIssueProjectDetails?.identifier}-{parentIssue.sequence_id}
|
||||
{parentIssue?.project_id && parentIssueProjectDetails && (
|
||||
<IssueIdentifier
|
||||
projectId={parentIssue.project_id}
|
||||
issueTypeId={parentIssue.type_id}
|
||||
projectIdentifier={parentIssueProjectDetails?.identifier}
|
||||
issueSequenceId={parentIssue.sequence_id}
|
||||
textContainerClassName="text-xs font-medium text-green-700"
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ import { TIssue } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// hooks
|
||||
import { useIssues, useProject, useProjectState } from "@/hooks/store";
|
||||
import { useIssues, useProjectState } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
// types
|
||||
import { TIssueOperations } from "../root";
|
||||
import { IssueParentSiblings } from "./siblings";
|
||||
@@ -26,7 +28,6 @@ export const IssueParentDetail: FC<TIssueParentDetail> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId, issue, issueOperations } = props;
|
||||
// hooks
|
||||
const { issueMap } = useIssues();
|
||||
const { getProjectById } = useProject();
|
||||
const { getProjectStates } = useProjectState();
|
||||
|
||||
const parentIssue = issueMap?.[issue.parent_id || ""] || undefined;
|
||||
@@ -42,12 +43,16 @@ export const IssueParentDetail: FC<TIssueParentDetail> = observer((props) => {
|
||||
<>
|
||||
<div className="mb-5 flex w-min items-center gap-3 whitespace-nowrap rounded-md border border-custom-border-300 bg-custom-background-80 px-2.5 py-1 text-xs">
|
||||
<Link href={`/${workspaceSlug}/projects/${parentIssue?.project_id}/issues/${parentIssue.id}`}>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className="block h-2 w-2 rounded-full" style={{ backgroundColor: stateColor }} />
|
||||
<span className="flex-shrink-0 text-custom-text-200">
|
||||
{getProjectById(parentIssue.project_id)?.identifier}-{parentIssue?.sequence_id}
|
||||
</span>
|
||||
{parentIssue.project_id && (
|
||||
<IssueIdentifier
|
||||
projectId={parentIssue.project_id}
|
||||
issueId={parentIssue.id}
|
||||
textContainerClassName="text-xs text-custom-text-200"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<span className="truncate text-custom-text-100">{(parentIssue?.name ?? "").substring(0, 50)}</span>
|
||||
</div>
|
||||
|
||||
@@ -4,9 +4,11 @@ import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
// ui
|
||||
import { CustomMenu, LayersIcon } from "@plane/ui";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// hooks
|
||||
import { useIssueDetail, useProject } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
|
||||
type TIssueParentSiblingItem = {
|
||||
workspaceSlug: string;
|
||||
@@ -31,10 +33,17 @@ export const IssueParentSiblingItem: FC<TIssueParentSiblingItem> = observer((pro
|
||||
<CustomMenu.MenuItem key={issueDetail.id}>
|
||||
<Link
|
||||
href={`/${workspaceSlug}/projects/${issueDetail?.project_id as string}/issues/${issueDetail.id}`}
|
||||
className="flex items-center gap-2 py-2"
|
||||
className="flex items-center gap-2 py-0.5"
|
||||
>
|
||||
<LayersIcon className="h-4 w-4" />
|
||||
{projectDetails?.identifier}-{issueDetail.sequence_id}
|
||||
{issueDetail.project_id && projectDetails?.identifier && (
|
||||
<IssueIdentifier
|
||||
projectId={issueDetail.project_id}
|
||||
issueTypeId={issueDetail.type_id}
|
||||
projectIdentifier={projectDetails?.identifier}
|
||||
issueSequenceId={issueDetail.sequence_id}
|
||||
textContainerClassName="text-xs"
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
</CustomMenu.MenuItem>
|
||||
</>
|
||||
|
||||
@@ -34,7 +34,7 @@ export const IssueParentSiblings: FC<TIssueParentSiblings> = observer((props) =>
|
||||
const subIssueIds = (parentIssue && subIssuesByIssueId(parentIssue.id)) || undefined;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="my-1">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center gap-2 whitespace-nowrap px-1 py-1 text-left text-xs text-custom-text-200">
|
||||
Loading
|
||||
|
||||
@@ -21,22 +21,22 @@ export type TRelationObject = { className: string; icon: (size: number) => React
|
||||
export const issueRelationObject: Record<TIssueRelationTypes, TRelationObject> = {
|
||||
relates_to: {
|
||||
className: "bg-custom-background-80 text-custom-text-200",
|
||||
icon: (size) => <RelatedIcon height={size} width={size} />,
|
||||
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} />,
|
||||
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} />,
|
||||
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} />,
|
||||
icon: (size) => <CopyPlus size={size} className="text-custom-text-200" />,
|
||||
placeholder: "None",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,16 +5,19 @@ import { useState, useRef, forwardRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { MoreHorizontal } from "lucide-react";
|
||||
import { TIssue } from "@plane/types";
|
||||
// components
|
||||
import { Tooltip, ControlLink } from "@plane/ui";
|
||||
// hooks
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { useIssueDetail, useProject, useProjectState } from "@/hooks/store";
|
||||
import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
|
||||
// helpers
|
||||
// types
|
||||
import { TIssue } from "@plane/types";
|
||||
// ui
|
||||
import { Tooltip, ControlLink } from "@plane/ui";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useIssueDetail, useProjectState } from "@/hooks/store";
|
||||
import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues/issue-details";
|
||||
// local components
|
||||
import { TRenderQuickActions } from "../list/list-view-types";
|
||||
|
||||
type Props = {
|
||||
@@ -33,7 +36,6 @@ export const CalendarIssueBlock = observer(
|
||||
const menuActionRef = useRef<HTMLDivElement | null>(null);
|
||||
// hooks
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const { getProjectIdentifierById } = useProject();
|
||||
const { getProjectStates } = useProjectState();
|
||||
const { getIsIssuePeeked, setPeekIssue } = useIssueDetail();
|
||||
const { isMobile } = usePlatformOS();
|
||||
@@ -99,9 +101,13 @@ export const CalendarIssueBlock = observer(
|
||||
backgroundColor: stateColor,
|
||||
}}
|
||||
/>
|
||||
<div className="flex-shrink-0 text-sm md:text-xs text-custom-text-300">
|
||||
{getProjectIdentifierById(issue?.project_id)}-{issue.sequence_id}
|
||||
</div>
|
||||
{issue.project_id && (
|
||||
<IssueIdentifier
|
||||
issueId={issue.id}
|
||||
projectId={issue.project_id}
|
||||
textContainerClassName="text-sm md:text-xs text-custom-text-300"
|
||||
/>
|
||||
)}
|
||||
<Tooltip tooltipContent={issue.name} isMobile={isMobile}>
|
||||
<div className="truncate text-sm font-medium md:font-normal md:text-xs">{issue.name}</div>
|
||||
</Tooltip>
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// hooks
|
||||
// ui
|
||||
import { Tooltip, StateGroupIcon, ControlLink } from "@plane/ui";
|
||||
import { Tooltip, ControlLink } from "@plane/ui";
|
||||
// helpers
|
||||
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
||||
import { useIssueDetail, useProject, useProjectState } from "@/hooks/store";
|
||||
// hooks
|
||||
import { useIssueDetail, useProjectState } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
|
||||
type Props = {
|
||||
issueId: string;
|
||||
@@ -78,16 +80,12 @@ export const IssueGanttSidebarBlock: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug: routerWorkspaceSlug } = useParams();
|
||||
const workspaceSlug = routerWorkspaceSlug?.toString();
|
||||
// store hooks
|
||||
const { getStateById } = useProjectState();
|
||||
const { getProjectIdentifierById } = useProject();
|
||||
const {
|
||||
issue: { getIssueById },
|
||||
setPeekIssue,
|
||||
} = useIssueDetail();
|
||||
// derived values
|
||||
const issueDetails = getIssueById(issueId);
|
||||
const projectIdentifier = issueDetails && getProjectIdentifierById(issueDetails?.project_id);
|
||||
const stateDetails = issueDetails && getStateById(issueDetails?.state_id);
|
||||
|
||||
const handleIssuePeekOverview = () =>
|
||||
workspaceSlug &&
|
||||
@@ -104,10 +102,13 @@ export const IssueGanttSidebarBlock: React.FC<Props> = observer((props) => {
|
||||
disabled={!!issueDetails?.tempId}
|
||||
>
|
||||
<div className="relative flex h-full w-full cursor-pointer items-center gap-2">
|
||||
{stateDetails && <StateGroupIcon stateGroup={stateDetails?.group} color={stateDetails?.color} />}
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-300">
|
||||
{projectIdentifier} {issueDetails?.sequence_id}
|
||||
</div>
|
||||
{issueDetails?.project_id && (
|
||||
<IssueIdentifier
|
||||
issueId={issueDetails.id}
|
||||
projectId={issueDetails.project_id}
|
||||
textContainerClassName="text-xs text-custom-text-300"
|
||||
/>
|
||||
)}
|
||||
<Tooltip tooltipContent={issueDetails?.name} isMobile={isMobile}>
|
||||
<span className="flex-grow truncate text-sm font-medium">{issueDetails?.name}</span>
|
||||
</Tooltip>
|
||||
|
||||
@@ -5,24 +5,26 @@ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
||||
import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// types
|
||||
import { TIssue, IIssueDisplayProperties, IIssueMap } from "@plane/types";
|
||||
// hooks
|
||||
// ui
|
||||
import { ControlLink, DropIndicator, TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
||||
// components
|
||||
import RenderIfVisible from "@/components/core/render-if-visible-HOC";
|
||||
import { HIGHLIGHT_CLASS } from "@/components/issues/issue-layouts/utils";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useIssueDetail, useProject, useKanbanView } from "@/hooks/store";
|
||||
import { useIssueDetail, useKanbanView } from "@/hooks/store";
|
||||
import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// components
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
// local components
|
||||
import { TRenderQuickActions } from "../list/list-view-types";
|
||||
import { IssueProperties } from "../properties/all-properties";
|
||||
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
|
||||
import { getIssueBlockId } from "../utils";
|
||||
// ui
|
||||
// types
|
||||
// helper
|
||||
|
||||
interface IssueBlockProps {
|
||||
issueId: string;
|
||||
@@ -51,7 +53,6 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
|
||||
const { cardRef, issue, updateIssue, quickActions, isReadOnly, displayProperties } = props;
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { getProjectIdentifierById } = useProject();
|
||||
|
||||
const handleEventPropagation = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
@@ -62,9 +63,13 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
|
||||
<>
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties || {}} displayPropertyKey="key">
|
||||
<div className="relative">
|
||||
<div className="line-clamp-1 text-xs text-custom-text-300">
|
||||
{getProjectIdentifierById(issue.project_id)}-{issue.sequence_id}
|
||||
</div>
|
||||
{issue.project_id && (
|
||||
<IssueIdentifier
|
||||
issueId={issue.id}
|
||||
projectId={issue.project_id}
|
||||
textContainerClassName="line-clamp-1 text-xs text-custom-text-300"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={cn("absolute -top-1 right-0", {
|
||||
"hidden group-hover/kanban-block:block": !isMobile,
|
||||
|
||||
@@ -19,6 +19,8 @@ import { cn } from "@/helpers/common.helper";
|
||||
import { useAppTheme, useIssueDetail, useProject } from "@/hooks/store";
|
||||
import { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
// types
|
||||
import { TRenderQuickActions } from "./list-view-types";
|
||||
|
||||
@@ -187,11 +189,14 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
|
||||
</Tooltip>
|
||||
)}
|
||||
{displayProperties && displayProperties?.key && (
|
||||
<div
|
||||
className="flex-shrink-0 text-xs font-medium text-custom-text-300 pl-2"
|
||||
style={{ minWidth: `${keyMinWidth}px` }}
|
||||
>
|
||||
{projectIdentifier}-{issue.sequence_id}
|
||||
<div className="flex-shrink-0 pl-2" style={{ minWidth: `${keyMinWidth}px` }}>
|
||||
{issue.project_id && (
|
||||
<IssueIdentifier
|
||||
issueId={issueId}
|
||||
projectId={issue.project_id}
|
||||
textContainerClassName="text-xs font-medium text-custom-text-300"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import { useIssueDetail, useProject } from "@/hooks/store";
|
||||
import { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||
import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
// local components
|
||||
import { TRenderQuickActions } from "../list/list-view-types";
|
||||
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
|
||||
@@ -287,7 +289,13 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="key">
|
||||
<div className="relative flex cursor-pointer items-center text-center text-xs hover:text-custom-text-100">
|
||||
<p className={`flex font-medium leading-7`} style={{ minWidth: `${keyMinWidth}px` }}>
|
||||
{getProjectIdentifierById(issueDetail.project_id)}-{issueDetail.sequence_id}
|
||||
{issueDetail.project_id && (
|
||||
<IssueIdentifier
|
||||
issueId={issueDetail.id}
|
||||
projectId={issueDetail.project_id}
|
||||
textContainerClassName="text-sm md:text-xs text-custom-text-300"
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</WithDisplayPropertiesHOC>
|
||||
|
||||
@@ -2,21 +2,23 @@
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
// headless ui
|
||||
// icons
|
||||
import { Rocket, Search } from "lucide-react";
|
||||
// headless ui
|
||||
import { Combobox, Dialog, Transition } from "@headlessui/react";
|
||||
// services
|
||||
// types
|
||||
import { ISearchIssueResponse } from "@plane/types";
|
||||
// ui
|
||||
import { Loader, ToggleSwitch, Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import { IssueSearchModalEmptyState } from "@/components/core";
|
||||
// hooks
|
||||
import useDebounce from "@/hooks/use-debounce";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
// services
|
||||
import { ProjectService } from "@/services/project";
|
||||
// hooks
|
||||
// components
|
||||
// ui
|
||||
// icons
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
@@ -182,7 +184,7 @@ export const ParentIssuesListModal: React.FC<Props> = ({
|
||||
key={issue.id}
|
||||
value={issue}
|
||||
className={({ active, selected }) =>
|
||||
`group flex w-full cursor-pointer select-none items-center justify-between gap-2 rounded-md px-3 py-2 text-custom-text-200 ${
|
||||
`group flex w-full cursor-pointer select-none items-center justify-between gap-2 rounded-md px-3 py-2 my-0.5 text-custom-text-200 ${
|
||||
active ? "bg-custom-background-80 text-custom-text-100" : ""
|
||||
} ${selected ? "text-custom-text-100" : ""}`
|
||||
}
|
||||
@@ -194,8 +196,14 @@ export const ParentIssuesListModal: React.FC<Props> = ({
|
||||
backgroundColor: issue.state__color,
|
||||
}}
|
||||
/>
|
||||
<span className="flex-shrink-0 text-xs">
|
||||
{issue.project__identifier}-{issue.sequence_id}
|
||||
<span className="flex-shrink-0">
|
||||
<IssueIdentifier
|
||||
projectId={issue.project_id}
|
||||
issueTypeId={issue.type_id}
|
||||
projectIdentifier={issue.project__identifier}
|
||||
issueSequenceId={issue.sequence_id}
|
||||
textContainerClassName="text-xs text-custom-text-200"
|
||||
/>
|
||||
</span>{" "}
|
||||
<span className="truncate">{issue.name}</span>
|
||||
</div>
|
||||
|
||||
@@ -56,7 +56,7 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = observer(
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<IssueIdentifier issueId={issueId} projectId={issue.project_id} />
|
||||
<IssueIdentifier issueId={issueId} projectId={issue.project_id} size="md" />
|
||||
<IssueTitleInput
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={issue.project_id}
|
||||
|
||||
@@ -241,7 +241,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`h-full !w-[400px] flex-shrink-0 border-l border-custom-border-200 p-4 py-5 ${
|
||||
className={`h-full !w-[400px] flex-shrink-0 border-l border-custom-border-200 p-4 py-5 overflow-hidden vertical-scrollbar scrollbar-sm ${
|
||||
is_archived ? "pointer-events-none" : ""
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -10,6 +10,8 @@ import { RelationIssueProperty } from "@/components/issues/relations";
|
||||
// hooks
|
||||
import { useIssueDetail, useProject, useProjectState } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
// types
|
||||
import { TRelationIssueOperations } from "../issue-detail-widgets/relations/helper";
|
||||
|
||||
@@ -91,15 +93,23 @@ export const RelationIssueListItem: FC<Props> = observer((props) => {
|
||||
{issue && (
|
||||
<div className="group relative flex min-h-11 h-full w-full items-center gap-3 px-1.5 py-1 transition-all hover:bg-custom-background-90">
|
||||
<span className="size-5 flex-shrink-0" />
|
||||
<div className="flex w-full cursor-pointer items-center gap-2">
|
||||
<div className="flex w-full cursor-pointer items-center gap-3">
|
||||
<div
|
||||
className="h-2 w-2 flex-shrink-0 rounded-full"
|
||||
style={{
|
||||
backgroundColor: currentIssueStateDetail?.color ?? "#737373",
|
||||
}}
|
||||
/>
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-200">
|
||||
{projectDetail?.identifier}-{issue?.sequence_id}
|
||||
<div className="flex-shrink-0">
|
||||
{projectDetail && (
|
||||
<IssueIdentifier
|
||||
projectId={projectDetail.id}
|
||||
issueTypeId={issue.type_id}
|
||||
projectIdentifier={projectDetail.identifier}
|
||||
issueSequenceId={issue.sequence_id}
|
||||
textContainerClassName="text-xs text-custom-text-200"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ControlLink
|
||||
|
||||
@@ -4,11 +4,16 @@ import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { ChevronRight, X, Pencil, Trash, Link as LinkIcon, Loader } from "lucide-react";
|
||||
import { TIssue } from "@plane/types";
|
||||
// components
|
||||
// ui
|
||||
import { ControlLink, CustomMenu, Tooltip } from "@plane/ui";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useIssueDetail, useProject, useProjectState } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
// local components
|
||||
import { IssueList } from "./issues-list";
|
||||
import { IssueProperty } from "./properties";
|
||||
// ui
|
||||
@@ -117,15 +122,23 @@ export const IssueListItem: React.FC<ISubIssues> = observer((props) => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex w-full cursor-pointer items-center gap-2">
|
||||
<div className="flex w-full cursor-pointer items-center gap-3">
|
||||
<div
|
||||
className="h-2 w-2 flex-shrink-0 rounded-full"
|
||||
style={{
|
||||
backgroundColor: currentIssueStateDetail?.color ?? "#737373",
|
||||
}}
|
||||
/>
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-200">
|
||||
{projectDetail?.identifier}-{issue?.sequence_id}
|
||||
<div className="flex-shrink-0">
|
||||
{projectDetail && (
|
||||
<IssueIdentifier
|
||||
projectId={projectDetail.id}
|
||||
issueTypeId={issue.type_id}
|
||||
projectIdentifier={projectDetail.identifier}
|
||||
issueSequenceId={issue.sequence_id}
|
||||
textContainerClassName="text-xs text-custom-text-200"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ControlLink
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./issue-identifier";
|
||||
export * from "./issue-properties-activity";
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./root";
|
||||
@@ -0,0 +1 @@
|
||||
export * from "ce/components/issues/issue-details/issue-properties-activity/root";
|
||||
Reference in New Issue
Block a user