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:
Prateek Shourya
2024-08-19 13:40:19 +05:30
committed by GitHub
parent c829b52c0f
commit 807dfec7ad
59 changed files with 470 additions and 266 deletions

View File

@@ -19,5 +19,6 @@ export type TLogoProps = {
icon?: {
name?: string;
color?: string;
background_color?: string;
};
};

View File

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

View File

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

View File

@@ -144,4 +144,5 @@ export interface ISearchIssueResponse {
state__group: TStateGroups;
state__name: string;
workspace__slug: string;
type_id: string;
}

View File

@@ -118,6 +118,7 @@ export interface IWorkspaceIssueSearchResult {
project_id: string;
sequence_id: number;
workspace__slug: string;
type_id: string;
}
export interface IWorkspacePageSearchResult {

View File

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

View File

@@ -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%",
}}
>

View File

@@ -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,9 +87,13 @@ export const Sortable = <T,>({ data, render, onChange, keyExtractor, containerCl
return (
<>
{enhancedData.map((item, index) => (
<Draggable key={keyExtractor(item, index)} data={item} className={containerClassName}>
<Fragment>{render(item, index)} </Fragment>
{data.map((item, index) => (
<Draggable
key={keyExtractor(enhancedData[index], index)}
data={enhancedData[index]}
className={containerClassName}
>
<Fragment>{render(item, index)}</Fragment>
</Draggable>
))}
</>

View File

@@ -1 +1,2 @@
export * from "./issue-identifier";
export * from "./issue-properties-activity";

View File

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

View File

@@ -0,0 +1 @@
export * from "./root";

View File

@@ -0,0 +1,8 @@
import { FC } from "react";
type TIssueAdditionalPropertiesActivity = {
activityId: string;
ends: "top" | "bottom" | undefined;
};
export const IssueAdditionalPropertiesActivity: FC<TIssueAdditionalPropertiesActivity> = () => <></>;

View File

@@ -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 && (

View File

@@ -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}`,

View File

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

View File

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

View File

@@ -6,22 +6,21 @@ 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
// components
import { EmptyState } from "@/components/empty-state";
//constants
// constants
import { EmptyStateType } from "@/constants/empty-state";
import { EIssuesStoreType } from "@/constants/issue";
//hooks
import { useIssues, useProject } from "@/hooks/store";
// hooks
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}
/>

View File

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

View File

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

View File

@@ -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) => {

View File

@@ -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) => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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."}</>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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",
},
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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" : ""
}`}
>

View File

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

View File

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

View File

@@ -1 +1,2 @@
export * from "./issue-identifier";
export * from "./issue-properties-activity";

View File

@@ -0,0 +1 @@
export * from "./root";

View File

@@ -0,0 +1 @@
export * from "ce/components/issues/issue-details/issue-properties-activity/root";