mirror of
https://github.com/makeplane/plane.git
synced 2026-01-21 22:00:35 -06:00
[WEB-1883] chore: moved workspace settings to respective folders for CE and EE (#5151)
* chore: moved workspace settings to respective folders for ce and ee * chore: updated imports * chore: updated imports for ee * chore: resolved import error * chore: resolved import error * chore: ee imports in the issue sidebar * chore: updated file structure * chore: table UI * chore: resolved build errors * chore: added worklog on issue peekoverview
This commit is contained in:
5
packages/types/src/issues/activity/base.d.ts
vendored
5
packages/types/src/issues/activity/base.d.ts
vendored
@@ -55,4 +55,9 @@ export type TIssueActivityComment =
|
||||
id: string;
|
||||
activity_type: "ACTIVITY";
|
||||
created_at?: string;
|
||||
}
|
||||
| {
|
||||
id: string;
|
||||
activity_type: "WORKLOG";
|
||||
created_at?: string;
|
||||
};
|
||||
|
||||
306
packages/types/src/workspace.d.ts
vendored
306
packages/types/src/workspace.d.ts
vendored
@@ -1,199 +1,199 @@
|
||||
import {EUserWorkspaceRoles} from "@/constants/workspace";
|
||||
import { EUserWorkspaceRoles } from "@/constants/workspace";
|
||||
import type {
|
||||
IProjectMember,
|
||||
IUser,
|
||||
IUserLite,
|
||||
IWorkspaceViewProps,
|
||||
IProjectMember,
|
||||
IUser,
|
||||
IUserLite,
|
||||
IWorkspaceViewProps,
|
||||
} from "@plane/types";
|
||||
|
||||
export interface IWorkspace {
|
||||
readonly id: string;
|
||||
readonly owner: IUser;
|
||||
readonly created_at: Date;
|
||||
readonly updated_at: Date;
|
||||
name: string;
|
||||
url: string;
|
||||
logo: string | null;
|
||||
slug: string;
|
||||
readonly total_members: number;
|
||||
readonly slug: string;
|
||||
readonly created_by: string;
|
||||
readonly updated_by: string;
|
||||
organization_size: string;
|
||||
total_issues: number;
|
||||
readonly id: string;
|
||||
readonly owner: IUser;
|
||||
readonly created_at: Date;
|
||||
readonly updated_at: Date;
|
||||
name: string;
|
||||
url: string;
|
||||
logo: string | null;
|
||||
slug: string;
|
||||
readonly total_members: number;
|
||||
readonly slug: string;
|
||||
readonly created_by: string;
|
||||
readonly updated_by: string;
|
||||
organization_size: string;
|
||||
total_issues: number;
|
||||
}
|
||||
|
||||
export interface IWorkspaceLite {
|
||||
readonly id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
readonly id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
export interface IWorkspaceMemberInvitation {
|
||||
accepted: boolean;
|
||||
email: string;
|
||||
id: string;
|
||||
message: string;
|
||||
responded_at: Date;
|
||||
role: EUserWorkspaceRoles;
|
||||
token: string;
|
||||
workspace: {
|
||||
id: string;
|
||||
logo: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
accepted: boolean;
|
||||
email: string;
|
||||
id: string;
|
||||
message: string;
|
||||
responded_at: Date;
|
||||
role: EUserWorkspaceRoles;
|
||||
token: string;
|
||||
workspace: {
|
||||
id: string;
|
||||
logo: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IWorkspaceBulkInviteFormData {
|
||||
emails: {email: string; role: EUserWorkspaceRoles}[];
|
||||
emails: { email: string; role: EUserWorkspaceRoles }[];
|
||||
}
|
||||
|
||||
export type Properties = {
|
||||
assignee: boolean;
|
||||
start_date: boolean;
|
||||
due_date: boolean;
|
||||
labels: boolean;
|
||||
key: boolean;
|
||||
priority: boolean;
|
||||
state: boolean;
|
||||
sub_issue_count: boolean;
|
||||
link: boolean;
|
||||
attachment_count: boolean;
|
||||
estimate: boolean;
|
||||
created_on: boolean;
|
||||
updated_on: boolean;
|
||||
assignee: boolean;
|
||||
start_date: boolean;
|
||||
due_date: boolean;
|
||||
labels: boolean;
|
||||
key: boolean;
|
||||
priority: boolean;
|
||||
state: boolean;
|
||||
sub_issue_count: boolean;
|
||||
link: boolean;
|
||||
attachment_count: boolean;
|
||||
estimate: boolean;
|
||||
created_on: boolean;
|
||||
updated_on: boolean;
|
||||
};
|
||||
|
||||
export interface IWorkspaceMember {
|
||||
id: string;
|
||||
member: IUserLite;
|
||||
role: EUserWorkspaceRoles;
|
||||
created_at: string;
|
||||
avatar?: string;
|
||||
email?: string;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
joining_date: string;
|
||||
display_name?: string;
|
||||
id: string;
|
||||
member: IUserLite;
|
||||
role: EUserWorkspaceRoles;
|
||||
created_at?: string;
|
||||
avatar?: string;
|
||||
email?: string;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
joining_date?: string;
|
||||
display_name?: string;
|
||||
}
|
||||
|
||||
export interface IWorkspaceMemberMe {
|
||||
company_role: string | null;
|
||||
created_at: Date;
|
||||
created_by: string;
|
||||
default_props: IWorkspaceViewProps;
|
||||
id: string;
|
||||
member: string;
|
||||
role: EUserWorkspaceRoles;
|
||||
updated_at: Date;
|
||||
updated_by: string;
|
||||
view_props: IWorkspaceViewProps;
|
||||
workspace: string;
|
||||
company_role: string | null;
|
||||
created_at: Date;
|
||||
created_by: string;
|
||||
default_props: IWorkspaceViewProps;
|
||||
id: string;
|
||||
member: string;
|
||||
role: EUserWorkspaceRoles;
|
||||
updated_at: Date;
|
||||
updated_by: string;
|
||||
view_props: IWorkspaceViewProps;
|
||||
workspace: string;
|
||||
}
|
||||
|
||||
export interface ILastActiveWorkspaceDetails {
|
||||
workspace_details: IWorkspace;
|
||||
project_details?: IProjectMember[];
|
||||
workspace_details: IWorkspace;
|
||||
project_details?: IProjectMember[];
|
||||
}
|
||||
|
||||
export interface IWorkspaceDefaultSearchResult {
|
||||
id: string;
|
||||
name: string;
|
||||
project_id: string;
|
||||
project__identifier: string;
|
||||
workspace__slug: string;
|
||||
id: string;
|
||||
name: string;
|
||||
project_id: string;
|
||||
project__identifier: string;
|
||||
workspace__slug: string;
|
||||
}
|
||||
export interface IWorkspaceSearchResult {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
export interface IWorkspaceIssueSearchResult {
|
||||
id: string;
|
||||
name: string;
|
||||
project__identifier: string;
|
||||
project_id: string;
|
||||
sequence_id: number;
|
||||
workspace__slug: string;
|
||||
id: string;
|
||||
name: string;
|
||||
project__identifier: string;
|
||||
project_id: string;
|
||||
sequence_id: number;
|
||||
workspace__slug: string;
|
||||
}
|
||||
|
||||
export interface IWorkspacePageSearchResult {
|
||||
id: string;
|
||||
name: string;
|
||||
project_ids: string[];
|
||||
project__identifiers: string[];
|
||||
workspace__slug: string;
|
||||
id: string;
|
||||
name: string;
|
||||
project_ids: string[];
|
||||
project__identifiers: string[];
|
||||
workspace__slug: string;
|
||||
}
|
||||
|
||||
export interface IWorkspaceProjectSearchResult {
|
||||
id: string;
|
||||
identifier: string;
|
||||
name: string;
|
||||
workspace__slug: string;
|
||||
id: string;
|
||||
identifier: string;
|
||||
name: string;
|
||||
workspace__slug: string;
|
||||
}
|
||||
|
||||
export interface IWorkspaceSearchResults {
|
||||
results: {
|
||||
workspace: IWorkspaceSearchResult[];
|
||||
project: IWorkspaceProjectSearchResult[];
|
||||
issue: IWorkspaceIssueSearchResult[];
|
||||
cycle: IWorkspaceDefaultSearchResult[];
|
||||
module: IWorkspaceDefaultSearchResult[];
|
||||
issue_view: IWorkspaceDefaultSearchResult[];
|
||||
page: IWorkspacePageSearchResult[];
|
||||
};
|
||||
results: {
|
||||
workspace: IWorkspaceSearchResult[];
|
||||
project: IWorkspaceProjectSearchResult[];
|
||||
issue: IWorkspaceIssueSearchResult[];
|
||||
cycle: IWorkspaceDefaultSearchResult[];
|
||||
module: IWorkspaceDefaultSearchResult[];
|
||||
issue_view: IWorkspaceDefaultSearchResult[];
|
||||
page: IWorkspacePageSearchResult[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface IProductUpdateResponse {
|
||||
url: string;
|
||||
assets_url: string;
|
||||
upload_url: string;
|
||||
html_url: string;
|
||||
id: number;
|
||||
author: {
|
||||
login: string;
|
||||
id: string;
|
||||
node_id: string;
|
||||
avatar_url: string;
|
||||
gravatar_id: "";
|
||||
url: string;
|
||||
html_url: string;
|
||||
followers_url: string;
|
||||
following_url: string;
|
||||
gists_url: string;
|
||||
starred_url: string;
|
||||
subscriptions_url: string;
|
||||
organizations_url: string;
|
||||
repos_url: string;
|
||||
events_url: string;
|
||||
received_events_url: string;
|
||||
type: string;
|
||||
site_admin: false;
|
||||
};
|
||||
node_id: string;
|
||||
tag_name: string;
|
||||
target_commitish: string;
|
||||
name: string;
|
||||
draft: boolean;
|
||||
prerelease: true;
|
||||
created_at: string;
|
||||
published_at: string;
|
||||
assets: [];
|
||||
tarball_url: string;
|
||||
zipball_url: string;
|
||||
body: string;
|
||||
reactions: {
|
||||
url: string;
|
||||
total_count: number;
|
||||
"+1": number;
|
||||
"-1": number;
|
||||
laugh: number;
|
||||
hooray: number;
|
||||
confused: number;
|
||||
heart: number;
|
||||
rocket: number;
|
||||
eyes: number;
|
||||
};
|
||||
url: string;
|
||||
assets_url: string;
|
||||
upload_url: string;
|
||||
html_url: string;
|
||||
id: number;
|
||||
author: {
|
||||
login: string;
|
||||
id: string;
|
||||
node_id: string;
|
||||
avatar_url: string;
|
||||
gravatar_id: "";
|
||||
url: string;
|
||||
html_url: string;
|
||||
followers_url: string;
|
||||
following_url: string;
|
||||
gists_url: string;
|
||||
starred_url: string;
|
||||
subscriptions_url: string;
|
||||
organizations_url: string;
|
||||
repos_url: string;
|
||||
events_url: string;
|
||||
received_events_url: string;
|
||||
type: string;
|
||||
site_admin: false;
|
||||
};
|
||||
node_id: string;
|
||||
tag_name: string;
|
||||
target_commitish: string;
|
||||
name: string;
|
||||
draft: boolean;
|
||||
prerelease: true;
|
||||
created_at: string;
|
||||
published_at: string;
|
||||
assets: [];
|
||||
tarball_url: string;
|
||||
zipball_url: string;
|
||||
body: string;
|
||||
reactions: {
|
||||
url: string;
|
||||
total_count: number;
|
||||
"+1": number;
|
||||
"-1": number;
|
||||
laugh: number;
|
||||
hooray: number;
|
||||
confused: number;
|
||||
heart: number;
|
||||
rocket: number;
|
||||
eyes: number;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export const PopoverMenu = <T,>(props: TPopoverMenu<T>) => {
|
||||
popperPadding = 0,
|
||||
buttonClassName = "",
|
||||
button,
|
||||
disabled,
|
||||
panelClassName = "",
|
||||
data,
|
||||
popoverClassName = "",
|
||||
@@ -25,6 +26,7 @@ export const PopoverMenu = <T,>(props: TPopoverMenu<T>) => {
|
||||
popperPadding={popperPadding}
|
||||
buttonClassName={buttonClassName}
|
||||
button={button}
|
||||
disabled={disabled}
|
||||
panelClassName={cn(
|
||||
"my-1 w-48 rounded border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2 text-xs shadow-custom-shadow-rg focus:outline-none",
|
||||
panelClassName
|
||||
|
||||
@@ -14,6 +14,7 @@ export const Popover = (props: TPopover) => {
|
||||
buttonClassName = "",
|
||||
popoverClassName = "",
|
||||
button,
|
||||
disabled = false,
|
||||
panelClassName = "",
|
||||
children,
|
||||
} = props;
|
||||
@@ -36,19 +37,18 @@ export const Popover = (props: TPopover) => {
|
||||
|
||||
return (
|
||||
<HeadlessReactPopover className={cn("relative flex h-full w-full items-center justify-center", popoverClassName)}>
|
||||
<HeadlessReactPopover.Button ref={setReferenceElement} className="flex justify-center items-center">
|
||||
{button ? (
|
||||
button
|
||||
) : (
|
||||
<div
|
||||
className={cn(
|
||||
"flex justify-center items-center text-base h-6 w-6 rounded transition-all bg-custom-background-90 hover:bg-custom-background-80",
|
||||
buttonClassName
|
||||
)}
|
||||
>
|
||||
<EllipsisVertical className="h-3 w-3" />
|
||||
</div>
|
||||
<HeadlessReactPopover.Button
|
||||
ref={setReferenceElement}
|
||||
className={cn(
|
||||
{
|
||||
"flex justify-center items-center text-base h-6 w-6 rounded transition-all bg-custom-background-90 hover:bg-custom-background-80":
|
||||
!button,
|
||||
},
|
||||
buttonClassName
|
||||
)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{button ? button : <EllipsisVertical className="h-3 w-3" />}
|
||||
</HeadlessReactPopover.Button>
|
||||
|
||||
<Transition
|
||||
|
||||
@@ -5,6 +5,7 @@ export type TPopoverButtonDefaultOptions = {
|
||||
// button and button styling
|
||||
button?: ReactNode;
|
||||
buttonClassName?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export type TPopoverDefaultOptions = TPopoverButtonDefaultOptions & {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
// constants
|
||||
import { WORKSPACE_SETTINGS_LINKS } from "@/constants/workspace";
|
||||
// hooks
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// plane web constants
|
||||
import { WORKSPACE_SETTINGS_LINKS } from "@/plane-web/constants/workspace";
|
||||
|
||||
export const MobileWorkspaceSettingsTabs = () => {
|
||||
const router = useAppRouter();
|
||||
@@ -12,10 +12,11 @@ export const MobileWorkspaceSettingsTabs = () => {
|
||||
<div className="flex-shrink-0 md:hidden sticky inset-0 flex overflow-x-auto bg-custom-background-100 z-10">
|
||||
{WORKSPACE_SETTINGS_LINKS.map((item, index) => (
|
||||
<div
|
||||
className={`${item.highlight(pathname, `/${workspaceSlug}`)
|
||||
className={`${
|
||||
item.highlight(pathname, `/${workspaceSlug}`)
|
||||
? "text-custom-primary-100 text-sm py-2 px-3 whitespace-nowrap flex flex-grow cursor-pointer justify-around border-b border-custom-primary-200"
|
||||
: "text-custom-text-200 flex flex-grow cursor-pointer justify-around border-b border-custom-border-200 text-sm py-2 px-3 whitespace-nowrap"
|
||||
}`}
|
||||
}`}
|
||||
key={index}
|
||||
onClick={() => router.push(`/${workspaceSlug}${item.href}`)}
|
||||
>
|
||||
|
||||
@@ -5,9 +5,11 @@ import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles, WORKSPACE_SETTINGS_LINKS } from "@/constants/workspace";
|
||||
import { EUserWorkspaceRoles } from "@/constants/workspace";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store";
|
||||
// plane web constants
|
||||
import { WORKSPACE_SETTINGS_LINKS } from "@/plane-web/constants/workspace";
|
||||
|
||||
export const WorkspaceSettingsSidebar = observer(() => {
|
||||
// router
|
||||
@@ -31,10 +33,11 @@ export const WorkspaceSettingsSidebar = observer(() => {
|
||||
<Link key={link.key} href={`/${workspaceSlug}${link.href}`}>
|
||||
<span>
|
||||
<div
|
||||
className={`rounded-md px-4 py-2 text-sm font-medium ${link.highlight(pathname, `/${workspaceSlug}`)
|
||||
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
|
||||
}`}
|
||||
className={`rounded-md px-4 py-2 text-sm font-medium ${
|
||||
link.highlight(pathname, `/${workspaceSlug}`)
|
||||
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
|
||||
}`}
|
||||
>
|
||||
{link.label}
|
||||
</div>
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./bulk-operations";
|
||||
export * from "./bulk-operations";
|
||||
export * from "./worklog";
|
||||
|
||||
2
web/ce/components/issues/worklog/activity/index.ts
Normal file
2
web/ce/components/issues/worklog/activity/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./root";
|
||||
export * from "./worklog-create-button";
|
||||
13
web/ce/components/issues/worklog/activity/root.tsx
Normal file
13
web/ce/components/issues/worklog/activity/root.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { TIssueActivityComment } from "@plane/types";
|
||||
|
||||
type TIssueActivityWorklog = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
issueId: string;
|
||||
activityComment: TIssueActivityComment;
|
||||
};
|
||||
|
||||
export const IssueActivityWorklog: FC<TIssueActivityWorklog> = () => <></>;
|
||||
@@ -0,0 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
|
||||
type TIssueActivityWorklogCreateButton = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
issueId: string;
|
||||
disabled: boolean;
|
||||
};
|
||||
|
||||
export const IssueActivityWorklogCreateButton: FC<TIssueActivityWorklogCreateButton> = () => <></>;
|
||||
2
web/ce/components/issues/worklog/index.ts
Normal file
2
web/ce/components/issues/worklog/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./property";
|
||||
export * from "./activity";
|
||||
1
web/ce/components/issues/worklog/property/index.ts
Normal file
1
web/ce/components/issues/worklog/property/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./root";
|
||||
12
web/ce/components/issues/worklog/property/root.tsx
Normal file
12
web/ce/components/issues/worklog/property/root.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
|
||||
type TIssueWorklogProperty = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
issueId: string;
|
||||
disabled: boolean;
|
||||
};
|
||||
|
||||
export const IssueWorklogProperty: FC<TIssueWorklogProperty> = () => <></>;
|
||||
@@ -66,7 +66,7 @@ const useProjectColumns = () => {
|
||||
{
|
||||
key: "Joining Date",
|
||||
content: "Joining Date",
|
||||
tdRender: (rowData: RowData) => <div>{getFormattedDate(rowData.member.joining_date)}</div>,
|
||||
tdRender: (rowData: RowData) => <div>{getFormattedDate(rowData?.member?.joining_date || "")}</div>,
|
||||
},
|
||||
];
|
||||
return { columns, workspaceSlug, projectId, removeMemberModal, setRemoveMemberModal };
|
||||
|
||||
@@ -60,7 +60,7 @@ const useMemberColumns = () => {
|
||||
{
|
||||
key: "Joining Date",
|
||||
content: "Joining Date",
|
||||
tdRender: (rowData: RowData) => <div>{getFormattedDate(rowData.member.joining_date)}</div>,
|
||||
tdRender: (rowData: RowData) => <div>{getFormattedDate(rowData?.member?.joining_date || "")}</div>,
|
||||
},
|
||||
];
|
||||
return { columns, workspaceSlug, removeMemberModal, setRemoveMemberModal };
|
||||
|
||||
23
web/ce/constants/issues.ts
Normal file
23
web/ce/constants/issues.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { TIssueActivityComment } from "@plane/types";
|
||||
|
||||
export enum EActivityFilterType {
|
||||
ACTIVITY = "activity",
|
||||
COMMENT = "comment",
|
||||
}
|
||||
|
||||
export type TActivityFilters = EActivityFilterType;
|
||||
|
||||
export const ACTIVITY_FILTER_TYPE_OPTIONS: Record<EActivityFilterType, { label: string }> = {
|
||||
[EActivityFilterType.ACTIVITY]: {
|
||||
label: "Updates",
|
||||
},
|
||||
[EActivityFilterType.COMMENT]: {
|
||||
label: "Comments",
|
||||
},
|
||||
};
|
||||
|
||||
export const filterActivityOnSelectedFilters = (
|
||||
activity: TIssueActivityComment[],
|
||||
filter: TActivityFilters[]
|
||||
): TIssueActivityComment[] =>
|
||||
activity.filter((activity) => filter.includes(activity.activity_type as TActivityFilters));
|
||||
63
web/ce/constants/workspace.ts
Normal file
63
web/ce/constants/workspace.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
// icons
|
||||
import { SettingIcon } from "@/components/icons/attachment";
|
||||
import { Props } from "@/components/icons/types";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "@/constants/workspace";
|
||||
|
||||
export const WORKSPACE_SETTINGS_LINKS: {
|
||||
key: string;
|
||||
label: string;
|
||||
href: string;
|
||||
access: EUserWorkspaceRoles;
|
||||
highlight: (pathname: string, baseUrl: string) => boolean;
|
||||
Icon: React.FC<Props>;
|
||||
}[] = [
|
||||
{
|
||||
key: "general",
|
||||
label: "General",
|
||||
href: `/settings`,
|
||||
access: EUserWorkspaceRoles.GUEST,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "members",
|
||||
label: "Members",
|
||||
href: `/settings/members`,
|
||||
access: EUserWorkspaceRoles.GUEST,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/members/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "billing-and-plans",
|
||||
label: "Billing and plans",
|
||||
href: `/settings/billing`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/billing/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "export",
|
||||
label: "Exports",
|
||||
href: `/settings/exports`,
|
||||
access: EUserWorkspaceRoles.MEMBER,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/exports/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "webhooks",
|
||||
label: "Webhooks",
|
||||
href: `/settings/webhooks`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/webhooks/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "api-tokens",
|
||||
label: "API tokens",
|
||||
href: `/settings/api-tokens`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/api-tokens/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
];
|
||||
@@ -5,10 +5,12 @@ import { Command } from "cmdk";
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles, WORKSPACE_SETTINGS_LINKS } from "@/constants/workspace";
|
||||
import { EUserWorkspaceRoles } from "@/constants/workspace";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// plane wev constants
|
||||
import { WORKSPACE_SETTINGS_LINKS } from "@/plane-web/constants/workspace";
|
||||
|
||||
type Props = {
|
||||
closePalette: () => void;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// hooks
|
||||
import { EActivityFilterType } from "@/constants/issue";
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
// plane wev components
|
||||
import { IssueActivityWorklog } from "@/plane-web/components/issues/worklog/activity/root";
|
||||
// plane web constants
|
||||
import { TActivityFilters, filterActivityOnSelectedFilters } from "@/plane-web/constants/issues";
|
||||
// components
|
||||
import { IssueActivityItem } from "./activity/activity-list";
|
||||
import { IssueCommentCard } from "./comments/comment-card";
|
||||
@@ -13,7 +16,7 @@ type TIssueActivityCommentRoot = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
issueId: string;
|
||||
selectedFilters: EActivityFilterType[];
|
||||
selectedFilters: TActivityFilters[];
|
||||
activityOperations: TActivityOperations;
|
||||
showAccessSpecifier?: boolean;
|
||||
disabled?: boolean;
|
||||
@@ -32,14 +35,7 @@ export const IssueActivityCommentRoot: FC<TIssueActivityCommentRoot> = observer(
|
||||
|
||||
if (!activityComments || (activityComments && activityComments.length <= 0)) return <></>;
|
||||
|
||||
const isCommentFilterSelected = selectedFilters.includes(EActivityFilterType.COMMENT);
|
||||
const isActivityFilterSelected = selectedFilters.includes(EActivityFilterType.ACTIVITY);
|
||||
|
||||
const filteredActivityComments = activityComments.filter(
|
||||
(activityComment) =>
|
||||
(activityComment.activity_type === "COMMENT" && isCommentFilterSelected) ||
|
||||
(activityComment.activity_type === "ACTIVITY" && isActivityFilterSelected)
|
||||
);
|
||||
const filteredActivityComments = filterActivityOnSelectedFilters(activityComments, selectedFilters);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -60,6 +56,13 @@ export const IssueActivityCommentRoot: FC<TIssueActivityCommentRoot> = observer(
|
||||
activityId={activityComment.id}
|
||||
ends={index === 0 ? "top" : index === filteredActivityComments.length - 1 ? "bottom" : undefined}
|
||||
/>
|
||||
) : activityComment.activity_type === "WORKLOG" ? (
|
||||
<IssueActivityWorklog
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
activityComment={activityComment}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
|
||||
@@ -4,14 +4,14 @@ import { Check, ListFilter } from "lucide-react";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// constants
|
||||
import { ACTIVITY_FILTER_TYPE_OPTIONS, EActivityFilterType } from "@/constants/issue";
|
||||
// helper
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// constants
|
||||
import { TActivityFilters, ACTIVITY_FILTER_TYPE_OPTIONS } from "@/plane-web/constants/issues";
|
||||
|
||||
type Props = {
|
||||
selectedFilters: EActivityFilterType[];
|
||||
toggleFilter: (filter: EActivityFilterType) => void;
|
||||
selectedFilters: TActivityFilters[];
|
||||
toggleFilter: (filter: TActivityFilters) => void;
|
||||
};
|
||||
|
||||
export const ActivityFilter: FC<Props> = observer((props) => {
|
||||
@@ -42,13 +42,15 @@ export const ActivityFilter: FC<Props> = observer((props) => {
|
||||
>
|
||||
<Popover.Panel className="absolute mt-2 right-0 z-10 min-w-40">
|
||||
<div className="p-2 rounded-md border border-custom-border-200 bg-custom-background-100">
|
||||
{ACTIVITY_FILTER_TYPE_OPTIONS.map((filter) => {
|
||||
const isSelected = selectedFilters.includes(filter.value);
|
||||
{Object.keys(ACTIVITY_FILTER_TYPE_OPTIONS).map((key) => {
|
||||
const filterKey = key as TActivityFilters;
|
||||
const filter = ACTIVITY_FILTER_TYPE_OPTIONS[filterKey];
|
||||
const isSelected = selectedFilters.includes(filterKey);
|
||||
return (
|
||||
<div
|
||||
key={filter.value}
|
||||
key={filterKey}
|
||||
className="flex items-center gap-2 text-sm cursor-pointer px-2 p-1 transition-all hover:bg-custom-background-80 rounded-sm"
|
||||
onClick={() => toggleFilter(filter.value)}
|
||||
onClick={() => toggleFilter(filterKey)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
|
||||
@@ -7,11 +7,14 @@ import { TIssueComment } from "@plane/types";
|
||||
// ui
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { ActivityFilter, IssueActivityCommentRoot, IssueCommentCreate } from "@/components/issues";
|
||||
// constants
|
||||
import { EActivityFilterType } from "@/constants/issue";
|
||||
import { ActivityFilter, IssueCommentCreate } from "@/components/issues";
|
||||
import { IssueActivityCommentRoot } from "@/components/issues/issue-detail";
|
||||
// hooks
|
||||
import { useIssueDetail, useProject } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { IssueActivityWorklogCreateButton } from "@/plane-web/components/issues/worklog";
|
||||
// plane web constants
|
||||
import { TActivityFilters, EActivityFilterType } from "@/plane-web/constants/issues";
|
||||
|
||||
type TIssueActivity = {
|
||||
workspaceSlug: string;
|
||||
@@ -32,9 +35,12 @@ export const IssueActivity: FC<TIssueActivity> = observer((props) => {
|
||||
const { createComment, updateComment, removeComment } = useIssueDetail();
|
||||
const { getProjectById } = useProject();
|
||||
// state
|
||||
const [selectedFilters, setSelectedFilters] = useState([EActivityFilterType.COMMENT, EActivityFilterType.ACTIVITY]);
|
||||
const [selectedFilters, setSelectedFilters] = useState<TActivityFilters[]>([
|
||||
EActivityFilterType.ACTIVITY,
|
||||
EActivityFilterType.COMMENT,
|
||||
]);
|
||||
// toggle filter
|
||||
const toggleFilter = (filter: EActivityFilterType) => {
|
||||
const toggleFilter = (filter: TActivityFilters) => {
|
||||
setSelectedFilters((prevFilters) => {
|
||||
if (prevFilters.includes(filter)) {
|
||||
if (prevFilters.length === 1) return prevFilters; // Ensure at least one filter is applied
|
||||
@@ -110,7 +116,15 @@ export const IssueActivity: FC<TIssueActivity> = observer((props) => {
|
||||
{/* header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-lg text-custom-text-100">Activity</div>
|
||||
<ActivityFilter selectedFilters={selectedFilters} toggleFilter={toggleFilter} />
|
||||
<div className="flex items-center gap-2">
|
||||
<IssueActivityWorklogCreateButton
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<ActivityFilter selectedFilters={selectedFilters} toggleFilter={toggleFilter} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* rendering activity */}
|
||||
|
||||
@@ -21,6 +21,8 @@ import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper"
|
||||
import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper";
|
||||
// hooks
|
||||
import { useProjectEstimates, useIssueDetail, useProject, useProjectState, useMember } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { IssueWorklogProperty } from "@/plane-web/components/issues";
|
||||
// components
|
||||
import type { TIssueOperations } from "./root";
|
||||
|
||||
@@ -279,6 +281,13 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IssueWorklogProperty
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={!isEditable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,6 +27,8 @@ import { cn } from "@/helpers/common.helper";
|
||||
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper";
|
||||
import { useIssueDetail, useMember, useProject, useProjectState } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { IssueWorklogProperty } from "@/plane-web/components/issues";
|
||||
|
||||
interface IPeekOverviewProperties {
|
||||
workspaceSlug: string;
|
||||
@@ -279,6 +281,13 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
<IssueLabel workspaceSlug={workspaceSlug} projectId={projectId} issueId={issueId} disabled={disabled} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IssueWorklogProperty
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Button } from "@plane/ui";
|
||||
import { useProject, useUser } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
data: IUserLite;
|
||||
data: Partial<IUserLite>;
|
||||
onSubmit: () => Promise<void>;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
|
||||
@@ -80,7 +80,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
|
||||
)}
|
||||
<Table
|
||||
columns={columns}
|
||||
data={memberDetails?.filter((member): member is IProjectMemberDetails => member !== null) ?? []}
|
||||
data={(memberDetails?.filter((member): member is IProjectMemberDetails => member !== null) ?? []) as any}
|
||||
keyExtractor={(rowData) => rowData?.member.id ?? ""}
|
||||
thClassName="text-left font-medium divide-x-0 border-b border-t divide-custom-border-200"
|
||||
tBodyClassName="divide-y-0"
|
||||
|
||||
@@ -12,7 +12,6 @@ import { WORKSPACE_MEMBER_LEAVE } from "@/constants/event-tracker";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useUser } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
import useMemberColumns from "@/plane-web/components/workspace/settings/useMemberColumns";
|
||||
|
||||
type Props = {
|
||||
@@ -33,7 +32,6 @@ export const WorkspaceMembersListItem: FC<Props> = observer((props) => {
|
||||
workspace: { removeMemberFromWorkspace },
|
||||
} = useMember();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { isMobile } = usePlatformOS();
|
||||
// derived values
|
||||
|
||||
const handleLeaveWorkspace = async () => {
|
||||
@@ -96,9 +94,10 @@ export const WorkspaceMembersListItem: FC<Props> = observer((props) => {
|
||||
)}
|
||||
<Table
|
||||
columns={columns}
|
||||
data={memberDetails?.filter((member): member is IWorkspaceMember => member !== null) ?? []}
|
||||
data={(memberDetails?.filter((member): member is IWorkspaceMember => member !== null) ?? []) as any}
|
||||
keyExtractor={(rowData) => rowData?.member.id ?? ""}
|
||||
thClassName="text-left font-medium divide-x-0 border-b border-t divide-custom-border-200"
|
||||
tHeadClassName="border-b border-custom-border-100"
|
||||
thClassName="text-left font-medium divide-x-0"
|
||||
tBodyClassName="divide-y-0"
|
||||
tBodyTrClassName="divide-x-0"
|
||||
tHeadTrClassName="divide-x-0"
|
||||
|
||||
@@ -456,19 +456,3 @@ export const groupReactionEmojis = (reactions: any) => {
|
||||
|
||||
return groupedEmojis;
|
||||
};
|
||||
|
||||
export enum EActivityFilterType {
|
||||
COMMENT = "COMMENT",
|
||||
ACTIVITY = "ACTIVITY",
|
||||
}
|
||||
|
||||
export const ACTIVITY_FILTER_TYPE_OPTIONS = [
|
||||
{
|
||||
value: EActivityFilterType.COMMENT,
|
||||
label: "Comments",
|
||||
},
|
||||
{
|
||||
value: EActivityFilterType.ACTIVITY,
|
||||
label: "Updates",
|
||||
},
|
||||
];
|
||||
@@ -1,8 +1,5 @@
|
||||
// types
|
||||
import { TStaticViewTypes } from "@plane/types";
|
||||
// icons
|
||||
import { SettingIcon } from "@/components/icons/attachment";
|
||||
import { Props } from "@/components/icons/types";
|
||||
// services images
|
||||
import CSVLogo from "@/public/services/csv.svg";
|
||||
import ExcelLogo from "@/public/services/excel.svg";
|
||||
@@ -135,61 +132,3 @@ export const RESTRICTED_URLS = [
|
||||
"spaces",
|
||||
"workspace-invitations",
|
||||
];
|
||||
|
||||
export const WORKSPACE_SETTINGS_LINKS: {
|
||||
key: string;
|
||||
label: string;
|
||||
href: string;
|
||||
access: EUserWorkspaceRoles;
|
||||
highlight: (pathname: string, baseUrl: string) => boolean;
|
||||
Icon: React.FC<Props>;
|
||||
}[] = [
|
||||
{
|
||||
key: "general",
|
||||
label: "General",
|
||||
href: `/settings`,
|
||||
access: EUserWorkspaceRoles.GUEST,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "members",
|
||||
label: "Members",
|
||||
href: `/settings/members`,
|
||||
access: EUserWorkspaceRoles.GUEST,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/members/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "billing-and-plans",
|
||||
label: "Billing and plans",
|
||||
href: `/settings/billing`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/billing/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "export",
|
||||
label: "Exports",
|
||||
href: `/settings/exports`,
|
||||
access: EUserWorkspaceRoles.MEMBER,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/exports/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "webhooks",
|
||||
label: "Webhooks",
|
||||
href: `/settings/webhooks`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/webhooks/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "api-tokens",
|
||||
label: "API tokens",
|
||||
href: `/settings/api-tokens`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/api-tokens/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
];
|
||||
|
||||
2
web/ee/components/issues/activity/index.ts
Normal file
2
web/ee/components/issues/activity/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./root";
|
||||
export * from "./worklog-create-button";
|
||||
1
web/ee/components/issues/activity/root.tsx
Normal file
1
web/ee/components/issues/activity/root.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from "@/plane-web/components/issues/worklog/activity/root";
|
||||
@@ -0,0 +1 @@
|
||||
export * from "@/plane-web/components/issues/worklog/activity/worklog-create-button";
|
||||
1
web/ee/components/issues/properties/index.ts
Normal file
1
web/ee/components/issues/properties/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./root";
|
||||
1
web/ee/components/issues/properties/root.tsx
Normal file
1
web/ee/components/issues/properties/root.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from "@/plane-web/components/issues/worklog/property/root";
|
||||
1
web/ee/constants/issues.ts
Normal file
1
web/ee/constants/issues.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "ce/constants/issues";
|
||||
1
web/ee/constants/workspace.ts
Normal file
1
web/ee/constants/workspace.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "ce/constants/workspace";
|
||||
2
web/ee/issues/index.ts
Normal file
2
web/ee/issues/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./worklog/activity";
|
||||
export * from "./worklog/property";
|
||||
2
web/ee/issues/worklog/activity/index.ts
Normal file
2
web/ee/issues/worklog/activity/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./root";
|
||||
export * from "./worklog-create-button";
|
||||
1
web/ee/issues/worklog/activity/root.tsx
Normal file
1
web/ee/issues/worklog/activity/root.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from "@/plane-web/components/issues/worklog/activity/root";
|
||||
1
web/ee/issues/worklog/activity/worklog-create-button.tsx
Normal file
1
web/ee/issues/worklog/activity/worklog-create-button.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from "@/plane-web/components/issues/worklog/activity/worklog-create-button";
|
||||
2
web/ee/issues/worklog/index.ts
Normal file
2
web/ee/issues/worklog/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./property";
|
||||
export * from "./activity";
|
||||
1
web/ee/issues/worklog/property/index.ts
Normal file
1
web/ee/issues/worklog/property/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./root";
|
||||
1
web/ee/issues/worklog/property/root.tsx
Normal file
1
web/ee/issues/worklog/property/root.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from "@/plane-web/components/issues/worklog/property/root";
|
||||
@@ -299,3 +299,38 @@ export const getCurrentDateTimeInISO = () => {
|
||||
const date = new Date();
|
||||
return date.toISOString();
|
||||
};
|
||||
|
||||
/**
|
||||
* @description converts hours and minutes to minutes
|
||||
* @param { number } hours
|
||||
* @param { number } minutes
|
||||
* @returns { number } minutes
|
||||
* @example convertHoursMinutesToMinutes(2, 30) // Output: 150
|
||||
*/
|
||||
export const convertHoursMinutesToMinutes = (hours: number, minutes: number): number => hours * 60 + minutes;
|
||||
|
||||
/**
|
||||
* @description converts minutes to hours and minutes
|
||||
* @param { number } mins
|
||||
* @returns { number, number } hours and minutes
|
||||
* @example convertMinutesToHoursAndMinutes(150) // Output: { hours: 2, minutes: 30 }
|
||||
*/
|
||||
export const convertMinutesToHoursAndMinutes = (mins: number): { hours: number; minutes: number } => {
|
||||
const hours = Math.floor(mins / 60);
|
||||
const minutes = Math.floor(mins % 60);
|
||||
|
||||
return { hours: hours, minutes: minutes };
|
||||
};
|
||||
|
||||
/**
|
||||
* @description converts minutes to days, hours and minutes
|
||||
* @param { number } totalMinutes
|
||||
* @returns { string } days, hours and minutes
|
||||
*/
|
||||
export const convertMinutesToDaysHoursMinutes = (totalMinutes: number): string => {
|
||||
const days = Math.floor(totalMinutes / (60 * 24));
|
||||
const hours = Math.floor((totalMinutes % (60 * 24)) / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
|
||||
return `${days ? `${days}d ` : ``}${hours ? `${hours}h ` : ``}${minutes ? `${minutes}m ` : ``} `;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user