[WEB-2848] improvement: enhanced components modularity (#6196)
* improvement: enhanced componenets modularity * fix: lint errors resolved
This commit is contained in:
parent
ab11e83535
commit
9234f21f26
25 changed files with 240 additions and 77 deletions
|
|
@ -23,20 +23,23 @@ export type CalendarStoreType =
|
||||||
| EIssuesStoreType.PROJECT
|
| EIssuesStoreType.PROJECT
|
||||||
| EIssuesStoreType.MODULE
|
| EIssuesStoreType.MODULE
|
||||||
| EIssuesStoreType.CYCLE
|
| EIssuesStoreType.CYCLE
|
||||||
| EIssuesStoreType.PROJECT_VIEW;
|
| EIssuesStoreType.PROJECT_VIEW
|
||||||
|
| EIssuesStoreType.TEAM
|
||||||
|
| EIssuesStoreType.TEAM_VIEW;
|
||||||
|
|
||||||
interface IBaseCalendarRoot {
|
interface IBaseCalendarRoot {
|
||||||
QuickActions: FC<IQuickActionProps>;
|
QuickActions: FC<IQuickActionProps>;
|
||||||
addIssuesToView?: (issueIds: string[]) => Promise<any>;
|
addIssuesToView?: (issueIds: string[]) => Promise<any>;
|
||||||
isCompletedCycle?: boolean;
|
isCompletedCycle?: boolean;
|
||||||
viewId?: string | undefined;
|
viewId?: string | undefined;
|
||||||
|
canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
||||||
const { QuickActions, addIssuesToView, isCompletedCycle = false, viewId } = props;
|
const { QuickActions, addIssuesToView, isCompletedCycle = false, viewId, canEditPropertiesBasedOnProject } = props;
|
||||||
|
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug, projectId } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
const storeType = useIssueStoreType() as CalendarStoreType;
|
const storeType = useIssueStoreType() as CalendarStoreType;
|
||||||
|
|
@ -61,6 +64,8 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
||||||
EUserPermissionsLevel.PROJECT
|
EUserPermissionsLevel.PROJECT
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { enableInlineEditing } = issues?.viewFlags || {};
|
||||||
|
|
||||||
const displayFilters = issuesFilter.issueFilters?.displayFilters;
|
const displayFilters = issuesFilter.issueFilters?.displayFilters;
|
||||||
|
|
||||||
const groupedIssueIds = (issues.groupedIssueIds ?? {}) as TGroupedIssues;
|
const groupedIssueIds = (issues.groupedIssueIds ?? {}) as TGroupedIssues;
|
||||||
|
|
@ -69,9 +74,7 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
||||||
const { startDate, endDate } = issueCalendarView.getStartAndEndDate(layout) ?? {};
|
const { startDate, endDate } = issueCalendarView.getStartAndEndDate(layout) ?? {};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
startDate &&
|
if (startDate && endDate && layout) {
|
||||||
endDate &&
|
|
||||||
layout &&
|
|
||||||
fetchIssues(
|
fetchIssues(
|
||||||
"init-loader",
|
"init-loader",
|
||||||
{
|
{
|
||||||
|
|
@ -83,21 +86,23 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
||||||
},
|
},
|
||||||
viewId
|
viewId
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}, [fetchIssues, storeType, startDate, endDate, layout, viewId]);
|
}, [fetchIssues, storeType, startDate, endDate, layout, viewId]);
|
||||||
|
|
||||||
const handleDragAndDrop = async (
|
const handleDragAndDrop = async (
|
||||||
issueId: string | undefined,
|
issueId: string | undefined,
|
||||||
|
issueProjectId: string | undefined,
|
||||||
sourceDate: string | undefined,
|
sourceDate: string | undefined,
|
||||||
destinationDate: string | undefined
|
destinationDate: string | undefined
|
||||||
) => {
|
) => {
|
||||||
if (!issueId || !destinationDate || !sourceDate) return;
|
if (!issueId || !destinationDate || !sourceDate || !issueProjectId) return;
|
||||||
|
|
||||||
await handleDragDrop(
|
await handleDragDrop(
|
||||||
issueId,
|
issueId,
|
||||||
sourceDate,
|
sourceDate,
|
||||||
destinationDate,
|
destinationDate,
|
||||||
workspaceSlug?.toString(),
|
workspaceSlug?.toString(),
|
||||||
projectId?.toString(),
|
issueProjectId,
|
||||||
updateIssue
|
updateIssue
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
setToast({
|
setToast({
|
||||||
|
|
@ -125,6 +130,16 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
||||||
[issues?.getGroupIssueCount]
|
[issues?.getGroupIssueCount]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const canEditProperties = useCallback(
|
||||||
|
(projectId: string | undefined) => {
|
||||||
|
const isEditingAllowedBasedOnProject =
|
||||||
|
canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed;
|
||||||
|
|
||||||
|
return enableInlineEditing && isEditingAllowedBasedOnProject;
|
||||||
|
},
|
||||||
|
[canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="h-full w-full overflow-hidden bg-custom-background-100 pt-4">
|
<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)}
|
handleRemoveFromView={async () => removeIssueFromView && removeIssueFromView(issue.project_id, issue.id)}
|
||||||
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
|
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
|
||||||
handleRestore={async () => restoreIssue && restoreIssue(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}
|
placements={placement}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -154,9 +169,10 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
||||||
getGroupIssueCount={getGroupIssueCount}
|
getGroupIssueCount={getGroupIssueCount}
|
||||||
addIssuesToView={addIssuesToView}
|
addIssuesToView={addIssuesToView}
|
||||||
quickAddCallback={quickAddIssue}
|
quickAddCallback={quickAddIssue}
|
||||||
readOnly={!isEditingAllowed || isCompletedCycle}
|
readOnly={isCompletedCycle}
|
||||||
updateFilters={updateFilters}
|
updateFilters={updateFilters}
|
||||||
handleDragAndDrop={handleDragAndDrop}
|
handleDragAndDrop={handleDragAndDrop}
|
||||||
|
canEditProperties={canEditProperties}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,8 @@ import { EIssueFilterType, EIssueLayoutTypes, EIssuesStoreType } from "@/constan
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssues, useUserPermissions } from "@/hooks/store";
|
import { useIssues } from "@/hooks/store";
|
||||||
import useSize from "@/hooks/use-window-size";
|
import useSize from "@/hooks/use-window-size";
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
// store
|
// store
|
||||||
import { ICycleIssuesFilter } from "@/store/issue/cycle";
|
import { ICycleIssuesFilter } from "@/store/issue/cycle";
|
||||||
import { ICalendarStore } from "@/store/issue/issue_calendar_view.store";
|
import { ICalendarStore } from "@/store/issue/issue_calendar_view.store";
|
||||||
|
|
@ -53,6 +52,7 @@ type Props = {
|
||||||
quickActions: TRenderQuickActions;
|
quickActions: TRenderQuickActions;
|
||||||
handleDragAndDrop: (
|
handleDragAndDrop: (
|
||||||
issueId: string | undefined,
|
issueId: string | undefined,
|
||||||
|
issueProjectId: string | undefined,
|
||||||
sourceDate: string | undefined,
|
sourceDate: string | undefined,
|
||||||
destinationDate: string | undefined
|
destinationDate: string | undefined
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
|
@ -63,6 +63,7 @@ type Props = {
|
||||||
filterType: EIssueFilterType,
|
filterType: EIssueFilterType,
|
||||||
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
|
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CalendarChart: React.FC<Props> = observer((props) => {
|
export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||||
|
|
@ -81,6 +82,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||||
getPaginationData,
|
getPaginationData,
|
||||||
getGroupIssueCount,
|
getGroupIssueCount,
|
||||||
updateFilters,
|
updateFilters,
|
||||||
|
canEditProperties,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
} = props;
|
} = props;
|
||||||
// states
|
// states
|
||||||
|
|
@ -91,15 +93,10 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
issues: { viewFlags },
|
issues: { viewFlags },
|
||||||
} = useIssues(EIssuesStoreType.PROJECT);
|
} = useIssues(EIssuesStoreType.PROJECT);
|
||||||
const { allowPermissions } = useUserPermissions();
|
|
||||||
|
|
||||||
const [windowWidth] = useSize();
|
const [windowWidth] = useSize();
|
||||||
|
|
||||||
const { enableIssueCreation } = viewFlags || {};
|
const { enableIssueCreation, enableQuickAdd } = viewFlags || {};
|
||||||
const isEditingAllowed = allowPermissions(
|
|
||||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
|
||||||
EUserPermissionsLevel.PROJECT
|
|
||||||
);
|
|
||||||
|
|
||||||
const calendarPayload = issueCalendarView.calendarPayload;
|
const calendarPayload = issueCalendarView.calendarPayload;
|
||||||
|
|
||||||
|
|
@ -163,12 +160,13 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||||
loadMoreIssues={loadMoreIssues}
|
loadMoreIssues={loadMoreIssues}
|
||||||
getPaginationData={getPaginationData}
|
getPaginationData={getPaginationData}
|
||||||
getGroupIssueCount={getGroupIssueCount}
|
getGroupIssueCount={getGroupIssueCount}
|
||||||
enableQuickIssueCreate
|
enableQuickIssueCreate={enableQuickAdd}
|
||||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
disableIssueCreation={!enableIssueCreation}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
quickAddCallback={quickAddCallback}
|
quickAddCallback={quickAddCallback}
|
||||||
addIssuesToView={addIssuesToView}
|
addIssuesToView={addIssuesToView}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
|
canEditProperties={canEditProperties}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -185,12 +183,13 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||||
loadMoreIssues={loadMoreIssues}
|
loadMoreIssues={loadMoreIssues}
|
||||||
getPaginationData={getPaginationData}
|
getPaginationData={getPaginationData}
|
||||||
getGroupIssueCount={getGroupIssueCount}
|
getGroupIssueCount={getGroupIssueCount}
|
||||||
enableQuickIssueCreate
|
enableQuickIssueCreate={enableQuickAdd}
|
||||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
disableIssueCreation={!enableIssueCreation}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
quickAddCallback={quickAddCallback}
|
quickAddCallback={quickAddCallback}
|
||||||
addIssuesToView={addIssuesToView}
|
addIssuesToView={addIssuesToView}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
|
canEditProperties={canEditProperties}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -209,11 +208,12 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||||
getPaginationData={getPaginationData}
|
getPaginationData={getPaginationData}
|
||||||
getGroupIssueCount={getGroupIssueCount}
|
getGroupIssueCount={getGroupIssueCount}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
enableQuickIssueCreate
|
enableQuickIssueCreate={enableQuickAdd}
|
||||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
disableIssueCreation={!enableIssueCreation}
|
||||||
quickAddCallback={quickAddCallback}
|
quickAddCallback={quickAddCallback}
|
||||||
addIssuesToView={addIssuesToView}
|
addIssuesToView={addIssuesToView}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
|
canEditProperties={canEditProperties}
|
||||||
isDragDisabled
|
isDragDisabled
|
||||||
isMobileView
|
isMobileView
|
||||||
/>
|
/>
|
||||||
|
|
@ -235,11 +235,12 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||||
loadMoreIssues={loadMoreIssues}
|
loadMoreIssues={loadMoreIssues}
|
||||||
getPaginationData={getPaginationData}
|
getPaginationData={getPaginationData}
|
||||||
getGroupIssueCount={getGroupIssueCount}
|
getGroupIssueCount={getGroupIssueCount}
|
||||||
enableQuickIssueCreate
|
enableQuickIssueCreate={enableQuickAdd}
|
||||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
disableIssueCreation={!enableIssueCreation}
|
||||||
quickAddCallback={quickAddCallback}
|
quickAddCallback={quickAddCallback}
|
||||||
addIssuesToView={addIssuesToView}
|
addIssuesToView={addIssuesToView}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
|
canEditProperties={canEditProperties}
|
||||||
isDragDisabled
|
isDragDisabled
|
||||||
isMobileView
|
isMobileView
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ type Props = {
|
||||||
quickActions: TRenderQuickActions;
|
quickActions: TRenderQuickActions;
|
||||||
handleDragAndDrop: (
|
handleDragAndDrop: (
|
||||||
issueId: string | undefined,
|
issueId: string | undefined,
|
||||||
|
issueProjectId: string | undefined,
|
||||||
sourceDate: string | undefined,
|
sourceDate: string | undefined,
|
||||||
destinationDate: string | undefined
|
destinationDate: string | undefined
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
|
@ -45,6 +46,7 @@ type Props = {
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
selectedDate: Date;
|
selectedDate: Date;
|
||||||
setSelectedDate: (date: Date) => void;
|
setSelectedDate: (date: Date) => void;
|
||||||
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
||||||
|
|
@ -65,6 +67,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
||||||
selectedDate,
|
selectedDate,
|
||||||
handleDragAndDrop,
|
handleDragAndDrop,
|
||||||
setSelectedDate,
|
setSelectedDate,
|
||||||
|
canEditProperties,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [isDraggingOver, setIsDraggingOver] = useState(false);
|
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);
|
highlightIssueOnDrop(source?.element?.id, false);
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -176,6 +184,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||||
quickAddCallback={quickAddCallback}
|
quickAddCallback={quickAddCallback}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
|
canEditProperties={canEditProperties}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,11 @@ type Props = {
|
||||||
issueId: string;
|
issueId: string;
|
||||||
quickActions: TRenderQuickActions;
|
quickActions: TRenderQuickActions;
|
||||||
isDragDisabled: boolean;
|
isDragDisabled: boolean;
|
||||||
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CalendarIssueBlockRoot: React.FC<Props> = observer((props) => {
|
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 issueRef = useRef<HTMLAnchorElement | null>(null);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
|
@ -29,6 +30,8 @@ export const CalendarIssueBlockRoot: React.FC<Props> = observer((props) => {
|
||||||
|
|
||||||
const issue = getIssueById(issueId);
|
const issue = getIssueById(issueId);
|
||||||
|
|
||||||
|
const canDrag = !isDragDisabled && canEditProperties(issue?.project_id ?? undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = issueRef.current;
|
const element = issueRef.current;
|
||||||
|
|
||||||
|
|
@ -37,7 +40,7 @@ export const CalendarIssueBlockRoot: React.FC<Props> = observer((props) => {
|
||||||
return combine(
|
return combine(
|
||||||
draggable({
|
draggable({
|
||||||
element,
|
element,
|
||||||
canDrag: () => !isDragDisabled,
|
canDrag: () => canDrag,
|
||||||
getInitialData: () => ({ id: issue?.id, date: issue?.target_date }),
|
getInitialData: () => ({ id: issue?.id, date: issue?.target_date }),
|
||||||
onDragStart: () => {
|
onDragStart: () => {
|
||||||
setIsDragging(true);
|
setIsDragging(true);
|
||||||
|
|
@ -47,7 +50,7 @@ export const CalendarIssueBlockRoot: React.FC<Props> = observer((props) => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}, [issueRef?.current, issue]);
|
}, [issueRef?.current, issue, canDrag]);
|
||||||
|
|
||||||
useOutsideClickDetector(issueRef, () => {
|
useOutsideClickDetector(issueRef, () => {
|
||||||
issueRef?.current?.classList?.remove(HIGHLIGHT_CLASS);
|
issueRef?.current?.classList?.remove(HIGHLIGHT_CLASS);
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ type Props = {
|
||||||
addIssuesToView?: (issueIds: string[]) => Promise<any>;
|
addIssuesToView?: (issueIds: string[]) => Promise<any>;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
isMobileView?: boolean;
|
isMobileView?: boolean;
|
||||||
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||||
|
|
@ -37,6 +38,7 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||||
addIssuesToView,
|
addIssuesToView,
|
||||||
readOnly,
|
readOnly,
|
||||||
isMobileView = false,
|
isMobileView = false,
|
||||||
|
canEditProperties,
|
||||||
} = props;
|
} = props;
|
||||||
const formattedDatePayload = renderFormattedPayloadDate(date);
|
const formattedDatePayload = renderFormattedPayloadDate(date);
|
||||||
|
|
||||||
|
|
@ -63,6 +65,7 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||||
issueId={issueId}
|
issueId={issueId}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
isDragDisabled={isDragDisabled || isMobileView}
|
isDragDisabled={isDragDisabled || isMobileView}
|
||||||
|
canEditProperties={canEditProperties}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,32 @@
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
// hooks
|
// hooks
|
||||||
import { ProjectIssueQuickActions } from "@/components/issues";
|
import { ProjectIssueQuickActions } from "@/components/issues";
|
||||||
|
// hooks
|
||||||
|
import { useUserPermissions } from "@/hooks/store";
|
||||||
|
// plane web constants
|
||||||
|
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||||
// components
|
// components
|
||||||
import { BaseCalendarRoot } from "../base-calendar-root";
|
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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ type Props = {
|
||||||
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
|
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
|
||||||
handleDragAndDrop: (
|
handleDragAndDrop: (
|
||||||
issueId: string | undefined,
|
issueId: string | undefined,
|
||||||
|
issueProjectId: string | undefined,
|
||||||
sourceDate: string | undefined,
|
sourceDate: string | undefined,
|
||||||
destinationDate: string | undefined
|
destinationDate: string | undefined
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
|
@ -33,6 +34,7 @@ type Props = {
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
selectedDate: Date;
|
selectedDate: Date;
|
||||||
setSelectedDate: (date: Date) => void;
|
setSelectedDate: (date: Date) => void;
|
||||||
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CalendarWeekDays: React.FC<Props> = observer((props) => {
|
export const CalendarWeekDays: React.FC<Props> = observer((props) => {
|
||||||
|
|
@ -53,6 +55,7 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
selectedDate,
|
selectedDate,
|
||||||
setSelectedDate,
|
setSelectedDate,
|
||||||
|
canEditProperties,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const calendarLayout = issuesFilterStore?.issueFilters?.displayFilters?.calendar?.layout ?? "month";
|
const calendarLayout = issuesFilterStore?.issueFilters?.displayFilters?.calendar?.layout ?? "month";
|
||||||
|
|
@ -88,6 +91,7 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
|
||||||
addIssuesToView={addIssuesToView}
|
addIssuesToView={addIssuesToView}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
handleDragAndDrop={handleDragAndDrop}
|
handleDragAndDrop={handleDragAndDrop}
|
||||||
|
canEditProperties={canEditProperties}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,13 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
||||||
values={value}
|
values={value}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{filterKey === "team_project" && (
|
||||||
|
<AppliedProjectFilters
|
||||||
|
editable={isEditingAllowed}
|
||||||
|
handleRemove={(val) => handleRemoveFilter("team_project", val)}
|
||||||
|
values={value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{isEditingAllowed && (
|
{isEditingAllowed && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import { ILayoutDisplayFiltersOptions } from "@/constants/issue";
|
||||||
// hooks
|
// hooks
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// plane web components
|
// plane web components
|
||||||
import { FilterIssueTypes } from "@/plane-web/components/issues";
|
import { FilterIssueTypes, FilterTeamProjects } from "@/plane-web/components/issues";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
filters: IIssueFilterOptions;
|
filters: IIssueFilterOptions;
|
||||||
|
|
@ -208,6 +208,18 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 */}
|
{/* issue type */}
|
||||||
{isDisplayFilterEnabled("type") && displayFilters && handleDisplayFiltersUpdate && (
|
{isDisplayFilterEnabled("type") && displayFilters && handleDisplayFiltersUpdate && (
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,10 @@ export type KanbanStoreType =
|
||||||
| EIssuesStoreType.CYCLE
|
| EIssuesStoreType.CYCLE
|
||||||
| EIssuesStoreType.PROJECT_VIEW
|
| EIssuesStoreType.PROJECT_VIEW
|
||||||
| EIssuesStoreType.DRAFT
|
| EIssuesStoreType.DRAFT
|
||||||
| EIssuesStoreType.PROFILE;
|
| EIssuesStoreType.PROFILE
|
||||||
|
| EIssuesStoreType.TEAM
|
||||||
|
| EIssuesStoreType.TEAM_VIEW;
|
||||||
|
|
||||||
export interface IBaseKanBanLayout {
|
export interface IBaseKanBanLayout {
|
||||||
QuickActions: FC<IQuickActionProps>;
|
QuickActions: FC<IQuickActionProps>;
|
||||||
addIssuesToView?: (issueIds: string[]) => Promise<any>;
|
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)}
|
handleRemoveFromView={async () => removeIssueFromView && removeIssueFromView(issue.project_id, issue.id)}
|
||||||
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
|
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
|
||||||
handleRestore={async () => restoreIssue && restoreIssue(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
|
// 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 () => {
|
const handleDeleteIssue = async () => {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ interface IssueBlockProps {
|
||||||
displayProperties: IIssueDisplayProperties | undefined;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
draggableId: string;
|
draggableId: string;
|
||||||
canDropOverIssue: boolean;
|
canDropOverIssue: boolean;
|
||||||
|
canDragIssuesInCurrentGrouping: boolean;
|
||||||
updateIssue: ((projectId: string | null, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue: ((projectId: string | null, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
||||||
quickActions: TRenderQuickActions;
|
quickActions: TRenderQuickActions;
|
||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
|
|
@ -111,6 +112,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
||||||
issuesMap,
|
issuesMap,
|
||||||
displayProperties,
|
displayProperties,
|
||||||
canDropOverIssue,
|
canDropOverIssue,
|
||||||
|
canDragIssuesInCurrentGrouping,
|
||||||
updateIssue,
|
updateIssue,
|
||||||
quickActions,
|
quickActions,
|
||||||
canEditProperties,
|
canEditProperties,
|
||||||
|
|
@ -139,7 +141,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
||||||
|
|
||||||
const canEditIssueProperties = canEditProperties(issue?.project_id ?? undefined);
|
const canEditIssueProperties = canEditProperties(issue?.project_id ?? undefined);
|
||||||
|
|
||||||
const isDragAllowed = !issue?.tempId && canEditIssueProperties;
|
const isDragAllowed = canDragIssuesInCurrentGrouping && !issue?.tempId && canEditIssueProperties;
|
||||||
|
|
||||||
useOutsideClickDetector(cardRef, () => {
|
useOutsideClickDetector(cardRef, () => {
|
||||||
cardRef?.current?.classList?.remove(HIGHLIGHT_CLASS);
|
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 })}
|
className={cn("group/kanban-block relative mb-2", { "z-[1]": isCurrentBlockDragging })}
|
||||||
onDragStart={() => {
|
onDragStart={() => {
|
||||||
if (isDragAllowed) setIsCurrentBlockDragging(true);
|
if (isDragAllowed) setIsCurrentBlockDragging(true);
|
||||||
else
|
else {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.WARNING,
|
type: TOAST_TYPE.WARNING,
|
||||||
title: "Cannot move issue",
|
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
|
<ControlLink
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ interface IssueBlocksListProps {
|
||||||
quickActions: TRenderQuickActions;
|
quickActions: TRenderQuickActions;
|
||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
canDropOverIssue: boolean;
|
canDropOverIssue: boolean;
|
||||||
|
canDragIssuesInCurrentGrouping: boolean;
|
||||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,6 +28,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = observer((p
|
||||||
issueIds,
|
issueIds,
|
||||||
displayProperties,
|
displayProperties,
|
||||||
canDropOverIssue,
|
canDropOverIssue,
|
||||||
|
canDragIssuesInCurrentGrouping,
|
||||||
updateIssue,
|
updateIssue,
|
||||||
quickActions,
|
quickActions,
|
||||||
canEditProperties,
|
canEditProperties,
|
||||||
|
|
@ -57,6 +59,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = observer((p
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
draggableId={draggableId}
|
draggableId={draggableId}
|
||||||
canDropOverIssue={canDropOverIssue}
|
canDropOverIssue={canDropOverIssue}
|
||||||
|
canDragIssuesInCurrentGrouping={canDragIssuesInCurrentGrouping}
|
||||||
canEditProperties={canEditProperties}
|
canEditProperties={canEditProperties}
|
||||||
scrollableContainerRef={scrollableContainerRef}
|
scrollableContainerRef={scrollableContainerRef}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import { KanbanQuickAddIssueButton, QuickAddIssueRoot } from "@/components/issue
|
||||||
import { highlightIssueOnDrop } from "@/components/issues/issue-layouts/utils";
|
import { highlightIssueOnDrop } from "@/components/issues/issue-layouts/utils";
|
||||||
import { KanbanIssueBlockLoader } from "@/components/ui";
|
import { KanbanIssueBlockLoader } from "@/components/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { EIssueLayoutTypes } from "@/constants/issue";
|
import { DRAG_ALLOWED_GROUPS, EIssueLayoutTypes } from "@/constants/issue";
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProjectState } from "@/hooks/store";
|
import { useProjectState } from "@/hooks/store";
|
||||||
|
|
@ -258,6 +258,10 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
||||||
const shouldLoadMore = nextPageResults === undefined ? issueIds?.length < groupIssueCount : !!nextPageResults;
|
const shouldLoadMore = nextPageResults === undefined ? issueIds?.length < groupIssueCount : !!nextPageResults;
|
||||||
const canOverlayBeVisible = isWorkflowDropDisabled || orderBy !== "sort_order" || isDropDisabled;
|
const canOverlayBeVisible = isWorkflowDropDisabled || orderBy !== "sort_order" || isDropDisabled;
|
||||||
const shouldOverlayBeVisible = isDraggingOverColumn && canOverlayBeVisible;
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -289,6 +293,7 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
||||||
canEditProperties={canEditProperties}
|
canEditProperties={canEditProperties}
|
||||||
scrollableContainerRef={scrollableContainerRef}
|
scrollableContainerRef={scrollableContainerRef}
|
||||||
canDropOverIssue={!canOverlayBeVisible}
|
canDropOverIssue={!canOverlayBeVisible}
|
||||||
|
canDragIssuesInCurrentGrouping={canDragIssuesInCurrentGrouping}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{shouldLoadMore && (isSubGroup ? <>{loadMore}</> : <KanbanIssueBlockLoader ref={setIntersectionElement} />)}
|
{shouldLoadMore && (isSubGroup ? <>{loadMore}</> : <KanbanIssueBlockLoader ref={setIntersectionElement} />)}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,32 @@
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// mobx store
|
import { useParams } from "next/navigation";
|
||||||
import { ProjectIssueQuickActions } from "@/components/issues";
|
|
||||||
// components
|
// components
|
||||||
// types
|
import { ProjectIssueQuickActions } from "@/components/issues";
|
||||||
// constants
|
// 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";
|
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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,10 @@ type ListStoreType =
|
||||||
| EIssuesStoreType.DRAFT
|
| EIssuesStoreType.DRAFT
|
||||||
| EIssuesStoreType.PROFILE
|
| EIssuesStoreType.PROFILE
|
||||||
| EIssuesStoreType.ARCHIVED
|
| EIssuesStoreType.ARCHIVED
|
||||||
| EIssuesStoreType.WORKSPACE_DRAFT;
|
| EIssuesStoreType.WORKSPACE_DRAFT
|
||||||
|
| EIssuesStoreType.TEAM
|
||||||
|
| EIssuesStoreType.TEAM_VIEW;
|
||||||
|
|
||||||
interface IBaseListRoot {
|
interface IBaseListRoot {
|
||||||
QuickActions: FC<IQuickActionProps>;
|
QuickActions: FC<IQuickActionProps>;
|
||||||
addIssuesToView?: (issueIds: string[]) => Promise<any>;
|
addIssuesToView?: (issueIds: string[]) => Promise<any>;
|
||||||
|
|
@ -100,11 +103,11 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||||
handleRemoveFromView={async () => removeIssueFromView && removeIssueFromView(issue.project_id, issue.id)}
|
handleRemoveFromView={async () => removeIssueFromView && removeIssueFromView(issue.project_id, issue.id)}
|
||||||
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
|
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
|
||||||
handleRestore={async () => restoreIssue && restoreIssue(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
|
// 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(
|
const loadMoreIssues = useCallback(
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,11 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
|
||||||
isArchived: !!issue.archived_at,
|
isArchived: !!issue.archived_at,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// derived values
|
||||||
const issue = issuesMap[issueId];
|
const issue = issuesMap[issueId];
|
||||||
const subIssuesCount = issue?.sub_issues_count ?? 0;
|
const subIssuesCount = issue?.sub_issues_count ?? 0;
|
||||||
|
const canEditIssueProperties = canEditProperties(issue?.project_id ?? undefined);
|
||||||
|
const isDraggingAllowed = canDrag && canEditIssueProperties;
|
||||||
|
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
|
|
||||||
|
|
@ -98,7 +101,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
|
||||||
return combine(
|
return combine(
|
||||||
draggable({
|
draggable({
|
||||||
element,
|
element,
|
||||||
canDrag: () => canDrag,
|
canDrag: () => isDraggingAllowed,
|
||||||
getInitialData: () => ({ id: issueId, type: "ISSUE", groupId }),
|
getInitialData: () => ({ id: issueId, type: "ISSUE", groupId }),
|
||||||
onDragStart: () => {
|
onDragStart: () => {
|
||||||
setIsCurrentBlockDragging(true);
|
setIsCurrentBlockDragging(true);
|
||||||
|
|
@ -108,11 +111,10 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}, [canDrag, issueId, groupId, setIsCurrentBlockDragging]);
|
}, [isDraggingAllowed, issueId, groupId, setIsCurrentBlockDragging]);
|
||||||
|
|
||||||
if (!issue) return null;
|
if (!issue) return null;
|
||||||
|
|
||||||
const canEditIssueProperties = canEditProperties(issue.project_id ?? undefined);
|
|
||||||
const projectIdentifier = getProjectIdentifierById(issue.project_id);
|
const projectIdentifier = getProjectIdentifierById(issue.project_id);
|
||||||
const isIssueSelected = selectionHelpers.getIsEntitySelected(issue.id);
|
const isIssueSelected = selectionHelpers.getIsEntitySelected(issue.id);
|
||||||
const isIssueActive = selectionHelpers.getIsEntityActive(issue.id);
|
const isIssueActive = selectionHelpers.getIsEntityActive(issue.id);
|
||||||
|
|
@ -161,11 +163,13 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
onDragStart={() => {
|
onDragStart={() => {
|
||||||
if (!canDrag) {
|
if (!isDraggingAllowed) {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.WARNING,
|
type: TOAST_TYPE.WARNING,
|
||||||
title: "Cannot move issue",
|
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",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { MutableRefObject, useEffect, useRef, useState } from "react";
|
||||||
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
||||||
import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
|
||||||
import { cn } from "@plane/editor";
|
import { cn } from "@plane/editor";
|
||||||
// plane packages
|
// plane packages
|
||||||
import {
|
import {
|
||||||
|
|
@ -98,7 +97,6 @@ export const ListGroup = observer((props: Props) => {
|
||||||
const isExpanded = !collapsedGroups?.group_by.includes(group.id);
|
const isExpanded = !collapsedGroups?.group_by.includes(group.id);
|
||||||
const groupRef = useRef<HTMLDivElement | null>(null);
|
const groupRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const { projectId } = useParams();
|
|
||||||
const projectState = useProjectState();
|
const projectState = useProjectState();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -237,8 +235,7 @@ export const ListGroup = observer((props: Props) => {
|
||||||
isWorkflowDropDisabled,
|
isWorkflowDropDisabled,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const isDragAllowed =
|
const isDragAllowed = !!group_by && DRAG_ALLOWED_GROUPS.includes(group_by);
|
||||||
!!group_by && DRAG_ALLOWED_GROUPS.includes(group_by) && canEditProperties(projectId?.toString());
|
|
||||||
const canOverlayBeVisible = isWorkflowDropDisabled || orderBy !== "sort_order" || !!group.isDropDisabled;
|
const canOverlayBeVisible = isWorkflowDropDisabled || orderBy !== "sort_order" || !!group.isDropDisabled;
|
||||||
|
|
||||||
const isGroupByCreatedBy = group_by === "created_by";
|
const isGroupByCreatedBy = group_by === "created_by";
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,22 @@
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// hooks
|
|
||||||
import { ProjectIssueQuickActions } from "@/components/issues";
|
|
||||||
// components
|
// components
|
||||||
// types
|
import { ProjectIssueQuickActions } from "@/components/issues";
|
||||||
// constants
|
// hooks
|
||||||
import { useUserPermissions } from "@/hooks/store";
|
import { useUserPermissions } from "@/hooks/store";
|
||||||
|
// plane web constants
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||||
|
// components
|
||||||
import { BaseListRoot } from "../base-list-root";
|
import { BaseListRoot } from "../base-list-root";
|
||||||
|
|
||||||
export const ListLayout: FC = observer(() => {
|
export const ListLayout: FC = observer(() => {
|
||||||
const { workspaceSlug, projectId } = useParams();
|
// router
|
||||||
|
const { workspaceSlug } = useParams();
|
||||||
|
// hooks
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
|
||||||
if (!workspaceSlug || !projectId) return null;
|
if (!workspaceSlug) return null;
|
||||||
|
|
||||||
const canEditPropertiesBasedOnProject = (projectId: string) =>
|
const canEditPropertiesBasedOnProject = (projectId: string) =>
|
||||||
allowPermissions(
|
allowPermissions(
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,12 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
||||||
const stateDetails = getStateById(issue.state_id);
|
const stateDetails = getStateById(issue.state_id);
|
||||||
// auth
|
// auth
|
||||||
const isEditingAllowed =
|
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 isArchivingAllowed = handleArchive && isEditingAllowed;
|
||||||
const isInArchivableGroup = !!stateDetails && ARCHIVABLE_STATE_GROUPS.includes(stateDetails?.group);
|
const isInArchivableGroup = !!stateDetails && ARCHIVABLE_STATE_GROUPS.includes(stateDetails?.group);
|
||||||
const isDeletingAllowed = isEditingAllowed;
|
const isDeletingAllowed = isEditingAllowed;
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,10 @@ export type SpreadsheetStoreType =
|
||||||
| EIssuesStoreType.PROJECT
|
| EIssuesStoreType.PROJECT
|
||||||
| EIssuesStoreType.MODULE
|
| EIssuesStoreType.MODULE
|
||||||
| EIssuesStoreType.CYCLE
|
| EIssuesStoreType.CYCLE
|
||||||
| EIssuesStoreType.PROJECT_VIEW;
|
| EIssuesStoreType.PROJECT_VIEW
|
||||||
|
| EIssuesStoreType.TEAM
|
||||||
|
| EIssuesStoreType.TEAM_VIEW;
|
||||||
|
|
||||||
interface IBaseSpreadsheetRoot {
|
interface IBaseSpreadsheetRoot {
|
||||||
QuickActions: FC<IQuickActionProps>;
|
QuickActions: FC<IQuickActionProps>;
|
||||||
canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
|
canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
|
||||||
|
|
@ -99,11 +102,11 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
||||||
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
|
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
|
||||||
handleRestore={async () => restoreIssue && restoreIssue(issue.project_id, issue.id)}
|
handleRestore={async () => restoreIssue && restoreIssue(issue.project_id, issue.id)}
|
||||||
portalElement={portalElement}
|
portalElement={portalElement}
|
||||||
readOnly={!isEditingAllowed || isCompletedCycle}
|
readOnly={!canEditProperties(issue.project_id ?? undefined) || isCompletedCycle}
|
||||||
placements={placement}
|
placements={placement}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[isEditingAllowed, isCompletedCycle, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue]
|
[isCompletedCycle, canEditProperties, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!Array.isArray(issueIds)) return null;
|
if (!Array.isArray(issueIds)) return null;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,33 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// mobx store
|
import { useParams } from "next/navigation";
|
||||||
import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
|
// 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";
|
import { BaseSpreadsheetRoot } from "../base-spreadsheet-root";
|
||||||
|
|
||||||
export const ProjectSpreadsheetLayout: React.FC = observer(() => (
|
export const ProjectSpreadsheetLayout: React.FC = observer(() => {
|
||||||
<BaseSpreadsheetRoot QuickActions={ProjectIssueQuickActions} />
|
// 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
"use-client";
|
"use-client";
|
||||||
import { FC, useEffect } from "react";
|
import { FC, useEffect } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
// types
|
||||||
|
import { TNameDescriptionLoader } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { IssueParentDetail, TIssueOperations } from "@/components/issues";
|
import { IssueParentDetail, TIssueOperations } from "@/components/issues";
|
||||||
// helpers
|
// helpers
|
||||||
|
|
@ -25,8 +27,8 @@ interface IPeekOverviewIssueDetails {
|
||||||
issueOperations: TIssueOperations;
|
issueOperations: TIssueOperations;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
isArchived: boolean;
|
isArchived: boolean;
|
||||||
isSubmitting: "submitting" | "submitted" | "saved";
|
isSubmitting: TNameDescriptionLoader;
|
||||||
setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void;
|
setIsSubmitting: (value: TNameDescriptionLoader) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = observer((props) => {
|
export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = observer((props) => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
import { FC, useRef, useState } from "react";
|
import { FC, useRef, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
// types
|
||||||
|
import { TNameDescriptionLoader } from "@plane/types";
|
||||||
|
// components
|
||||||
import {
|
import {
|
||||||
DeleteIssueModal,
|
DeleteIssueModal,
|
||||||
IssuePeekOverviewHeader,
|
IssuePeekOverviewHeader,
|
||||||
|
|
@ -49,7 +52,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||||
} = props;
|
} = props;
|
||||||
// states
|
// states
|
||||||
const [peekMode, setPeekMode] = useState<TPeekModes>("side-peek");
|
const [peekMode, setPeekMode] = useState<TPeekModes>("side-peek");
|
||||||
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
|
const [isSubmitting, setIsSubmitting] = useState<TNameDescriptionLoader>("saved");
|
||||||
// ref
|
// ref
|
||||||
const issuePeekOverviewRef = useRef<HTMLDivElement>(null);
|
const issuePeekOverviewRef = useRef<HTMLDivElement>(null);
|
||||||
// store hooks
|
// store hooks
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { FC, useState, useEffect, useCallback } from "react";
|
import { FC, useState, useEffect, useCallback } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
import { TNameDescriptionLoader } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { TextArea } from "@plane/ui";
|
import { TextArea } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
|
|
@ -14,8 +15,8 @@ export type IssueTitleInputProps = {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
value: string | undefined | null;
|
value: string | undefined | null;
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
isSubmitting: "submitting" | "submitted" | "saved";
|
isSubmitting: TNameDescriptionLoader;
|
||||||
setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void;
|
setIsSubmitting: (value: TNameDescriptionLoader) => void;
|
||||||
issueOperations: TIssueOperations;
|
issueOperations: TIssueOperations;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
issueId: string;
|
issueId: string;
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ export const EmailNotificationForm: FC<IEmailNotificationFormProps> = (props) =>
|
||||||
<div className="grow">
|
<div className="grow">
|
||||||
<div className="pb-1 text-base font-medium text-custom-text-100">Property changes</div>
|
<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">
|
<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's properties like assignees, priority, estimates or anything else changes.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="shrink-0">
|
<div className="shrink-0">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue