mirror of
https://github.com/makeplane/plane
synced 2025-08-07 19:59:33 +00:00
Compare commits
2 Commits
refactor/e
...
issues-loc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a4f8ab833 | ||
|
|
7b459da829 |
@@ -21,6 +21,7 @@ from plane.app.views import (
|
||||
LabelViewSet,
|
||||
BulkIssueOperationsEndpoint,
|
||||
BulkArchiveIssuesEndpoint,
|
||||
DeletedIssuesListViewSet,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
@@ -310,4 +311,9 @@ urlpatterns = [
|
||||
BulkIssueOperationsEndpoint.as_view(),
|
||||
name="bulk-operations-issues",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/deleted-issues/",
|
||||
DeletedIssuesListViewSet.as_view(),
|
||||
name="deleted-issues",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -112,6 +112,7 @@ from .issue.base import (
|
||||
IssueViewSet,
|
||||
IssueUserDisplayPropertyEndpoint,
|
||||
BulkDeleteIssuesEndpoint,
|
||||
DeletedIssuesListViewSet,
|
||||
)
|
||||
|
||||
from .issue.activity import (
|
||||
|
||||
@@ -234,10 +234,17 @@ class IssueViewSet(BaseViewSet):
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
def list(self, request, slug, project_id):
|
||||
extra_filters = {}
|
||||
if request.GET.get("updated_at__gt", None) is not None:
|
||||
extra_filters = {
|
||||
"updated_at__gt": request.GET.get("updated_at__gt")
|
||||
}
|
||||
|
||||
print (extra_filters)
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
order_by_param = request.GET.get("order_by", "-created_at")
|
||||
|
||||
issue_queryset = self.get_queryset().filter(**filters)
|
||||
issue_queryset = self.get_queryset().filter(**filters, **extra_filters)
|
||||
# Custom ordering for priority and state
|
||||
|
||||
# Issue queryset
|
||||
@@ -652,3 +659,18 @@ class BulkDeleteIssuesEndpoint(BaseAPIView):
|
||||
{"message": f"{total_issues} issues were deleted"},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
class DeletedIssuesListViewSet(BaseAPIView):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
|
||||
def get(self, request, slug, project_id):
|
||||
deleted_issues = Issue.all_objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
deleted_at__isnull=False,
|
||||
).values_list("id", flat=True)
|
||||
|
||||
return Response(deleted_issues, status=status.HTTP_200_OK)
|
||||
|
||||
@@ -29,7 +29,6 @@ from plane.authentication.adapter.error import (
|
||||
AuthenticationException,
|
||||
AUTHENTICATION_ERROR_CODES,
|
||||
)
|
||||
from plane.authentication.rate_limit import AuthenticationThrottle
|
||||
|
||||
|
||||
class MagicGenerateEndpoint(APIView):
|
||||
@@ -38,10 +37,6 @@ class MagicGenerateEndpoint(APIView):
|
||||
AllowAny,
|
||||
]
|
||||
|
||||
throttle_classes = [
|
||||
AuthenticationThrottle,
|
||||
]
|
||||
|
||||
def post(self, request):
|
||||
# Check if instance is configured
|
||||
instance = Instance.objects.first()
|
||||
|
||||
@@ -27,11 +27,14 @@ class ProjectStatesEndpoint(BaseAPIView):
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
states = State.objects.filter(
|
||||
~Q(name="Triage"),
|
||||
workspace__slug=deploy_board.workspace.slug,
|
||||
project_id=deploy_board.project_id,
|
||||
).values("name", "group", "color", "id", "sequence")
|
||||
states = (
|
||||
State.objects.filter(
|
||||
~Q(name="Triage"),
|
||||
workspace__slug=deploy_board.workspace.slug,
|
||||
project_id=deploy_board.project_id,
|
||||
)
|
||||
.values("name", "group", "color", "id")
|
||||
)
|
||||
|
||||
return Response(
|
||||
states,
|
||||
|
||||
@@ -82,7 +82,7 @@ class CursorResult(Sequence):
|
||||
return f"<{type(self).__name__}: results={len(self.results)}>"
|
||||
|
||||
|
||||
MAX_LIMIT = 100
|
||||
MAX_LIMIT = 1000
|
||||
|
||||
|
||||
class BadPaginationError(Exception):
|
||||
@@ -118,7 +118,7 @@ class OffsetPaginator:
|
||||
self.max_offset = max_offset
|
||||
self.on_results = on_results
|
||||
|
||||
def get_result(self, limit=100, cursor=None):
|
||||
def get_result(self, limit=1000, cursor=None):
|
||||
# offset is page #
|
||||
# value is page limit
|
||||
if cursor is None:
|
||||
@@ -727,7 +727,7 @@ class BasePaginator:
|
||||
cursor_name = "cursor"
|
||||
|
||||
# get the per page parameter from request
|
||||
def get_per_page(self, request, default_per_page=100, max_per_page=100):
|
||||
def get_per_page(self, request, default_per_page=1000, max_per_page=1000):
|
||||
try:
|
||||
per_page = int(request.GET.get("per_page", default_per_page))
|
||||
except ValueError:
|
||||
@@ -747,8 +747,8 @@ class BasePaginator:
|
||||
on_results=None,
|
||||
paginator=None,
|
||||
paginator_cls=OffsetPaginator,
|
||||
default_per_page=100,
|
||||
max_per_page=100,
|
||||
default_per_page=1000,
|
||||
max_per_page=1000,
|
||||
cursor_cls=Cursor,
|
||||
extra_stats=None,
|
||||
controller=None,
|
||||
|
||||
8
packages/types/src/issues/base.d.ts
vendored
8
packages/types/src/issues/base.d.ts
vendored
@@ -10,17 +10,19 @@ export * from "./issue_relation";
|
||||
export * from "./issue_sub_issues";
|
||||
export * from "./activity/base";
|
||||
|
||||
export type TLoader = "init-loader" | "mutation" | "pagination" | undefined;
|
||||
export type TLoader = "init-loader" | "mutation" | undefined;
|
||||
|
||||
export type TUngroupedIssues = string[];
|
||||
|
||||
export type TGroupedIssues = {
|
||||
[group_id: string]: string[];
|
||||
[group_id: string]: TUngroupedIssues;
|
||||
};
|
||||
|
||||
export type TSubGroupedIssues = {
|
||||
[sub_grouped_id: string]: TGroupedIssues;
|
||||
};
|
||||
|
||||
export type TIssues = TGroupedIssues | TSubGroupedIssues;
|
||||
export type TIssues = TGroupedIssues | TSubGroupedIssues | TUngroupedIssues;
|
||||
|
||||
export type TPaginationData = {
|
||||
nextCursor: string;
|
||||
|
||||
@@ -26,13 +26,13 @@ export const ProjectArchivesHeader: FC<TProps> = observer((props: TProps) => {
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// store hooks
|
||||
const {
|
||||
issues: { getGroupIssueCount },
|
||||
issues: { issueIds },
|
||||
} = useIssues(EIssuesStoreType.ARCHIVED);
|
||||
const { currentProjectDetails, loader } = useProject();
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
const issueCount = getGroupIssueCount(undefined, undefined, false);
|
||||
const issueCount = issueIds?.length;
|
||||
|
||||
const activeTabBreadcrumbDetail =
|
||||
PROJECT_ARCHIVES_BREADCRUMB_LIST[activeTab as keyof typeof PROJECT_ARCHIVES_BREADCRUMB_LIST];
|
||||
|
||||
@@ -76,7 +76,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
// store hooks
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
issues: { getGroupIssueCount },
|
||||
issues: { issueIds },
|
||||
} = useIssues(EIssuesStoreType.CYCLE);
|
||||
const { currentProjectCycleIds, getCycleById } = useCycle();
|
||||
const { toggleCreateIssueModal } = useCommandPalette();
|
||||
@@ -152,7 +152,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
const canUserCreateIssue =
|
||||
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
|
||||
|
||||
const issuesCount = getGroupIssueCount(undefined, undefined, false);
|
||||
const issuesCount = issueIds?.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -50,7 +50,7 @@ export const ProjectIssuesHeader = observer(() => {
|
||||
} = useMember();
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
issues: { getGroupIssueCount },
|
||||
issues: { issueIds },
|
||||
} = useIssues(EIssuesStoreType.PROJECT);
|
||||
const { toggleCreateIssueModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
@@ -113,7 +113,7 @@ export const ProjectIssuesHeader = observer(() => {
|
||||
const canUserCreateIssue =
|
||||
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
|
||||
|
||||
const issuesCount = getGroupIssueCount(undefined, undefined, false);
|
||||
const issuesCount = issueIds?.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -77,7 +77,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
// store hooks
|
||||
const {
|
||||
issuesFilter: { issueFilters },
|
||||
issues: { getGroupIssueCount },
|
||||
issues: { issueIds },
|
||||
} = useIssues(EIssuesStoreType.MODULE);
|
||||
const { updateFilters } = useIssuesActions(EIssuesStoreType.MODULE);
|
||||
const { projectModuleIds, getModuleById } = useModule();
|
||||
@@ -152,7 +152,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
const canUserCreateIssue =
|
||||
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
|
||||
|
||||
const issuesCount = getGroupIssueCount(undefined, undefined, false);
|
||||
const issuesCount = issueIds?.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -10,6 +10,7 @@ type Props = {
|
||||
as?: keyof JSX.IntrinsicElements;
|
||||
classNames?: string;
|
||||
placeholderChildren?: ReactNode;
|
||||
shouldRenderByDefault?: boolean;
|
||||
};
|
||||
|
||||
const RenderIfVisible: React.FC<Props> = (props) => {
|
||||
@@ -22,8 +23,9 @@ const RenderIfVisible: React.FC<Props> = (props) => {
|
||||
children,
|
||||
classNames = "",
|
||||
placeholderChildren = null, //placeholder children
|
||||
shouldRenderByDefault = false,
|
||||
} = props;
|
||||
const [shouldVisible, setShouldVisible] = useState<boolean>();
|
||||
const [shouldVisible, setShouldVisible] = useState<boolean>(shouldRenderByDefault);
|
||||
const placeholderHeight = useRef<string>(defaultHeight);
|
||||
const intersectionRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
|
||||
@@ -33,8 +33,6 @@ import { useGanttChart } from "../hooks/use-gantt-chart";
|
||||
type Props = {
|
||||
blockIds: string[];
|
||||
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||
canLoadMoreBlocks?: boolean;
|
||||
loadMoreBlocks?: () => void;
|
||||
blockToRender: (data: any) => React.ReactNode;
|
||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||
bottomSpacing: boolean;
|
||||
@@ -56,7 +54,6 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
||||
const {
|
||||
blockIds,
|
||||
getBlockById,
|
||||
loadMoreBlocks,
|
||||
blockToRender,
|
||||
blockUpdateHandler,
|
||||
bottomSpacing,
|
||||
@@ -70,7 +67,6 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
||||
showAllBlocks,
|
||||
sidebarToRender,
|
||||
title,
|
||||
canLoadMoreBlocks,
|
||||
updateCurrentViewRenderPayload,
|
||||
quickAdd,
|
||||
} = props;
|
||||
@@ -145,38 +141,36 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
||||
onScroll={onScroll}
|
||||
>
|
||||
<GanttChartSidebar
|
||||
blockIds={blockIds}
|
||||
getBlockById={getBlockById}
|
||||
loadMoreBlocks={loadMoreBlocks}
|
||||
canLoadMoreBlocks={canLoadMoreBlocks}
|
||||
ganttContainerRef={ganttContainerRef}
|
||||
blockUpdateHandler={blockUpdateHandler}
|
||||
enableReorder={enableReorder}
|
||||
enableSelection={enableSelection}
|
||||
sidebarToRender={sidebarToRender}
|
||||
title={title}
|
||||
quickAdd={quickAdd}
|
||||
selectionHelpers={helpers}
|
||||
/>
|
||||
<div className="relative min-h-full h-max flex-shrink-0 flex-grow">
|
||||
<ActiveChartView />
|
||||
{currentViewData && (
|
||||
<GanttChartBlocksList
|
||||
itemsContainerWidth={itemsContainerWidth}
|
||||
blockIds={blockIds}
|
||||
getBlockById={getBlockById}
|
||||
blockToRender={blockToRender}
|
||||
blockUpdateHandler={blockUpdateHandler}
|
||||
enableBlockLeftResize={enableBlockLeftResize}
|
||||
enableBlockRightResize={enableBlockRightResize}
|
||||
enableBlockMove={enableBlockMove}
|
||||
enableAddBlock={enableAddBlock}
|
||||
ganttContainerRef={ganttContainerRef}
|
||||
showAllBlocks={showAllBlocks}
|
||||
selectionHelpers={helpers}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
blockIds={blockIds}
|
||||
getBlockById={getBlockById}
|
||||
ganttContainerRef={ganttContainerRef}
|
||||
blockUpdateHandler={blockUpdateHandler}
|
||||
enableReorder={enableReorder}
|
||||
enableSelection={enableSelection}
|
||||
sidebarToRender={sidebarToRender}
|
||||
title={title}
|
||||
quickAdd={quickAdd}
|
||||
selectionHelpers={helpers}
|
||||
/>
|
||||
<div className="relative min-h-full h-max flex-shrink-0 flex-grow">
|
||||
<ActiveChartView />
|
||||
{currentViewData && (
|
||||
<GanttChartBlocksList
|
||||
itemsContainerWidth={itemsContainerWidth}
|
||||
blockIds={blockIds}
|
||||
getBlockById={getBlockById}
|
||||
blockToRender={blockToRender}
|
||||
blockUpdateHandler={blockUpdateHandler}
|
||||
enableBlockLeftResize={enableBlockLeftResize}
|
||||
enableBlockRightResize={enableBlockRightResize}
|
||||
enableBlockMove={enableBlockMove}
|
||||
enableAddBlock={enableAddBlock}
|
||||
ganttContainerRef={ganttContainerRef}
|
||||
showAllBlocks={showAllBlocks}
|
||||
selectionHelpers={helpers}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<IssueBulkOperationsRoot selectionHelpers={helpers} />
|
||||
</>
|
||||
|
||||
@@ -32,8 +32,6 @@ type ChartViewRootProps = {
|
||||
bottomSpacing: boolean;
|
||||
showAllBlocks: boolean;
|
||||
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||
loadMoreBlocks?: () => void;
|
||||
canLoadMoreBlocks?: boolean;
|
||||
quickAdd?: React.JSX.Element | undefined;
|
||||
};
|
||||
|
||||
@@ -43,12 +41,10 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
||||
title,
|
||||
blockIds,
|
||||
getBlockById,
|
||||
loadMoreBlocks,
|
||||
loaderTitle,
|
||||
blockUpdateHandler,
|
||||
sidebarToRender,
|
||||
blockToRender,
|
||||
canLoadMoreBlocks,
|
||||
enableBlockLeftResize,
|
||||
enableBlockRightResize,
|
||||
enableBlockMove,
|
||||
@@ -165,8 +161,6 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
||||
<GanttChartMainContent
|
||||
blockIds={blockIds}
|
||||
getBlockById={getBlockById}
|
||||
loadMoreBlocks={loadMoreBlocks}
|
||||
canLoadMoreBlocks={canLoadMoreBlocks}
|
||||
blockToRender={blockToRender}
|
||||
blockUpdateHandler={blockUpdateHandler}
|
||||
bottomSpacing={bottomSpacing}
|
||||
|
||||
@@ -14,8 +14,6 @@ type GanttChartRootProps = {
|
||||
sidebarToRender: (props: any) => React.ReactNode;
|
||||
quickAdd?: React.JSX.Element | undefined;
|
||||
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||
canLoadMoreBlocks?: boolean;
|
||||
loadMoreBlocks?: () => void;
|
||||
enableBlockLeftResize?: boolean;
|
||||
enableBlockRightResize?: boolean;
|
||||
enableBlockMove?: boolean;
|
||||
@@ -36,8 +34,6 @@ export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
|
||||
sidebarToRender,
|
||||
blockToRender,
|
||||
getBlockById,
|
||||
loadMoreBlocks,
|
||||
canLoadMoreBlocks,
|
||||
enableBlockLeftResize = false,
|
||||
enableBlockRightResize = false,
|
||||
enableBlockMove = false,
|
||||
@@ -56,8 +52,6 @@ export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
|
||||
title={title}
|
||||
blockIds={blockIds}
|
||||
getBlockById={getBlockById}
|
||||
loadMoreBlocks={loadMoreBlocks}
|
||||
canLoadMoreBlocks={canLoadMoreBlocks}
|
||||
loaderTitle={loaderTitle}
|
||||
blockUpdateHandler={blockUpdateHandler}
|
||||
sidebarToRender={sidebarToRender}
|
||||
|
||||
@@ -18,8 +18,6 @@ import { IssuesSidebarBlock } from "./block";
|
||||
type Props = {
|
||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||
getBlockById: (id: string) => IGanttBlock;
|
||||
canLoadMoreBlocks?: boolean;
|
||||
loadMoreBlocks?: () => void;
|
||||
ganttContainerRef: RefObject<HTMLDivElement>;
|
||||
blockIds: string[];
|
||||
enableReorder: boolean;
|
||||
@@ -35,28 +33,10 @@ export const IssueGanttSidebar: React.FC<Props> = observer((props) => {
|
||||
getBlockById,
|
||||
enableReorder,
|
||||
enableSelection,
|
||||
loadMoreBlocks,
|
||||
canLoadMoreBlocks,
|
||||
ganttContainerRef,
|
||||
showAllBlocks = false,
|
||||
selectionHelpers,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
issues: { getIssueLoader },
|
||||
} = useIssuesStore();
|
||||
|
||||
const [intersectionElement, setIntersectionElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const isPaginating = !!getIssueLoader();
|
||||
|
||||
useIntersectionObserver(
|
||||
ganttContainerRef,
|
||||
isPaginating ? null : intersectionElement,
|
||||
loadMoreBlocks,
|
||||
"100% 0% 100% 0%"
|
||||
);
|
||||
|
||||
const handleOnDrop = (
|
||||
draggingBlockId: string | undefined,
|
||||
droppedBlockId: string | undefined,
|
||||
@@ -95,11 +75,6 @@ export const IssueGanttSidebar: React.FC<Props> = observer((props) => {
|
||||
</GanttDnDHOC>
|
||||
);
|
||||
})}
|
||||
{canLoadMoreBlocks && (
|
||||
<div ref={setIntersectionElement} className="p-2">
|
||||
<div className="flex h-10 md:h-8 w-full items-center justify-between gap-1.5 rounded md:px-1 px-4 py-1.5 bg-custom-background-80 animate-pulse" />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Loader className="space-y-3 pr-2">
|
||||
|
||||
@@ -13,8 +13,6 @@ import { GANTT_SELECT_GROUP, HEADER_HEIGHT, SIDEBAR_WIDTH } from "../constants";
|
||||
type Props = {
|
||||
blockIds: string[];
|
||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||
canLoadMoreBlocks?: boolean;
|
||||
loadMoreBlocks?: () => void;
|
||||
ganttContainerRef: RefObject<HTMLDivElement>;
|
||||
enableReorder: boolean;
|
||||
enableSelection: boolean;
|
||||
@@ -33,8 +31,6 @@ export const GanttChartSidebar: React.FC<Props> = observer((props) => {
|
||||
enableSelection,
|
||||
sidebarToRender,
|
||||
getBlockById,
|
||||
loadMoreBlocks,
|
||||
canLoadMoreBlocks,
|
||||
ganttContainerRef,
|
||||
title,
|
||||
quickAdd,
|
||||
@@ -91,10 +87,8 @@ export const GanttChartSidebar: React.FC<Props> = observer((props) => {
|
||||
getBlockById,
|
||||
enableReorder,
|
||||
enableSelection,
|
||||
canLoadMoreBlocks,
|
||||
ganttContainerRef,
|
||||
loadMoreBlocks,
|
||||
selectionHelpers
|
||||
selectionHelpers,
|
||||
})}
|
||||
</div>
|
||||
{quickAdd ? quickAdd : null}
|
||||
|
||||
@@ -44,17 +44,8 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { issues, issuesFilter, issueMap } = useIssues(storeType);
|
||||
const {
|
||||
fetchIssues,
|
||||
fetchNextIssues,
|
||||
quickAddIssue,
|
||||
updateIssue,
|
||||
removeIssue,
|
||||
removeIssueFromView,
|
||||
archiveIssue,
|
||||
restoreIssue,
|
||||
updateFilters,
|
||||
} = useIssuesActions(storeType);
|
||||
const { quickAddIssue, updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue, updateFilters } =
|
||||
useIssuesActions(storeType);
|
||||
|
||||
const issueCalendarView = useCalendarView();
|
||||
|
||||
@@ -64,25 +55,6 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
||||
|
||||
const groupedIssueIds = (issues.groupedIssueIds ?? {}) as TGroupedIssues;
|
||||
|
||||
const layout = displayFilters?.calendar?.layout ?? "month";
|
||||
const { startDate, endDate } = issueCalendarView.getStartAndEndDate(layout) ?? {};
|
||||
|
||||
useEffect(() => {
|
||||
startDate &&
|
||||
endDate &&
|
||||
layout &&
|
||||
fetchIssues(
|
||||
"init-loader",
|
||||
{
|
||||
canGroup: true,
|
||||
perPageCount: layout === "month" ? 4 : 30,
|
||||
before: endDate,
|
||||
after: startDate,
|
||||
groupedBy: EIssueGroupByToServerOptions["target_date"],
|
||||
},
|
||||
viewId
|
||||
);
|
||||
}, [fetchIssues, storeType, startDate, endDate, layout, viewId]);
|
||||
|
||||
const handleDragAndDrop = async (
|
||||
issueId: string | undefined,
|
||||
@@ -107,23 +79,6 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
||||
});
|
||||
};
|
||||
|
||||
const loadMoreIssues = useCallback(
|
||||
(dateString: string) => {
|
||||
fetchNextIssues(dateString);
|
||||
},
|
||||
[fetchNextIssues]
|
||||
);
|
||||
|
||||
const getPaginationData = useCallback(
|
||||
(groupId: string | undefined) => issues?.getPaginationData(groupId, undefined),
|
||||
[issues?.getPaginationData]
|
||||
);
|
||||
|
||||
const getGroupIssueCount = useCallback(
|
||||
(groupId: string | undefined) => issues?.getGroupIssueCount(groupId, undefined, false),
|
||||
[issues?.getGroupIssueCount]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-full w-full overflow-hidden bg-custom-background-100 pt-4">
|
||||
@@ -148,9 +103,6 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
||||
placements={placement}
|
||||
/>
|
||||
)}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
getPaginationData={getPaginationData}
|
||||
getGroupIssueCount={getGroupIssueCount}
|
||||
addIssuesToView={addIssuesToView}
|
||||
quickAddCallback={quickAddIssue}
|
||||
readOnly={!isEditingAllowed || isCompletedCycle}
|
||||
|
||||
@@ -46,9 +46,6 @@ type Props = {
|
||||
layout: "month" | "week" | undefined;
|
||||
showWeekends: boolean;
|
||||
issueCalendarView: ICalendarStore;
|
||||
loadMoreIssues: (dateString: string) => void;
|
||||
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
|
||||
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
|
||||
quickActions: TRenderQuickActions;
|
||||
handleDragAndDrop: (
|
||||
@@ -73,13 +70,10 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||
layout,
|
||||
showWeekends,
|
||||
issueCalendarView,
|
||||
loadMoreIssues,
|
||||
handleDragAndDrop,
|
||||
quickActions,
|
||||
quickAddCallback,
|
||||
addIssuesToView,
|
||||
getPaginationData,
|
||||
getGroupIssueCount,
|
||||
updateFilters,
|
||||
readOnly = false,
|
||||
} = props;
|
||||
@@ -159,9 +153,6 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||
week={week}
|
||||
issues={issues}
|
||||
groupedIssueIds={groupedIssueIds}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
getPaginationData={getPaginationData}
|
||||
getGroupIssueCount={getGroupIssueCount}
|
||||
enableQuickIssueCreate
|
||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
||||
quickActions={quickActions}
|
||||
@@ -181,9 +172,6 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||
week={issueCalendarView.allDaysOfActiveWeek}
|
||||
issues={issues}
|
||||
groupedIssueIds={groupedIssueIds}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
getPaginationData={getPaginationData}
|
||||
getGroupIssueCount={getGroupIssueCount}
|
||||
enableQuickIssueCreate
|
||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
||||
quickActions={quickActions}
|
||||
@@ -204,9 +192,6 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||
<CalendarIssueBlocks
|
||||
date={selectedDate}
|
||||
issueIdList={issueIdList}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
getPaginationData={getPaginationData}
|
||||
getGroupIssueCount={getGroupIssueCount}
|
||||
quickActions={quickActions}
|
||||
enableQuickIssueCreate
|
||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
||||
@@ -231,9 +216,6 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||
date={selectedDate}
|
||||
issueIdList={issueIdList}
|
||||
quickActions={quickActions}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
getPaginationData={getPaginationData}
|
||||
getGroupIssueCount={getGroupIssueCount}
|
||||
enableQuickIssueCreate
|
||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
||||
quickAddCallback={quickAddCallback}
|
||||
|
||||
@@ -29,9 +29,6 @@ type Props = {
|
||||
date: ICalendarDate;
|
||||
issues: TIssueMap | undefined;
|
||||
groupedIssueIds: TGroupedIssues;
|
||||
loadMoreIssues: (dateString: string) => void;
|
||||
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
|
||||
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
disableIssueCreation?: boolean;
|
||||
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
|
||||
@@ -53,9 +50,6 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
||||
date,
|
||||
issues,
|
||||
groupedIssueIds,
|
||||
loadMoreIssues,
|
||||
getPaginationData,
|
||||
getGroupIssueCount,
|
||||
quickActions,
|
||||
enableQuickIssueCreate,
|
||||
disableIssueCreation,
|
||||
@@ -167,9 +161,6 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
||||
date={date.date}
|
||||
issueIdList={issueIds}
|
||||
quickActions={quickActions}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
getPaginationData={getPaginationData}
|
||||
getGroupIssueCount={getGroupIssueCount}
|
||||
isDragDisabled={readOnly}
|
||||
addIssuesToView={addIssuesToView}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
|
||||
@@ -6,13 +6,11 @@ import { CalendarQuickAddIssueForm, CalendarIssueBlockRoot } from "@/components/
|
||||
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
import { useIssuesStore } from "@/hooks/use-issue-layout-store";
|
||||
import { TRenderQuickActions } from "../list/list-view-types";
|
||||
import { useState } from "react";
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
date: Date;
|
||||
loadMoreIssues: (dateString: string) => void;
|
||||
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
|
||||
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||
issueIdList: string[];
|
||||
quickActions: TRenderQuickActions;
|
||||
isDragDisabled?: boolean;
|
||||
@@ -29,7 +27,6 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||
date,
|
||||
issueIdList,
|
||||
quickActions,
|
||||
loadMoreIssues,
|
||||
isDragDisabled = false,
|
||||
enableQuickIssueCreate,
|
||||
disableIssueCreation,
|
||||
@@ -40,24 +37,17 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||
} = props;
|
||||
const formattedDatePayload = renderFormattedPayloadDate(date);
|
||||
|
||||
const {
|
||||
issues: { getGroupIssueCount, getPaginationData, getIssueLoader },
|
||||
} = useIssuesStore();
|
||||
const [isExpanded, setIsExpanded] = useState(isMobileView);
|
||||
|
||||
if (!formattedDatePayload) return null;
|
||||
|
||||
const dayIssueCount = getGroupIssueCount(formattedDatePayload, undefined, false);
|
||||
const nextPageResults = getPaginationData(formattedDatePayload, undefined)?.nextPageResults;
|
||||
const isPaginating = !!getIssueLoader(formattedDatePayload);
|
||||
const dayIssueCount = issueIdList?.length ?? 0;
|
||||
|
||||
const shouldLoadMore =
|
||||
nextPageResults === undefined && dayIssueCount !== undefined
|
||||
? issueIdList?.length < dayIssueCount
|
||||
: !!nextPageResults;
|
||||
const currentIssueIds = isExpanded ? issueIdList : issueIdList?.slice(0, 4);
|
||||
|
||||
return (
|
||||
<>
|
||||
{issueIdList?.map((issueId) => (
|
||||
{currentIssueIds?.map((issueId) => (
|
||||
<div key={issueId} className="relative cursor-pointer p-1 px-2">
|
||||
<CalendarIssueBlockRoot
|
||||
issueId={issueId}
|
||||
@@ -67,12 +57,6 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
))}
|
||||
|
||||
{isPaginating && (
|
||||
<div className="p-1 px-2">
|
||||
<div className="flex h-10 md:h-8 w-full items-center justify-between gap-1.5 rounded md:px-1 px-4 py-1.5 bg-custom-background-80 animate-pulse" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{enableQuickIssueCreate && !disableIssueCreation && !readOnly && (
|
||||
<div className="border-b border-custom-border-200 px-1 py-1 md:border-none md:px-2">
|
||||
<CalendarQuickAddIssueForm
|
||||
@@ -87,12 +71,12 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shouldLoadMore && !isPaginating && (
|
||||
{!isExpanded && dayIssueCount > 4 && (
|
||||
<div className="flex items-center px-2.5 py-1">
|
||||
<button
|
||||
type="button"
|
||||
className="w-min whitespace-nowrap rounded text-xs px-1.5 py-1 font-medium hover:bg-custom-background-80 text-custom-primary-100 hover:text-custom-primary-200"
|
||||
onClick={() => loadMoreIssues(formattedDatePayload)}
|
||||
onClick={() => setIsExpanded(true)}
|
||||
>
|
||||
Load More
|
||||
</button>
|
||||
|
||||
@@ -18,9 +18,6 @@ type Props = {
|
||||
groupedIssueIds: TGroupedIssues;
|
||||
week: ICalendarWeek | undefined;
|
||||
quickActions: TRenderQuickActions;
|
||||
loadMoreIssues: (dateString: string) => void;
|
||||
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
|
||||
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
disableIssueCreation?: boolean;
|
||||
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
|
||||
@@ -42,9 +39,6 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
|
||||
groupedIssueIds,
|
||||
handleDragAndDrop,
|
||||
week,
|
||||
loadMoreIssues,
|
||||
getPaginationData,
|
||||
getGroupIssueCount,
|
||||
quickActions,
|
||||
enableQuickIssueCreate,
|
||||
disableIssueCreation,
|
||||
@@ -78,9 +72,6 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
|
||||
date={date}
|
||||
issues={issues}
|
||||
groupedIssueIds={groupedIssueIds}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
getPaginationData={getPaginationData}
|
||||
getGroupIssueCount={getGroupIssueCount}
|
||||
quickActions={quickActions}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane constants
|
||||
import { ALL_ISSUES } from "@plane/constants";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { TIssue, TUngroupedIssues } from "@plane/types";
|
||||
// hooks
|
||||
import { ChartDataType, GanttChartRoot, IBlockUpdateData, IssueGanttSidebar } from "@/components/gantt-chart";
|
||||
import { getMonthChartItemPositionWidthInMonth } from "@/components/gantt-chart/views";
|
||||
@@ -48,12 +48,7 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
||||
// plane web hooks
|
||||
const isBulkOperationsEnabled = useBulkOperationStatus();
|
||||
|
||||
useEffect(() => {
|
||||
fetchIssues("init-loader", { canGroup: false, perPageCount: 100 }, viewId);
|
||||
}, [fetchIssues, storeType, viewId]);
|
||||
|
||||
const issuesIds = (issues.groupedIssueIds?.[ALL_ISSUES] as string[]) ?? [];
|
||||
const nextPageResults = issues.getPaginationData(undefined, undefined)?.nextPageResults;
|
||||
const issuesIds = (issues.groupedIssueIds as TUngroupedIssues) ?? [];
|
||||
|
||||
const { enableIssueCreation } = issues?.viewFlags || {};
|
||||
|
||||
@@ -111,8 +106,6 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
||||
enableAddBlock={isAllowed}
|
||||
enableSelection={isBulkOperationsEnabled && isAllowed}
|
||||
quickAdd={quickAdd}
|
||||
loadMoreBlocks={loadMoreIssues}
|
||||
canLoadMoreBlocks={nextPageResults}
|
||||
showAllBlocks
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -40,13 +40,13 @@ export const IssueLayoutHOC = observer((props: Props) => {
|
||||
const storeType = useIssueStoreType();
|
||||
const { issues } = useIssues(storeType);
|
||||
|
||||
const issueCount = issues.getGroupIssueCount(undefined, undefined, false);
|
||||
const issueCount = issues.issueIds?.length;
|
||||
|
||||
if (issues?.getIssueLoader() === "init-loader" || issueCount === undefined) {
|
||||
return <ActiveLoader layout={layout} />;
|
||||
}
|
||||
|
||||
if (issues.getGroupIssueCount(undefined, undefined, false) === 0 && layout !== EIssueLayoutTypes.CALENDAR) {
|
||||
if (issueCount === 0 && layout !== EIssueLayoutTypes.CALENDAR) {
|
||||
return <IssueLayoutEmptyState storeType={storeType} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ import { IQuickActionProps, TRenderQuickActions } from "../list/list-view-types"
|
||||
import { getSourceFromDropPayload } from "../utils";
|
||||
import { KanBan } from "./default";
|
||||
import { KanBanSwimLanes } from "./swimlanes";
|
||||
import { TGroupedIssues, TSubGroupedIssues } from "@plane/types";
|
||||
import useSWR from "swr";
|
||||
|
||||
export type KanbanStoreType =
|
||||
| EIssuesStoreType.PROJECT
|
||||
@@ -57,17 +59,8 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
const {
|
||||
issue: { getIssueById },
|
||||
} = useIssueDetail();
|
||||
const {
|
||||
fetchIssues,
|
||||
fetchNextIssues,
|
||||
quickAddIssue,
|
||||
updateIssue,
|
||||
removeIssue,
|
||||
removeIssueFromView,
|
||||
archiveIssue,
|
||||
restoreIssue,
|
||||
updateFilters,
|
||||
} = useIssuesActions(storeType);
|
||||
const { quickAddIssue, updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue, updateFilters } =
|
||||
useIssuesActions(storeType);
|
||||
|
||||
const deleteAreaRef = useRef<HTMLDivElement | null>(null);
|
||||
const [isDragOverDelete, setIsDragOverDelete] = useState(false);
|
||||
@@ -82,26 +75,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
|
||||
const orderBy = displayFilters?.order_by;
|
||||
|
||||
useEffect(() => {
|
||||
fetchIssues("init-loader", { canGroup: true, perPageCount: sub_group_by ? 10 : 30 }, viewId);
|
||||
}, [fetchIssues, storeType, group_by, sub_group_by, viewId]);
|
||||
|
||||
const fetchMoreIssues = useCallback(
|
||||
(groupId?: string, subgroupId?: string) => {
|
||||
if (issues?.getIssueLoader(groupId, subgroupId) !== "pagination") {
|
||||
fetchNextIssues(groupId, subgroupId);
|
||||
}
|
||||
},
|
||||
[fetchNextIssues]
|
||||
);
|
||||
|
||||
const debouncedFetchMoreIssues = debounce(
|
||||
(groupId?: string, subgroupId?: string) => fetchMoreIssues(groupId, subgroupId),
|
||||
300,
|
||||
{ leading: true, trailing: false }
|
||||
);
|
||||
|
||||
const groupedIssueIds = issues?.groupedIssueIds;
|
||||
const groupedIssueIds = issues?.groupedIssueIds as TSubGroupedIssues & TGroupedIssues;
|
||||
|
||||
const userDisplayFilters = displayFilters || null;
|
||||
|
||||
@@ -257,7 +231,6 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
<KanBanView
|
||||
issuesMap={issueMap}
|
||||
groupedIssueIds={groupedIssueIds ?? {}}
|
||||
getGroupIssueCount={issues.getGroupIssueCount}
|
||||
displayProperties={displayProperties}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
@@ -274,7 +247,6 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
addIssuesToView={addIssuesToView}
|
||||
scrollableContainerRef={scrollableContainerRef}
|
||||
handleOnDrop={handleOnDrop}
|
||||
loadMoreIssues={debouncedFetchMoreIssues}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -32,6 +32,7 @@ interface IssueBlockProps {
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
draggableId: string;
|
||||
canDropOverIssue: boolean;
|
||||
shouldRenderByDefault?: boolean;
|
||||
updateIssue: ((projectId: string | null, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
||||
quickActions: TRenderQuickActions;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
@@ -114,6 +115,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
||||
updateIssue,
|
||||
quickActions,
|
||||
canEditProperties,
|
||||
shouldRenderByDefault,
|
||||
scrollableContainerRef,
|
||||
} = props;
|
||||
|
||||
@@ -226,6 +228,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
||||
defaultHeight="100px"
|
||||
horizontalOffset={100}
|
||||
verticalOffset={200}
|
||||
shouldRenderByDefault={shouldRenderByDefault}
|
||||
>
|
||||
<KanbanIssueDetailsBlock
|
||||
cardRef={cardRef}
|
||||
|
||||
@@ -37,7 +37,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = observer((p
|
||||
<>
|
||||
{issueIds && issueIds.length > 0 ? (
|
||||
<>
|
||||
{issueIds.map((issueId) => {
|
||||
{issueIds.slice(0, 30).map((issueId, index) => {
|
||||
if (!issueId) return null;
|
||||
|
||||
let draggableId = issueId;
|
||||
@@ -50,6 +50,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = observer((p
|
||||
issueId={issueId}
|
||||
groupId={groupId}
|
||||
subGroupId={sub_group_id}
|
||||
shouldRenderByDefault={index <= 10}
|
||||
issuesMap={issuesMap}
|
||||
displayProperties={displayProperties}
|
||||
updateIssue={updateIssue}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
TIssue,
|
||||
IIssueDisplayProperties,
|
||||
IIssueMap,
|
||||
TSubGroupedIssues,
|
||||
TIssueKanbanFilters,
|
||||
TIssueGroupByOptions,
|
||||
TIssueOrderByOptions,
|
||||
@@ -26,12 +25,7 @@ import { KanbanGroup } from "./kanban-group";
|
||||
|
||||
export interface IKanBan {
|
||||
issuesMap: IIssueMap;
|
||||
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
|
||||
getGroupIssueCount: (
|
||||
groupId: string | undefined,
|
||||
subGroupId: string | undefined,
|
||||
isSubGroupCumulative: boolean
|
||||
) => number | undefined;
|
||||
groupedIssueIds: TGroupedIssues;
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
sub_group_by: TIssueGroupByOptions | undefined;
|
||||
group_by: TIssueGroupByOptions | undefined;
|
||||
@@ -43,7 +37,6 @@ export interface IKanBan {
|
||||
quickActions: TRenderQuickActions;
|
||||
kanbanFilters: TIssueKanbanFilters;
|
||||
handleKanbanFilters: any;
|
||||
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
|
||||
disableIssueCreation?: boolean;
|
||||
@@ -58,7 +51,6 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
const {
|
||||
issuesMap,
|
||||
groupedIssueIds,
|
||||
getGroupIssueCount,
|
||||
displayProperties,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
@@ -69,7 +61,6 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
handleKanbanFilters,
|
||||
enableQuickIssueCreate,
|
||||
quickAddCallback,
|
||||
loadMoreIssues,
|
||||
disableIssueCreation,
|
||||
addIssuesToView,
|
||||
canEditProperties,
|
||||
@@ -108,13 +99,14 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
if (!list) return null;
|
||||
|
||||
const visibilityGroupBy = (_list: IGroupByColumn): { showGroup: boolean; showIssues: boolean } => {
|
||||
const count = Array.isArray(groupedIssueIds?.[_list.id]) ? groupedIssueIds?.[_list.id].length : 0;
|
||||
if (sub_group_by) {
|
||||
const groupVisibility = {
|
||||
showGroup: true,
|
||||
showIssues: true,
|
||||
};
|
||||
if (!showEmptyGroup) {
|
||||
groupVisibility.showGroup = (getGroupIssueCount(_list.id, undefined, false) ?? 0) > 0;
|
||||
groupVisibility.showGroup = count > 0;
|
||||
}
|
||||
return groupVisibility;
|
||||
} else {
|
||||
@@ -123,7 +115,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
showIssues: true,
|
||||
};
|
||||
if (!showEmptyGroup) {
|
||||
if ((getGroupIssueCount(_list.id, undefined, false) ?? 0) > 0) groupVisibility.showGroup = true;
|
||||
if (count > 0) groupVisibility.showGroup = true;
|
||||
else groupVisibility.showGroup = false;
|
||||
}
|
||||
if (kanbanFilters?.group_by.includes(_list.id)) groupVisibility.showIssues = false;
|
||||
@@ -137,13 +129,13 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
<div className={`relative w-full flex gap-2 px-2 ${sub_group_by ? "h-full" : "h-full"}`}>
|
||||
{list &&
|
||||
list.length > 0 &&
|
||||
list.map((subList: IGroupByColumn) => {
|
||||
const groupByVisibilityToggle = visibilityGroupBy(subList);
|
||||
list.map((group: IGroupByColumn) => {
|
||||
const groupByVisibilityToggle = visibilityGroupBy(group);
|
||||
|
||||
if (groupByVisibilityToggle.showGroup === false) return <></>;
|
||||
return (
|
||||
<div
|
||||
key={subList.id}
|
||||
key={group.id}
|
||||
className={`group relative flex flex-shrink-0 flex-col ${
|
||||
groupByVisibilityToggle.showIssues ? `w-[350px]` : ``
|
||||
} `}
|
||||
@@ -153,11 +145,11 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={subList.id}
|
||||
icon={subList.icon}
|
||||
title={subList.name}
|
||||
count={getGroupIssueCount(subList.id, undefined, false) ?? 0}
|
||||
issuePayload={subList.payload}
|
||||
column_id={group.id}
|
||||
icon={group.icon}
|
||||
title={group.name}
|
||||
count={Array.isArray(groupedIssueIds?.[group.id]) ? groupedIssueIds?.[group.id].length : 0}
|
||||
issuePayload={group.payload}
|
||||
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy}
|
||||
addIssuesToView={addIssuesToView}
|
||||
kanbanFilters={kanbanFilters}
|
||||
@@ -168,17 +160,17 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
|
||||
{groupByVisibilityToggle.showIssues && (
|
||||
<KanbanGroup
|
||||
groupId={subList.id}
|
||||
groupId={group.id}
|
||||
issuesMap={issuesMap}
|
||||
groupedIssueIds={groupedIssueIds}
|
||||
issueIds={groupedIssueIds?.[group.id] ?? []}
|
||||
displayProperties={displayProperties}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
orderBy={orderBy}
|
||||
sub_group_id={sub_group_id}
|
||||
isDragDisabled={isDragDisabled}
|
||||
isDropDisabled={!!subList.isDropDisabled || !!isDropDisabled}
|
||||
dropErrorMessage={subList.dropErrorMessage ?? dropErrorMessage}
|
||||
isDropDisabled={!!group.isDropDisabled || !!isDropDisabled}
|
||||
dropErrorMessage={group.dropErrorMessage ?? dropErrorMessage}
|
||||
updateIssue={updateIssue}
|
||||
quickActions={quickActions}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
@@ -186,7 +178,6 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
canEditProperties={canEditProperties}
|
||||
scrollableContainerRef={scrollableContainerRef}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
handleOnDrop={handleOnDrop}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
TSubGroupedIssues,
|
||||
TIssueGroupByOptions,
|
||||
TIssueOrderByOptions,
|
||||
TUngroupedIssues,
|
||||
} from "@plane/types";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { highlightIssueOnDrop } from "@/components/issues/issue-layouts/utils";
|
||||
@@ -32,7 +33,7 @@ import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from ".";
|
||||
interface IKanbanGroup {
|
||||
groupId: string;
|
||||
issuesMap: IIssueMap;
|
||||
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
|
||||
issueIds: TUngroupedIssues;
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
sub_group_by: TIssueGroupByOptions | undefined;
|
||||
group_by: TIssueGroupByOptions | undefined;
|
||||
@@ -44,7 +45,6 @@ interface IKanbanGroup {
|
||||
quickActions: TRenderQuickActions;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
|
||||
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
|
||||
disableIssueCreation?: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
groupByVisibilityToggle?: boolean;
|
||||
@@ -62,13 +62,12 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
||||
sub_group_by,
|
||||
issuesMap,
|
||||
displayProperties,
|
||||
groupedIssueIds,
|
||||
issueIds,
|
||||
isDropDisabled,
|
||||
dropErrorMessage,
|
||||
updateIssue,
|
||||
quickActions,
|
||||
canEditProperties,
|
||||
loadMoreIssues,
|
||||
enableQuickIssueCreate,
|
||||
disableIssueCreation,
|
||||
quickAddCallback,
|
||||
@@ -79,26 +78,11 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
||||
const projectState = useProjectState();
|
||||
|
||||
const {
|
||||
issues: { getGroupIssueCount, getPaginationData, getIssueLoader },
|
||||
issues: { getIssueLoader },
|
||||
} = useIssuesStore();
|
||||
|
||||
const [intersectionElement, setIntersectionElement] = useState<HTMLSpanElement | null>(null);
|
||||
const columnRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const containerRef = sub_group_by && scrollableContainerRef ? scrollableContainerRef : columnRef;
|
||||
|
||||
const loadMoreIssuesInThisGroup = useCallback(() => {
|
||||
loadMoreIssues(groupId, sub_group_id === "null" ? undefined : sub_group_id);
|
||||
}, [loadMoreIssues, groupId, sub_group_id]);
|
||||
|
||||
const isPaginating = !!getIssueLoader(groupId, sub_group_id);
|
||||
|
||||
useIntersectionObserver(
|
||||
containerRef,
|
||||
isPaginating ? null : intersectionElement,
|
||||
loadMoreIssuesInThisGroup,
|
||||
`0% 100% 100% 100%`
|
||||
);
|
||||
const [isDraggingOverColumn, setIsDraggingOverColumn] = useState(false);
|
||||
|
||||
// Enable Kanban Columns as Drop Targets
|
||||
@@ -214,27 +198,8 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
||||
|
||||
const isSubGroup = !!sub_group_id && sub_group_id !== "null";
|
||||
|
||||
const issueIds = isSubGroup
|
||||
? (groupedIssueIds as TSubGroupedIssues)?.[groupId]?.[sub_group_id] ?? []
|
||||
: (groupedIssueIds as TGroupedIssues)?.[groupId] ?? [];
|
||||
const groupIssueCount = Array.isArray(issueIds) ? issueIds.lastIndexOf : 0;
|
||||
|
||||
const groupIssueCount = getGroupIssueCount(groupId, sub_group_id, false) ?? 0;
|
||||
|
||||
const nextPageResults = getPaginationData(groupId, sub_group_id)?.nextPageResults;
|
||||
|
||||
const loadMore = isPaginating ? (
|
||||
<KanbanIssueBlockLoader />
|
||||
) : (
|
||||
<div
|
||||
className="w-full sticky bottom-0 p-3 text-sm font-medium text-custom-primary-100 hover:text-custom-primary-200 hover:underline cursor-pointer"
|
||||
onClick={loadMoreIssuesInThisGroup}
|
||||
>
|
||||
{" "}
|
||||
Load More ↓
|
||||
</div>
|
||||
);
|
||||
|
||||
const shouldLoadMore = nextPageResults === undefined ? issueIds?.length < groupIssueCount : !!nextPageResults;
|
||||
const canOverlayBeVisible = orderBy !== "sort_order" || isDropDisabled;
|
||||
const shouldOverlayBeVisible = isDraggingOverColumn && canOverlayBeVisible;
|
||||
|
||||
@@ -269,8 +234,6 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
||||
canDropOverIssue={!canOverlayBeVisible}
|
||||
/>
|
||||
|
||||
{shouldLoadMore && (isSubGroup ? <>{loadMore}</> : <KanbanIssueBlockLoader ref={setIntersectionElement} />)}
|
||||
|
||||
{enableQuickIssueCreate && !disableIssueCreation && (
|
||||
<div className="w-full bg-custom-background-90 py-0.5 sticky bottom-0">
|
||||
<KanBanQuickAddIssueForm
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
TIssueKanbanFilters,
|
||||
TIssueGroupByOptions,
|
||||
TIssueOrderByOptions,
|
||||
TIssues,
|
||||
} from "@plane/types";
|
||||
// hooks
|
||||
import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "@/hooks/store";
|
||||
@@ -25,11 +26,7 @@ import { HeaderSubGroupByCard } from "./headers/sub-group-by-card";
|
||||
// constants
|
||||
|
||||
interface ISubGroupSwimlaneHeader {
|
||||
getGroupIssueCount: (
|
||||
groupId: string | undefined,
|
||||
subGroupId: string | undefined,
|
||||
isSubGroupCumulative: boolean
|
||||
) => number | undefined;
|
||||
getSubIssuesCount: (subGroupId: string) => number;
|
||||
sub_group_by: TIssueGroupByOptions | undefined;
|
||||
group_by: TIssueGroupByOptions | undefined;
|
||||
list: IGroupByColumn[];
|
||||
@@ -51,12 +48,12 @@ const visibilitySubGroupByGroupCount = (subGroupIssueCount: number, showEmptyGro
|
||||
};
|
||||
|
||||
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
|
||||
({ getGroupIssueCount, sub_group_by, group_by, list, kanbanFilters, handleKanbanFilters, showEmptyGroup }) => (
|
||||
({ getSubIssuesCount, sub_group_by, group_by, list, kanbanFilters, handleKanbanFilters, showEmptyGroup }) => (
|
||||
<div className="relative flex h-max min-h-full w-full items-center gap-2">
|
||||
{list &&
|
||||
list.length > 0 &&
|
||||
list.map((_list: IGroupByColumn) => {
|
||||
const groupCount = getGroupIssueCount(_list?.id, undefined, false) ?? 0;
|
||||
const groupCount = getSubIssuesCount(_list?.id) ?? 0;
|
||||
|
||||
const subGroupByVisibilityToggle = visibilitySubGroupByGroupCount(groupCount, showEmptyGroup);
|
||||
|
||||
@@ -84,12 +81,8 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
|
||||
|
||||
interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
||||
issuesMap: IIssueMap;
|
||||
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
|
||||
getGroupIssueCount: (
|
||||
groupId: string | undefined,
|
||||
subGroupId: string | undefined,
|
||||
isSubGroupCumulative: boolean
|
||||
) => number | undefined;
|
||||
groupedIssueIds: TSubGroupedIssues;
|
||||
getSubIssuesCount: (subGroupId: string) => number;
|
||||
showEmptyGroup: boolean;
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
updateIssue: ((projectId: string | null, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
||||
@@ -104,14 +97,13 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
|
||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
|
||||
}
|
||||
|
||||
const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||
const {
|
||||
issuesMap,
|
||||
groupedIssueIds,
|
||||
getGroupIssueCount,
|
||||
getSubIssuesCount,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
list,
|
||||
@@ -120,7 +112,6 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||
displayProperties,
|
||||
kanbanFilters,
|
||||
handleKanbanFilters,
|
||||
loadMoreIssues,
|
||||
showEmptyGroup,
|
||||
enableQuickIssueCreate,
|
||||
disableIssueCreation,
|
||||
@@ -154,7 +145,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||
{list &&
|
||||
list.length > 0 &&
|
||||
list.map((_list: IGroupByColumn) => {
|
||||
const issueCount = getGroupIssueCount(undefined, _list.id, true) ?? 0;
|
||||
const issueCount = getSubIssuesCount(_list.id);
|
||||
const subGroupByVisibilityToggle = visibilitySubGroupBy(_list, issueCount);
|
||||
if (subGroupByVisibilityToggle.showGroup === false) return <></>;
|
||||
return (
|
||||
@@ -176,8 +167,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||
<div className="relative">
|
||||
<KanBan
|
||||
issuesMap={issuesMap}
|
||||
groupedIssueIds={groupedIssueIds}
|
||||
getGroupIssueCount={getGroupIssueCount}
|
||||
groupedIssueIds={groupedIssueIds?.[_list.id]}
|
||||
displayProperties={displayProperties}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
@@ -193,7 +183,6 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||
addIssuesToView={addIssuesToView}
|
||||
quickAddCallback={quickAddCallback}
|
||||
scrollableContainerRef={scrollableContainerRef}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
handleOnDrop={handleOnDrop}
|
||||
orderBy={orderBy}
|
||||
isDropDisabled={_list.isDropDisabled}
|
||||
@@ -210,12 +199,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||
|
||||
export interface IKanBanSwimLanes {
|
||||
issuesMap: IIssueMap;
|
||||
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
|
||||
getGroupIssueCount: (
|
||||
groupId: string | undefined,
|
||||
subGroupId: string | undefined,
|
||||
isSubGroupCumulative: boolean
|
||||
) => number | undefined;
|
||||
groupedIssueIds: TSubGroupedIssues;
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
sub_group_by: TIssueGroupByOptions | undefined;
|
||||
group_by: TIssueGroupByOptions | undefined;
|
||||
@@ -223,7 +207,6 @@ export interface IKanBanSwimLanes {
|
||||
quickActions: TRenderQuickActions;
|
||||
kanbanFilters: TIssueKanbanFilters;
|
||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
|
||||
showEmptyGroup: boolean;
|
||||
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
||||
disableIssueCreation?: boolean;
|
||||
@@ -239,7 +222,6 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
const {
|
||||
issuesMap,
|
||||
groupedIssueIds,
|
||||
getGroupIssueCount,
|
||||
displayProperties,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
@@ -248,7 +230,6 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
quickActions,
|
||||
kanbanFilters,
|
||||
handleKanbanFilters,
|
||||
loadMoreIssues,
|
||||
showEmptyGroup,
|
||||
handleOnDrop,
|
||||
disableIssueCreation,
|
||||
@@ -293,11 +274,22 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
|
||||
if (!groupByList || !subGroupByList) return null;
|
||||
|
||||
const getSubIssuesCount = (subGroupId: string) => {
|
||||
const subGroupIssues = groupedIssueIds?.[subGroupId] as TGroupedIssues;
|
||||
let count = 0;
|
||||
if (!subGroupIssues) return count;
|
||||
for (const groupId of Object.keys(subGroupIssues)) {
|
||||
count += Array.isArray(subGroupIssues?.[groupId]) ? subGroupIssues?.[groupId].length : 0;
|
||||
}
|
||||
|
||||
return count;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="sticky top-0 z-[4] h-[50px] bg-custom-background-90 px-2">
|
||||
<SubGroupSwimlaneHeader
|
||||
getGroupIssueCount={getGroupIssueCount}
|
||||
getSubIssuesCount={getSubIssuesCount}
|
||||
group_by={group_by}
|
||||
sub_group_by={sub_group_by}
|
||||
kanbanFilters={kanbanFilters}
|
||||
@@ -311,8 +303,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
<SubGroupSwimlane
|
||||
issuesMap={issuesMap}
|
||||
list={subGroupByList}
|
||||
getSubIssuesCount={getSubIssuesCount}
|
||||
groupedIssueIds={groupedIssueIds}
|
||||
getGroupIssueCount={getGroupIssueCount}
|
||||
displayProperties={displayProperties}
|
||||
group_by={group_by}
|
||||
sub_group_by={sub_group_by}
|
||||
@@ -321,7 +313,6 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
quickActions={quickActions}
|
||||
kanbanFilters={kanbanFilters}
|
||||
handleKanbanFilters={handleKanbanFilters}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleOnDrop={handleOnDrop}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
|
||||
@@ -38,16 +38,8 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||
const storeType = useIssueStoreType() as ListStoreType;
|
||||
//stores
|
||||
const { issuesFilter, issues } = useIssues(storeType);
|
||||
const {
|
||||
fetchIssues,
|
||||
fetchNextIssues,
|
||||
quickAddIssue,
|
||||
updateIssue,
|
||||
removeIssue,
|
||||
removeIssueFromView,
|
||||
archiveIssue,
|
||||
restoreIssue,
|
||||
} = useIssuesActions(storeType);
|
||||
const { fetchIssues, quickAddIssue, updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue } =
|
||||
useIssuesActions(storeType);
|
||||
// mobx store
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
@@ -61,10 +53,6 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||
const group_by = (displayFilters?.group_by || null) as GroupByColumnTypes | null;
|
||||
const showEmptyGroup = displayFilters?.show_empty_groups ?? false;
|
||||
|
||||
useEffect(() => {
|
||||
fetchIssues("init-loader", { canGroup: true, perPageCount: group_by ? 50 : 100 }, viewId);
|
||||
}, [fetchIssues, storeType, group_by, viewId]);
|
||||
|
||||
const groupedIssueIds = issues?.groupedIssueIds as TGroupedIssues | undefined;
|
||||
// auth
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||
@@ -99,13 +87,6 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||
[isEditingAllowed, isCompletedCycle, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue]
|
||||
);
|
||||
|
||||
const loadMoreIssues = useCallback(
|
||||
(groupId?: string) => {
|
||||
fetchNextIssues(groupId);
|
||||
},
|
||||
[fetchNextIssues]
|
||||
);
|
||||
|
||||
return (
|
||||
<IssueLayoutHOC layout={EIssueLayoutTypes.LIST}>
|
||||
<div className={`relative size-full bg-custom-background-90`}>
|
||||
@@ -117,7 +98,6 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||
updateIssue={updateIssue}
|
||||
quickActions={renderQuickActions}
|
||||
groupedIssueIds={groupedIssueIds ?? {}}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
quickAddCallback={quickAddIssue}
|
||||
enableIssueQuickAdd={!!enableQuickAdd}
|
||||
|
||||
@@ -46,7 +46,6 @@ export interface IList {
|
||||
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||
isCompletedCycle?: boolean;
|
||||
loadMoreIssues: (groupId?: string) => void;
|
||||
}
|
||||
|
||||
export const List: React.FC<IList> = observer((props) => {
|
||||
@@ -66,7 +65,6 @@ export const List: React.FC<IList> = observer((props) => {
|
||||
handleOnDrop,
|
||||
addIssuesToView,
|
||||
isCompletedCycle = false,
|
||||
loadMoreIssues,
|
||||
} = props;
|
||||
|
||||
const storeType = useIssueStoreType();
|
||||
@@ -142,7 +140,7 @@ export const List: React.FC<IList> = observer((props) => {
|
||||
{groups.map((group: IGroupByColumn) => (
|
||||
<ListGroup
|
||||
key={group.id}
|
||||
groupIssueIds={groupedIssueIds?.[group.id]}
|
||||
issueIds={groupedIssueIds?.[group.id]}
|
||||
issuesMap={issuesMap}
|
||||
group_by={group_by}
|
||||
group={group}
|
||||
@@ -159,7 +157,6 @@ export const List: React.FC<IList> = observer((props) => {
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
addIssuesToView={addIssuesToView}
|
||||
isCompletedCycle={isCompletedCycle}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
containerRef={containerRef}
|
||||
selectionHelpers={helpers}
|
||||
/>
|
||||
|
||||
@@ -39,7 +39,7 @@ import { TRenderQuickActions } from "./list-view-types";
|
||||
import { ListQuickAddIssueForm } from "./quick-add-issue-form";
|
||||
|
||||
interface Props {
|
||||
groupIssueIds: string[] | undefined;
|
||||
issueIds: string[] | undefined;
|
||||
group: IGroupByColumn;
|
||||
issuesMap: TIssueMap;
|
||||
group_by: TIssueGroupByOptions | null;
|
||||
@@ -57,13 +57,12 @@ interface Props {
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||
isCompletedCycle?: boolean;
|
||||
showEmptyGroup?: boolean;
|
||||
loadMoreIssues: (groupId?: string) => void;
|
||||
selectionHelpers: TSelectionHelper;
|
||||
}
|
||||
|
||||
export const ListGroup = observer((props: Props) => {
|
||||
const {
|
||||
groupIssueIds = [],
|
||||
issueIds = [],
|
||||
group,
|
||||
issuesMap,
|
||||
group_by,
|
||||
@@ -81,7 +80,6 @@ export const ListGroup = observer((props: Props) => {
|
||||
addIssuesToView,
|
||||
isCompletedCycle,
|
||||
showEmptyGroup,
|
||||
loadMoreIssues,
|
||||
selectionHelpers,
|
||||
} = props;
|
||||
|
||||
@@ -92,35 +90,7 @@ export const ListGroup = observer((props: Props) => {
|
||||
|
||||
const projectState = useProjectState();
|
||||
|
||||
const {
|
||||
issues: { getGroupIssueCount, getPaginationData, getIssueLoader },
|
||||
} = useIssuesStore();
|
||||
|
||||
const [intersectionElement, setIntersectionElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const groupIssueCount = getGroupIssueCount(group.id, undefined, false) ?? 0;
|
||||
const nextPageResults = getPaginationData(group.id, undefined)?.nextPageResults;
|
||||
const isPaginating = !!getIssueLoader(group.id);
|
||||
|
||||
useIntersectionObserver(containerRef, isPaginating ? null : intersectionElement, loadMoreIssues, `100% 0% 100% 0%`);
|
||||
|
||||
const shouldLoadMore =
|
||||
nextPageResults === undefined && groupIssueCount !== undefined && groupIssueIds
|
||||
? groupIssueIds.length < groupIssueCount
|
||||
: !!nextPageResults;
|
||||
|
||||
const loadMore = isPaginating ? (
|
||||
<ListLoaderItemRow />
|
||||
) : (
|
||||
<div
|
||||
className={
|
||||
"h-11 relative flex items-center gap-3 bg-custom-background-100 border border-transparent border-t-custom-border-200 pl-8 p-3 text-sm font-medium text-custom-primary-100 hover:text-custom-primary-200 hover:underline cursor-pointer"
|
||||
}
|
||||
onClick={() => loadMoreIssues(group.id)}
|
||||
>
|
||||
Load More ↓
|
||||
</div>
|
||||
);
|
||||
const groupIssueCount = issueIds?.length ?? 0;
|
||||
|
||||
const validateEmptyIssueGroups = (issueCount: number = 0) => {
|
||||
if (!showEmptyGroup && issueCount <= 0) return false;
|
||||
@@ -254,9 +224,9 @@ export const ListGroup = observer((props: Props) => {
|
||||
orderBy={orderBy}
|
||||
isDraggingOverColumn={isDraggingOverColumn}
|
||||
/>
|
||||
{groupIssueIds && (
|
||||
{issueIds && (
|
||||
<IssueBlocksList
|
||||
issueIds={groupIssueIds}
|
||||
issueIds={issueIds}
|
||||
groupId={group.id}
|
||||
issuesMap={issuesMap}
|
||||
updateIssue={updateIssue}
|
||||
@@ -270,8 +240,6 @@ export const ListGroup = observer((props: Props) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{shouldLoadMore && (group_by ? <>{loadMore}</> : <ListLoaderItemRow ref={setIntersectionElement} />)}
|
||||
|
||||
{enableIssueQuickAdd && !disableIssueCreation && !isGroupByCreatedBy && !isCompletedCycle && (
|
||||
<div className="sticky bottom-0 z-[1] w-full flex-shrink-0">
|
||||
<ListQuickAddIssueForm
|
||||
|
||||
@@ -45,21 +45,26 @@ const CycleIssueLayout = (props: {
|
||||
export const CycleLayoutRoot: React.FC = observer(() => {
|
||||
const { workspaceSlug, projectId, cycleId } = useParams();
|
||||
// store hooks
|
||||
const { issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
|
||||
const { getCycleById } = useCycle();
|
||||
// state
|
||||
const [transferIssuesModal, setTransferIssuesModal] = useState(false);
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && projectId && cycleId
|
||||
? `CYCLE_ISSUES_${workspaceSlug.toString()}_${projectId.toString()}_${cycleId.toString()}`
|
||||
? `CYCLE_ISSUES_FILTERS_${workspaceSlug.toString()}_${projectId.toString()}_${cycleId.toString()}`
|
||||
: null,
|
||||
async () => {
|
||||
if (workspaceSlug && projectId && cycleId) {
|
||||
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
|
||||
await issues.fetchIssues(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
issues.issueIds ? "mutation" : "init-loader",
|
||||
cycleId.toString()
|
||||
);
|
||||
}
|
||||
},
|
||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||
}
|
||||
);
|
||||
|
||||
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
|
||||
|
||||
@@ -40,7 +40,7 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const { workspaceSlug, projectId, moduleId } = useParams();
|
||||
// hooks
|
||||
const { issuesFilter } = useIssues(EIssuesStoreType.MODULE);
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && projectId && moduleId
|
||||
@@ -49,9 +49,14 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
|
||||
async () => {
|
||||
if (workspaceSlug && projectId && moduleId) {
|
||||
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
|
||||
await issues.fetchIssues(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
issues.issueIds ? "mutation" : "init-loader",
|
||||
moduleId.toString()
|
||||
);
|
||||
}
|
||||
},
|
||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||
}
|
||||
);
|
||||
|
||||
if (!workspaceSlug || !projectId || !moduleId) return <></>;
|
||||
|
||||
@@ -44,15 +44,17 @@ export const ProjectLayoutRoot: FC = observer(() => {
|
||||
// hooks
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && projectId ? `PROJECT_ISSUES_${workspaceSlug}_${projectId}` : null,
|
||||
async () => {
|
||||
if (workspaceSlug && projectId) {
|
||||
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString());
|
||||
}
|
||||
},
|
||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||
);
|
||||
useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES${workspaceSlug}_${projectId}` : null, async () => {
|
||||
if (workspaceSlug && projectId) {
|
||||
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString());
|
||||
await issues.fetchIssues(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
issues.issueIds ? "mutation" : "init-loader"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
|
||||
|
||||
|
||||
@@ -41,16 +41,21 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const { workspaceSlug, projectId, viewId } = useParams();
|
||||
// hooks
|
||||
const { issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
|
||||
|
||||
const { isLoading } = useSWR(
|
||||
workspaceSlug && projectId && viewId ? `PROJECT_VIEW_ISSUES_${workspaceSlug}_${projectId}_${viewId}` : null,
|
||||
async () => {
|
||||
if (workspaceSlug && projectId && viewId) {
|
||||
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString(), viewId.toString());
|
||||
await issues.fetchIssues(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
viewId.toString(),
|
||||
issues.issueIds ? "mutation" : "init-loader"
|
||||
);
|
||||
}
|
||||
},
|
||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||
}
|
||||
);
|
||||
|
||||
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane constants
|
||||
import { ALL_ISSUES } from "@plane/constants";
|
||||
import { IIssueDisplayFilterOptions } from "@plane/types";
|
||||
import { IIssueDisplayFilterOptions, TUngroupedIssues } from "@plane/types";
|
||||
// hooks
|
||||
import { EIssueFilterType, EIssueLayoutTypes, EIssuesStoreType } from "@/constants/issue";
|
||||
import { EUserProjectRoles } from "@/constants/project";
|
||||
@@ -44,7 +44,6 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
||||
const { issues, issuesFilter } = useIssues(storeType);
|
||||
const {
|
||||
fetchIssues,
|
||||
fetchNextIssues,
|
||||
quickAddIssue,
|
||||
updateIssue,
|
||||
removeIssue,
|
||||
@@ -58,10 +57,6 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
||||
// user role validation
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||
|
||||
useEffect(() => {
|
||||
fetchIssues("init-loader", { canGroup: false, perPageCount: 100 }, viewId);
|
||||
}, [fetchIssues, storeType, viewId]);
|
||||
|
||||
const canEditProperties = useCallback(
|
||||
(projectId: string | undefined) => {
|
||||
const isEditingAllowedBasedOnProject =
|
||||
@@ -72,8 +67,7 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
||||
[canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed]
|
||||
);
|
||||
|
||||
const issueIds = issues.groupedIssueIds?.[ALL_ISSUES] ?? [];
|
||||
const nextPageResults = issues.getPaginationData(ALL_ISSUES, undefined)?.nextPageResults;
|
||||
const issueIds = (issues.groupedIssueIds ?? []) as TUngroupedIssues;
|
||||
|
||||
const handleDisplayFiltersUpdate = useCallback(
|
||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||
@@ -120,8 +114,6 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
||||
quickAddCallback={quickAddIssue}
|
||||
enableQuickCreateIssue={enableQuickAdd}
|
||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed || isCompletedCycle}
|
||||
canLoadMoreIssues={!!nextPageResults}
|
||||
loadMoreIssues={fetchNextIssues}
|
||||
/>
|
||||
</IssueLayoutHOC>
|
||||
);
|
||||
|
||||
@@ -25,8 +25,6 @@ type Props = {
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
portalElement: React.MutableRefObject<HTMLDivElement | null>;
|
||||
containerRef: MutableRefObject<HTMLTableElement | null>;
|
||||
canLoadMoreIssues: boolean;
|
||||
loadMoreIssues: () => void;
|
||||
spreadsheetColumnsList: (keyof IIssueDisplayProperties)[];
|
||||
selectionHelpers: TSelectionHelper;
|
||||
};
|
||||
@@ -42,20 +40,13 @@ export const SpreadsheetTable = observer((props: Props) => {
|
||||
quickActions,
|
||||
updateIssue,
|
||||
canEditProperties,
|
||||
canLoadMoreIssues,
|
||||
containerRef,
|
||||
loadMoreIssues,
|
||||
spreadsheetColumnsList,
|
||||
selectionHelpers,
|
||||
} = props;
|
||||
|
||||
// states
|
||||
const isScrolled = useRef(false);
|
||||
const [intersectionElement, setIntersectionElement] = useState<HTMLTableSectionElement | null>(null);
|
||||
|
||||
const {
|
||||
issues: { getIssueLoader },
|
||||
} = useIssuesStore();
|
||||
|
||||
const handleScroll = useCallback(() => {
|
||||
if (!containerRef.current) return;
|
||||
@@ -90,10 +81,6 @@ export const SpreadsheetTable = observer((props: Props) => {
|
||||
};
|
||||
}, [handleScroll, containerRef]);
|
||||
|
||||
const isPaginating = !!getIssueLoader();
|
||||
|
||||
useIntersectionObserver(containerRef, isPaginating ? null : intersectionElement, loadMoreIssues, `100% 0% 100% 0%`);
|
||||
|
||||
const handleKeyBoardNavigation = useTableKeyboardNavigation();
|
||||
|
||||
const ignoreFieldsForCounting: (keyof IIssueDisplayProperties)[] = ["key"];
|
||||
@@ -131,11 +118,6 @@ export const SpreadsheetTable = observer((props: Props) => {
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
{canLoadMoreIssues && (
|
||||
<tfoot ref={setIntersectionElement}>
|
||||
<SpreadsheetIssueRowLoader columnCount={displayPropertiesCount} />
|
||||
</tfoot>
|
||||
)}
|
||||
</table>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -28,8 +28,6 @@ type Props = {
|
||||
openIssuesListModal?: (() => void) | null;
|
||||
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
canLoadMoreIssues: boolean;
|
||||
loadMoreIssues: () => void;
|
||||
enableQuickCreateIssue?: boolean;
|
||||
disableIssueCreation?: boolean;
|
||||
isWorkspaceLevel?: boolean;
|
||||
@@ -47,8 +45,6 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
||||
canEditProperties,
|
||||
enableQuickCreateIssue,
|
||||
disableIssueCreation,
|
||||
canLoadMoreIssues,
|
||||
loadMoreIssues,
|
||||
isWorkspaceLevel = false,
|
||||
} = props;
|
||||
// refs
|
||||
@@ -100,8 +96,6 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
||||
updateIssue={updateIssue}
|
||||
canEditProperties={canEditProperties}
|
||||
containerRef={containerRef}
|
||||
canLoadMoreIssues={canLoadMoreIssues}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
spreadsheetColumnsList={spreadsheetColumnsList}
|
||||
selectionHelpers={helpers}
|
||||
/>
|
||||
|
||||
197
web/core/db/local.index.ts
Normal file
197
web/core/db/local.index.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import { rootStore } from "@/lib/store-context";
|
||||
import { IssueService } from "@/services/issue";
|
||||
import { IIssueDisplayFilterOptions, IIssueFilterOptions, IIssueFilters, TIssue } from "@plane/types";
|
||||
import Dexie, { type EntityTable } from "dexie";
|
||||
import { get, set } from "lodash";
|
||||
import { keys } from "ts-transformer-keys";
|
||||
|
||||
type ProjectSyncState = {
|
||||
id: string;
|
||||
lastSynced?: Date;
|
||||
isSyncing?: Promise<void>;
|
||||
};
|
||||
|
||||
export const ISSUE_FILTER_MAP: Record<keyof IIssueFilterOptions, keyof TIssue> = {
|
||||
project: "project_id",
|
||||
cycle: "cycle_id",
|
||||
module: "module_ids",
|
||||
state: "state_id",
|
||||
state_group: "state_group" as keyof TIssue, // state_detail.group is only being used for state_group display,
|
||||
priority: "priority",
|
||||
labels: "label_ids",
|
||||
created_by: "created_by",
|
||||
assignees: "assignee_ids",
|
||||
target_date: "target_date",
|
||||
start_date: "start_date",
|
||||
};
|
||||
|
||||
type DexieDB = Dexie & {
|
||||
issues: EntityTable<
|
||||
TIssue,
|
||||
"id" // primary key "id" (for the typings only)
|
||||
>;
|
||||
projects: EntityTable<
|
||||
ProjectSyncState,
|
||||
"id" // primary key "id" (for the typings only)
|
||||
>;
|
||||
};
|
||||
class IssueDB {
|
||||
db: DexieDB;
|
||||
issueService: IssueService;
|
||||
projectSyncMap: Record<string, ProjectSyncState> = {};
|
||||
|
||||
constructor() {
|
||||
this.db = this.getNewInstantiatedDB();
|
||||
this.db.version(1).stores({
|
||||
projects: "id,lastSynced",
|
||||
issues:
|
||||
"id,sequence_id,name,sort_order,state_id,priority,label_ids,assignee_ids,estimate_point,sub_issues_count,attachment_count,link_count,project_id,parent_id,cycle_id,module_ids,created_at,updated_at,start_date,target_date,completed_at,archived_at,created_by,updated_by,is_draft,description_html,is_subscribed,parent,issue_reactions,issue_attachment,issue_link",
|
||||
});
|
||||
this.issueService = new IssueService();
|
||||
this.initiateProjectMap();
|
||||
}
|
||||
|
||||
getNewInstantiatedDB() {
|
||||
this.clearDB();
|
||||
return new Dexie("IssuesDB") as DexieDB;
|
||||
}
|
||||
|
||||
clearDB() {
|
||||
this.db?.delete();
|
||||
}
|
||||
|
||||
async initiateProjectMap() {
|
||||
const projectSyncDetails = await this.db.projects.toArray();
|
||||
|
||||
for (const syncState of projectSyncDetails) {
|
||||
set(this.projectSyncMap, syncState.id, syncState);
|
||||
}
|
||||
}
|
||||
|
||||
async updateProjectSyncState(syncState: ProjectSyncState) {
|
||||
await this.db.projects.bulkPut([syncState]);
|
||||
}
|
||||
|
||||
async loadIssues(workSpaceSlug: string, projectId: string, params?: any) {
|
||||
try {
|
||||
const issueResponse = await this.issueService.getIssues(workSpaceSlug, projectId, {
|
||||
cursor: "1000:0:0",
|
||||
per_page: 1000,
|
||||
...params,
|
||||
});
|
||||
|
||||
await this.db.issues.bulkPut(issueResponse.results as TIssue[]);
|
||||
|
||||
if (issueResponse.total_pages > 1) {
|
||||
const promiseArray = [];
|
||||
for (let i = 1; i < issueResponse.total_pages; i++) {
|
||||
promiseArray.push(
|
||||
this.issueService.getIssues(workSpaceSlug, projectId, { cursor: `1000:${i}:0`, per_page: 1000, ...params })
|
||||
);
|
||||
}
|
||||
|
||||
const responseArray = await Promise.all(promiseArray);
|
||||
|
||||
for (const currIssueResponse of responseArray) {
|
||||
await this.db.issues.bulkPut(currIssueResponse.results as TIssue[]);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("error while syncing project, ", projectId, e);
|
||||
throw new Error("Error while syncing Project Issues" + projectId);
|
||||
}
|
||||
}
|
||||
|
||||
async syncIssues(workSpaceSlug: string, projectId: string, lastSynced: Date) {
|
||||
await this.loadIssues(workSpaceSlug, projectId, { updated_at__gt: lastSynced });
|
||||
//this.issueService.getDeletedIssues(workSpaceSlug, projectId, )
|
||||
}
|
||||
|
||||
async getIssues(workSpaceSlug: string, projectId: string, issueFilters: IIssueFilters | undefined) {
|
||||
try {
|
||||
const startTime = performance.now();
|
||||
let projectSyncState: ProjectSyncState | undefined = get(this.projectSyncMap, projectId);
|
||||
|
||||
if (!projectSyncState) {
|
||||
set(this.projectSyncMap, projectId, { id: projectId });
|
||||
projectSyncState = get(this.projectSyncMap, projectId);
|
||||
}
|
||||
|
||||
const syncTime = new Date();
|
||||
// if (projectSyncState?.isSyncing) await projectSyncState?.isSyncing;
|
||||
// else
|
||||
if (projectSyncState?.lastSynced) {
|
||||
projectSyncState.isSyncing = this.syncIssues(workSpaceSlug, projectId, projectSyncState?.lastSynced);
|
||||
await projectSyncState?.isSyncing;
|
||||
projectSyncState.lastSynced = syncTime;
|
||||
} else {
|
||||
projectSyncState.isSyncing = this.loadIssues(workSpaceSlug, projectId);
|
||||
await projectSyncState?.isSyncing;
|
||||
projectSyncState.lastSynced = syncTime;
|
||||
}
|
||||
projectSyncState.isSyncing = undefined;
|
||||
|
||||
this.updateProjectSyncState(projectSyncState);
|
||||
|
||||
const filterFunction = this.issueFilterByValues(issueFilters?.filters, issueFilters?.displayFilters);
|
||||
|
||||
const queryStartTime = performance.now();
|
||||
let query: any = this.db.issues;
|
||||
if (filterFunction) query = this.db.issues.filter(filterFunction);
|
||||
|
||||
const issueArray = (await query.toArray()) as TIssue[];
|
||||
|
||||
console.log("### IndexDB Querying Took, ", performance.now() - queryStartTime, "ms");
|
||||
console.log("### getIssues Took, ", performance.now() - startTime, "ms");
|
||||
return issueArray;
|
||||
} catch (e) {
|
||||
console.error("error while fetching issues", projectId, issueFilters, e);
|
||||
throw new Error("Error while fetching Issues" + projectId);
|
||||
}
|
||||
}
|
||||
|
||||
issueFilterByValues = (
|
||||
filters: IIssueFilterOptions | undefined,
|
||||
displayFilters: IIssueDisplayFilterOptions | undefined
|
||||
) => {
|
||||
if (!filters || !displayFilters) return;
|
||||
|
||||
return (issue: TIssue) => {
|
||||
const filterKeys = Object.keys(filters) as (keyof IIssueFilterOptions)[];
|
||||
|
||||
for (const filterKey of filterKeys) {
|
||||
const filterIssueKey = ISSUE_FILTER_MAP[filterKey];
|
||||
|
||||
const filterValue = filters[filterKey];
|
||||
const issueValue = issue[filterIssueKey] as string | string[] | null | undefined;
|
||||
|
||||
if (!filterValue || filterValue.length <= 0) continue;
|
||||
|
||||
if (!issueValue || this.shouldFilterOutIssue(issueValue, filterValue, filterKey)) return false;
|
||||
}
|
||||
|
||||
if (!displayFilters.sub_issue && issue.parent) return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
shouldFilterOutIssue(issueValue: string | string[], filterValue: string[], filterKey: keyof IIssueFilterOptions) {
|
||||
if (filterKey.endsWith("date")) {
|
||||
} else if (filterKey === "state_group" && !Array.isArray(issueValue)) {
|
||||
const issueStateGroup = rootStore?.state?.stateMap?.[issueValue]?.group;
|
||||
|
||||
if (!issueStateGroup || !filterValue.includes(issueStateGroup)) return true;
|
||||
} else {
|
||||
if (Array.isArray(issueValue)) {
|
||||
if (!filterValue.every((value: string) => issueValue.includes(value))) return true;
|
||||
} else if (!filterValue.includes(issueValue)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const issueDB = new IssueDB();
|
||||
@@ -40,6 +40,16 @@ export class IssueService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async getDeletedIssues(workspaceSlug: string, projectId: string, queries?: any): Promise<TIssuesResponse> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/deleted-issues/`, {
|
||||
params: queries,
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getIssuesWithParams(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
|
||||
@@ -88,7 +88,7 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
|
||||
const displayFilters = this.filters[cycleId] || undefined;
|
||||
if (isEmpty(displayFilters)) return undefined;
|
||||
|
||||
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters);
|
||||
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters, { cycle: [cycleId] });
|
||||
|
||||
return _filters;
|
||||
}
|
||||
@@ -192,12 +192,7 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
|
||||
|
||||
const appliedFilters = _filters.filters || {};
|
||||
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
|
||||
this.rootIssueStore.cycleIssues.fetchIssuesWithExistingPagination(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
isEmpty(filteredFilters) ? "init-loader" : "mutation",
|
||||
cycleId
|
||||
);
|
||||
this.rootIssueStore.cycleIssues.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
|
||||
await this.issueFilterService.patchCycleIssueFilters(workspaceSlug, projectId, cycleId, {
|
||||
filters: _filters.filters,
|
||||
});
|
||||
@@ -237,12 +232,7 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
|
||||
});
|
||||
|
||||
if (this.getShouldReFetchIssues(updatedDisplayFilters)) {
|
||||
this.rootIssueStore.cycleIssues.fetchIssuesWithExistingPagination(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
"mutation",
|
||||
cycleId
|
||||
);
|
||||
this.rootIssueStore.cycleIssues.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
|
||||
}
|
||||
|
||||
await this.issueFilterService.patchCycleIssueFilters(workspaceSlug, projectId, cycleId, {
|
||||
|
||||
@@ -24,6 +24,7 @@ import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store"
|
||||
//
|
||||
import { IIssueRootStore } from "../root.store";
|
||||
import { ICycleIssuesFilter } from "./filter.store";
|
||||
import { issueDB } from "@/db/local.index";
|
||||
|
||||
export const ACTIVE_CYCLE_ISSUES = "ACTIVE_CYCLE_ISSUES";
|
||||
|
||||
@@ -41,27 +42,7 @@ export interface ICycleIssues extends IBaseIssuesStore {
|
||||
//action helpers
|
||||
getActiveCycleById: (cycleId: string) => ActiveCycleIssueDetails | undefined;
|
||||
// actions
|
||||
getIssueIds: (groupId?: string, subGroupId?: string) => string[] | undefined;
|
||||
fetchIssues: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader,
|
||||
options: IssuePaginationOptions,
|
||||
cycleId: string
|
||||
) => Promise<TIssuesResponse | undefined>;
|
||||
fetchIssuesWithExistingPagination: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader,
|
||||
cycleId: string
|
||||
) => Promise<TIssuesResponse | undefined>;
|
||||
fetchNextIssues: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
cycleId: string,
|
||||
groupId?: string,
|
||||
subGroupId?: string
|
||||
) => Promise<TIssuesResponse | undefined>;
|
||||
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader, cycleId: string) => Promise<TIssue[]>;
|
||||
|
||||
fetchActiveCycleIssues: (
|
||||
workspaceSlug: string,
|
||||
@@ -115,8 +96,6 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
||||
activeCycleIds: observable,
|
||||
// action
|
||||
fetchIssues: action,
|
||||
fetchNextIssues: action,
|
||||
fetchIssuesWithExistingPagination: action,
|
||||
|
||||
transferIssuesFromCycle: action,
|
||||
fetchActiveCycleIssues: action,
|
||||
@@ -163,30 +142,18 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
||||
* @param cycleId
|
||||
* @returns
|
||||
*/
|
||||
fetchIssues = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader,
|
||||
options: IssuePaginationOptions,
|
||||
cycleId: string,
|
||||
isExistingPaginationOptions: boolean = false
|
||||
) => {
|
||||
fetchIssues = async (workspaceSlug: string, projectId: string, loadType: TLoader, cycleId: string) => {
|
||||
try {
|
||||
// set loader and clear store
|
||||
runInAction(() => {
|
||||
this.setLoader(loadType);
|
||||
});
|
||||
this.clear(!isExistingPaginationOptions);
|
||||
if (loadType === "init-loader") this.clear();
|
||||
|
||||
// get params from pagination options
|
||||
const params = this.issueFilterStore?.getFilterParams(options, cycleId, undefined, undefined, undefined);
|
||||
// call the fetch issues API with the params
|
||||
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params, {
|
||||
signal: this.controller.signal,
|
||||
});
|
||||
const response = await issueDB.getIssues(workspaceSlug, projectId, this.issueFilterStore.issueFilters);
|
||||
|
||||
// after fetching issues, call the base method to process the response further
|
||||
this.onfetchIssues(response, options, workspaceSlug, projectId, cycleId);
|
||||
this.onfetchIssues(response);
|
||||
return response;
|
||||
} catch (error) {
|
||||
// set loader to undefined once errored out
|
||||
@@ -195,71 +162,6 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is called subsequent pages of pagination
|
||||
* if groupId/subgroupId is provided, only that specific group's next page is fetched
|
||||
* else all the groups' next page is fetched
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @param cycleId
|
||||
* @param groupId
|
||||
* @param subGroupId
|
||||
* @returns
|
||||
*/
|
||||
fetchNextIssues = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
cycleId: string,
|
||||
groupId?: string,
|
||||
subGroupId?: string
|
||||
) => {
|
||||
const cursorObject = this.getPaginationData(groupId, subGroupId);
|
||||
// if there are no pagination options and the next page results do not exist the return
|
||||
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
|
||||
try {
|
||||
// set Loader
|
||||
this.setLoader("pagination", groupId, subGroupId);
|
||||
|
||||
// get params from stored pagination options
|
||||
const params = this.issueFilterStore?.getFilterParams(
|
||||
this.paginationOptions,
|
||||
cycleId,
|
||||
this.getNextCursor(groupId, subGroupId),
|
||||
groupId,
|
||||
subGroupId
|
||||
);
|
||||
// call the fetch issues API with the params for next page in issues
|
||||
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params);
|
||||
|
||||
// after the next page of issues are fetched, call the base method to process the response
|
||||
this.onfetchNexIssues(response, groupId, subGroupId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
// set Loader as undefined if errored out
|
||||
this.setLoader(undefined, groupId, subGroupId);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This Method exists to fetch the first page of the issues with the existing stored pagination
|
||||
* This is useful for refetching when filters, groupBy, orderBy etc changes
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @param loadType
|
||||
* @param cycleId
|
||||
* @returns
|
||||
*/
|
||||
fetchIssuesWithExistingPagination = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader,
|
||||
cycleId: string
|
||||
) => {
|
||||
if (!this.paginationOptions) return;
|
||||
return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions, cycleId, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Override inherited create issue, to also add issue to cycle
|
||||
* @param workspaceSlug
|
||||
@@ -304,8 +206,7 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
||||
payload
|
||||
);
|
||||
// call fetch issues
|
||||
this.paginationOptions &&
|
||||
(await this.fetchIssues(workspaceSlug, projectId, "mutation", this.paginationOptions, cycleId));
|
||||
await this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
@@ -333,11 +234,11 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
||||
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params);
|
||||
|
||||
// Process issue response
|
||||
const { issueList, groupedIssues } = this.processIssueResponse(response);
|
||||
const issueList = this.processIssueResponse(response);
|
||||
|
||||
// add issues to the main Issue Map
|
||||
this.rootIssueStore.issues.addIssue(issueList);
|
||||
const activeIssueIds = groupedIssues[ALL_ISSUES] as string[];
|
||||
const activeIssueIds = issueList?.map((issue) => issue.id);
|
||||
|
||||
// store the processed data in the current store
|
||||
set(this.activeCycleIds, [cycleId], {
|
||||
@@ -376,21 +277,20 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
||||
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params);
|
||||
|
||||
// Process the response
|
||||
const { issueList, groupedIssues } = this.processIssueResponse(response);
|
||||
const issueList = this.processIssueResponse(response);
|
||||
|
||||
// add issues to main issue Map
|
||||
this.rootIssueStore.issues.addIssue(issueList);
|
||||
|
||||
const activeIssueIds = groupedIssues[ALL_ISSUES] as string[];
|
||||
|
||||
// store the processed data for subsequent pages
|
||||
set(this.activeCycleIds, [cycleId, "issueCount"], response.total_count);
|
||||
set(this.activeCycleIds, [cycleId, "nextCursor"], response.next_cursor);
|
||||
set(this.activeCycleIds, [cycleId, "nextPageResults"], response.next_page_results);
|
||||
set(this.activeCycleIds, [cycleId, "issueCount"], response.total_count);
|
||||
update(this.activeCycleIds, [cycleId, "issueIds"], (issueIds: string[] = []) =>
|
||||
this.issuesSortWithOrderBy(uniq(concat(issueIds, activeIssueIds)), this.orderBy)
|
||||
);
|
||||
// TODO: FIXME
|
||||
// update(this.activeCycleIds, [cycleId, "issueIds"], (issueIds: TIssue[] = []) =>
|
||||
// this.issuesSortWithOrderBy(uniq(concat(issueIds, activeIssueIds)), this.orderBy)
|
||||
// );
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -58,12 +58,15 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
|
||||
* @param {IIssueFilters} filters
|
||||
* @returns {IIssueFilters}
|
||||
*/
|
||||
computedIssueFilters = (filters: IIssueFilters): IIssueFilters => ({
|
||||
filters: isEmpty(filters?.filters) ? undefined : filters?.filters,
|
||||
displayFilters: isEmpty(filters?.displayFilters) ? undefined : filters?.displayFilters,
|
||||
displayProperties: isEmpty(filters?.displayProperties) ? undefined : filters?.displayProperties,
|
||||
kanbanFilters: isEmpty(filters?.kanbanFilters) ? undefined : filters?.kanbanFilters,
|
||||
});
|
||||
computedIssueFilters = (filters: IIssueFilters, defaultFilters?: IIssueFilterOptions): IIssueFilters => {
|
||||
const currFilters = isEmpty(filters?.filters) ? defaultFilters : { ...filters?.filters, ...defaultFilters };
|
||||
return {
|
||||
filters: currFilters,
|
||||
displayFilters: isEmpty(filters?.displayFilters) ? undefined : filters?.displayFilters,
|
||||
displayProperties: isEmpty(filters?.displayProperties) ? undefined : filters?.displayProperties,
|
||||
kanbanFilters: isEmpty(filters?.kanbanFilters) ? undefined : filters?.kanbanFilters,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @description This method is used to convert the filters array params to string params
|
||||
|
||||
334384
web/core/store/issue/helpers/test.ts
Normal file
334384
web/core/store/issue/helpers/test.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -88,7 +88,7 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
|
||||
const displayFilters = this.filters[moduleId] || undefined;
|
||||
if (isEmpty(displayFilters)) return undefined;
|
||||
|
||||
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters);
|
||||
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters, { module: [moduleId] });
|
||||
|
||||
return _filters;
|
||||
}
|
||||
@@ -191,12 +191,7 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
|
||||
});
|
||||
const appliedFilters = _filters.filters || {};
|
||||
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
|
||||
this.rootIssueStore.moduleIssues.fetchIssuesWithExistingPagination(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
isEmpty(filteredFilters) ? "init-loader" : "mutation",
|
||||
moduleId
|
||||
);
|
||||
this.rootIssueStore.moduleIssues.fetchIssues(workspaceSlug, projectId, "mutation", moduleId);
|
||||
await this.issueFilterService.patchModuleIssueFilters(workspaceSlug, projectId, moduleId, {
|
||||
filters: _filters.filters,
|
||||
});
|
||||
@@ -236,12 +231,7 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
|
||||
});
|
||||
|
||||
if (this.getShouldReFetchIssues(updatedDisplayFilters)) {
|
||||
this.rootIssueStore.moduleIssues.fetchIssuesWithExistingPagination(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
"mutation",
|
||||
moduleId
|
||||
);
|
||||
this.rootIssueStore.moduleIssues.fetchIssues(workspaceSlug, projectId, "mutation", moduleId);
|
||||
}
|
||||
|
||||
await this.issueFilterService.patchModuleIssueFilters(workspaceSlug, projectId, moduleId, {
|
||||
|
||||
@@ -14,31 +14,12 @@ import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store"
|
||||
//
|
||||
import { IIssueRootStore } from "../root.store";
|
||||
import { IModuleIssuesFilter } from "./filter.store";
|
||||
import { issueDB } from "@/db/local.index";
|
||||
|
||||
export interface IModuleIssues extends IBaseIssuesStore {
|
||||
viewFlags: ViewFlags;
|
||||
// actions
|
||||
getIssueIds: (groupId?: string, subGroupId?: string) => string[] | undefined;
|
||||
fetchIssues: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader,
|
||||
options: IssuePaginationOptions,
|
||||
moduleId: string
|
||||
) => Promise<TIssuesResponse | undefined>;
|
||||
fetchIssuesWithExistingPagination: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader,
|
||||
moduleId: string
|
||||
) => Promise<TIssuesResponse | undefined>;
|
||||
fetchNextIssues: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
moduleId: string,
|
||||
groupId?: string,
|
||||
subGroupId?: string
|
||||
) => Promise<TIssuesResponse | undefined>;
|
||||
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader, moduleId: string) => Promise<TIssue[]>;
|
||||
|
||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>, moduleId: string) => Promise<TIssue>;
|
||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||
@@ -69,8 +50,6 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
|
||||
makeObservable(this, {
|
||||
// action
|
||||
fetchIssues: action,
|
||||
fetchNextIssues: action,
|
||||
fetchIssuesWithExistingPagination: action,
|
||||
|
||||
quickAddIssue: action,
|
||||
});
|
||||
@@ -120,30 +99,18 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
|
||||
* @param moduleId
|
||||
* @returns
|
||||
*/
|
||||
fetchIssues = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader,
|
||||
options: IssuePaginationOptions,
|
||||
moduleId: string,
|
||||
isExistingPaginationOptions: boolean = false
|
||||
) => {
|
||||
fetchIssues = async (workspaceSlug: string, projectId: string, loadType: TLoader, moduleId: string) => {
|
||||
try {
|
||||
// set loader and clear store
|
||||
runInAction(() => {
|
||||
this.setLoader(loadType);
|
||||
});
|
||||
this.clear(!isExistingPaginationOptions);
|
||||
if (loadType === "init-loader") this.clear();
|
||||
|
||||
// get params from pagination options
|
||||
const params = this.issueFilterStore?.getFilterParams(options, moduleId, undefined, undefined, undefined);
|
||||
// call the fetch issues API with the params
|
||||
const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params, {
|
||||
signal: this.controller.signal,
|
||||
});
|
||||
const response = await issueDB.getIssues(workspaceSlug, projectId, this.issueFilterStore.issueFilters);
|
||||
|
||||
// after fetching issues, call the base method to process the response further
|
||||
this.onfetchIssues(response, options, workspaceSlug, projectId, moduleId);
|
||||
this.onfetchIssues(response);
|
||||
return response;
|
||||
} catch (error) {
|
||||
// set loader to undefined once errored out
|
||||
@@ -152,71 +119,6 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is called subsequent pages of pagination
|
||||
* if groupId/subgroupId is provided, only that specific group's next page is fetched
|
||||
* else all the groups' next page is fetched
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @param moduleId
|
||||
* @param groupId
|
||||
* @param subGroupId
|
||||
* @returns
|
||||
*/
|
||||
fetchNextIssues = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
moduleId: string,
|
||||
groupId?: string,
|
||||
subGroupId?: string
|
||||
) => {
|
||||
const cursorObject = this.getPaginationData(groupId, subGroupId);
|
||||
// if there are no pagination options and the next page results do not exist the return
|
||||
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
|
||||
try {
|
||||
// set Loader
|
||||
this.setLoader("pagination", groupId, subGroupId);
|
||||
|
||||
// get params from stored pagination options
|
||||
const params = this.issueFilterStore?.getFilterParams(
|
||||
this.paginationOptions,
|
||||
moduleId,
|
||||
this.getNextCursor(groupId, subGroupId),
|
||||
groupId,
|
||||
subGroupId
|
||||
);
|
||||
// call the fetch issues API with the params for next page in issues
|
||||
const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params);
|
||||
|
||||
// after the next page of issues are fetched, call the base method to process the response
|
||||
this.onfetchNexIssues(response, groupId, subGroupId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
// set Loader as undefined if errored out
|
||||
this.setLoader(undefined, groupId, subGroupId);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This Method exists to fetch the first page of the issues with the existing stored pagination
|
||||
* This is useful for refetching when filters, groupBy, orderBy etc changes
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @param loadType
|
||||
* @param moduleId
|
||||
* @returns
|
||||
*/
|
||||
fetchIssuesWithExistingPagination = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader,
|
||||
moduleId: string
|
||||
) => {
|
||||
if (!this.paginationOptions) return;
|
||||
return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions, moduleId, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Override inherited create issue, to also add issue to module
|
||||
* @param workspaceSlug
|
||||
|
||||
@@ -86,9 +86,10 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
|
||||
|
||||
getIssueFilters(viewId: string) {
|
||||
const displayFilters = this.filters[viewId] || undefined;
|
||||
if (isEmpty(displayFilters)) return undefined;
|
||||
const projectId = this.rootIssueStore.projectId;
|
||||
if (isEmpty(displayFilters) || !projectId) return undefined;
|
||||
|
||||
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters);
|
||||
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters, { project: [projectId] });
|
||||
|
||||
return _filters;
|
||||
}
|
||||
@@ -190,12 +191,7 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
|
||||
|
||||
const appliedFilters = _filters.filters || {};
|
||||
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
|
||||
this.rootIssueStore.projectViewIssues.fetchIssuesWithExistingPagination(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
viewId,
|
||||
isEmpty(filteredFilters) ? "init-loader" : "mutation"
|
||||
);
|
||||
this.rootIssueStore.projectViewIssues.fetchIssues(workspaceSlug, projectId, viewId, "mutation");
|
||||
break;
|
||||
}
|
||||
case EIssueFilterType.DISPLAY_FILTERS: {
|
||||
@@ -232,12 +228,7 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
|
||||
});
|
||||
|
||||
if (this.getShouldReFetchIssues(updatedDisplayFilters)) {
|
||||
this.rootIssueStore.projectViewIssues.fetchIssuesWithExistingPagination(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
viewId,
|
||||
"mutation"
|
||||
);
|
||||
this.rootIssueStore.projectViewIssues.fetchIssues(workspaceSlug, projectId, viewId, "mutation");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -13,30 +13,12 @@ import {
|
||||
import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store";
|
||||
import { IIssueRootStore } from "../root.store";
|
||||
import { IProjectViewIssuesFilter } from "./filter.store";
|
||||
import { issueDB } from "@/db/local.index";
|
||||
|
||||
export interface IProjectViewIssues extends IBaseIssuesStore {
|
||||
viewFlags: ViewFlags;
|
||||
// actions
|
||||
fetchIssues: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
viewId: string,
|
||||
loadType: TLoader,
|
||||
options: IssuePaginationOptions
|
||||
) => Promise<TIssuesResponse | undefined>;
|
||||
fetchIssuesWithExistingPagination: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
viewId: string,
|
||||
loadType: TLoader
|
||||
) => Promise<TIssuesResponse | undefined>;
|
||||
fetchNextIssues: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
viewId: string,
|
||||
groupId?: string,
|
||||
subGroupId?: string
|
||||
) => Promise<TIssuesResponse | undefined>;
|
||||
fetchIssues: (workspaceSlug: string, projectId: string, viewId: string, loadType: TLoader) => Promise<TIssue[]>;
|
||||
|
||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||
@@ -61,8 +43,6 @@ export class ProjectViewIssues extends BaseIssuesStore implements IProjectViewIs
|
||||
makeObservable(this, {
|
||||
// action
|
||||
fetchIssues: action,
|
||||
fetchNextIssues: action,
|
||||
fetchIssuesWithExistingPagination: action,
|
||||
});
|
||||
//filter store
|
||||
this.issueFilterStore = issueFilterStore;
|
||||
@@ -81,30 +61,18 @@ export class ProjectViewIssues extends BaseIssuesStore implements IProjectViewIs
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
fetchIssues = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
viewId: string,
|
||||
loadType: TLoader,
|
||||
options: IssuePaginationOptions,
|
||||
isExistingPaginationOptions: boolean = false
|
||||
) => {
|
||||
fetchIssues = async (workspaceSlug: string, projectId: string, viewId: string, loadType: TLoader) => {
|
||||
try {
|
||||
// set loader and clear store
|
||||
runInAction(() => {
|
||||
this.setLoader(loadType);
|
||||
});
|
||||
this.clear(!isExistingPaginationOptions);
|
||||
if (loadType === "init-loader") this.clear();
|
||||
|
||||
// get params from pagination options
|
||||
const params = this.issueFilterStore?.getFilterParams(options, viewId, undefined, undefined, undefined);
|
||||
// call the fetch issues API with the params
|
||||
const response = await this.issueService.getIssues(workspaceSlug, projectId, params, {
|
||||
signal: this.controller.signal,
|
||||
});
|
||||
const response = await issueDB.getIssues(workspaceSlug, projectId, this.issueFilterStore.issueFilters);
|
||||
|
||||
// after fetching issues, call the base method to process the response further
|
||||
this.onfetchIssues(response, options, workspaceSlug, projectId);
|
||||
this.onfetchIssues(response);
|
||||
return response;
|
||||
} catch (error) {
|
||||
// set loader to undefined if errored out
|
||||
@@ -113,69 +81,6 @@ export class ProjectViewIssues extends BaseIssuesStore implements IProjectViewIs
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is called subsequent pages of pagination
|
||||
* if groupId/subgroupId is provided, only that specific group's next page is fetched
|
||||
* else all the groups' next page is fetched
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @param groupId
|
||||
* @param subGroupId
|
||||
* @returns
|
||||
*/
|
||||
fetchNextIssues = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
viewId: string,
|
||||
groupId?: string,
|
||||
subGroupId?: string
|
||||
) => {
|
||||
const cursorObject = this.getPaginationData(groupId, subGroupId);
|
||||
// if there are no pagination options and the next page results do not exist the return
|
||||
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
|
||||
try {
|
||||
// set Loader
|
||||
this.setLoader("pagination", groupId, subGroupId);
|
||||
|
||||
// get params from stored pagination options
|
||||
const params = this.issueFilterStore?.getFilterParams(
|
||||
this.paginationOptions,
|
||||
viewId,
|
||||
this.getNextCursor(groupId, subGroupId),
|
||||
groupId,
|
||||
subGroupId
|
||||
);
|
||||
// call the fetch issues API with the params for next page in issues
|
||||
const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
|
||||
|
||||
// after the next page of issues are fetched, call the base method to process the response
|
||||
this.onfetchNexIssues(response, groupId, subGroupId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
// set Loader as undefined if errored out
|
||||
this.setLoader(undefined, groupId, subGroupId);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This Method exists to fetch the first page of the issues with the existing stored pagination
|
||||
* This is useful for refetching when filters, groupBy, orderBy etc changes
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @param loadType
|
||||
* @returns
|
||||
*/
|
||||
fetchIssuesWithExistingPagination = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
viewId: string,
|
||||
loadType: TLoader
|
||||
) => {
|
||||
if (!this.paginationOptions) return;
|
||||
return await this.fetchIssues(workspaceSlug, projectId, viewId, loadType, this.paginationOptions, true);
|
||||
};
|
||||
|
||||
// Using aliased names as they cannot be overridden in other stores
|
||||
archiveBulkIssues = this.bulkArchiveIssues;
|
||||
quickAddIssue = this.issueQuickAdd;
|
||||
|
||||
@@ -87,7 +87,7 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj
|
||||
const displayFilters = this.filters[projectId] || undefined;
|
||||
if (isEmpty(displayFilters)) return undefined;
|
||||
|
||||
return this.computedIssueFilters(displayFilters);
|
||||
return this.computedIssueFilters(displayFilters, { project: [projectId] });
|
||||
}
|
||||
|
||||
getAppliedFilters(projectId: string) {
|
||||
@@ -185,7 +185,7 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj
|
||||
|
||||
const appliedFilters = _filters.filters || {};
|
||||
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
|
||||
this.rootIssueStore.projectIssues.fetchIssuesWithExistingPagination(
|
||||
this.rootIssueStore.projectIssues.fetchIssues(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
isEmpty(filteredFilters) ? "init-loader" : "mutation"
|
||||
@@ -229,7 +229,7 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj
|
||||
});
|
||||
|
||||
if (this.getShouldReFetchIssues(updatedDisplayFilters)) {
|
||||
this.rootIssueStore.projectIssues.fetchIssuesWithExistingPagination(workspaceSlug, projectId, "mutation");
|
||||
this.rootIssueStore.projectIssues.fetchIssues(workspaceSlug, projectId, "mutation");
|
||||
}
|
||||
|
||||
await this.issueFilterService.patchProjectIssueFilters(workspaceSlug, projectId, {
|
||||
|
||||
@@ -14,27 +14,12 @@ import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store"
|
||||
// services
|
||||
import { IIssueRootStore } from "../root.store";
|
||||
import { IProjectIssuesFilter } from "./filter.store";
|
||||
import { issueDB } from "@/db/local.index";
|
||||
|
||||
export interface IProjectIssues extends IBaseIssuesStore {
|
||||
viewFlags: ViewFlags;
|
||||
// action
|
||||
fetchIssues: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader,
|
||||
option: IssuePaginationOptions
|
||||
) => Promise<TIssuesResponse | undefined>;
|
||||
fetchIssuesWithExistingPagination: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader
|
||||
) => Promise<TIssuesResponse | undefined>;
|
||||
fetchNextIssues: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
groupId?: string,
|
||||
subGroupId?: string
|
||||
) => Promise<TIssuesResponse | undefined>;
|
||||
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise<TIssue[]>;
|
||||
|
||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||
@@ -60,8 +45,6 @@ export class ProjectIssues extends BaseIssuesStore implements IProjectIssues {
|
||||
super(_rootStore, issueFilterStore);
|
||||
makeObservable(this, {
|
||||
fetchIssues: action,
|
||||
fetchNextIssues: action,
|
||||
fetchIssuesWithExistingPagination: action,
|
||||
|
||||
quickAddIssue: action,
|
||||
});
|
||||
@@ -90,29 +73,18 @@ export class ProjectIssues extends BaseIssuesStore implements IProjectIssues {
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
fetchIssues = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader = "init-loader",
|
||||
options: IssuePaginationOptions,
|
||||
isExistingPaginationOptions: boolean = false
|
||||
) => {
|
||||
fetchIssues = async (workspaceSlug: string, projectId: string, loadType: TLoader = "init-loader") => {
|
||||
try {
|
||||
// set loader and clear store
|
||||
runInAction(() => {
|
||||
this.setLoader(loadType);
|
||||
});
|
||||
this.clear(!isExistingPaginationOptions);
|
||||
if (loadType === "init-loader") this.clear();
|
||||
|
||||
// get params from pagination options
|
||||
const params = this.issueFilterStore?.getFilterParams(options, projectId, undefined, undefined, undefined);
|
||||
// call the fetch issues API with the params
|
||||
const response = await this.issueService.getIssues(workspaceSlug, projectId, params, {
|
||||
signal: this.controller.signal,
|
||||
});
|
||||
const response = await issueDB.getIssues(workspaceSlug, projectId, this.issueFilterStore.issueFilters);
|
||||
|
||||
// after fetching issues, call the base method to process the response further
|
||||
this.onfetchIssues(response, options, workspaceSlug, projectId);
|
||||
this.onfetchIssues(response);
|
||||
return response;
|
||||
} catch (error) {
|
||||
// set loader to undefined if errored out
|
||||
@@ -121,62 +93,6 @@ export class ProjectIssues extends BaseIssuesStore implements IProjectIssues {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is called subsequent pages of pagination
|
||||
* if groupId/subgroupId is provided, only that specific group's next page is fetched
|
||||
* else all the groups' next page is fetched
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @param groupId
|
||||
* @param subGroupId
|
||||
* @returns
|
||||
*/
|
||||
fetchNextIssues = async (workspaceSlug: string, projectId: string, groupId?: string, subGroupId?: string) => {
|
||||
const cursorObject = this.getPaginationData(groupId, subGroupId);
|
||||
// if there are no pagination options and the next page results do not exist the return
|
||||
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
|
||||
try {
|
||||
// set Loader
|
||||
this.setLoader("pagination", groupId, subGroupId);
|
||||
|
||||
// get params from stored pagination options
|
||||
const params = this.issueFilterStore?.getFilterParams(
|
||||
this.paginationOptions,
|
||||
projectId,
|
||||
this.getNextCursor(groupId, subGroupId),
|
||||
groupId,
|
||||
subGroupId
|
||||
);
|
||||
// call the fetch issues API with the params for next page in issues
|
||||
const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
|
||||
|
||||
// after the next page of issues are fetched, call the base method to process the response
|
||||
this.onfetchNexIssues(response, groupId, subGroupId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
// set Loader as undefined if errored out
|
||||
this.setLoader(undefined, groupId, subGroupId);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This Method exists to fetch the first page of the issues with the existing stored pagination
|
||||
* This is useful for refetching when filters, groupBy, orderBy etc changes
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @param loadType
|
||||
* @returns
|
||||
*/
|
||||
fetchIssuesWithExistingPagination = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader = "mutation"
|
||||
) => {
|
||||
if (!this.paginationOptions) return;
|
||||
return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Override inherited create issue, to update list only if user is on current project
|
||||
* @param workspaceSlug
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"clsx": "^2.0.0",
|
||||
"cmdk": "^1.0.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"dexie": "^4.0.8",
|
||||
"dotenv": "^16.0.3",
|
||||
"isomorphic-dompurify": "^2.12.0",
|
||||
"js-cookie": "^3.0.1",
|
||||
@@ -60,6 +61,7 @@
|
||||
"smooth-scroll-into-view-if-needed": "^2.0.2",
|
||||
"swr": "^2.1.3",
|
||||
"tailwind-merge": "^2.0.0",
|
||||
"ts-transformer-keys": "^0.4.4",
|
||||
"use-debounce": "^9.0.4",
|
||||
"use-font-face-observer": "^1.2.2",
|
||||
"uuid": "^9.0.0",
|
||||
|
||||
Reference in New Issue
Block a user