[WEB-4321]chore: workspace views refactor (#7214)
* chore: workspace views reafactor * chore: resolved coderabbit suggestions * chore: added project level workspace filter * chore: added enum for roles * chore: removed redundant type definition * chore: optimised the query --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
parent
8988cf9a85
commit
64fd0b2830
24 changed files with 381 additions and 172 deletions
|
|
@ -944,9 +944,33 @@ class IssueDetailEndpoint(BaseAPIView):
|
||||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||||
def get(self, request, slug, project_id):
|
def get(self, request, slug, project_id):
|
||||||
filters = issue_filters(request.query_params, "GET")
|
filters = issue_filters(request.query_params, "GET")
|
||||||
|
|
||||||
|
# check for the project member role, if the role is 5 then check for the guest_view_all_features
|
||||||
|
# if it is true then show all the issues else show only the issues created by the user
|
||||||
|
project_member_subquery = ProjectMember.objects.filter(
|
||||||
|
project_id=OuterRef("project_id"),
|
||||||
|
member=self.request.user,
|
||||||
|
is_active=True,
|
||||||
|
).filter(
|
||||||
|
Q(role__gt=ROLE.GUEST.value)
|
||||||
|
| Q(
|
||||||
|
role=ROLE.GUEST.value, project__guest_view_all_features=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Main issue query
|
||||||
issue = (
|
issue = (
|
||||||
Issue.issue_objects.filter(workspace__slug=slug, project_id=project_id)
|
Issue.issue_objects.filter(workspace__slug=slug, project_id=project_id)
|
||||||
.select_related("workspace", "project", "state", "parent")
|
.filter(
|
||||||
|
Q(Exists(project_member_subquery))
|
||||||
|
| Q(
|
||||||
|
project__project_projectmember__member=self.request.user,
|
||||||
|
project__project_projectmember__is_active=True,
|
||||||
|
project__project_projectmember__role=ROLE.GUEST.value,
|
||||||
|
project__guest_view_all_features=False,
|
||||||
|
created_by=self.request.user,
|
||||||
|
)
|
||||||
|
)
|
||||||
.prefetch_related("assignees", "labels", "issue_module__module")
|
.prefetch_related("assignees", "labels", "issue_module__module")
|
||||||
.annotate(
|
.annotate(
|
||||||
cycle_id=Subquery(
|
cycle_id=Subquery(
|
||||||
|
|
@ -1014,6 +1038,7 @@ class IssueDetailEndpoint(BaseAPIView):
|
||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
issue = issue.filter(**filters)
|
issue = issue.filter(**filters)
|
||||||
order_by_param = request.GET.get("order_by", "-created_at")
|
order_by_param = request.GET.get("order_by", "-created_at")
|
||||||
# Issue queryset
|
# Issue queryset
|
||||||
|
|
|
||||||
2
packages/types/src/layout/gantt.d.ts
vendored
2
packages/types/src/layout/gantt.d.ts
vendored
|
|
@ -9,6 +9,7 @@ export interface IGanttBlock {
|
||||||
sort_order: number | undefined;
|
sort_order: number | undefined;
|
||||||
start_date: string | undefined;
|
start_date: string | undefined;
|
||||||
target_date: string | undefined;
|
target_date: string | undefined;
|
||||||
|
project_id: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBlockUpdateData {
|
export interface IBlockUpdateData {
|
||||||
|
|
@ -25,6 +26,7 @@ export interface IBlockUpdateDependencyData {
|
||||||
id: string;
|
id: string;
|
||||||
start_date?: string;
|
start_date?: string;
|
||||||
target_date?: string;
|
target_date?: string;
|
||||||
|
project_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TGanttViews = "week" | "month" | "quarter";
|
export type TGanttViews = "week" | "month" | "quarter";
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,7 @@ export const getIssueBlocksStructure = (block: TIssue): IGanttBlock => ({
|
||||||
sort_order: block?.sort_order,
|
sort_order: block?.sort_order,
|
||||||
start_date: block?.start_date ?? undefined,
|
start_date: block?.start_date ?? undefined,
|
||||||
target_date: block?.target_date ?? undefined,
|
target_date: block?.target_date ?? undefined,
|
||||||
|
project_id: block?.project_id ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const formatTextList = (TextArray: string[]): string => {
|
export const formatTextList = (TextArray: string[]): string => {
|
||||||
|
|
@ -260,7 +261,7 @@ export const getComputedDisplayFilters = (
|
||||||
displayFilters: IIssueDisplayFilterOptions = {},
|
displayFilters: IIssueDisplayFilterOptions = {},
|
||||||
defaultValues?: IIssueDisplayFilterOptions
|
defaultValues?: IIssueDisplayFilterOptions
|
||||||
): IIssueDisplayFilterOptions => {
|
): IIssueDisplayFilterOptions => {
|
||||||
const filters = displayFilters || defaultValues;
|
const filters = !isEmpty(displayFilters) ? displayFilters : defaultValues;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
calendar: {
|
calendar: {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { Layers } from "lucide-react";
|
import { Layers } from "lucide-react";
|
||||||
// plane constants
|
// plane constants
|
||||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
|
import { EIssueFilterType, EIssueLayoutTypes, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
// types
|
// types
|
||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||||
|
|
@ -19,6 +19,7 @@ import { CreateUpdateWorkspaceViewModal } from "@/components/workspace";
|
||||||
// helpers
|
// helpers
|
||||||
// hooks
|
// hooks
|
||||||
import { useLabel, useMember, useIssues, useGlobalView } from "@/hooks/store";
|
import { useLabel, useMember, useIssues, useGlobalView } from "@/hooks/store";
|
||||||
|
import { GlobalViewLayoutSelection } from "@/plane-web/components/views/helper";
|
||||||
|
|
||||||
export const GlobalIssuesHeader = observer(() => {
|
export const GlobalIssuesHeader = observer(() => {
|
||||||
// states
|
// states
|
||||||
|
|
@ -38,6 +39,7 @@ export const GlobalIssuesHeader = observer(() => {
|
||||||
|
|
||||||
const issueFilters = globalViewId ? filters[globalViewId.toString()] : undefined;
|
const issueFilters = globalViewId ? filters[globalViewId.toString()] : undefined;
|
||||||
|
|
||||||
|
const activeLayout = issueFilters?.displayFilters?.layout;
|
||||||
const viewDetails = getViewDetailsById(globalViewId.toString());
|
const viewDetails = getViewDetailsById(globalViewId.toString());
|
||||||
|
|
||||||
const handleFiltersUpdate = useCallback(
|
const handleFiltersUpdate = useCallback(
|
||||||
|
|
@ -95,8 +97,27 @@ export const GlobalIssuesHeader = observer(() => {
|
||||||
[workspaceSlug, updateFilters, globalViewId]
|
[workspaceSlug, updateFilters, globalViewId]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleLayoutChange = useCallback(
|
||||||
|
(layout: EIssueLayoutTypes) => {
|
||||||
|
if (!workspaceSlug || !globalViewId) return;
|
||||||
|
updateFilters(
|
||||||
|
workspaceSlug.toString(),
|
||||||
|
undefined,
|
||||||
|
EIssueFilterType.DISPLAY_FILTERS,
|
||||||
|
{ layout: layout },
|
||||||
|
globalViewId.toString()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[workspaceSlug, updateFilters, globalViewId]
|
||||||
|
);
|
||||||
|
|
||||||
const isLocked = viewDetails?.is_locked;
|
const isLocked = viewDetails?.is_locked;
|
||||||
|
|
||||||
|
const currentLayoutFilters = useMemo(() => {
|
||||||
|
const layout = activeLayout ?? EIssueLayoutTypes.SPREADSHEET;
|
||||||
|
return ISSUE_DISPLAY_FILTERS_BY_PAGE.my_issues[layout];
|
||||||
|
}, [activeLayout]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CreateUpdateWorkspaceViewModal isOpen={createViewModal} onClose={() => setCreateViewModal(false)} />
|
<CreateUpdateWorkspaceViewModal isOpen={createViewModal} onClose={() => setCreateViewModal(false)} />
|
||||||
|
|
@ -113,13 +134,18 @@ export const GlobalIssuesHeader = observer(() => {
|
||||||
<Header.RightItem>
|
<Header.RightItem>
|
||||||
{!isLocked ? (
|
{!isLocked ? (
|
||||||
<>
|
<>
|
||||||
|
<GlobalViewLayoutSelection
|
||||||
|
onChange={handleLayoutChange}
|
||||||
|
selectedLayout={activeLayout ?? EIssueLayoutTypes.SPREADSHEET}
|
||||||
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
|
/>
|
||||||
<FiltersDropdown
|
<FiltersDropdown
|
||||||
title={t("common.filters")}
|
title={t("common.filters")}
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
isFiltersApplied={isIssueFilterActive(issueFilters)}
|
isFiltersApplied={isIssueFilterActive(issueFilters)}
|
||||||
>
|
>
|
||||||
<FilterSelection
|
<FilterSelection
|
||||||
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_PAGE.my_issues.spreadsheet}
|
layoutDisplayFiltersOptions={currentLayoutFilters}
|
||||||
filters={issueFilters?.filters ?? {}}
|
filters={issueFilters?.filters ?? {}}
|
||||||
handleFiltersUpdate={handleFiltersUpdate}
|
handleFiltersUpdate={handleFiltersUpdate}
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
|
|
@ -130,7 +156,7 @@ export const GlobalIssuesHeader = observer(() => {
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
<FiltersDropdown title={t("common.display")} placement="bottom-end">
|
<FiltersDropdown title={t("common.display")} placement="bottom-end">
|
||||||
<DisplayFiltersSelection
|
<DisplayFiltersSelection
|
||||||
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_PAGE.my_issues.spreadsheet}
|
layoutDisplayFiltersOptions={currentLayoutFilters}
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||||
|
|
|
||||||
12
web/ce/components/views/helper.tsx
Normal file
12
web/ce/components/views/helper.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { EIssueLayoutTypes } from "@plane/constants";
|
||||||
|
import { TWorkspaceLayoutProps } from "@/components/views/helper";
|
||||||
|
|
||||||
|
export type TLayoutSelectionProps = {
|
||||||
|
onChange: (layout: EIssueLayoutTypes) => void;
|
||||||
|
selectedLayout: EIssueLayoutTypes;
|
||||||
|
workspaceSlug: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GlobalViewLayoutSelection = (props: TLayoutSelectionProps) => <></>;
|
||||||
|
|
||||||
|
export const WorkspaceAdditionalLayouts = (props: TWorkspaceLayoutProps) => <></>;
|
||||||
1
web/ce/store/issue/workspace/issue.store.ts
Normal file
1
web/ce/store/issue/workspace/issue.store.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "@/store/issue/workspace/issue.store";
|
||||||
|
|
@ -22,6 +22,7 @@ type BlockData = {
|
||||||
sort_order: number | null;
|
sort_order: number | null;
|
||||||
start_date?: string | undefined | null;
|
start_date?: string | undefined | null;
|
||||||
target_date?: string | undefined | null;
|
target_date?: string | undefined | null;
|
||||||
|
project_id?: string | undefined | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IBaseTimelineStore {
|
export interface IBaseTimelineStore {
|
||||||
|
|
@ -194,6 +195,7 @@ export class BaseTimeLineStore implements IBaseTimelineStore {
|
||||||
sort_order: blockData?.sort_order ?? undefined,
|
sort_order: blockData?.sort_order ?? undefined,
|
||||||
start_date: blockData?.start_date ?? undefined,
|
start_date: blockData?.start_date ?? undefined,
|
||||||
target_date: blockData?.target_date ?? undefined,
|
target_date: blockData?.target_date ?? undefined,
|
||||||
|
project_id: blockData?.project_id ?? undefined,
|
||||||
};
|
};
|
||||||
if (this.currentViewData && (this.currentViewData?.data?.startDate || this.currentViewData?.data?.dayWidth)) {
|
if (this.currentViewData && (this.currentViewData?.data?.startDate || this.currentViewData?.data?.dayWidth)) {
|
||||||
block.position = getItemPositionWidth(this.currentViewData, block);
|
block.position = getItemPositionWidth(this.currentViewData, block);
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ type Props = {
|
||||||
enableBlockLeftResize: boolean;
|
enableBlockLeftResize: boolean;
|
||||||
enableBlockRightResize: boolean;
|
enableBlockRightResize: boolean;
|
||||||
enableBlockMove: boolean;
|
enableBlockMove: boolean;
|
||||||
|
enableDependency: boolean;
|
||||||
ganttContainerRef: RefObject<HTMLDivElement>;
|
ganttContainerRef: RefObject<HTMLDivElement>;
|
||||||
updateBlockDates?: (updates: IBlockUpdateDependencyData[]) => Promise<void>;
|
updateBlockDates?: (updates: IBlockUpdateDependencyData[]) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
@ -33,6 +34,7 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
||||||
enableBlockRightResize,
|
enableBlockRightResize,
|
||||||
enableBlockMove,
|
enableBlockMove,
|
||||||
ganttContainerRef,
|
ganttContainerRef,
|
||||||
|
enableDependency,
|
||||||
updateBlockDates,
|
updateBlockDates,
|
||||||
} = props;
|
} = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
|
|
@ -90,6 +92,7 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
||||||
enableBlockLeftResize={enableBlockLeftResize}
|
enableBlockLeftResize={enableBlockLeftResize}
|
||||||
enableBlockRightResize={enableBlockRightResize}
|
enableBlockRightResize={enableBlockRightResize}
|
||||||
enableBlockMove={enableBlockMove && !!isBlockComplete}
|
enableBlockMove={enableBlockMove && !!isBlockComplete}
|
||||||
|
enableDependency={enableDependency}
|
||||||
isMoving={isMoving}
|
isMoving={isMoving}
|
||||||
ganttContainerRef={ganttContainerRef}
|
ganttContainerRef={ganttContainerRef}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ export type GanttChartBlocksProps = {
|
||||||
ganttContainerRef: React.RefObject<HTMLDivElement>;
|
ganttContainerRef: React.RefObject<HTMLDivElement>;
|
||||||
showAllBlocks: boolean;
|
showAllBlocks: boolean;
|
||||||
updateBlockDates?: (updates: IBlockUpdateDependencyData[]) => Promise<void>;
|
updateBlockDates?: (updates: IBlockUpdateDependencyData[]) => Promise<void>;
|
||||||
|
enableDependency: boolean | ((blockId: string) => boolean);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
|
export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
|
||||||
|
|
@ -24,6 +25,7 @@ export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
|
||||||
ganttContainerRef,
|
ganttContainerRef,
|
||||||
showAllBlocks,
|
showAllBlocks,
|
||||||
updateBlockDates,
|
updateBlockDates,
|
||||||
|
enableDependency,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -41,6 +43,7 @@ export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
|
||||||
typeof enableBlockRightResize === "function" ? enableBlockRightResize(blockId) : enableBlockRightResize
|
typeof enableBlockRightResize === "function" ? enableBlockRightResize(blockId) : enableBlockRightResize
|
||||||
}
|
}
|
||||||
enableBlockMove={typeof enableBlockMove === "function" ? enableBlockMove(blockId) : enableBlockMove}
|
enableBlockMove={typeof enableBlockMove === "function" ? enableBlockMove(blockId) : enableBlockMove}
|
||||||
|
enableDependency={typeof enableDependency === "function" ? enableDependency(blockId) : enableDependency}
|
||||||
ganttContainerRef={ganttContainerRef}
|
ganttContainerRef={ganttContainerRef}
|
||||||
updateBlockDates={updateBlockDates}
|
updateBlockDates={updateBlockDates}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ type Props = {
|
||||||
enableReorder: boolean | ((blockId: string) => boolean);
|
enableReorder: boolean | ((blockId: string) => boolean);
|
||||||
enableSelection: boolean | ((blockId: string) => boolean);
|
enableSelection: boolean | ((blockId: string) => boolean);
|
||||||
enableAddBlock: boolean | ((blockId: string) => boolean);
|
enableAddBlock: boolean | ((blockId: string) => boolean);
|
||||||
|
enableDependency: boolean | ((blockId: string) => boolean);
|
||||||
itemsContainerWidth: number;
|
itemsContainerWidth: number;
|
||||||
showAllBlocks: boolean;
|
showAllBlocks: boolean;
|
||||||
sidebarToRender: (props: any) => React.ReactNode;
|
sidebarToRender: (props: any) => React.ReactNode;
|
||||||
|
|
@ -67,6 +68,7 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
||||||
enableReorder,
|
enableReorder,
|
||||||
enableAddBlock,
|
enableAddBlock,
|
||||||
enableSelection,
|
enableSelection,
|
||||||
|
enableDependency,
|
||||||
itemsContainerWidth,
|
itemsContainerWidth,
|
||||||
showAllBlocks,
|
showAllBlocks,
|
||||||
sidebarToRender,
|
sidebarToRender,
|
||||||
|
|
@ -215,6 +217,7 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
||||||
enableBlockRightResize={enableBlockRightResize}
|
enableBlockRightResize={enableBlockRightResize}
|
||||||
enableBlockMove={enableBlockMove}
|
enableBlockMove={enableBlockMove}
|
||||||
ganttContainerRef={ganttContainerRef}
|
ganttContainerRef={ganttContainerRef}
|
||||||
|
enableDependency={enableDependency}
|
||||||
showAllBlocks={showAllBlocks}
|
showAllBlocks={showAllBlocks}
|
||||||
updateBlockDates={updateBlockDates}
|
updateBlockDates={updateBlockDates}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ type ChartViewRootProps = {
|
||||||
enableReorder: boolean | ((blockId: string) => boolean);
|
enableReorder: boolean | ((blockId: string) => boolean);
|
||||||
enableAddBlock: boolean | ((blockId: string) => boolean);
|
enableAddBlock: boolean | ((blockId: string) => boolean);
|
||||||
enableSelection: boolean | ((blockId: string) => boolean);
|
enableSelection: boolean | ((blockId: string) => boolean);
|
||||||
|
enableDependency: boolean | ((blockId: string) => boolean);
|
||||||
bottomSpacing: boolean;
|
bottomSpacing: boolean;
|
||||||
showAllBlocks: boolean;
|
showAllBlocks: boolean;
|
||||||
loadMoreBlocks?: () => void;
|
loadMoreBlocks?: () => void;
|
||||||
|
|
@ -70,6 +71,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
||||||
enableReorder,
|
enableReorder,
|
||||||
enableAddBlock,
|
enableAddBlock,
|
||||||
enableSelection,
|
enableSelection,
|
||||||
|
enableDependency,
|
||||||
bottomSpacing,
|
bottomSpacing,
|
||||||
showAllBlocks,
|
showAllBlocks,
|
||||||
quickAdd,
|
quickAdd,
|
||||||
|
|
@ -204,6 +206,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
||||||
enableReorder={enableReorder}
|
enableReorder={enableReorder}
|
||||||
enableSelection={enableSelection}
|
enableSelection={enableSelection}
|
||||||
enableAddBlock={enableAddBlock}
|
enableAddBlock={enableAddBlock}
|
||||||
|
enableDependency={enableDependency}
|
||||||
itemsContainerWidth={itemsContainerWidth}
|
itemsContainerWidth={itemsContainerWidth}
|
||||||
showAllBlocks={showAllBlocks}
|
showAllBlocks={showAllBlocks}
|
||||||
sidebarToRender={sidebarToRender}
|
sidebarToRender={sidebarToRender}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ type Props = {
|
||||||
enableBlockLeftResize: boolean;
|
enableBlockLeftResize: boolean;
|
||||||
enableBlockRightResize: boolean;
|
enableBlockRightResize: boolean;
|
||||||
enableBlockMove: boolean;
|
enableBlockMove: boolean;
|
||||||
|
enableDependency: boolean | ((blockId: string) => boolean);
|
||||||
ganttContainerRef: RefObject<HTMLDivElement>;
|
ganttContainerRef: RefObject<HTMLDivElement>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -29,6 +30,7 @@ export const ChartDraggable: React.FC<Props> = observer((props) => {
|
||||||
enableBlockLeftResize,
|
enableBlockLeftResize,
|
||||||
enableBlockRightResize,
|
enableBlockRightResize,
|
||||||
enableBlockMove,
|
enableBlockMove,
|
||||||
|
enableDependency,
|
||||||
isMoving,
|
isMoving,
|
||||||
ganttContainerRef,
|
ganttContainerRef,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
@ -36,7 +38,9 @@ export const ChartDraggable: React.FC<Props> = observer((props) => {
|
||||||
return (
|
return (
|
||||||
<div className="group w-full z-[5] relative inline-flex h-full cursor-pointer items-center font-medium transition-all">
|
<div className="group w-full z-[5] relative inline-flex h-full cursor-pointer items-center font-medium transition-all">
|
||||||
{/* left resize drag handle */}
|
{/* left resize drag handle */}
|
||||||
|
{(typeof enableDependency === "function" ? enableDependency(block.id) : enableDependency) && (
|
||||||
<LeftDependencyDraggable block={block} ganttContainerRef={ganttContainerRef} />
|
<LeftDependencyDraggable block={block} ganttContainerRef={ganttContainerRef} />
|
||||||
|
)}
|
||||||
<LeftResizable
|
<LeftResizable
|
||||||
enableBlockLeftResize={enableBlockLeftResize}
|
enableBlockLeftResize={enableBlockLeftResize}
|
||||||
handleBlockDrag={handleBlockDrag}
|
handleBlockDrag={handleBlockDrag}
|
||||||
|
|
@ -58,7 +62,9 @@ export const ChartDraggable: React.FC<Props> = observer((props) => {
|
||||||
isMoving={isMoving}
|
isMoving={isMoving}
|
||||||
position={block.position}
|
position={block.position}
|
||||||
/>
|
/>
|
||||||
|
{(typeof enableDependency === "function" ? enableDependency(block.id) : enableDependency) && (
|
||||||
<RightDependencyDraggable block={block} ganttContainerRef={ganttContainerRef} />
|
<RightDependencyDraggable block={block} ganttContainerRef={ganttContainerRef} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ type GanttChartRootProps = {
|
||||||
enableReorder?: boolean | ((blockId: string) => boolean);
|
enableReorder?: boolean | ((blockId: string) => boolean);
|
||||||
enableAddBlock?: boolean | ((blockId: string) => boolean);
|
enableAddBlock?: boolean | ((blockId: string) => boolean);
|
||||||
enableSelection?: boolean | ((blockId: string) => boolean);
|
enableSelection?: boolean | ((blockId: string) => boolean);
|
||||||
|
enableDependency?: boolean | ((blockId: string) => boolean);
|
||||||
bottomSpacing?: boolean;
|
bottomSpacing?: boolean;
|
||||||
showAllBlocks?: boolean;
|
showAllBlocks?: boolean;
|
||||||
showToday?: boolean;
|
showToday?: boolean;
|
||||||
|
|
@ -47,6 +48,7 @@ export const GanttChartRoot: FC<GanttChartRootProps> = observer((props) => {
|
||||||
enableReorder = false,
|
enableReorder = false,
|
||||||
enableAddBlock = false,
|
enableAddBlock = false,
|
||||||
enableSelection = false,
|
enableSelection = false,
|
||||||
|
enableDependency = false,
|
||||||
bottomSpacing = false,
|
bottomSpacing = false,
|
||||||
showAllBlocks = false,
|
showAllBlocks = false,
|
||||||
showToday = true,
|
showToday = true,
|
||||||
|
|
@ -79,6 +81,7 @@ export const GanttChartRoot: FC<GanttChartRootProps> = observer((props) => {
|
||||||
enableReorder={enableReorder}
|
enableReorder={enableReorder}
|
||||||
enableAddBlock={enableAddBlock}
|
enableAddBlock={enableAddBlock}
|
||||||
enableSelection={enableSelection}
|
enableSelection={enableSelection}
|
||||||
|
enableDependency={enableDependency}
|
||||||
bottomSpacing={bottomSpacing}
|
bottomSpacing={bottomSpacing}
|
||||||
showAllBlocks={showAllBlocks}
|
showAllBlocks={showAllBlocks}
|
||||||
quickAdd={quickAdd}
|
quickAdd={quickAdd}
|
||||||
|
|
|
||||||
|
|
@ -98,14 +98,14 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
||||||
target_date?: string;
|
target_date?: string;
|
||||||
}[]
|
}[]
|
||||||
) =>
|
) =>
|
||||||
issues.updateIssueDates(workspaceSlug.toString(), projectId.toString(), updates).catch(() => {
|
issues.updateIssueDates(workspaceSlug.toString(), updates, projectId.toString()).catch(() => {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: t("toast.error"),
|
title: t("toast.error"),
|
||||||
message: "Error while updating work item dates, Please try again Later",
|
message: "Error while updating work item dates, Please try again Later",
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
[issues]
|
[issues, projectId, workspaceSlug]
|
||||||
);
|
);
|
||||||
|
|
||||||
const quickAdd =
|
const quickAdd =
|
||||||
|
|
|
||||||
|
|
@ -82,8 +82,6 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
||||||
}
|
}
|
||||||
}, [isOpen, isMobile]);
|
}, [isOpen, isMobile]);
|
||||||
|
|
||||||
if (!value) return null;
|
|
||||||
|
|
||||||
let projectLabels: IIssueLabel[] = defaultOptions as IIssueLabel[];
|
let projectLabels: IIssueLabel[] = defaultOptions as IIssueLabel[];
|
||||||
if (storeLabels && storeLabels.length > 0) projectLabels = storeLabels;
|
if (storeLabels && storeLabels.length > 0) projectLabels = storeLabels;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,20 @@
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
import isEmpty from "lodash/isEmpty";
|
import { isEmpty } from "lodash";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams, useSearchParams } from "next/navigation";
|
import { useParams, useSearchParams } from "next/navigation";
|
||||||
import useSWR from "swr";
|
|
||||||
// plane constants
|
// plane constants
|
||||||
import {
|
import useSWR from "swr";
|
||||||
ALL_ISSUES,
|
import { EIssueFilterType, EIssueLayoutTypes, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
|
||||||
EIssueLayoutTypes,
|
|
||||||
EIssueFilterType,
|
|
||||||
EIssuesStoreType,
|
|
||||||
ISSUE_DISPLAY_FILTERS_BY_PAGE
|
|
||||||
,EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
|
||||||
import { IIssueDisplayFilterOptions } from "@plane/types";
|
|
||||||
// hooks
|
// hooks
|
||||||
// components
|
// components
|
||||||
import { EmptyState } from "@/components/common";
|
|
||||||
import { SpreadsheetView } from "@/components/issues/issue-layouts";
|
|
||||||
import { AllIssueQuickActions } from "@/components/issues/issue-layouts/quick-action-dropdowns";
|
|
||||||
import { SpreadsheetLayoutLoader } from "@/components/ui";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useGlobalView, useIssues, useUserPermissions } from "@/hooks/store";
|
import { EmptyState } from "@/components/common";
|
||||||
|
import { WorkspaceActiveLayout } from "@/components/views/helper";
|
||||||
|
import { useGlobalView, useIssues } from "@/hooks/store";
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
import { IssuesStoreContext } from "@/hooks/use-issue-layout-store";
|
|
||||||
import { useIssuesActions } from "@/hooks/use-issues-actions";
|
|
||||||
import { useWorkspaceIssueProperties } from "@/hooks/use-workspace-issue-properties";
|
import { useWorkspaceIssueProperties } from "@/hooks/use-workspace-issue-properties";
|
||||||
// store
|
// store
|
||||||
import emptyView from "@/public/empty-state/view.svg";
|
import emptyView from "@/public/empty-state/view.svg";
|
||||||
import { IssuePeekOverview } from "../../peek-overview";
|
|
||||||
import { IssueLayoutHOC } from "../issue-layout-HOC";
|
|
||||||
import { TRenderQuickActions } from "../list/list-view-types";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isDefaultView: boolean;
|
isDefaultView: boolean;
|
||||||
|
|
@ -38,32 +24,34 @@ type Props = {
|
||||||
|
|
||||||
export const AllIssueLayoutRoot: React.FC<Props> = observer((props: Props) => {
|
export const AllIssueLayoutRoot: React.FC<Props> = observer((props: Props) => {
|
||||||
const { isDefaultView, isLoading = false, toggleLoading } = props;
|
const { isDefaultView, isLoading = false, toggleLoading } = props;
|
||||||
// router
|
|
||||||
const { workspaceSlug, globalViewId } = useParams();
|
// Router hooks
|
||||||
const router = useAppRouter();
|
const router = useAppRouter();
|
||||||
|
const { workspaceSlug, globalViewId } = useParams();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const routeFilters: {
|
|
||||||
[key: string]: string;
|
// Store hooks
|
||||||
} = {};
|
const {
|
||||||
|
issuesFilter: { filters, fetchFilters, updateFilters },
|
||||||
|
issues: { clear, groupedIssueIds, fetchIssues, fetchNextIssues },
|
||||||
|
} = useIssues(EIssuesStoreType.GLOBAL);
|
||||||
|
const { fetchAllGlobalViews, getViewDetailsById } = useGlobalView();
|
||||||
|
|
||||||
|
// Custom hooks
|
||||||
|
useWorkspaceIssueProperties(workspaceSlug);
|
||||||
|
|
||||||
|
// Derived values
|
||||||
|
const viewDetails = getViewDetailsById(globalViewId?.toString());
|
||||||
|
const issueFilters = globalViewId ? filters?.[globalViewId.toString()] : undefined;
|
||||||
|
const activeLayout: EIssueLayoutTypes | undefined = issueFilters?.displayFilters?.layout;
|
||||||
|
|
||||||
|
// Route filters
|
||||||
|
const routeFilters: { [key: string]: string } = {};
|
||||||
searchParams.forEach((value: string, key: string) => {
|
searchParams.forEach((value: string, key: string) => {
|
||||||
routeFilters[key] = value;
|
routeFilters[key] = value;
|
||||||
});
|
});
|
||||||
//swr hook for fetching issue properties
|
|
||||||
useWorkspaceIssueProperties(workspaceSlug);
|
|
||||||
// store
|
|
||||||
const {
|
|
||||||
issuesFilter: { filters, fetchFilters, updateFilters },
|
|
||||||
issues: { clear, getIssueLoader, getPaginationData, groupedIssueIds, fetchIssues, fetchNextIssues },
|
|
||||||
} = useIssues(EIssuesStoreType.GLOBAL);
|
|
||||||
const { updateIssue, removeIssue, archiveIssue } = useIssuesActions(EIssuesStoreType.GLOBAL);
|
|
||||||
|
|
||||||
const { allowPermissions } = useUserPermissions();
|
|
||||||
|
|
||||||
const { fetchAllGlobalViews, getViewDetailsById } = useGlobalView();
|
|
||||||
|
|
||||||
const viewDetails = getViewDetailsById(globalViewId?.toString());
|
|
||||||
// filter init from the query params
|
|
||||||
|
|
||||||
|
// Apply route filters to store
|
||||||
const routerFilterParams = () => {
|
const routerFilterParams = () => {
|
||||||
if (
|
if (
|
||||||
workspaceSlug &&
|
workspaceSlug &&
|
||||||
|
|
@ -89,10 +77,12 @@ export const AllIssueLayoutRoot: React.FC<Props> = observer((props: Props) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Fetch next pages callback
|
||||||
const fetchNextPages = useCallback(() => {
|
const fetchNextPages = useCallback(() => {
|
||||||
if (workspaceSlug && globalViewId) fetchNextIssues(workspaceSlug.toString(), globalViewId.toString());
|
if (workspaceSlug && globalViewId) fetchNextIssues(workspaceSlug.toString(), globalViewId.toString());
|
||||||
}, [fetchNextIssues, workspaceSlug, globalViewId]);
|
}, [fetchNextIssues, workspaceSlug, globalViewId]);
|
||||||
|
|
||||||
|
// Fetch global views
|
||||||
const { isLoading: globalViewsLoading } = useSWR(
|
const { isLoading: globalViewsLoading } = useSWR(
|
||||||
workspaceSlug ? `WORKSPACE_GLOBAL_VIEWS_${workspaceSlug}` : null,
|
workspaceSlug ? `WORKSPACE_GLOBAL_VIEWS_${workspaceSlug}` : null,
|
||||||
async () => {
|
async () => {
|
||||||
|
|
@ -103,6 +93,7 @@ export const AllIssueLayoutRoot: React.FC<Props> = observer((props: Props) => {
|
||||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Fetch issues
|
||||||
const { isLoading: issuesLoading } = useSWR(
|
const { isLoading: issuesLoading } = useSWR(
|
||||||
workspaceSlug && globalViewId ? `WORKSPACE_GLOBAL_VIEW_ISSUES_${workspaceSlug}_${globalViewId}` : null,
|
workspaceSlug && globalViewId ? `WORKSPACE_GLOBAL_VIEW_ISSUES_${workspaceSlug}_${globalViewId}` : null,
|
||||||
async () => {
|
async () => {
|
||||||
|
|
@ -126,54 +117,7 @@ export const AllIssueLayoutRoot: React.FC<Props> = observer((props: Props) => {
|
||||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
const canEditProperties = useCallback(
|
// Empty state
|
||||||
(projectId: string | undefined) => {
|
|
||||||
if (!projectId) return false;
|
|
||||||
return allowPermissions(
|
|
||||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
|
||||||
EUserPermissionsLevel.PROJECT,
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[workspaceSlug]
|
|
||||||
);
|
|
||||||
|
|
||||||
const issueFilters = globalViewId ? filters?.[globalViewId.toString()] : undefined;
|
|
||||||
|
|
||||||
const handleDisplayFiltersUpdate = useCallback(
|
|
||||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
|
||||||
if (!workspaceSlug || !globalViewId) return;
|
|
||||||
|
|
||||||
updateFilters(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
undefined,
|
|
||||||
EIssueFilterType.DISPLAY_FILTERS,
|
|
||||||
{ ...updatedDisplayFilter },
|
|
||||||
globalViewId.toString()
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[updateFilters, workspaceSlug, globalViewId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderQuickActions: TRenderQuickActions = useCallback(
|
|
||||||
({ issue, parentRef, customActionButton, placement, portalElement }) => (
|
|
||||||
<AllIssueQuickActions
|
|
||||||
parentRef={parentRef}
|
|
||||||
customActionButton={customActionButton}
|
|
||||||
issue={issue}
|
|
||||||
handleDelete={async () => removeIssue(issue.project_id, issue.id)}
|
|
||||||
handleUpdate={async (data) => updateIssue && updateIssue(issue.project_id, issue.id, data)}
|
|
||||||
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
|
|
||||||
portalElement={portalElement}
|
|
||||||
readOnly={!canEditProperties(issue.project_id ?? undefined)}
|
|
||||||
placements={placement}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[canEditProperties, removeIssue, updateIssue, archiveIssue]
|
|
||||||
);
|
|
||||||
|
|
||||||
// when the call is not loading and the view does not exist and the view is not a default view, show empty state
|
|
||||||
if (!isLoading && !globalViewsLoading && !issuesLoading && !viewDetails && !isDefaultView) {
|
if (!isLoading && !globalViewsLoading && !issuesLoading && !viewDetails && !isDefaultView) {
|
||||||
return (
|
return (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
|
|
@ -188,31 +132,18 @@ export const AllIssueLayoutRoot: React.FC<Props> = observer((props: Props) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((isLoading && issuesLoading && getIssueLoader() === "init-loader") || !globalViewId || !groupedIssueIds) {
|
|
||||||
return <SpreadsheetLayoutLoader />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const issueIds = groupedIssueIds[ALL_ISSUES];
|
|
||||||
const nextPageResults = getPaginationData(ALL_ISSUES, undefined)?.nextPageResults;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IssuesStoreContext.Provider value={EIssuesStoreType.GLOBAL}>
|
<WorkspaceActiveLayout
|
||||||
<IssueLayoutHOC layout={EIssueLayoutTypes.SPREADSHEET}>
|
activeLayout={activeLayout}
|
||||||
<SpreadsheetView
|
isDefaultView={isDefaultView}
|
||||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
isLoading={isLoading}
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
toggleLoading={toggleLoading}
|
||||||
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
|
workspaceSlug={workspaceSlug?.toString()}
|
||||||
issueIds={Array.isArray(issueIds) ? issueIds : []}
|
globalViewId={globalViewId?.toString()}
|
||||||
quickActions={renderQuickActions}
|
routeFilters={routeFilters}
|
||||||
updateIssue={updateIssue}
|
fetchNextPages={fetchNextPages}
|
||||||
canEditProperties={canEditProperties}
|
globalViewsLoading={globalViewsLoading}
|
||||||
canLoadMoreIssues={!!nextPageResults}
|
issuesLoading={issuesLoading}
|
||||||
loadMoreIssues={fetchNextPages}
|
|
||||||
isWorkspaceLevel
|
|
||||||
/>
|
/>
|
||||||
{/* peek overview */}
|
|
||||||
<IssuePeekOverview />
|
|
||||||
</IssueLayoutHOC>
|
|
||||||
</IssuesStoreContext.Provider>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
import React, { useCallback } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
// plane constants
|
||||||
|
import {
|
||||||
|
ALL_ISSUES,
|
||||||
|
EIssueLayoutTypes,
|
||||||
|
EIssueFilterType,
|
||||||
|
EIssuesStoreType,
|
||||||
|
EUserPermissions,
|
||||||
|
EUserPermissionsLevel,
|
||||||
|
} from "@plane/constants";
|
||||||
|
import { IIssueDisplayFilterOptions } from "@plane/types";
|
||||||
|
// hooks
|
||||||
|
// components
|
||||||
|
import { SpreadsheetView } from "@/components/issues/issue-layouts";
|
||||||
|
import { AllIssueQuickActions } from "@/components/issues/issue-layouts/quick-action-dropdowns";
|
||||||
|
import { SpreadsheetLayoutLoader } from "@/components/ui";
|
||||||
|
// hooks
|
||||||
|
import { useIssues, useUserPermissions } from "@/hooks/store";
|
||||||
|
import { IssuesStoreContext } from "@/hooks/use-issue-layout-store";
|
||||||
|
import { useIssuesActions } from "@/hooks/use-issues-actions";
|
||||||
|
import { useWorkspaceIssueProperties } from "@/hooks/use-workspace-issue-properties";
|
||||||
|
// store
|
||||||
|
import { IssuePeekOverview } from "../../../peek-overview";
|
||||||
|
import { IssueLayoutHOC } from "../../issue-layout-HOC";
|
||||||
|
import { TRenderQuickActions } from "../../list/list-view-types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isDefaultView: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
toggleLoading: (value: boolean) => void;
|
||||||
|
workspaceSlug: string;
|
||||||
|
globalViewId: string;
|
||||||
|
routeFilters: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
fetchNextPages: () => void;
|
||||||
|
globalViewsLoading: boolean;
|
||||||
|
issuesLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WorkspaceSpreadsheetRoot: React.FC<Props> = observer((props: Props) => {
|
||||||
|
const { isLoading = false, workspaceSlug, globalViewId, fetchNextPages, issuesLoading } = props;
|
||||||
|
|
||||||
|
// Custom hooks
|
||||||
|
useWorkspaceIssueProperties(workspaceSlug);
|
||||||
|
|
||||||
|
// Store hooks
|
||||||
|
const {
|
||||||
|
issuesFilter: { filters, updateFilters },
|
||||||
|
issues: { getIssueLoader, getPaginationData, groupedIssueIds },
|
||||||
|
} = useIssues(EIssuesStoreType.GLOBAL);
|
||||||
|
const { updateIssue, removeIssue, archiveIssue } = useIssuesActions(EIssuesStoreType.GLOBAL);
|
||||||
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
|
||||||
|
// Derived values
|
||||||
|
const issueFilters = globalViewId ? filters?.[globalViewId.toString()] : undefined;
|
||||||
|
|
||||||
|
// Permission checker
|
||||||
|
const canEditProperties = useCallback(
|
||||||
|
(projectId: string | undefined) => {
|
||||||
|
if (!projectId) return false;
|
||||||
|
return allowPermissions(
|
||||||
|
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||||
|
EUserPermissionsLevel.PROJECT,
|
||||||
|
workspaceSlug.toString(),
|
||||||
|
projectId
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[allowPermissions, workspaceSlug]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Display filters handler
|
||||||
|
const handleDisplayFiltersUpdate = useCallback(
|
||||||
|
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||||
|
if (!workspaceSlug || !globalViewId) return;
|
||||||
|
|
||||||
|
updateFilters(
|
||||||
|
workspaceSlug.toString(),
|
||||||
|
undefined,
|
||||||
|
EIssueFilterType.DISPLAY_FILTERS,
|
||||||
|
{ ...updatedDisplayFilter },
|
||||||
|
globalViewId.toString()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[updateFilters, workspaceSlug, globalViewId]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Quick actions renderer
|
||||||
|
const renderQuickActions: TRenderQuickActions = useCallback(
|
||||||
|
({ issue, parentRef, customActionButton, placement, portalElement }) => (
|
||||||
|
<AllIssueQuickActions
|
||||||
|
parentRef={parentRef}
|
||||||
|
customActionButton={customActionButton}
|
||||||
|
issue={issue}
|
||||||
|
handleDelete={async () => removeIssue(issue.project_id, issue.id)}
|
||||||
|
handleUpdate={async (data) => updateIssue && updateIssue(issue.project_id, issue.id, data)}
|
||||||
|
handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)}
|
||||||
|
portalElement={portalElement}
|
||||||
|
readOnly={!canEditProperties(issue.project_id ?? undefined)}
|
||||||
|
placements={placement}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[canEditProperties, removeIssue, updateIssue, archiveIssue]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Loading state
|
||||||
|
if ((isLoading && issuesLoading && getIssueLoader() === "init-loader") || !globalViewId || !groupedIssueIds) {
|
||||||
|
return <SpreadsheetLayoutLoader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computed values
|
||||||
|
const issueIds = groupedIssueIds[ALL_ISSUES];
|
||||||
|
const nextPageResults = getPaginationData(ALL_ISSUES, undefined)?.nextPageResults;
|
||||||
|
|
||||||
|
// Render spreadsheet
|
||||||
|
return (
|
||||||
|
<IssuesStoreContext.Provider value={EIssuesStoreType.GLOBAL}>
|
||||||
|
<IssueLayoutHOC layout={EIssueLayoutTypes.SPREADSHEET}>
|
||||||
|
<SpreadsheetView
|
||||||
|
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||||
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
|
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
|
||||||
|
issueIds={Array.isArray(issueIds) ? issueIds : []}
|
||||||
|
quickActions={renderQuickActions}
|
||||||
|
updateIssue={updateIssue}
|
||||||
|
canEditProperties={canEditProperties}
|
||||||
|
canLoadMoreIssues={!!nextPageResults}
|
||||||
|
loadMoreIssues={fetchNextPages}
|
||||||
|
isWorkspaceLevel
|
||||||
|
/>
|
||||||
|
{/* peek overview */}
|
||||||
|
<IssuePeekOverview />
|
||||||
|
</IssueLayoutHOC>
|
||||||
|
</IssuesStoreContext.Provider>
|
||||||
|
);
|
||||||
|
});
|
||||||
51
web/core/components/views/helper.tsx
Normal file
51
web/core/components/views/helper.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { EIssueLayoutTypes } from "@plane/constants";
|
||||||
|
import { WorkspaceAdditionalLayouts } from "@/plane-web/components/views/helper";
|
||||||
|
import { WorkspaceSpreadsheetRoot } from "../issues/issue-layouts/spreadsheet/roots/workspace-root";
|
||||||
|
|
||||||
|
export type TWorkspaceLayoutProps = {
|
||||||
|
activeLayout: EIssueLayoutTypes | undefined;
|
||||||
|
isDefaultView: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
toggleLoading: (value: boolean) => void;
|
||||||
|
workspaceSlug: string;
|
||||||
|
globalViewId: string;
|
||||||
|
routeFilters: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
fetchNextPages: () => void;
|
||||||
|
globalViewsLoading: boolean;
|
||||||
|
issuesLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WorkspaceActiveLayout = (props: TWorkspaceLayoutProps) => {
|
||||||
|
const {
|
||||||
|
activeLayout = EIssueLayoutTypes.SPREADSHEET,
|
||||||
|
isDefaultView,
|
||||||
|
isLoading,
|
||||||
|
toggleLoading,
|
||||||
|
workspaceSlug,
|
||||||
|
globalViewId,
|
||||||
|
routeFilters,
|
||||||
|
fetchNextPages,
|
||||||
|
globalViewsLoading,
|
||||||
|
issuesLoading,
|
||||||
|
} = props;
|
||||||
|
switch (activeLayout) {
|
||||||
|
case EIssueLayoutTypes.SPREADSHEET:
|
||||||
|
return (
|
||||||
|
<WorkspaceSpreadsheetRoot
|
||||||
|
isDefaultView={isDefaultView}
|
||||||
|
isLoading={isLoading}
|
||||||
|
toggleLoading={toggleLoading}
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
|
globalViewId={globalViewId}
|
||||||
|
routeFilters={routeFilters}
|
||||||
|
fetchNextPages={fetchNextPages}
|
||||||
|
globalViewsLoading={globalViewsLoading}
|
||||||
|
issuesLoading={issuesLoading}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return <WorkspaceAdditionalLayouts {...props} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -9,6 +9,7 @@ import { IProjectEpics, IProjectEpicsFilter } from "@/plane-web/store/issue/epic
|
||||||
// types
|
// types
|
||||||
import { ITeamIssues, ITeamIssuesFilter } from "@/plane-web/store/issue/team";
|
import { ITeamIssues, ITeamIssuesFilter } from "@/plane-web/store/issue/team";
|
||||||
import { ITeamViewIssues, ITeamViewIssuesFilter } from "@/plane-web/store/issue/team-views";
|
import { ITeamViewIssues, ITeamViewIssuesFilter } from "@/plane-web/store/issue/team-views";
|
||||||
|
import { IWorkspaceIssues } from "@/plane-web/store/issue/workspace/issue.store";
|
||||||
import { IArchivedIssues, IArchivedIssuesFilter } from "@/store/issue/archived";
|
import { IArchivedIssues, IArchivedIssuesFilter } from "@/store/issue/archived";
|
||||||
import { ICycleIssues, ICycleIssuesFilter } from "@/store/issue/cycle";
|
import { ICycleIssues, ICycleIssuesFilter } from "@/store/issue/cycle";
|
||||||
import { IDraftIssues, IDraftIssuesFilter } from "@/store/issue/draft";
|
import { IDraftIssues, IDraftIssuesFilter } from "@/store/issue/draft";
|
||||||
|
|
@ -16,7 +17,7 @@ import { IModuleIssues, IModuleIssuesFilter } from "@/store/issue/module";
|
||||||
import { IProfileIssues, IProfileIssuesFilter } from "@/store/issue/profile";
|
import { IProfileIssues, IProfileIssuesFilter } from "@/store/issue/profile";
|
||||||
import { IProjectIssues, IProjectIssuesFilter } from "@/store/issue/project";
|
import { IProjectIssues, IProjectIssuesFilter } from "@/store/issue/project";
|
||||||
import { IProjectViewIssues, IProjectViewIssuesFilter } from "@/store/issue/project-views";
|
import { IProjectViewIssues, IProjectViewIssuesFilter } from "@/store/issue/project-views";
|
||||||
import { IWorkspaceIssues, IWorkspaceIssuesFilter } from "@/store/issue/workspace";
|
import { IWorkspaceIssuesFilter } from "@/store/issue/workspace";
|
||||||
import { IWorkspaceDraftIssues, IWorkspaceDraftIssuesFilter } from "@/store/issue/workspace-draft";
|
import { IWorkspaceDraftIssues, IWorkspaceDraftIssuesFilter } from "@/store/issue/workspace-draft";
|
||||||
// constants
|
// constants
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -263,8 +263,11 @@ export class WorkspaceService extends APIService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getViewIssues(workspaceSlug: string, params: any, config = {}): Promise<TIssuesResponse> {
|
async getViewIssues(workspaceSlug: string, params: any, config = {}): Promise<TIssuesResponse> {
|
||||||
|
const path = params.expand?.includes("issue_relation")
|
||||||
|
? `/api/workspaces/${workspaceSlug}/issues-detail/`
|
||||||
|
: `/api/workspaces/${workspaceSlug}/issues/`;
|
||||||
return this.get(
|
return this.get(
|
||||||
`/api/workspaces/${workspaceSlug}/issues/`,
|
path,
|
||||||
{
|
{
|
||||||
params,
|
params,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ export interface IBaseIssuesStore {
|
||||||
addModuleIds: string[],
|
addModuleIds: string[],
|
||||||
removeModuleIds: string[]
|
removeModuleIds: string[]
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
updateIssueDates(workspaceSlug: string, projectId: string, updates: IBlockUpdateDependencyData[]): Promise<void>;
|
updateIssueDates(workspaceSlug: string, updates: IBlockUpdateDependencyData[], projectId?: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This constant maps the group by keys to the respective issue property that the key relies on
|
// This constant maps the group by keys to the respective issue property that the key relies on
|
||||||
|
|
@ -826,9 +826,10 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
|
||||||
|
|
||||||
async updateIssueDates(
|
async updateIssueDates(
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
updates: { id: string; start_date?: string; target_date?: string }[],
|
||||||
updates: { id: string; start_date?: string; target_date?: string }[]
|
projectId?: string
|
||||||
) {
|
) {
|
||||||
|
if(!projectId) return;
|
||||||
const issueDatesBeforeChange: { id: string; start_date?: string; target_date?: string }[] = [];
|
const issueDatesBeforeChange: { id: string; start_date?: string; target_date?: string }[] = [];
|
||||||
try {
|
try {
|
||||||
const getIssueById = this.rootIssueStore.issues.getIssueById;
|
const getIssueById = this.rootIssueStore.issues.getIssueById;
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ export class IssueRelationStore implements IIssueRelationStore {
|
||||||
*/
|
*/
|
||||||
createCurrentRelation = async (issueId: string, relationType: TIssueRelationTypes, relatedIssueId: string) => {
|
createCurrentRelation = async (issueId: string, relationType: TIssueRelationTypes, relatedIssueId: string) => {
|
||||||
const workspaceSlug = this.rootIssueDetailStore.rootIssueStore.workspaceSlug;
|
const workspaceSlug = this.rootIssueDetailStore.rootIssueStore.workspaceSlug;
|
||||||
const projectId = this.rootIssueDetailStore.rootIssueStore.projectId;
|
const projectId = this.rootIssueDetailStore.issue.getIssueById(issueId)?.project_id;
|
||||||
|
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
TeamViewIssuesFilter,
|
TeamViewIssuesFilter,
|
||||||
} from "@/plane-web/store/issue/team-views";
|
} from "@/plane-web/store/issue/team-views";
|
||||||
// root store
|
// root store
|
||||||
|
import { IWorkspaceIssues, WorkspaceIssues } from "@/plane-web/store/issue/workspace/issue.store";
|
||||||
import { RootStore } from "@/plane-web/store/root.store";
|
import { RootStore } from "@/plane-web/store/root.store";
|
||||||
import { IWorkspaceMembership } from "@/store/member/workspace-member.store";
|
import { IWorkspaceMembership } from "@/store/member/workspace-member.store";
|
||||||
// issues data store
|
// issues data store
|
||||||
|
|
@ -32,7 +33,7 @@ import {
|
||||||
IProjectViewIssues,
|
IProjectViewIssues,
|
||||||
ProjectViewIssues,
|
ProjectViewIssues,
|
||||||
} from "./project-views";
|
} from "./project-views";
|
||||||
import { WorkspaceIssuesFilter, IWorkspaceIssues, WorkspaceIssues, IWorkspaceIssuesFilter } from "./workspace";
|
import { WorkspaceIssuesFilter, IWorkspaceIssuesFilter } from "./workspace";
|
||||||
import {
|
import {
|
||||||
IWorkspaceDraftIssues,
|
IWorkspaceDraftIssues,
|
||||||
IWorkspaceDraftIssuesFilter,
|
IWorkspaceDraftIssuesFilter,
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,6 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
|
||||||
);
|
);
|
||||||
|
|
||||||
fetchFilters = async (workspaceSlug: string, viewId: TWorkspaceFilters) => {
|
fetchFilters = async (workspaceSlug: string, viewId: TWorkspaceFilters) => {
|
||||||
try {
|
|
||||||
let filters: IIssueFilterOptions;
|
let filters: IIssueFilterOptions;
|
||||||
let displayFilters: IIssueDisplayFilterOptions;
|
let displayFilters: IIssueDisplayFilterOptions;
|
||||||
let displayProperties: IIssueDisplayProperties;
|
let displayProperties: IIssueDisplayProperties;
|
||||||
|
|
@ -176,9 +175,6 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
|
||||||
set(this.filters, [viewId, "displayProperties"], displayProperties);
|
set(this.filters, [viewId, "displayProperties"], displayProperties);
|
||||||
set(this.filters, [viewId, "kanbanFilters"], kanbanFilters);
|
set(this.filters, [viewId, "kanbanFilters"], kanbanFilters);
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
updateFilters = async (
|
updateFilters = async (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue