Initial commit: Plane
Some checks failed
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Admin Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Sync Repositories / sync_changes (push) Has been cancelled

Synced from upstream: 8853637e981ed7d8a6cff32bd98e7afe20f54362
This commit is contained in:
chuan
2025-11-07 00:00:52 +08:00
commit 8ebde8aa05
4886 changed files with 462270 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,21 @@
import { observer } from "mobx-react";
// components
import { BulkOperationsUpgradeBanner } from "@/components/issues/bulk-operations/upgrade-banner";
// hooks
import { useMultipleSelectStore } from "@/hooks/store/use-multiple-select-store";
import type { TSelectionHelper } from "@/hooks/use-multiple-select";
type Props = {
className?: string;
selectionHelpers: TSelectionHelper;
};
export const IssueBulkOperationsRoot: React.FC<Props> = observer((props) => {
const { className, selectionHelpers } = props;
// store hooks
const { isSelectionActive } = useMultipleSelectStore();
if (!isSelectionActive || selectionHelpers.isSelectionDisabled) return null;
return <BulkOperationsUpgradeBanner className={className} />;
});

View File

@@ -0,0 +1,11 @@
"use client";
import { observer } from "mobx-react";
type Props = {
handleRemove: (val: string) => void;
values: string[];
editable: boolean | undefined;
};
export const AppliedIssueTypeFilters: React.FC<Props> = observer(() => null);

View File

@@ -0,0 +1,12 @@
"use client";
import type React from "react";
import { observer } from "mobx-react";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
searchQuery: string;
};
export const FilterIssueTypes: React.FC<Props> = observer(() => null);

View File

@@ -0,0 +1,12 @@
"use client";
import type React from "react";
import { observer } from "mobx-react";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
searchQuery: string;
};
export const FilterTeamProjects: React.FC<Props> = observer(() => null);

View File

@@ -0,0 +1,125 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// icons
import { Circle, ExternalLink } from "lucide-react";
// plane imports
import {
EUserPermissions,
EUserPermissionsLevel,
SPACE_BASE_PATH,
SPACE_BASE_URL,
WORK_ITEM_TRACKER_ELEMENTS,
EProjectFeatureKey,
} from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";
import { Tooltip } from "@plane/propel/tooltip";
import { EIssuesStoreType } from "@plane/types";
import { Breadcrumbs, Header } from "@plane/ui";
// components
import { CountChip } from "@/components/common/count-chip";
// constants
import { HeaderFilters } from "@/components/issues/filters";
// helpers
// hooks
import { useCommandPalette } from "@/hooks/store/use-command-palette";
import { useIssues } from "@/hooks/store/use-issues";
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router";
import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web
import { CommonProjectBreadcrumbs } from "../breadcrumbs/common";
export const IssuesHeader = observer(() => {
// router
const router = useAppRouter();
const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string };
// store hooks
const {
issues: { getGroupIssueCount },
} = useIssues(EIssuesStoreType.PROJECT);
// i18n
const { t } = useTranslation();
const { currentProjectDetails, loader } = useProject();
const { toggleCreateIssueModal } = useCommandPalette();
const { allowPermissions } = useUserPermissions();
const { isMobile } = usePlatformOS();
const SPACE_APP_URL = (SPACE_BASE_URL.trim() === "" ? window.location.origin : SPACE_BASE_URL) + SPACE_BASE_PATH;
const publishedURL = `${SPACE_APP_URL}/issues/${currentProjectDetails?.anchor}`;
const issuesCount = getGroupIssueCount(undefined, undefined, false);
const canUserCreateIssue = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.PROJECT
);
return (
<Header>
<Header.LeftItem>
<div className="flex items-center gap-2.5">
<Breadcrumbs onBack={() => router.back()} isLoading={loader === "init-loader"} className="flex-grow-0">
<CommonProjectBreadcrumbs
workspaceSlug={workspaceSlug?.toString()}
projectId={projectId?.toString()}
featureKey={EProjectFeatureKey.WORK_ITEMS}
isLast
/>
</Breadcrumbs>
{issuesCount && issuesCount > 0 ? (
<Tooltip
isMobile={isMobile}
tooltipContent={`There are ${issuesCount} ${issuesCount > 1 ? "work items" : "work item"} in this project`}
position="bottom"
>
<CountChip count={issuesCount} />
</Tooltip>
) : null}
</div>
{currentProjectDetails?.anchor ? (
<a
href={publishedURL}
className="group flex items-center gap-1.5 rounded bg-custom-primary-100/10 px-2.5 py-1 text-xs font-medium text-custom-primary-100"
target="_blank"
rel="noopener noreferrer"
>
<Circle className="h-1.5 w-1.5 fill-custom-primary-100" strokeWidth={2} />
{t("workspace_projects.network.public.title")}
<ExternalLink className="hidden h-3 w-3 group-hover:block" strokeWidth={2} />
</a>
) : (
<></>
)}
</Header.LeftItem>
<Header.RightItem>
<div className="hidden gap-3 md:flex">
<HeaderFilters
projectId={projectId}
currentProjectDetails={currentProjectDetails}
workspaceSlug={workspaceSlug}
canUserCreateIssue={canUserCreateIssue}
/>
</div>
{canUserCreateIssue ? (
<Button
onClick={() => {
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
}}
data-ph-element={WORK_ITEM_TRACKER_ELEMENTS.HEADER_ADD_BUTTON.WORK_ITEMS}
size="sm"
>
<div className="block sm:hidden">{t("issue.label", { count: 1 })}</div>
<div className="hidden sm:block">{t("issue.add.label")}</div>
</Button>
) : (
<></>
)}
</Header.RightItem>
</Header>
);
});

View File

@@ -0,0 +1,14 @@
import type { FC } from "react";
// plane types
import type { TIssueServiceType, TWorkItemWidgets } from "@plane/types";
export type TWorkItemAdditionalWidgetActionButtonsProps = {
disabled: boolean;
hideWidgets: TWorkItemWidgets[];
issueServiceType: TIssueServiceType;
projectId: string;
workItemId: string;
workspaceSlug: string;
};
export const WorkItemAdditionalWidgetActionButtons: FC<TWorkItemAdditionalWidgetActionButtonsProps> = () => null;

View File

@@ -0,0 +1,14 @@
import type { FC } from "react";
// plane types
import type { TIssueServiceType, TWorkItemWidgets } from "@plane/types";
export type TWorkItemAdditionalWidgetCollapsiblesProps = {
disabled: boolean;
hideWidgets: TWorkItemWidgets[];
issueServiceType: TIssueServiceType;
projectId: string;
workItemId: string;
workspaceSlug: string;
};
export const WorkItemAdditionalWidgetCollapsibles: FC<TWorkItemAdditionalWidgetCollapsiblesProps> = () => null;

View File

@@ -0,0 +1,13 @@
import type { FC } from "react";
// plane types
import type { TIssueServiceType, TWorkItemWidgets } from "@plane/types";
export type TWorkItemAdditionalWidgetModalsProps = {
hideWidgets: TWorkItemWidgets[];
issueServiceType: TIssueServiceType;
projectId: string;
workItemId: string;
workspaceSlug: string;
};
export const WorkItemAdditionalWidgetModals: FC<TWorkItemAdditionalWidgetModalsProps> = () => null;

View File

@@ -0,0 +1,13 @@
"use client";
import type { FC } from "react";
import { observer } from "mobx-react";
export type TAdditionalActivityRoot = {
activityId: string;
showIssue?: boolean;
ends: "top" | "bottom" | undefined;
field: string | undefined;
};
export const AdditionalActivityRoot: FC<TAdditionalActivityRoot> = observer(() => <></>);

View File

@@ -0,0 +1,14 @@
import type { FC } from "react";
import React from "react";
// plane imports
export type TWorkItemAdditionalSidebarProperties = {
workItemId: string;
workItemTypeId: string | null;
projectId: string;
workspaceSlug: string;
isEditable: boolean;
isPeekView?: boolean;
};
export const WorkItemAdditionalSidebarProperties: FC<TWorkItemAdditionalSidebarProperties> = () => <></>;

View File

@@ -0,0 +1,7 @@
export * from "./issue-identifier";
export * from "./issue-properties-activity";
export * from "./issue-type-switcher";
export * from "./issue-type-activity";
export * from "./parent-select-root";
export * from "./issue-creator";
export * from "./additional-activity-root";

View File

@@ -0,0 +1,36 @@
import type { FC } from "react";
import Link from "next/link";
// hooks
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
type TIssueUser = {
activityId: string;
customUserName?: string;
};
export const IssueCreatorDisplay: FC<TIssueUser> = (props) => {
const { activityId, customUserName } = props;
// hooks
const {
activity: { getActivityById },
} = useIssueDetail();
const activity = getActivityById(activityId);
if (!activity) return <></>;
return (
<>
{customUserName ? (
<span className="text-custom-text-100 font-medium">{customUserName || "Plane"}</span>
) : (
<Link
href={`/${activity?.workspace_detail?.slug}/profile/${activity?.actor_detail?.id}`}
className="hover:underline text-custom-text-100 font-medium"
>
{activity.actor_detail?.display_name}
</Link>
)}
</>
);
};

View File

@@ -0,0 +1,105 @@
import type { FC } from "react";
import { observer } from "mobx-react";
// types
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import { Tooltip } from "@plane/propel/tooltip";
import type { IIssueDisplayProperties } from "@plane/types";
// ui
// helpers
import { cn } from "@plane/utils";
// hooks
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
import { useProject } from "@/hooks/store/use-project";
type TIssueIdentifierBaseProps = {
projectId: string;
size?: "xs" | "sm" | "md" | "lg";
textContainerClassName?: string;
displayProperties?: IIssueDisplayProperties | undefined;
enableClickToCopyIdentifier?: boolean;
};
type TIssueIdentifierFromStore = TIssueIdentifierBaseProps & {
issueId: string;
};
type TIssueIdentifierWithDetails = TIssueIdentifierBaseProps & {
issueTypeId?: string | null;
projectIdentifier: string;
issueSequenceId: string | number;
};
export type TIssueIdentifierProps = TIssueIdentifierFromStore | TIssueIdentifierWithDetails;
type TIssueTypeIdentifier = {
issueTypeId: string;
size?: "xs" | "sm" | "md" | "lg";
};
export const IssueTypeIdentifier: FC<TIssueTypeIdentifier> = observer((props) => <></>);
type TIdentifierTextProps = {
identifier: string;
enableClickToCopyIdentifier?: boolean;
textContainerClassName?: string;
};
export const IdentifierText: React.FC<TIdentifierTextProps> = (props) => {
const { identifier, enableClickToCopyIdentifier = false, textContainerClassName } = props;
// handlers
const handleCopyIssueIdentifier = () => {
if (enableClickToCopyIdentifier) {
navigator.clipboard.writeText(identifier).then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Work item ID copied to clipboard",
});
});
}
};
return (
<Tooltip tooltipContent="Click to copy" disabled={!enableClickToCopyIdentifier} position="top">
<span
className={cn(
"text-base font-medium text-custom-text-300",
{
"cursor-pointer": enableClickToCopyIdentifier,
},
textContainerClassName
)}
onClick={handleCopyIssueIdentifier}
>
{identifier}
</span>
</Tooltip>
);
};
export const IssueIdentifier: React.FC<TIssueIdentifierProps> = observer((props) => {
const { projectId, textContainerClassName, displayProperties, enableClickToCopyIdentifier = false } = props;
// store hooks
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 = isUsingStoreData ? getIssueById(props.issueId) : null;
const projectIdentifier = isUsingStoreData ? getProjectIdentifierById(projectId) : props.projectIdentifier;
const issueSequenceId = isUsingStoreData ? issue?.sequence_id : props.issueSequenceId;
const shouldRenderIssueID = displayProperties ? displayProperties.key : true;
if (!shouldRenderIssueID) return null;
return (
<div className="flex items-center space-x-2">
<IdentifierText
identifier={`${projectIdentifier}-${issueSequenceId}`}
enableClickToCopyIdentifier={enableClickToCopyIdentifier}
textContainerClassName={textContainerClassName}
/>
</div>
);
});

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
"use client";
import type { FC } from "react";
import { observer } from "mobx-react";
export type TIssueTypeActivity = { activityId: string; showIssue?: boolean; ends: "top" | "bottom" | undefined };
export const IssueTypeActivity: FC<TIssueTypeActivity> = observer(() => <></>);

View File

@@ -0,0 +1,24 @@
import { observer } from "mobx-react";
// store hooks
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
// plane web components
import { IssueIdentifier } from "@/plane-web/components/issues/issue-details/issue-identifier";
export type TIssueTypeSwitcherProps = {
issueId: string;
disabled: boolean;
};
export const IssueTypeSwitcher: React.FC<TIssueTypeSwitcherProps> = observer((props) => {
const { issueId } = props;
// store hooks
const {
issue: { getIssueById },
} = useIssueDetail();
// derived values
const issue = getIssueById(issueId);
if (!issue || !issue.project_id) return <></>;
return <IssueIdentifier issueId={issueId} projectId={issue.project_id} size="md" enableClickToCopyIdentifier />;
});

View File

@@ -0,0 +1,83 @@
"use client";
import React from "react";
import { observer } from "mobx-react";
// plane imports
import { useTranslation } from "@plane/i18n";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
// components
import type { TIssueOperations } from "@/components/issues/issue-detail";
import { IssueParentSelect } from "@/components/issues/issue-detail/parent-select";
// hooks
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
type TIssueParentSelect = {
className?: string;
disabled?: boolean;
issueId: string;
issueOperations: TIssueOperations;
projectId: string;
workspaceSlug: string;
};
export const IssueParentSelectRoot: React.FC<TIssueParentSelect> = observer((props) => {
const { issueId, issueOperations, projectId, workspaceSlug } = props;
const { t } = useTranslation();
// store hooks
const {
issue: { getIssueById },
} = useIssueDetail();
const {
toggleParentIssueModal,
removeSubIssue,
subIssues: { setSubIssueHelpers, fetchSubIssues },
} = useIssueDetail();
// derived values
const issue = getIssueById(issueId);
const parentIssue = issue?.parent_id ? getIssueById(issue.parent_id) : undefined;
const handleParentIssue = async (_issueId: string | null = null) => {
try {
await issueOperations.update(workspaceSlug, projectId, issueId, { parent_id: _issueId });
await issueOperations.fetch(workspaceSlug, projectId, issueId, false);
if (_issueId) await fetchSubIssues(workspaceSlug, projectId, _issueId);
toggleParentIssueModal(null);
} catch (error) {
console.error("something went wrong while fetching the issue");
}
};
const handleRemoveSubIssue = async (
workspaceSlug: string,
projectId: string,
parentIssueId: string,
issueId: string
) => {
try {
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
await removeSubIssue(workspaceSlug, projectId, parentIssueId, issueId);
await fetchSubIssues(workspaceSlug, projectId, parentIssueId);
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
} catch (error) {
setToast({
type: TOAST_TYPE.ERROR,
title: t("common.error.label"),
message: t("common.something_went_wrong"),
});
}
};
const workItemLink = `/${workspaceSlug}/projects/${parentIssue?.project_id}/issues/${parentIssue?.id}`;
if (!issue) return <></>;
return (
<IssueParentSelect
{...props}
handleParentIssue={handleParentIssue}
handleRemoveSubIssue={handleRemoveSubIssue}
workItemLink={workItemLink}
/>
);
});

View File

@@ -0,0 +1,9 @@
import type { TIssue } from "@plane/types";
export type TDateAlertProps = {
date: string;
workItem: TIssue;
projectId: string;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const DateAlert = (props: TDateAlertProps) => <></>;

View File

@@ -0,0 +1,3 @@
import type { TIssue } from "@plane/types";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const TransferHopInfo = ({ workItem }: { workItem: TIssue }) => <></>;

View File

@@ -0,0 +1,10 @@
import type { FC } from "react";
import React from "react";
import type { IIssueDisplayProperties, TIssue } from "@plane/types";
export type TWorkItemLayoutAdditionalProperties = {
displayProperties: IIssueDisplayProperties;
issue: TIssue;
};
export const WorkItemLayoutAdditionalProperties: FC<TWorkItemLayoutAdditionalProperties> = (props) => <></>;

View File

@@ -0,0 +1,2 @@
export * from "./team-issues";
export * from "./team-view-issues";

View File

@@ -0,0 +1,3 @@
import { observer } from "mobx-react";
export const TeamEmptyState: React.FC = observer(() => <></>);

View File

@@ -0,0 +1,3 @@
import { observer } from "mobx-react";
export const TeamProjectWorkItemEmptyState: React.FC = observer(() => <></>);

View File

@@ -0,0 +1,3 @@
import { observer } from "mobx-react";
export const TeamViewEmptyState: React.FC = observer(() => <></>);

View File

@@ -0,0 +1,14 @@
"use client";
import type { FC } from "react";
import React from "react";
type Props = {
issueId: string;
className?: string;
size?: number;
showProgressText?: boolean;
showLabel?: boolean;
};
export const IssueStats: FC<Props> = (props) => <></>;

View File

@@ -0,0 +1,22 @@
import type { Copy } from "lucide-react";
import type { TContextMenuItem } from "@plane/ui";
export interface CopyMenuHelperProps {
baseItem: {
key: string;
title: string;
icon: typeof Copy;
action: () => void;
shouldRender: boolean;
};
activeLayout: string;
setCreateUpdateIssueModal: (open: boolean) => void;
setDuplicateWorkItemModal?: (open: boolean) => void;
workspaceSlug?: string;
}
export const createCopyMenuWithDuplication = (props: CopyMenuHelperProps): TContextMenuItem => {
const { baseItem } = props;
return baseItem;
};

View File

@@ -0,0 +1,11 @@
import type { FC } from "react";
type TDuplicateWorkItemModalProps = {
workItemId: string;
onClose: () => void;
isOpen: boolean;
workspaceSlug: string;
projectId: string;
};
export const DuplicateWorkItemModal: FC<TDuplicateWorkItemModalProps> = () => <></>;

View File

@@ -0,0 +1,2 @@
export * from "./duplicate-modal";
export * from "./copy-menu-helper";

View File

@@ -0,0 +1,115 @@
import type { FC } from "react";
import { CalendarDays, LayersIcon, Link2, Paperclip } from "lucide-react";
// types
import { ISSUE_GROUP_BY_OPTIONS } from "@plane/constants";
import type { ISvgIcons } from "@plane/propel/icons";
import {
CycleIcon,
StatePropertyIcon,
ModuleIcon,
MembersPropertyIcon,
DueDatePropertyIcon,
EstimatePropertyIcon,
LabelPropertyIcon,
PriorityPropertyIcon,
StartDatePropertyIcon,
} from "@plane/propel/icons";
import type {
IGroupByColumn,
IIssueDisplayProperties,
TGetColumns,
TIssueGroupByOptions,
TSpreadsheetColumn,
} from "@plane/types";
// components
import {
SpreadsheetAssigneeColumn,
SpreadsheetAttachmentColumn,
SpreadsheetCreatedOnColumn,
SpreadsheetDueDateColumn,
SpreadsheetEstimateColumn,
SpreadsheetLabelColumn,
SpreadsheetModuleColumn,
SpreadsheetCycleColumn,
SpreadsheetLinkColumn,
SpreadsheetPriorityColumn,
SpreadsheetStartDateColumn,
SpreadsheetStateColumn,
SpreadsheetSubIssueColumn,
SpreadsheetUpdatedOnColumn,
} from "@/components/issues/issue-layouts/spreadsheet/columns";
// store
import { store } from "@/lib/store-context";
export type TGetScopeMemberIdsResult = {
memberIds: string[];
includeNone: boolean;
};
export const getScopeMemberIds = ({ isWorkspaceLevel, projectId }: TGetColumns): TGetScopeMemberIdsResult => {
// store values
const { workspaceMemberIds } = store.memberRoot.workspace;
const { projectMemberIds } = store.memberRoot.project;
// derived values
const memberIds = workspaceMemberIds;
if (isWorkspaceLevel) {
return { memberIds: memberIds ?? [], includeNone: true };
}
if (projectId || (projectMemberIds && projectMemberIds.length > 0)) {
const { getProjectMemberIds } = store.memberRoot.project;
const _projectMemberIds = projectId ? getProjectMemberIds(projectId, false) : projectMemberIds;
return {
memberIds: _projectMemberIds ?? [],
includeNone: true,
};
}
return { memberIds: [], includeNone: true };
};
export const getTeamProjectColumns = (): IGroupByColumn[] | undefined => undefined;
export const SpreadSheetPropertyIconMap: Record<string, FC<ISvgIcons>> = {
MembersPropertyIcon: MembersPropertyIcon,
CalenderDays: CalendarDays,
DueDatePropertyIcon: DueDatePropertyIcon,
EstimatePropertyIcon: EstimatePropertyIcon,
LabelPropertyIcon: LabelPropertyIcon,
ModuleIcon: ModuleIcon,
ContrastIcon: CycleIcon,
PriorityPropertyIcon: PriorityPropertyIcon,
StartDatePropertyIcon: StartDatePropertyIcon,
StatePropertyIcon: StatePropertyIcon,
Link2: Link2,
Paperclip: Paperclip,
LayersIcon: LayersIcon,
};
export const SPREADSHEET_COLUMNS: { [key in keyof IIssueDisplayProperties]: TSpreadsheetColumn } = {
assignee: SpreadsheetAssigneeColumn,
created_on: SpreadsheetCreatedOnColumn,
due_date: SpreadsheetDueDateColumn,
estimate: SpreadsheetEstimateColumn,
labels: SpreadsheetLabelColumn,
modules: SpreadsheetModuleColumn,
cycle: SpreadsheetCycleColumn,
link: SpreadsheetLinkColumn,
priority: SpreadsheetPriorityColumn,
start_date: SpreadsheetStartDateColumn,
state: SpreadsheetStateColumn,
sub_issue_count: SpreadsheetSubIssueColumn,
updated_on: SpreadsheetUpdatedOnColumn,
attachment_count: SpreadsheetAttachmentColumn,
};
export const useGroupByOptions = (
options: TIssueGroupByOptions[]
): {
key: TIssueGroupByOptions;
titleTranslationKey: string;
}[] => {
const groupByOptions = ISSUE_GROUP_BY_OPTIONS.filter((option) => options.includes(option.key));
return groupByOptions;
};

View File

@@ -0,0 +1,3 @@
export * from "./provider";
export * from "./issue-type-select";
export * from "./template-select";

View File

@@ -0,0 +1,26 @@
import type { Control } from "react-hook-form";
// plane imports
import type { EditorRefApi } from "@plane/editor";
// types
import type { TBulkIssueProperties, TIssue } from "@plane/types";
export type TIssueFields = TIssue & TBulkIssueProperties;
export type TIssueTypeDropdownVariant = "xs" | "sm";
export type TIssueTypeSelectProps<T extends Partial<TIssueFields>> = {
control: Control<T>;
projectId: string | null;
editorRef?: React.MutableRefObject<EditorRefApi | null>;
disabled?: boolean;
variant?: TIssueTypeDropdownVariant;
placeholder?: string;
isRequired?: boolean;
renderChevron?: boolean;
dropDownContainerClassName?: string;
showMandatoryFieldInfo?: boolean; // Show info about mandatory fields
handleFormChange?: () => void;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const IssueTypeSelect = <T extends Partial<TIssueFields>>(props: TIssueTypeSelectProps<T>) => <></>;

View File

@@ -0,0 +1,10 @@
import type React from "react";
export type TWorkItemModalAdditionalPropertiesProps = {
isDraft?: boolean;
projectId: string | null;
workItemId: string | undefined;
workspaceSlug: string;
};
export const WorkItemModalAdditionalProperties: React.FC<TWorkItemModalAdditionalPropertiesProps> = () => null;

View File

@@ -0,0 +1,53 @@
import React, { useState } from "react";
import { observer } from "mobx-react";
// plane imports
import type { ISearchIssueResponse, TIssue } from "@plane/types";
// components
import { IssueModalContext } from "@/components/issues/issue-modal/context";
// hooks
import { useUser } from "@/hooks/store/user/user-user";
export type TIssueModalProviderProps = {
templateId?: string;
dataForPreload?: Partial<TIssue>;
allowedProjectIds?: string[];
children: React.ReactNode;
};
export const IssueModalProvider = observer((props: TIssueModalProviderProps) => {
const { children, allowedProjectIds } = props;
// states
const [selectedParentIssue, setSelectedParentIssue] = useState<ISearchIssueResponse | null>(null);
// store hooks
const { projectsWithCreatePermissions } = useUser();
// derived values
const projectIdsWithCreatePermissions = Object.keys(projectsWithCreatePermissions ?? {});
return (
<IssueModalContext.Provider
value={{
allowedProjectIds: allowedProjectIds ?? projectIdsWithCreatePermissions,
workItemTemplateId: null,
setWorkItemTemplateId: () => {},
isApplyingTemplate: false,
setIsApplyingTemplate: () => {},
selectedParentIssue,
setSelectedParentIssue,
issuePropertyValues: {},
setIssuePropertyValues: () => {},
issuePropertyValueErrors: {},
setIssuePropertyValueErrors: () => {},
getIssueTypeIdOnProjectChange: () => null,
getActiveAdditionalPropertiesLength: () => 0,
handlePropertyValuesValidation: () => true,
handleCreateUpdatePropertyValues: () => Promise.resolve(),
handleProjectEntitiesFetch: () => Promise.resolve(),
handleTemplateChange: () => Promise.resolve(),
handleConvert: () => Promise.resolve(),
handleCreateSubWorkItem: () => Promise.resolve(),
}}
>
{children}
</IssueModalContext.Provider>
);
});

View File

@@ -0,0 +1,16 @@
export type TWorkItemTemplateDropdownSize = "xs" | "sm";
export type TWorkItemTemplateSelect = {
projectId: string | null;
typeId: string | null;
disabled?: boolean;
size?: TWorkItemTemplateDropdownSize;
placeholder?: string;
renderChevron?: boolean;
dropDownContainerClassName?: string;
handleModalClose: () => void;
handleFormChange?: () => void;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const WorkItemTemplateSelect = (props: TWorkItemTemplateSelect) => <></>;

View File

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

View File

@@ -0,0 +1,78 @@
import type { FC } from "react";
import { useEffect, useRef } from "react";
import { observer } from "mobx-react";
import type { UseFormRegister, UseFormSetFocus } from "react-hook-form";
// plane constants
// plane helpers
import { useOutsideClickDetector } from "@plane/hooks";
// types
import type { TIssue } from "@plane/types";
import { EIssueLayoutTypes } from "@plane/types";
// components
import type { TQuickAddIssueForm } from "@/components/issues/issue-layouts/quick-add";
import {
CalendarQuickAddIssueForm,
GanttQuickAddIssueForm,
KanbanQuickAddIssueForm,
ListQuickAddIssueForm,
SpreadsheetQuickAddIssueForm,
} from "@/components/issues/issue-layouts/quick-add";
// hooks
import { useProject } from "@/hooks/store/use-project";
import useKeypress from "@/hooks/use-keypress";
export type TQuickAddIssueFormRoot = {
isOpen: boolean;
layout: EIssueLayoutTypes;
prePopulatedData?: Partial<TIssue>;
projectId: string;
hasError?: boolean;
setFocus: UseFormSetFocus<TIssue>;
register: UseFormRegister<TIssue>;
onSubmit: () => void;
onClose: () => void;
isEpic: boolean;
};
export const QuickAddIssueFormRoot: FC<TQuickAddIssueFormRoot> = observer((props) => {
const { isOpen, layout, projectId, hasError = false, setFocus, register, onSubmit, onClose, isEpic } = props;
// store hooks
const { getProjectById } = useProject();
// derived values
const projectDetail = getProjectById(projectId);
// refs
const ref = useRef<HTMLFormElement>(null);
// click detection
useKeypress("Escape", onClose);
useOutsideClickDetector(ref, onClose);
// set focus on name input
useEffect(() => {
setFocus("name");
}, [setFocus]);
if (!projectDetail) return <></>;
const QUICK_ADD_ISSUE_FORMS: Record<EIssueLayoutTypes, FC<TQuickAddIssueForm>> = {
[EIssueLayoutTypes.LIST]: ListQuickAddIssueForm,
[EIssueLayoutTypes.KANBAN]: KanbanQuickAddIssueForm,
[EIssueLayoutTypes.CALENDAR]: CalendarQuickAddIssueForm,
[EIssueLayoutTypes.GANTT]: GanttQuickAddIssueForm,
[EIssueLayoutTypes.SPREADSHEET]: SpreadsheetQuickAddIssueForm,
};
const CurrentLayoutQuickAddIssueForm = QUICK_ADD_ISSUE_FORMS[layout] ?? null;
if (!CurrentLayoutQuickAddIssueForm) return <></>;
return (
<CurrentLayoutQuickAddIssueForm
ref={ref}
isOpen={isOpen}
projectDetail={projectDetail}
hasError={hasError}
register={register}
onSubmit={onSubmit}
isEpic={isEpic}
/>
);
});

View File

@@ -0,0 +1,31 @@
"use client";
import type { FC } from "react";
// plane imports
import type { TActivityFilters, TActivityFilterOption } from "@plane/constants";
import { ACTIVITY_FILTER_TYPE_OPTIONS } from "@plane/constants";
// components
import { ActivityFilter } from "@/components/issues/issue-detail/issue-activity";
export type TActivityFilterRoot = {
selectedFilters: TActivityFilters[];
toggleFilter: (filter: TActivityFilters) => void;
projectId: string;
isIntakeIssue?: boolean;
};
export const ActivityFilterRoot: FC<TActivityFilterRoot> = (props) => {
const { selectedFilters, toggleFilter } = props;
const filters: TActivityFilterOption[] = Object.entries(ACTIVITY_FILTER_TYPE_OPTIONS).map(([key, value]) => {
const filterKey = key as TActivityFilters;
return {
key: filterKey,
labelTranslationKey: value.labelTranslationKey,
isSelected: selectedFilters.includes(filterKey),
onClick: () => toggleFilter(filterKey),
};
});
return <ActivityFilter selectedFilters={selectedFilters} filterOptions={filters} />;
};

View File

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

View File

@@ -0,0 +1,14 @@
"use client";
import type { FC } from "react";
import type { TIssueActivityComment } from "@plane/types";
type TIssueActivityWorklog = {
workspaceSlug: string;
projectId: string;
issueId: string;
activityComment: TIssueActivityComment;
ends?: "top" | "bottom";
};
export const IssueActivityWorklog: FC<TIssueActivityWorklog> = () => <></>;

View File

@@ -0,0 +1,12 @@
"use client";
import type { FC } from "react";
type TIssueActivityWorklogCreateButton = {
workspaceSlug: string;
projectId: string;
issueId: string;
disabled: boolean;
};
export const IssueActivityWorklogCreateButton: FC<TIssueActivityWorklogCreateButton> = () => <></>;

View File

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

View File

@@ -0,0 +1,12 @@
"use client";
import type { FC } from "react";
type TIssueWorklogProperty = {
workspaceSlug: string;
projectId: string;
issueId: string;
disabled: boolean;
};
export const IssueWorklogProperty: FC<TIssueWorklogProperty> = () => <></>;