[WEB-2848] improvement: enhanced components modularity (#6196)

* improvement: enhanced componenets modularity

* fix: lint errors resolved
This commit is contained in:
Prateek Shourya 2024-12-13 14:26:26 +05:30 committed by GitHub
parent ab11e83535
commit 9234f21f26
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 240 additions and 77 deletions

View file

@ -23,20 +23,23 @@ export type CalendarStoreType =
| EIssuesStoreType.PROJECT
| EIssuesStoreType.MODULE
| EIssuesStoreType.CYCLE
| EIssuesStoreType.PROJECT_VIEW;
| EIssuesStoreType.PROJECT_VIEW
| EIssuesStoreType.TEAM
| EIssuesStoreType.TEAM_VIEW;
interface IBaseCalendarRoot {
QuickActions: FC<IQuickActionProps>;
addIssuesToView?: (issueIds: string[]) => Promise<any>;
isCompletedCycle?: boolean;
viewId?: string | undefined;
canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
}
export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
const { QuickActions, addIssuesToView, isCompletedCycle = false, viewId } = props;
const { QuickActions, addIssuesToView, isCompletedCycle = false, viewId, canEditPropertiesBasedOnProject } = props;
// router
const { workspaceSlug, projectId } = useParams();
const { workspaceSlug } = useParams();
// hooks
const storeType = useIssueStoreType() as CalendarStoreType;
@ -61,6 +64,8 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
EUserPermissionsLevel.PROJECT
);
const { enableInlineEditing } = issues?.viewFlags || {};
const displayFilters = issuesFilter.issueFilters?.displayFilters;
const groupedIssueIds = (issues.groupedIssueIds ?? {}) as TGroupedIssues;
@ -69,9 +74,7 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
const { startDate, endDate } = issueCalendarView.getStartAndEndDate(layout) ?? {};
useEffect(() => {
startDate &&
endDate &&
layout &&
if (startDate && endDate && layout) {
fetchIssues(
"init-loader",
{
@ -83,21 +86,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({
@ -125,6 +130,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">
@ -145,7 +160,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}
/>
)}
@ -154,9 +169,10 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
getGroupIssueCount={getGroupIssueCount}
addIssuesToView={addIssuesToView}
quickAddCallback={quickAddIssue}
readOnly={!isEditingAllowed || isCompletedCycle}
readOnly={isCompletedCycle}
updateFilters={updateFilters}
handleDragAndDrop={handleDragAndDrop}
canEditProperties={canEditProperties}
/>
</div>
</>

View file

@ -26,9 +26,8 @@ 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 { ICycleIssuesFilter } from "@/store/issue/cycle";
import { ICalendarStore } from "@/store/issue/issue_calendar_view.store";
@ -53,6 +52,7 @@ type Props = {
quickActions: TRenderQuickActions;
handleDragAndDrop: (
issueId: string | undefined,
issueProjectId: string | undefined,
sourceDate: string | undefined,
destinationDate: string | undefined
) => Promise<void>;
@ -63,6 +63,7 @@ type Props = {
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => Promise<void>;
canEditProperties: (projectId: string | undefined) => boolean;
};
export const CalendarChart: React.FC<Props> = observer((props) => {
@ -81,6 +82,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
getPaginationData,
getGroupIssueCount,
updateFilters,
canEditProperties,
readOnly = false,
} = props;
// states
@ -91,15 +93,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;
@ -163,12 +160,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}
/>
))}
</div>
@ -185,12 +183,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}
/>
)}
</div>
@ -209,11 +208,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
/>
@ -235,11 +235,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
/>

View file

@ -38,6 +38,7 @@ type Props = {
quickActions: TRenderQuickActions;
handleDragAndDrop: (
issueId: string | undefined,
issueProjectId: string | undefined,
sourceDate: string | undefined,
destinationDate: string | undefined
) => Promise<void>;
@ -45,6 +46,7 @@ type Props = {
readOnly?: boolean;
selectedDate: Date;
setSelectedDate: (date: Date) => void;
canEditProperties: (projectId: string | undefined) => boolean;
};
export const CalendarDayTile: React.FC<Props> = observer((props) => {
@ -65,6 +67,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
selectedDate,
handleDragAndDrop,
setSelectedDate,
canEditProperties,
} = props;
const [isDraggingOver, setIsDraggingOver] = useState(false);
@ -111,7 +114,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);
},
})
@ -176,6 +184,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
enableQuickIssueCreate={enableQuickIssueCreate}
quickAddCallback={quickAddCallback}
readOnly={readOnly}
canEditProperties={canEditProperties}
/>
</div>
</div>

View file

@ -15,10 +15,11 @@ type Props = {
issueId: string;
quickActions: TRenderQuickActions;
isDragDisabled: boolean;
canEditProperties: (projectId: string | undefined) => boolean;
};
export const CalendarIssueBlockRoot: React.FC<Props> = observer((props) => {
const { issueId, quickActions, isDragDisabled } = props;
const { issueId, quickActions, isDragDisabled, canEditProperties } = props;
const issueRef = useRef<HTMLAnchorElement | null>(null);
const [isDragging, setIsDragging] = useState(false);
@ -29,6 +30,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;
@ -37,7 +40,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);
@ -47,7 +50,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;
};
export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
@ -37,6 +38,7 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
addIssuesToView,
readOnly,
isMobileView = false,
canEditProperties,
} = props;
const formattedDatePayload = renderFormattedPayloadDate(date);
@ -63,6 +65,7 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
issueId={issueId}
quickActions={quickActions}
isDragDisabled={isDragDisabled || isMobileView}
canEditProperties={canEditProperties}
/>
</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

@ -26,6 +26,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>;
@ -33,6 +34,7 @@ type Props = {
readOnly?: boolean;
selectedDate: Date;
setSelectedDate: (date: Date) => void;
canEditProperties: (projectId: string | undefined) => boolean;
};
export const CalendarWeekDays: React.FC<Props> = observer((props) => {
@ -53,6 +55,7 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
readOnly = false,
selectedDate,
setSelectedDate,
canEditProperties,
} = props;
const calendarLayout = issuesFilterStore?.issueFilters?.displayFilters?.calendar?.layout ?? "month";
@ -88,6 +91,7 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
addIssuesToView={addIssuesToView}
readOnly={readOnly}
handleDragAndDrop={handleDragAndDrop}
canEditProperties={canEditProperties}
/>
);
})}

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

@ -32,7 +32,10 @@ export type KanbanStoreType =
| EIssuesStoreType.CYCLE
| EIssuesStoreType.PROJECT_VIEW
| EIssuesStoreType.DRAFT
| EIssuesStoreType.PROFILE;
| EIssuesStoreType.PROFILE
| EIssuesStoreType.TEAM
| EIssuesStoreType.TEAM_VIEW;
export interface IBaseKanBanLayout {
QuickActions: FC<IQuickActionProps>;
addIssuesToView?: (issueIds: string[]) => Promise<any>;
@ -176,11 +179,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

@ -35,6 +35,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;
@ -111,6 +112,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
issuesMap,
displayProperties,
canDropOverIssue,
canDragIssuesInCurrentGrouping,
updateIssue,
quickActions,
canEditProperties,
@ -139,7 +141,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);
@ -195,12 +197,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>;
}
@ -27,6 +28,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = observer((p
issueIds,
displayProperties,
canDropOverIssue,
canDragIssuesInCurrentGrouping,
updateIssue,
quickActions,
canEditProperties,
@ -57,6 +59,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = observer((p
quickActions={quickActions}
draggableId={draggableId}
canDropOverIssue={canDropOverIssue}
canDragIssuesInCurrentGrouping={canDragIssuesInCurrentGrouping}
canEditProperties={canEditProperties}
scrollableContainerRef={scrollableContainerRef}
/>

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";
@ -258,6 +258,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
@ -289,6 +293,7 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
canEditProperties={canEditProperties}
scrollableContainerRef={scrollableContainerRef}
canDropOverIssue={!canOverlayBeVisible}
canDragIssuesInCurrentGrouping={canDragIssuesInCurrentGrouping}
/>
{shouldLoadMore && (isSubGroup ? <>{loadMore}</> : <KanbanIssueBlockLoader ref={setIntersectionElement} />)}

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

@ -26,7 +26,10 @@ type ListStoreType =
| EIssuesStoreType.DRAFT
| EIssuesStoreType.PROFILE
| EIssuesStoreType.ARCHIVED
| EIssuesStoreType.WORKSPACE_DRAFT;
| EIssuesStoreType.WORKSPACE_DRAFT
| EIssuesStoreType.TEAM
| EIssuesStoreType.TEAM_VIEW;
interface IBaseListRoot {
QuickActions: FC<IQuickActionProps>;
addIssuesToView?: (issueIds: string[]) => Promise<any>;
@ -100,11 +103,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

@ -85,8 +85,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();
@ -98,7 +101,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
return combine(
draggable({
element,
canDrag: () => canDrag,
canDrag: () => isDraggingAllowed,
getInitialData: () => ({ id: issueId, type: "ISSUE", groupId }),
onDragStart: () => {
setIsCurrentBlockDragging(true);
@ -108,11 +111,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);
@ -161,11 +163,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 {
@ -98,7 +97,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 {
@ -237,8 +235,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

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

@ -24,7 +24,10 @@ export type SpreadsheetStoreType =
| EIssuesStoreType.PROJECT
| EIssuesStoreType.MODULE
| EIssuesStoreType.CYCLE
| EIssuesStoreType.PROJECT_VIEW;
| EIssuesStoreType.PROJECT_VIEW
| EIssuesStoreType.TEAM
| EIssuesStoreType.TEAM_VIEW;
interface IBaseSpreadsheetRoot {
QuickActions: FC<IQuickActionProps>;
canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
@ -99,11 +102,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,5 +1,8 @@
import { FC, useRef, useState } from "react";
import { observer } from "mobx-react";
// types
import { TNameDescriptionLoader } from "@plane/types";
// components
import {
DeleteIssueModal,
IssuePeekOverviewHeader,
@ -49,7 +52,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">