Merge pull request #1921 from makeplane/sync/ce-ee

Sync: Community Changes
This commit is contained in:
pushya22
2024-12-13 14:32:27 +05:30
committed by GitHub
26 changed files with 238 additions and 78 deletions

View File

@@ -24,6 +24,8 @@ export type CalendarStoreType =
| EIssuesStoreType.MODULE
| EIssuesStoreType.CYCLE
| EIssuesStoreType.PROJECT_VIEW
| EIssuesStoreType.TEAM
| EIssuesStoreType.TEAM_VIEW
| EIssuesStoreType.EPIC;
interface IBaseCalendarRoot {
@@ -32,13 +34,14 @@ interface IBaseCalendarRoot {
isCompletedCycle?: boolean;
viewId?: string | undefined;
isEpic?: boolean;
canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
}
export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
const { QuickActions, addIssuesToView, isCompletedCycle = false, viewId, isEpic = false } = props;
const { QuickActions, addIssuesToView, isCompletedCycle = false, viewId, isEpic = false, canEditPropertiesBasedOnProject } = props;
// router
const { workspaceSlug, projectId } = useParams();
const { workspaceSlug } = useParams();
// hooks
const storeType = useIssueStoreType() as CalendarStoreType;
@@ -63,6 +66,8 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
EUserPermissionsLevel.PROJECT
);
const { enableInlineEditing } = issues?.viewFlags || {};
const displayFilters = issuesFilter.issueFilters?.displayFilters;
const groupedIssueIds = (issues.groupedIssueIds ?? {}) as TGroupedIssues;
@@ -71,9 +76,7 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
const { startDate, endDate } = issueCalendarView.getStartAndEndDate(layout) ?? {};
useEffect(() => {
startDate &&
endDate &&
layout &&
if (startDate && endDate && layout) {
fetchIssues(
"init-loader",
{
@@ -85,21 +88,23 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
},
viewId
);
}
}, [fetchIssues, storeType, startDate, endDate, layout, viewId]);
const handleDragAndDrop = async (
issueId: string | undefined,
issueProjectId: string | undefined,
sourceDate: string | undefined,
destinationDate: string | undefined
) => {
if (!issueId || !destinationDate || !sourceDate) return;
if (!issueId || !destinationDate || !sourceDate || !issueProjectId) return;
await handleDragDrop(
issueId,
sourceDate,
destinationDate,
workspaceSlug?.toString(),
projectId?.toString(),
issueProjectId,
updateIssue
).catch((err) => {
setToast({
@@ -127,6 +132,16 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
[issues?.getGroupIssueCount]
);
const canEditProperties = useCallback(
(projectId: string | undefined) => {
const isEditingAllowedBasedOnProject =
canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed;
return enableInlineEditing && isEditingAllowedBasedOnProject;
},
[canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed]
);
return (
<>
<div className="h-full w-full overflow-hidden bg-custom-background-100 pt-4">
@@ -147,7 +162,7 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
handleRemoveFromView={async () => removeIssueFromView && removeIssueFromView(issue.project_id, issue.id)}
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
handleRestore={async () => restoreIssue && restoreIssue(issue.project_id, issue.id)}
readOnly={!isEditingAllowed || isCompletedCycle}
readOnly={!canEditProperties(issue.project_id ?? undefined) || isCompletedCycle}
placements={placement}
/>
)}
@@ -156,9 +171,10 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
getGroupIssueCount={getGroupIssueCount}
addIssuesToView={addIssuesToView}
quickAddCallback={quickAddIssue}
readOnly={!isEditingAllowed || isCompletedCycle}
readOnly={isCompletedCycle}
updateFilters={updateFilters}
handleDragAndDrop={handleDragAndDrop}
canEditProperties={canEditProperties}
isEpic={isEpic}
/>
</div>

View File

@@ -26,10 +26,10 @@ import { EIssueFilterType, EIssueLayoutTypes, EIssuesStoreType } from "@/constan
import { cn } from "@/helpers/common.helper";
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
// hooks
import { useIssues, useUserPermissions } from "@/hooks/store";
import { useIssues } from "@/hooks/store";
import useSize from "@/hooks/use-window-size";
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
// store
import { IProjectEpicsFilter } from "@/plane-web/store/issue/epic";
import { ICycleIssuesFilter } from "@/store/issue/cycle";
import { ICalendarStore } from "@/store/issue/issue_calendar_view.store";
import { IModuleIssuesFilter } from "@/store/issue/module";
@@ -38,7 +38,6 @@ import { IProjectViewIssuesFilter } from "@/store/issue/project-views";
import { IssueLayoutHOC } from "../issue-layout-HOC";
import { TRenderQuickActions } from "../list/list-view-types";
import type { ICalendarWeek } from "./types";
import { IProjectEpicsFilter } from "@/plane-web/store/issue/epic";
type Props = {
issuesFilterStore:
@@ -59,6 +58,7 @@ type Props = {
quickActions: TRenderQuickActions;
handleDragAndDrop: (
issueId: string | undefined,
issueProjectId: string | undefined,
sourceDate: string | undefined,
destinationDate: string | undefined
) => Promise<void>;
@@ -69,6 +69,7 @@ type Props = {
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => Promise<void>;
canEditProperties: (projectId: string | undefined) => boolean;
isEpic?: boolean;
};
@@ -88,6 +89,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
getPaginationData,
getGroupIssueCount,
updateFilters,
canEditProperties,
readOnly = false,
isEpic = false,
} = props;
@@ -99,15 +101,10 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
const {
issues: { viewFlags },
} = useIssues(EIssuesStoreType.PROJECT);
const { allowPermissions } = useUserPermissions();
const [windowWidth] = useSize();
const { enableIssueCreation } = viewFlags || {};
const isEditingAllowed = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.PROJECT
);
const { enableIssueCreation, enableQuickAdd } = viewFlags || {};
const calendarPayload = issueCalendarView.calendarPayload;
@@ -171,12 +168,13 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
loadMoreIssues={loadMoreIssues}
getPaginationData={getPaginationData}
getGroupIssueCount={getGroupIssueCount}
enableQuickIssueCreate
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
enableQuickIssueCreate={enableQuickAdd}
disableIssueCreation={!enableIssueCreation}
quickActions={quickActions}
quickAddCallback={quickAddCallback}
addIssuesToView={addIssuesToView}
readOnly={readOnly}
canEditProperties={canEditProperties}
isEpic={isEpic}
/>
))}
@@ -194,12 +192,13 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
loadMoreIssues={loadMoreIssues}
getPaginationData={getPaginationData}
getGroupIssueCount={getGroupIssueCount}
enableQuickIssueCreate
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
enableQuickIssueCreate={enableQuickAdd}
disableIssueCreation={!enableIssueCreation}
quickActions={quickActions}
quickAddCallback={quickAddCallback}
addIssuesToView={addIssuesToView}
readOnly={readOnly}
canEditProperties={canEditProperties}
isEpic={isEpic}
/>
)}
@@ -219,11 +218,12 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
getPaginationData={getPaginationData}
getGroupIssueCount={getGroupIssueCount}
quickActions={quickActions}
enableQuickIssueCreate
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
enableQuickIssueCreate={enableQuickAdd}
disableIssueCreation={!enableIssueCreation}
quickAddCallback={quickAddCallback}
addIssuesToView={addIssuesToView}
readOnly={readOnly}
canEditProperties={canEditProperties}
isDragDisabled
isMobileView
isEpic={isEpic}
@@ -246,11 +246,12 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
loadMoreIssues={loadMoreIssues}
getPaginationData={getPaginationData}
getGroupIssueCount={getGroupIssueCount}
enableQuickIssueCreate
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
enableQuickIssueCreate={enableQuickAdd}
disableIssueCreation={!enableIssueCreation}
quickAddCallback={quickAddCallback}
addIssuesToView={addIssuesToView}
readOnly={readOnly}
canEditProperties={canEditProperties}
isDragDisabled
isMobileView
isEpic={isEpic}

View File

@@ -18,12 +18,12 @@ import { MONTHS_LIST } from "@/constants/calendar";
import { cn } from "@/helpers/common.helper";
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
// types
import { IProjectEpicsFilter } from "@/plane-web/store/issue/epic";
import { ICycleIssuesFilter } from "@/store/issue/cycle";
import { IModuleIssuesFilter } from "@/store/issue/module";
import { IProjectIssuesFilter } from "@/store/issue/project";
import { IProjectViewIssuesFilter } from "@/store/issue/project-views";
import { TRenderQuickActions } from "../list/list-view-types";
import { IProjectEpicsFilter } from "@/plane-web/store/issue/epic";
type Props = {
issuesFilterStore:
@@ -44,6 +44,7 @@ type Props = {
quickActions: TRenderQuickActions;
handleDragAndDrop: (
issueId: string | undefined,
issueProjectId: string | undefined,
sourceDate: string | undefined,
destinationDate: string | undefined
) => Promise<void>;
@@ -51,6 +52,7 @@ type Props = {
readOnly?: boolean;
selectedDate: Date;
setSelectedDate: (date: Date) => void;
canEditProperties: (projectId: string | undefined) => boolean;
isEpic?: boolean;
};
@@ -72,6 +74,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
selectedDate,
handleDragAndDrop,
setSelectedDate,
canEditProperties,
isEpic = false,
} = props;
@@ -119,7 +122,12 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
}
}
handleDragAndDrop(sourceData?.id, sourceData?.date, destinationData?.date);
handleDragAndDrop(
sourceData?.id,
issueDetails?.project_id ?? undefined,
sourceData?.date,
destinationData?.date
);
highlightIssueOnDrop(source?.element?.id, false);
},
})
@@ -184,6 +192,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
enableQuickIssueCreate={enableQuickIssueCreate}
quickAddCallback={quickAddCallback}
readOnly={readOnly}
canEditProperties={canEditProperties}
isEpic={isEpic}
/>
</div>

View File

@@ -16,10 +16,11 @@ type Props = {
quickActions: TRenderQuickActions;
isDragDisabled: boolean;
isEpic?: boolean;
canEditProperties: (projectId: string | undefined) => boolean;
};
export const CalendarIssueBlockRoot: React.FC<Props> = observer((props) => {
const { issueId, quickActions, isDragDisabled, isEpic = false } = props;
const { issueId, quickActions, isDragDisabled, isEpic = false, canEditProperties } = props;
const issueRef = useRef<HTMLAnchorElement | null>(null);
const [isDragging, setIsDragging] = useState(false);
@@ -30,6 +31,8 @@ export const CalendarIssueBlockRoot: React.FC<Props> = observer((props) => {
const issue = getIssueById(issueId);
const canDrag = !isDragDisabled && canEditProperties(issue?.project_id ?? undefined);
useEffect(() => {
const element = issueRef.current;
@@ -38,7 +41,7 @@ export const CalendarIssueBlockRoot: React.FC<Props> = observer((props) => {
return combine(
draggable({
element,
canDrag: () => !isDragDisabled,
canDrag: () => canDrag,
getInitialData: () => ({ id: issue?.id, date: issue?.target_date }),
onDragStart: () => {
setIsDragging(true);
@@ -48,7 +51,7 @@ export const CalendarIssueBlockRoot: React.FC<Props> = observer((props) => {
},
})
);
}, [issueRef?.current, issue]);
}, [issueRef?.current, issue, canDrag]);
useOutsideClickDetector(issueRef, () => {
issueRef?.current?.classList?.remove(HIGHLIGHT_CLASS);

View File

@@ -22,6 +22,7 @@ type Props = {
addIssuesToView?: (issueIds: string[]) => Promise<any>;
readOnly?: boolean;
isMobileView?: boolean;
canEditProperties: (projectId: string | undefined) => boolean;
isEpic?: boolean;
};
@@ -38,6 +39,7 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
addIssuesToView,
readOnly,
isMobileView = false,
canEditProperties,
isEpic = false,
} = props;
const formattedDatePayload = renderFormattedPayloadDate(date);
@@ -65,6 +67,7 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
issueId={issueId}
quickActions={quickActions}
isDragDisabled={isDragDisabled || isMobileView}
canEditProperties={canEditProperties}
isEpic={isEpic}
/>
</div>

View File

@@ -1,7 +1,32 @@
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// hooks
import { ProjectIssueQuickActions } from "@/components/issues";
// hooks
import { useUserPermissions } from "@/hooks/store";
// plane web constants
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
// components
import { BaseCalendarRoot } from "../base-calendar-root";
export const CalendarLayout: React.FC = observer(() => <BaseCalendarRoot QuickActions={ProjectIssueQuickActions} />);
export const CalendarLayout: React.FC = observer(() => {
// router
const { workspaceSlug } = useParams();
// hooks
const { allowPermissions } = useUserPermissions();
// derived values
const canEditPropertiesBasedOnProject = (projectId: string) =>
allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.PROJECT,
workspaceSlug?.toString(),
projectId
);
return (
<BaseCalendarRoot
QuickActions={ProjectIssueQuickActions}
canEditPropertiesBasedOnProject={canEditPropertiesBasedOnProject}
/>
);
});

View File

@@ -5,13 +5,13 @@ import { CalendarDayTile } from "@/components/issues";
// helpers
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
// types
import { IProjectEpicsFilter } from "@/plane-web/store/issue/epic";
import { ICycleIssuesFilter } from "@/store/issue/cycle";
import { IModuleIssuesFilter } from "@/store/issue/module";
import { IProjectIssuesFilter } from "@/store/issue/project";
import { IProjectViewIssuesFilter } from "@/store/issue/project-views";
import { TRenderQuickActions } from "../list/list-view-types";
import { ICalendarDate, ICalendarWeek } from "./types";
import { IProjectEpicsFilter } from "@/plane-web/store/issue/epic";
type Props = {
issuesFilterStore:
@@ -32,6 +32,7 @@ type Props = {
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
handleDragAndDrop: (
issueId: string | undefined,
issueProjectId: string | undefined,
sourceDate: string | undefined,
destinationDate: string | undefined
) => Promise<void>;
@@ -39,6 +40,7 @@ type Props = {
readOnly?: boolean;
selectedDate: Date;
setSelectedDate: (date: Date) => void;
canEditProperties: (projectId: string | undefined) => boolean;
isEpic?: boolean;
};
@@ -60,6 +62,7 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
readOnly = false,
selectedDate,
setSelectedDate,
canEditProperties,
isEpic = false,
} = props;
@@ -96,6 +99,7 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
addIssuesToView={addIssuesToView}
readOnly={readOnly}
handleDragAndDrop={handleDragAndDrop}
canEditProperties={canEditProperties}
isEpic={isEpic}
/>
);

View File

@@ -134,6 +134,13 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
values={value}
/>
)}
{filterKey === "team_project" && (
<AppliedProjectFilters
editable={isEditingAllowed}
handleRemove={(val) => handleRemoveFilter("team_project", val)}
values={value}
/>
)}
{isEditingAllowed && (
<button
type="button"

View File

@@ -25,7 +25,7 @@ import { ILayoutDisplayFiltersOptions } from "@/constants/issue";
// hooks
import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web components
import { FilterIssueTypes } from "@/plane-web/components/issues";
import { FilterIssueTypes, FilterTeamProjects } from "@/plane-web/components/issues";
type Props = {
filters: IIssueFilterOptions;
@@ -208,6 +208,18 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
/>
</div>
)}
{/* team project */}
{isFilterEnabled("team_project") && (
<div className="py-2">
<FilterTeamProjects
appliedFilters={filters.team_project ?? null}
handleUpdate={(val) => handleFiltersUpdate("team_project", val)}
searchQuery={filtersSearchQuery}
/>
</div>
)}
{/* issue type */}
{isDisplayFilterEnabled("type") && displayFilters && handleDisplayFiltersUpdate && (
<div className="py-2">

View File

@@ -6,6 +6,7 @@ import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
import { observer } from "mobx-react";
import { useParams, usePathname } from "next/navigation";
import { EIssueServiceType } from "@plane/constants";
import { DeleteIssueModal } from "@/components/issues";
//constants
import { ISSUE_DELETED } from "@/constants/event-tracker";
@@ -25,7 +26,6 @@ import { IQuickActionProps, TRenderQuickActions } from "../list/list-view-types"
import { getSourceFromDropPayload } from "../utils";
import { KanBan } from "./default";
import { KanBanSwimLanes } from "./swimlanes";
import { EIssueServiceType } from "@plane/constants";
export type KanbanStoreType =
| EIssuesStoreType.PROJECT
@@ -34,6 +34,8 @@ export type KanbanStoreType =
| EIssuesStoreType.PROJECT_VIEW
| EIssuesStoreType.DRAFT
| EIssuesStoreType.PROFILE
| EIssuesStoreType.TEAM
| EIssuesStoreType.TEAM_VIEW
| EIssuesStoreType.EPIC;
export interface IBaseKanBanLayout {
@@ -187,11 +189,11 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
handleRemoveFromView={async () => removeIssueFromView && removeIssueFromView(issue.project_id, issue.id)}
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
handleRestore={async () => restoreIssue && restoreIssue(issue.project_id, issue.id)}
readOnly={!isEditingAllowed || isCompletedCycle}
readOnly={!canEditProperties(issue.project_id ?? undefined) || isCompletedCycle}
/>
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[isEditingAllowed, isCompletedCycle, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue]
[isCompletedCycle, canEditProperties, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue]
);
const handleDeleteIssue = async () => {

View File

@@ -36,6 +36,7 @@ interface IssueBlockProps {
displayProperties: IIssueDisplayProperties | undefined;
draggableId: string;
canDropOverIssue: boolean;
canDragIssuesInCurrentGrouping: boolean;
updateIssue: ((projectId: string | null, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
quickActions: TRenderQuickActions;
canEditProperties: (projectId: string | undefined) => boolean;
@@ -115,6 +116,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
issuesMap,
displayProperties,
canDropOverIssue,
canDragIssuesInCurrentGrouping,
updateIssue,
quickActions,
canEditProperties,
@@ -144,7 +146,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
const canEditIssueProperties = canEditProperties(issue?.project_id ?? undefined);
const isDragAllowed = !issue?.tempId && canEditIssueProperties;
const isDragAllowed = canDragIssuesInCurrentGrouping && !issue?.tempId && canEditIssueProperties;
useOutsideClickDetector(cardRef, () => {
cardRef?.current?.classList?.remove(HIGHLIGHT_CLASS);
@@ -200,12 +202,15 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
className={cn("group/kanban-block relative mb-2", { "z-[1]": isCurrentBlockDragging })}
onDragStart={() => {
if (isDragAllowed) setIsCurrentBlockDragging(true);
else
else {
setToast({
type: TOAST_TYPE.WARNING,
title: "Cannot move issue",
message: "Drag and drop is disabled for the current grouping",
message: !canEditIssueProperties
? "You are not allowed to move this issue"
: "Drag and drop is disabled for the current grouping",
});
}
}}
>
<ControlLink

View File

@@ -16,6 +16,7 @@ interface IssueBlocksListProps {
quickActions: TRenderQuickActions;
canEditProperties: (projectId: string | undefined) => boolean;
canDropOverIssue: boolean;
canDragIssuesInCurrentGrouping: boolean;
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
isEpic?: boolean;
}
@@ -28,6 +29,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = observer((p
issueIds,
displayProperties,
canDropOverIssue,
canDragIssuesInCurrentGrouping,
updateIssue,
quickActions,
canEditProperties,
@@ -59,6 +61,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = observer((p
quickActions={quickActions}
draggableId={draggableId}
canDropOverIssue={canDropOverIssue}
canDragIssuesInCurrentGrouping={canDragIssuesInCurrentGrouping}
canEditProperties={canEditProperties}
scrollableContainerRef={scrollableContainerRef}
isEpic={isEpic}

View File

@@ -20,7 +20,7 @@ import { KanbanQuickAddIssueButton, QuickAddIssueRoot } from "@/components/issue
import { highlightIssueOnDrop } from "@/components/issues/issue-layouts/utils";
import { KanbanIssueBlockLoader } from "@/components/ui";
// helpers
import { EIssueLayoutTypes } from "@/constants/issue";
import { DRAG_ALLOWED_GROUPS, EIssueLayoutTypes } from "@/constants/issue";
import { cn } from "@/helpers/common.helper";
// hooks
import { useProjectState } from "@/hooks/store";
@@ -260,6 +260,10 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
const shouldLoadMore = nextPageResults === undefined ? issueIds?.length < groupIssueCount : !!nextPageResults;
const canOverlayBeVisible = isWorkflowDropDisabled || orderBy !== "sort_order" || isDropDisabled;
const shouldOverlayBeVisible = isDraggingOverColumn && canOverlayBeVisible;
const canDragIssuesInCurrentGrouping =
!!group_by &&
DRAG_ALLOWED_GROUPS.includes(group_by) &&
(!!sub_group_by ? DRAG_ALLOWED_GROUPS.includes(sub_group_by) : true);
return (
<div
@@ -291,6 +295,7 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
canEditProperties={canEditProperties}
scrollableContainerRef={scrollableContainerRef}
canDropOverIssue={!canOverlayBeVisible}
canDragIssuesInCurrentGrouping={canDragIssuesInCurrentGrouping}
isEpic={isEpic}
/>

View File

@@ -1,9 +1,32 @@
import { observer } from "mobx-react";
// mobx store
import { ProjectIssueQuickActions } from "@/components/issues";
import { useParams } from "next/navigation";
// components
// types
// constants
import { ProjectIssueQuickActions } from "@/components/issues";
// hooks
import { useUserPermissions } from "@/hooks/store";
// plane web constants
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
// local components
import { BaseKanBanRoot } from "../base-kanban-root";
export const KanBanLayout: React.FC = observer(() => <BaseKanBanRoot QuickActions={ProjectIssueQuickActions} />);
export const KanBanLayout: React.FC = observer(() => {
// router
const { workspaceSlug } = useParams();
// hooks
const { allowPermissions } = useUserPermissions();
// derived values
const canEditPropertiesBasedOnProject = (projectId: string) =>
allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.PROJECT,
workspaceSlug?.toString(),
projectId
);
return (
<BaseKanBanRoot
QuickActions={ProjectIssueQuickActions}
canEditPropertiesBasedOnProject={canEditPropertiesBasedOnProject}
/>
);
});

View File

@@ -27,6 +27,8 @@ type ListStoreType =
| EIssuesStoreType.PROFILE
| EIssuesStoreType.ARCHIVED
| EIssuesStoreType.WORKSPACE_DRAFT
| EIssuesStoreType.TEAM
| EIssuesStoreType.TEAM_VIEW
| EIssuesStoreType.EPIC;
interface IBaseListRoot {
@@ -110,11 +112,11 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
handleRemoveFromView={async () => removeIssueFromView && removeIssueFromView(issue.project_id, issue.id)}
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
handleRestore={async () => restoreIssue && restoreIssue(issue.project_id, issue.id)}
readOnly={!isEditingAllowed || isCompletedCycle}
readOnly={!canEditProperties(issue.project_id ?? undefined) || isCompletedCycle}
/>
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[isEditingAllowed, isCompletedCycle, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue]
[isCompletedCycle, canEditProperties, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue]
);
const loadMoreIssues = useCallback(

View File

@@ -93,8 +93,11 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
isArchived: !!issue.archived_at,
});
// derived values
const issue = issuesMap[issueId];
const subIssuesCount = issue?.sub_issues_count ?? 0;
const canEditIssueProperties = canEditProperties(issue?.project_id ?? undefined);
const isDraggingAllowed = canDrag && canEditIssueProperties;
const { isMobile } = usePlatformOS();
@@ -106,7 +109,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
return combine(
draggable({
element,
canDrag: () => canDrag,
canDrag: () => isDraggingAllowed,
getInitialData: () => ({ id: issueId, type: "ISSUE", groupId }),
onDragStart: () => {
setIsCurrentBlockDragging(true);
@@ -116,11 +119,10 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
},
})
);
}, [canDrag, issueId, groupId, setIsCurrentBlockDragging]);
}, [isDraggingAllowed, issueId, groupId, setIsCurrentBlockDragging]);
if (!issue) return null;
const canEditIssueProperties = canEditProperties(issue.project_id ?? undefined);
const projectIdentifier = getProjectIdentifierById(issue.project_id);
const isIssueSelected = selectionHelpers.getIsEntitySelected(issue.id);
const isIssueActive = selectionHelpers.getIsEntityActive(issue.id);
@@ -169,11 +171,13 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
}
)}
onDragStart={() => {
if (!canDrag) {
if (!isDraggingAllowed) {
setToast({
type: TOAST_TYPE.WARNING,
title: "Cannot move issue",
message: "Drag and drop is disabled for the current grouping",
message: !canEditIssueProperties
? "You are not allowed to move this issue"
: "Drag and drop is disabled for the current grouping",
});
}
}}

View File

@@ -4,7 +4,6 @@ import { MutableRefObject, useEffect, useRef, useState } from "react";
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { cn } from "@plane/editor";
// plane packages
import {
@@ -100,7 +99,6 @@ export const ListGroup = observer((props: Props) => {
const isExpanded = !collapsedGroups?.group_by.includes(group.id);
const groupRef = useRef<HTMLDivElement | null>(null);
const { projectId } = useParams();
const projectState = useProjectState();
const {
@@ -239,8 +237,7 @@ export const ListGroup = observer((props: Props) => {
isWorkflowDropDisabled,
]);
const isDragAllowed =
!!group_by && DRAG_ALLOWED_GROUPS.includes(group_by) && canEditProperties(projectId?.toString());
const isDragAllowed = !!group_by && DRAG_ALLOWED_GROUPS.includes(group_by);
const canOverlayBeVisible = isWorkflowDropDisabled || orderBy !== "sort_order" || !!group.isDropDisabled;
const isGroupByCreatedBy = group_by === "created_by";

View File

@@ -1,20 +1,22 @@
import { FC } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// hooks
import { ProjectIssueQuickActions } from "@/components/issues";
// components
// types
// constants
import { ProjectIssueQuickActions } from "@/components/issues";
// hooks
import { useUserPermissions } from "@/hooks/store";
// plane web constants
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
// components
import { BaseListRoot } from "../base-list-root";
export const ListLayout: FC = observer(() => {
const { workspaceSlug, projectId } = useParams();
// router
const { workspaceSlug } = useParams();
// hooks
const { allowPermissions } = useUserPermissions();
if (!workspaceSlug || !projectId) return null;
if (!workspaceSlug) return null;
const canEditPropertiesBasedOnProject = (projectId: string) =>
allowPermissions(

View File

@@ -56,7 +56,12 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
const stateDetails = getStateById(issue.state_id);
// auth
const isEditingAllowed =
allowPermissions([EUserPermissions.ADMIN, EUserPermissions.MEMBER], EUserPermissionsLevel.PROJECT) && !readOnly;
allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.PROJECT,
workspaceSlug?.toString(),
issue.project_id ?? undefined
) && !readOnly;
const isArchivingAllowed = handleArchive && isEditingAllowed;
const isInArchivableGroup = !!stateDetails && ARCHIVABLE_STATE_GROUPS.includes(stateDetails?.group);
const isDeletingAllowed = isEditingAllowed;

View File

@@ -25,6 +25,8 @@ export type SpreadsheetStoreType =
| EIssuesStoreType.MODULE
| EIssuesStoreType.CYCLE
| EIssuesStoreType.PROJECT_VIEW
| EIssuesStoreType.TEAM
| EIssuesStoreType.TEAM_VIEW
| EIssuesStoreType.EPIC;
interface IBaseSpreadsheetRoot {
@@ -102,11 +104,11 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
handleRestore={async () => restoreIssue && restoreIssue(issue.project_id, issue.id)}
portalElement={portalElement}
readOnly={!isEditingAllowed || isCompletedCycle}
readOnly={!canEditProperties(issue.project_id ?? undefined) || isCompletedCycle}
placements={placement}
/>
),
[isEditingAllowed, isCompletedCycle, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue]
[isCompletedCycle, canEditProperties, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue]
);
if (!Array.isArray(issueIds)) return null;

View File

@@ -1,9 +1,33 @@
import React from "react";
import { observer } from "mobx-react";
// mobx store
import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
import { useParams } from "next/navigation";
// components
import { ProjectIssueQuickActions } from "@/components/issues";
// hooks
import { useUserPermissions } from "@/hooks/store";
// plane web constants
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
// local components
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
export const ProjectSpreadsheetLayout: React.FC = observer(() => (
<BaseSpreadsheetRoot QuickActions={ProjectIssueQuickActions} />
));
export const ProjectSpreadsheetLayout: React.FC = observer(() => {
// router
const { workspaceSlug } = useParams();
// hooks
const { allowPermissions } = useUserPermissions();
// derived values
const canEditPropertiesBasedOnProject = (projectId: string) =>
allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.PROJECT,
workspaceSlug?.toString(),
projectId
);
return (
<BaseSpreadsheetRoot
QuickActions={ProjectIssueQuickActions}
canEditPropertiesBasedOnProject={canEditPropertiesBasedOnProject}
/>
);
});

View File

@@ -1,6 +1,8 @@
"use-client";
import { FC, useEffect } from "react";
import { observer } from "mobx-react";
// types
import { TNameDescriptionLoader } from "@plane/types";
// components
import { IssueParentDetail, TIssueOperations } from "@/components/issues";
// helpers
@@ -25,8 +27,8 @@ interface IPeekOverviewIssueDetails {
issueOperations: TIssueOperations;
disabled: boolean;
isArchived: boolean;
isSubmitting: "submitting" | "submitted" | "saved";
setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void;
isSubmitting: TNameDescriptionLoader;
setIsSubmitting: (value: TNameDescriptionLoader) => void;
}
export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = observer((props) => {

View File

@@ -1,6 +1,9 @@
import { FC, useRef, useState } from "react";
import { observer } from "mobx-react";
import { EIssueServiceType } from "@plane/constants";
// types
import { TNameDescriptionLoader } from "@plane/types";
// components
import {
DeleteIssueModal,
IssuePeekOverviewHeader,
@@ -50,7 +53,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
} = props;
// states
const [peekMode, setPeekMode] = useState<TPeekModes>("side-peek");
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
const [isSubmitting, setIsSubmitting] = useState<TNameDescriptionLoader>("saved");
// ref
const issuePeekOverviewRef = useRef<HTMLDivElement>(null);
// store hooks

View File

@@ -2,6 +2,7 @@
import { FC, useState, useEffect, useCallback } from "react";
import { observer } from "mobx-react";
import { TNameDescriptionLoader } from "@plane/types";
// components
import { TextArea } from "@plane/ui";
// types
@@ -14,8 +15,8 @@ export type IssueTitleInputProps = {
disabled?: boolean;
value: string | undefined | null;
workspaceSlug: string;
isSubmitting: "submitting" | "submitted" | "saved";
setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void;
isSubmitting: TNameDescriptionLoader;
setIsSubmitting: (value: TNameDescriptionLoader) => void;
issueOperations: TIssueOperations;
projectId: string;
issueId: string;

View File

@@ -61,7 +61,7 @@ export const EmailNotificationForm: FC<IEmailNotificationFormProps> = (props) =>
<div className="grow">
<div className="pb-1 text-base font-medium text-custom-text-100">Property changes</div>
<div className="text-sm font-normal text-custom-text-300">
Notify me when issue's properties like assignees, priority, estimates or anything else changes.
Notify me when issue&apos;s properties like assignees, priority, estimates or anything else changes.
</div>
</div>
<div className="shrink-0">

View File

@@ -19,7 +19,7 @@ type DNDStoreType =
| EIssuesStoreType.ARCHIVED
| EIssuesStoreType.WORKSPACE_DRAFT
| EIssuesStoreType.TEAM
| EIssuesStoreType.TEAM_VIEW;
| EIssuesStoreType.TEAM_VIEW
| EIssuesStoreType.EPIC;
export const useGroupIssuesDragNDrop = (