[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.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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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} />)}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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's properties like assignees, priority, estimates or anything else changes.
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue