diff --git a/apiserver/plane/app/serializers/issue.py b/apiserver/plane/app/serializers/issue.py index 6d39f17603..f9b5b579f6 100644 --- a/apiserver/plane/app/serializers/issue.py +++ b/apiserver/plane/app/serializers/issue.py @@ -30,6 +30,8 @@ CommentReaction, IssueVote, IssueRelation, + State, + Project, ) @@ -69,19 +71,16 @@ class Meta: ##TODO: Find a better way to write this serializer ## Find a better approach to save manytomany? class IssueCreateSerializer(BaseSerializer): - state_detail = StateSerializer(read_only=True, source="state") - created_by_detail = UserLiteSerializer(read_only=True, source="created_by") - project_detail = ProjectLiteSerializer(read_only=True, source="project") - workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace") - - assignees = serializers.ListField( - child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()), + # ids + state_id = serializers.PrimaryKeyRelatedField(source="state", queryset=State.objects.all(), required=False, allow_null=True) + parent_id = serializers.PrimaryKeyRelatedField(source='parent', queryset=Issue.objects.all(), required=False, allow_null=True) + label_ids = serializers.ListField( + child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()), write_only=True, required=False, ) - - labels = serializers.ListField( - child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()), + assignee_ids = serializers.ListField( + child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()), write_only=True, required=False, ) @@ -100,8 +99,10 @@ class Meta: def to_representation(self, instance): data = super().to_representation(instance) - data['assignees'] = [str(assignee.id) for assignee in instance.assignees.all()] - data['labels'] = [str(label.id) for label in instance.labels.all()] + assignee_ids = self.initial_data.get('assignee_ids') + data['assignee_ids'] = assignee_ids if assignee_ids else [] + label_ids = self.initial_data.get('label_ids') + data['label_ids'] = label_ids if label_ids else [] return data def validate(self, data): @@ -114,8 +115,8 @@ def validate(self, data): return data def create(self, validated_data): - assignees = validated_data.pop("assignees", None) - labels = validated_data.pop("labels", None) + assignees = validated_data.pop("assignee_ids", None) + labels = validated_data.pop("label_ids", None) project_id = self.context["project_id"] workspace_id = self.context["workspace_id"] @@ -173,8 +174,8 @@ def create(self, validated_data): return issue def update(self, instance, validated_data): - assignees = validated_data.pop("assignees", None) - labels = validated_data.pop("labels", None) + assignees = validated_data.pop("assignee_ids", None) + labels = validated_data.pop("labels_ids", None) # Related models project_id = instance.project_id @@ -544,7 +545,7 @@ class IssueSerializer(DynamicBaseSerializer): attachment_count = serializers.IntegerField(read_only=True) link_count = serializers.IntegerField(read_only=True) - # is + # is_subscribed is_subscribed = serializers.BooleanField(read_only=True) class Meta: diff --git a/apiserver/plane/app/views/base.py b/apiserver/plane/app/views/base.py index 5bd79cb961..3fae82e920 100644 --- a/apiserver/plane/app/views/base.py +++ b/apiserver/plane/app/views/base.py @@ -99,6 +99,7 @@ def handle_exception(self, exc): response = super().handle_exception(exc) return response except Exception as e: + print(e) if settings.DEBUG else print("Server Error") if isinstance(e, IntegrityError): return Response( {"error": "The payload is not valid"}, @@ -124,8 +125,7 @@ def handle_exception(self, exc): {"error": f"key {e} does not exist"}, status=status.HTTP_400_BAD_REQUEST, ) - - print(e) if settings.DEBUG else print("Server Error") + capture_exception(e) return Response({"error": "Something went wrong please try again later"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/apiserver/plane/app/views/cycle.py b/apiserver/plane/app/views/cycle.py index 73741b983d..15a6c24b09 100644 --- a/apiserver/plane/app/views/cycle.py +++ b/apiserver/plane/app/views/cycle.py @@ -31,6 +31,7 @@ CycleSerializer, CycleIssueSerializer, CycleFavoriteSerializer, + IssueSerializer, IssueStateSerializer, CycleWriteSerializer, CycleUserPropertiesSerializer, @@ -46,9 +47,9 @@ IssueAttachment, Label, CycleUserProperties, + IssueSubscriber, ) from plane.bgtasks.issue_activites_task import issue_activity -from plane.utils.grouper import group_results from plane.utils.issue_filters import issue_filters from plane.utils.analytics_plot import burndown_plot @@ -322,6 +323,8 @@ def create(self, request, slug, project_id): project_id=project_id, owned_by=request.user, ) + cycle = self.get_queryset().filter(pk=serializer.data["id"]).first() + serializer = CycleSerializer(cycle) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) else: @@ -548,6 +551,8 @@ def list(self, request, slug, project_id, cycle_id): .prefetch_related("labels") .order_by(order_by) .filter(**filters) + .annotate(cycle_id=F("issue_cycle__cycle_id")) + .annotate(module_id=F("issue_module__module_id")) .annotate( link_count=IssueLink.objects.filter(issue=OuterRef("id")) .order_by() @@ -560,8 +565,15 @@ def list(self, request, slug, project_id, cycle_id): .annotate(count=Func(F("id"), function="Count")) .values("count") ) + .annotate( + is_subscribed=Exists( + IssueSubscriber.objects.filter( + subscriber=self.request.user, issue_id=OuterRef("id") + ) + ) + ) ) - serializer = IssueStateSerializer( + serializer = IssueSerializer( issues, many=True, fields=fields if fields else None ) return Response(serializer.data, status=status.HTTP_200_OK) @@ -652,8 +664,10 @@ def create(self, request, slug, project_id, cycle_id): ) # Return all Cycle Issues + issues = self.get_queryset().values_list("issue_id", flat=True) + return Response( - CycleIssueSerializer(self.get_queryset(), many=True).data, + IssueSerializer(Issue.objects.filter(pk__in=issues), many=True).data, status=status.HTTP_200_OK, ) diff --git a/apiserver/plane/app/views/issue.py b/apiserver/plane/app/views/issue.py index 6c88ef0909..4a91c9fe1c 100644 --- a/apiserver/plane/app/views/issue.py +++ b/apiserver/plane/app/views/issue.py @@ -34,11 +34,11 @@ # Module imports from . import BaseViewSet, BaseAPIView, WebhookMixin from plane.app.serializers import ( - IssueCreateSerializer, IssueActivitySerializer, IssueCommentSerializer, IssuePropertySerializer, IssueSerializer, + IssueCreateSerializer, LabelSerializer, IssueFlatSerializer, IssueLinkSerializer, @@ -110,12 +110,7 @@ def get_serializer_class(self): def get_queryset(self): return ( - Issue.issue_objects.annotate( - sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) - .order_by() - .annotate(count=Func(F("id"), function="Count")) - .values("count") - ) + Issue.issue_objects .filter(project_id=self.kwargs.get("project_id")) .filter(workspace__slug=self.kwargs.get("slug")) .select_related("project") @@ -143,13 +138,11 @@ def get_queryset(self): .order_by() .annotate(count=Func(F("id"), function="Count")) .values("count") - ) - .annotate( - is_subscribed=Exists( - IssueSubscriber.objects.filter( - subscriber=self.request.user, issue_id=OuterRef("id") - ) - ) + ).annotate( + sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") ) ).distinct() @@ -251,16 +244,13 @@ def create(self, request, slug, project_id): current_instance=None, epoch=int(timezone.now().timestamp()), ) + issue = self.get_queryset().filter(pk=serializer.data["id"]).first() + serializer = IssueSerializer(issue) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def retrieve(self, request, slug, project_id, pk=None): - issue = Issue.issue_objects.annotate( - sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) - .order_by() - .annotate(count=Func(F("id"), function="Count")) - .values("count") - ).get(workspace__slug=slug, project_id=project_id, pk=pk) + issue = self.get_queryset().filter(pk=pk).first() return Response( IssueSerializer(issue, fields=self.fields, expand=self.expand).data, status=status.HTTP_200_OK, @@ -284,7 +274,8 @@ def partial_update(self, request, slug, project_id, pk=None): current_instance=current_instance, epoch=int(timezone.now().timestamp()), ) - return Response(serializer.data, status=status.HTTP_200_OK) + issue = self.get_queryset().filter(pk=pk).first() + return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def destroy(self, request, slug, project_id, pk=None): @@ -719,13 +710,6 @@ def get(self, request, slug, project_id, issue_id): .annotate(count=Func(F("id"), function="Count")) .values("count") ) - .annotate( - is_subscribed=Exists( - IssueSubscriber.objects.filter( - subscriber=self.request.user, issue_id=OuterRef("id") - ) - ) - ) .prefetch_related( Prefetch( "issue_reactions", @@ -1080,7 +1064,7 @@ def list(self, request, slug, project_id): else issue_queryset.filter(parent__isnull=True) ) - issues = IssueLiteSerializer( + issues = IssueSerializer( issue_queryset, many=True, fields=fields if fields else None ).data return Response(issues, status=status.HTTP_200_OK) @@ -1163,16 +1147,6 @@ def list(self, request, slug, project_id, issue_id): project_id=project_id, is_active=True, ) - .annotate( - is_subscribed=Exists( - IssueSubscriber.objects.filter( - workspace__slug=slug, - project_id=project_id, - issue_id=issue_id, - subscriber=OuterRef("member"), - ) - ) - ) .select_related("member") ) serializer = ProjectMemberLiteSerializer(members, many=True) @@ -1613,7 +1587,7 @@ def list(self, request, slug, project_id): else: issue_queryset = issue_queryset.order_by(order_by_param) - issues = IssueLiteSerializer( + issues = IssueSerializer( issue_queryset, many=True, fields=fields if fields else None ).data return Response(issues, status=status.HTTP_200_OK) diff --git a/apiserver/plane/app/views/module.py b/apiserver/plane/app/views/module.py index 6baf231213..576b763fde 100644 --- a/apiserver/plane/app/views/module.py +++ b/apiserver/plane/app/views/module.py @@ -20,7 +20,7 @@ ModuleIssueSerializer, ModuleLinkSerializer, ModuleFavoriteSerializer, - IssueStateSerializer, + IssueSerializer, ModuleUserPropertiesSerializer, ) from plane.app.permissions import ProjectEntityPermission, ProjectLitePermission @@ -33,6 +33,7 @@ ModuleFavorite, IssueLink, IssueAttachment, + IssueSubscriber, ModuleUserProperties, ) from plane.bgtasks.issue_activites_task import issue_activity @@ -353,6 +354,8 @@ def list(self, request, slug, project_id, module_id): .prefetch_related("labels") .order_by(order_by) .filter(**filters) + .annotate(cycle_id=F("issue_cycle__cycle_id")) + .annotate(module_id=F("issue_module__module_id")) .annotate( link_count=IssueLink.objects.filter(issue=OuterRef("id")) .order_by() @@ -365,8 +368,15 @@ def list(self, request, slug, project_id, module_id): .annotate(count=Func(F("id"), function="Count")) .values("count") ) + .annotate( + is_subscribed=Exists( + IssueSubscriber.objects.filter( + subscriber=self.request.user, issue_id=OuterRef("id") + ) + ) + ) ) - serializer = IssueStateSerializer( + serializer = IssueSerializer( issues, many=True, fields=fields if fields else None ) return Response(serializer.data, status=status.HTTP_200_OK) @@ -447,8 +457,10 @@ def create(self, request, slug, project_id, module_id): epoch=int(timezone.now().timestamp()), ) + issues = self.get_queryset().values_list("issue_id", flat=True) + return Response( - ModuleIssueSerializer(self.get_queryset(), many=True).data, + IssueSerializer(Issue.objects.filter(pk__in=issues), many=True).data, status=status.HTTP_200_OK, ) diff --git a/apiserver/plane/app/views/view.py b/apiserver/plane/app/views/view.py index a2f00a8197..0c9be5ae6d 100644 --- a/apiserver/plane/app/views/view.py +++ b/apiserver/plane/app/views/view.py @@ -24,7 +24,7 @@ from plane.app.serializers import ( GlobalViewSerializer, IssueViewSerializer, - IssueLiteSerializer, + IssueSerializer, IssueViewFavoriteSerializer, ) from plane.app.permissions import ( @@ -42,6 +42,7 @@ IssueReaction, IssueLink, IssueAttachment, + IssueSubscriber, ) from plane.utils.issue_filters import issue_filters from plane.utils.grouper import group_results @@ -127,6 +128,19 @@ def list(self, request, slug): .annotate(count=Func(F("id"), function="Count")) .values("count") ) + .annotate( + sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .annotate( + is_subscribed=Exists( + IssueSubscriber.objects.filter( + subscriber=self.request.user, issue_id=OuterRef("id") + ) + ) + ) ) # Priority Ordering @@ -185,7 +199,7 @@ def list(self, request, slug): else: issue_queryset = issue_queryset.order_by(order_by_param) - serializer = IssueLiteSerializer( + serializer = IssueSerializer( issue_queryset, many=True, fields=fields if fields else None ) return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/packages/types/src/inbox.d.ts b/packages/types/src/inbox.d.ts index 1b474c3aba..4d666ae835 100644 --- a/packages/types/src/inbox.d.ts +++ b/packages/types/src/inbox.d.ts @@ -1,7 +1,13 @@ -import { TIssue } from "./issues"; +import { TIssue } from "./issues/base"; import type { IProjectLite } from "./projects"; -export interface IInboxIssue extends TIssue { +export type TInboxIssueExtended = { + completed_at: string | null; + start_date: string | null; + target_date: string | null; +}; + +export interface IInboxIssue extends TIssue, TInboxIssueExtended { issue_inbox: { duplicate_to: string | null; id: string; @@ -48,7 +54,12 @@ interface StatusDuplicate { duplicate_to: string; } -export type TInboxStatus = StatusReject | StatusSnoozed | StatusAccepted | StatusDuplicate | StatePending; +export type TInboxStatus = + | StatusReject + | StatusSnoozed + | StatusAccepted + | StatusDuplicate + | StatePending; export interface IInboxFilterOptions { priority?: string[] | null; diff --git a/packages/types/src/issues.d.ts b/packages/types/src/issues.d.ts index c0ad7bc7f7..c9376b34bc 100644 --- a/packages/types/src/issues.d.ts +++ b/packages/types/src/issues.d.ts @@ -1,8 +1,6 @@ import { ReactElement } from "react"; import { KeyedMutator } from "swr"; import type { - IState, - IUser, ICycle, IModule, IUserLite, @@ -12,6 +10,7 @@ import type { Properties, IIssueDisplayFilterOptions, IIssueReaction, + TIssue, } from "@plane/types"; export interface IIssueCycle { @@ -78,59 +77,6 @@ export interface IssueRelation { relation: "blocking" | null; } -export interface IIssue { - archived_at: string; - assignees: string[]; - assignee_details: IUser[]; - attachment_count: number; - attachments: any[]; - issue_relations: IssueRelation[]; - issue_reactions: IIssueReaction[]; - related_issues: IssueRelation[]; - bridge_id?: string | null; - completed_at: Date; - created_at: string; - created_by: string; - cycle: string | null; - cycle_id: string | null; - cycle_detail: ICycle | null; - description: any; - description_html: any; - description_stripped: any; - estimate_point: number | null; - id: string; - // tempId is used for optimistic updates. It is not a part of the API response. - tempId?: string; - issue_cycle: IIssueCycle | null; - issue_link: ILinkDetails[]; - issue_module: IIssueModule | null; - labels: string[]; - label_details: any[]; - is_draft: boolean; - links_list: IIssueLink[]; - link_count: number; - module: string | null; - module_id: string | null; - name: string; - parent: string | null; - parent_detail: IIssueParent | null; - priority: TIssuePriorities; - project: string; - project_detail: IProjectLite; - sequence_id: number; - sort_order: number; - sprints: string | null; - start_date: string | null; - state: string; - state_detail: IState; - sub_issues_count: number; - target_date: string | null; - updated_at: string; - updated_by: string; - workspace: string; - workspace_detail: IWorkspaceLite; -} - export interface ISubIssuesState { backlog: number; unstarted: number; @@ -283,62 +229,3 @@ export interface IGroupByColumn { export interface IIssueMap { [key: string]: TIssue; } - -// new issue structure types -export type TIssue = { - id: string; - name: string; - state_id: string; - description_html: string; - sort_order: number; - completed_at: string | null; - estimate_point: number | null; - priority: TIssuePriorities; - start_date: string | null; - target_date: string | null; - sequence_id: number; - project_id: string; - parent_id: string | null; - cycle_id: string | null; - module_id: string | null; - label_ids: string[]; - assignee_ids: string[]; - sub_issues_count: number; - created_at: string; - updated_at: string; - created_by: string; - updated_by: string; - attachment_count: number; - link_count: number; - is_subscribed: boolean; - archived_at: boolean; - is_draft: boolean; - // tempId is used for optimistic updates. It is not a part of the API response. - tempId?: string; - // issue details - related_issues: any; - issue_reactions: any; - issue_relations: any; - issue_cycle: any; - issue_module: any; - parent_detail: any; - issue_link: any; -}; - -export type TIssueMap = { - [issue_id: string]: TIssue; -}; - -export type TLoader = "init-loader" | "mutation" | undefined; - -export type TGroupedIssues = { - [group_id: string]: string[]; -}; - -export type TSubGroupedIssues = { - [sub_grouped_id: string]: { - [group_id: string]: string[]; - }; -}; - -export type TUnGroupedIssues = string[]; diff --git a/packages/types/src/issues/issue.d.ts b/packages/types/src/issues/issue.d.ts index e9ec14528a..9734f85c2d 100644 --- a/packages/types/src/issues/issue.d.ts +++ b/packages/types/src/issues/issue.d.ts @@ -1,32 +1,41 @@ +import { TIssuePriorities } from "../issues"; + // new issue structure types export type TIssue = { id: string; + sequence_id: number; name: string; - state_id: string; description_html: string; sort_order: number; - completed_at: string | null; - estimate_point: number | null; + + state_id: string; priority: TIssuePriorities; - start_date: string; - target_date: string; - sequence_id: number; + label_ids: string[]; + assignee_ids: string[]; + estimate_point: number | null; + + sub_issues_count: number; + attachment_count: number; + link_count: number; + project_id: string; parent_id: string | null; cycle_id: string | null; module_id: string | null; - label_ids: string[]; - assignee_ids: string[]; - sub_issues_count: number; + created_at: string; updated_at: string; + start_date: string | null; + target_date: string | null; + completed_at: string | null; + archived_at: string | null; + created_by: string; updated_by: string; - attachment_count: number; - link_count: number; - is_subscribed: boolean; - archived_at: boolean; + is_draft: boolean; + is_subscribed: boolean; + // tempId is used for optimistic updates. It is not a part of the API response. tempId?: string; }; diff --git a/packages/ui/src/control-link/control-link.tsx b/packages/ui/src/control-link/control-link.tsx new file mode 100644 index 0000000000..dbdbaf0950 --- /dev/null +++ b/packages/ui/src/control-link/control-link.tsx @@ -0,0 +1,27 @@ +import * as React from "react"; + +export type TControlLink = React.AnchorHTMLAttributes & { + href: string; + onClick: () => void; + children: React.ReactNode; + target?: string; +}; + +export const ControlLink: React.FC = (props) => { + const { href, onClick, children, target = "_self", ...rest } = props; + const LEFT_CLICK_EVENT_CODE = 0; + + const _onClick = (event: React.MouseEvent) => { + const clickCondition = (event.metaKey || event.ctrlKey) && event.button === LEFT_CLICK_EVENT_CODE; + if (!clickCondition) { + event.preventDefault(); + onClick(); + } + }; + + return ( + + {children} + + ); +}; diff --git a/packages/ui/src/control-link/index.ts b/packages/ui/src/control-link/index.ts new file mode 100644 index 0000000000..86cdfc28ea --- /dev/null +++ b/packages/ui/src/control-link/index.ts @@ -0,0 +1 @@ +export * from "./control-link"; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 4b1bb2fcfe..b90b6993a7 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -9,3 +9,4 @@ export * from "./progress"; export * from "./spinners"; export * from "./tooltip"; export * from "./loader"; +export * from "./control-link"; diff --git a/web/components/cycles/cycles-list-item.tsx b/web/components/cycles/cycles-list-item.tsx index bbb30bc7a8..d25364bcdc 100644 --- a/web/components/cycles/cycles-list-item.tsx +++ b/web/components/cycles/cycles-list-item.tsx @@ -116,7 +116,8 @@ export const CyclesListItem: FC = (props) => { if (!cycleDetails) return null; // computed - const cycleStatus = cycleDetails.status.toLocaleLowerCase() as TCycleGroups; + // TODO: change this logic once backend fix the response + const cycleStatus = cycleDetails.status ? (cycleDetails.status.toLocaleLowerCase() as TCycleGroups) : "draft"; const isCompleted = cycleStatus === "completed"; const endDate = new Date(cycleDetails.end_date ?? ""); const startDate = new Date(cycleDetails.start_date ?? ""); diff --git a/web/components/issues/attachment/root.tsx b/web/components/issues/attachment/root.tsx index 9d8a31b05b..ac92bb5b62 100644 --- a/web/components/issues/attachment/root.tsx +++ b/web/components/issues/attachment/root.tsx @@ -22,15 +22,15 @@ export const IssueAttachmentRoot: FC = (props) => { const { router: { workspaceSlug, projectId }, } = useApplication(); - const { issueId, createAttachment, removeAttachment } = useIssueDetail(); + const { peekIssue, createAttachment, removeAttachment } = useIssueDetail(); const { setToastAlert } = useToast(); const handleAttachmentOperations: TAttachmentOperations = useMemo( () => ({ create: async (data: FormData) => { try { - if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields"); - await createAttachment(workspaceSlug, projectId, issueId, data); + if (!workspaceSlug || !projectId || !peekIssue?.issueId) throw new Error("Missing required fields"); + await createAttachment(workspaceSlug, projectId, peekIssue?.issueId, data); setToastAlert({ message: "The attachment has been successfully uploaded", type: "success", @@ -46,8 +46,8 @@ export const IssueAttachmentRoot: FC = (props) => { }, remove: async (attachmentId: string) => { try { - if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields"); - await removeAttachment(workspaceSlug, projectId, issueId, attachmentId); + if (!workspaceSlug || !projectId || !peekIssue?.issueId) throw new Error("Missing required fields"); + await removeAttachment(workspaceSlug, projectId, peekIssue?.issueId, attachmentId); setToastAlert({ message: "The attachment has been successfully removed", type: "success", @@ -62,7 +62,7 @@ export const IssueAttachmentRoot: FC = (props) => { } }, }), - [workspaceSlug, projectId, issueId, createAttachment, removeAttachment, setToastAlert] + [workspaceSlug, projectId, peekIssue, createAttachment, removeAttachment, setToastAlert] ); return ( diff --git a/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx b/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx index 2d7f5005ac..3b3ef887e1 100644 --- a/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx +++ b/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx @@ -3,7 +3,7 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { DragDropContext, DropResult } from "@hello-pangea/dnd"; // components -import { CalendarChart, IssuePeekOverview } from "components/issues"; +import { CalendarChart } from "components/issues"; // hooks import useToast from "hooks/use-toast"; // types @@ -34,7 +34,7 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => { // router const router = useRouter(); - const { workspaceSlug, projectId, peekIssueId, peekProjectId } = router.query; + const { workspaceSlug, projectId } = router.query; // hooks const { setToastAlert } = useToast(); @@ -113,16 +113,6 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => { /> - {workspaceSlug && peekIssueId && peekProjectId && ( - - await handleIssues(issueToUpdate.target_date ?? "", issueToUpdate as TIssue, EIssueActions.UPDATE) - } - /> - )} ); }); diff --git a/web/components/issues/issue-layouts/calendar/day-tile.tsx b/web/components/issues/issue-layouts/calendar/day-tile.tsx index 6bc3052a9c..5b4885bf3e 100644 --- a/web/components/issues/issue-layouts/calendar/day-tile.tsx +++ b/web/components/issues/issue-layouts/calendar/day-tile.tsx @@ -97,7 +97,7 @@ export const CalendarDayTile: React.FC = observer((props) => { formKey="target_date" groupId={formattedDatePayload} prePopulatedData={{ - target_date: renderFormattedPayloadDate(date.date), + target_date: renderFormattedPayloadDate(date.date) ?? undefined, }} quickAddCallback={quickAddCallback} viewId={viewId} diff --git a/web/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx b/web/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx index 7a3c014177..0f81d79a60 100644 --- a/web/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx +++ b/web/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx @@ -110,11 +110,11 @@ export const CalendarQuickAddIssueForm: React.FC = observer((props) => { }, [errors, setToastAlert]); const onSubmitHandler = async (formData: TIssue) => { - if (isSubmitting || !groupId || !workspaceDetail || !projectDetail || !workspaceSlug || !projectId) return; + if (isSubmitting || !workspaceSlug || !projectId) return; reset({ ...defaultValues }); - const payload = createIssuePayload(workspaceDetail, projectDetail, { + const payload = createIssuePayload(projectId.toString(), { ...(prePopulatedData ?? {}), ...formData, }); diff --git a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx index 13b324282a..73802886ea 100644 --- a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx +++ b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx @@ -1,10 +1,10 @@ -import React, { useCallback } from "react"; +import React from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; // hooks import { useIssues, useUser } from "hooks/store"; // components -import { IssueGanttBlock, IssuePeekOverview } from "components/issues"; +import { IssueGanttBlock } from "components/issues"; import { GanttChartRoot, IBlockUpdateData, @@ -32,10 +32,10 @@ interface IBaseGanttRoot { } export const BaseGanttRoot: React.FC = observer((props: IBaseGanttRoot) => { - const { issueFiltersStore, issueStore, viewId, issueActions } = props; + const { issueFiltersStore, issueStore, viewId } = props; // router const router = useRouter(); - const { workspaceSlug, peekIssueId, peekProjectId } = router.query; + const { workspaceSlug } = router.query; // store hooks const { membership: { currentProjectRole }, @@ -57,14 +57,6 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan await issueStore.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, payload, viewId); }; - const handleIssues = useCallback( - async (issue: TIssue, action: EIssueActions) => { - if (issueActions[action]) { - await issueActions[action]!(issue); - } - }, - [issueActions] - ); const isAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; return ( @@ -92,16 +84,6 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan enableReorder={appliedDisplayFilters?.order_by === "sort_order" && isAllowed} /> - {workspaceSlug && peekIssueId && peekProjectId && ( - { - await handleIssues(issueToUpdate as TIssue, action); - }} - /> - )} ); }); diff --git a/web/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx b/web/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx index 621a12d766..a370440f94 100644 --- a/web/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx +++ b/web/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx @@ -104,14 +104,11 @@ export const GanttInlineCreateIssueForm: React.FC = observer((props) => { const onSubmitHandler = async (formData: TIssue) => { if (isSubmitting || !workspaceSlug || !projectId) return; - // resetting the form so that user can add another issue quickly - reset({ ...defaultValues, ...(prePopulatedData ?? {}) }); + reset({ ...defaultValues }); - const payload = createIssuePayload(workspaceDetail!, currentProjectDetails!, { + const payload = createIssuePayload(projectId.toString(), { ...(prePopulatedData ?? {}), ...formData, - start_date: renderFormattedPayloadDate(new Date()), - target_date: renderFormattedPayloadDate(new Date(new Date().getTime() + 24 * 60 * 60 * 1000)), }); try { diff --git a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx index dcf6e7cc32..c262af2cab 100644 --- a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx +++ b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx @@ -276,14 +276,14 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas - {workspaceSlug && peekIssueId && peekProjectId && ( + {/* {workspaceSlug && peekIssueId && peekProjectId && ( await handleIssues(issueToUpdate as TIssue, EIssueActions.UPDATE)} /> - )} + )} */} ); }); diff --git a/web/components/issues/issue-layouts/kanban/default.tsx b/web/components/issues/issue-layouts/kanban/default.tsx index 4e81907bea..3820f3bac6 100644 --- a/web/components/issues/issue-layouts/kanban/default.tsx +++ b/web/components/issues/issue-layouts/kanban/default.tsx @@ -79,6 +79,8 @@ const GroupByKanBan: React.FC = observer((props) => { const verticalAlignPosition = (_list: IGroupByColumn) => kanBanToggle?.groupByHeaderMinMax.includes(_list.id); + const isGroupByCreatedBy = group_by === "created_by"; + return (
{list && @@ -100,7 +102,7 @@ const GroupByKanBan: React.FC = observer((props) => { kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} issuePayload={_list.payload} - disableIssueCreation={disableIssueCreation} + disableIssueCreation={disableIssueCreation || isGroupByCreatedBy} currentStore={currentStore} addIssuesToView={addIssuesToView} /> diff --git a/web/components/issues/issue-layouts/kanban/kanban-group.tsx b/web/components/issues/issue-layouts/kanban/kanban-group.tsx index ce0a4d105a..cbd1b1fc19 100644 --- a/web/components/issues/issue-layouts/kanban/kanban-group.tsx +++ b/web/components/issues/issue-layouts/kanban/kanban-group.tsx @@ -9,6 +9,8 @@ import { TUnGroupedIssues, } from "@plane/types"; import { EIssueActions } from "../types"; +// hooks +import { useProjectState } from "hooks/store"; //components import { KanBanQuickAddIssueForm, KanbanIssueBlocksList } from "."; @@ -56,6 +58,33 @@ export const KanbanGroup = (props: IKanbanGroup) => { viewId, } = props; + const projectState = useProjectState(); + + const prePopulateQuickAddData = (groupByKey: string | null, value: string) => { + const defaultState = projectState.projectStates?.find((state) => state.default); + let preloadedData: object = { state_id: defaultState?.id }; + + if (groupByKey) { + if (groupByKey === "state") { + preloadedData = { ...preloadedData, state_id: value }; + } else if (groupByKey === "priority") { + preloadedData = { ...preloadedData, priority: value }; + } else if (groupByKey === "labels" && value != "None") { + preloadedData = { ...preloadedData, label_ids: [value] }; + } else if (groupByKey === "assignees" && value != "None") { + preloadedData = { ...preloadedData, assignee_ids: [value] }; + } else if (groupByKey === "created_by") { + preloadedData = { ...preloadedData }; + } else { + preloadedData = { ...preloadedData, [groupByKey]: value }; + } + } + + return preloadedData; + }; + + const isGroupByCreatedBy = group_by === "created_by"; + return (
@@ -87,13 +116,13 @@ export const KanbanGroup = (props: IKanbanGroup) => {
- {enableQuickIssueCreate && !disableIssueCreation && ( + {enableQuickIssueCreate && !disableIssueCreation && !isGroupByCreatedBy && ( = obser const router = useRouter(); const { workspaceSlug, projectId } = router.query; // store hooks - const { getWorkspaceBySlug } = useWorkspace(); const { getProjectById } = useProject(); - const workspaceDetail = workspaceSlug ? getWorkspaceBySlug(workspaceSlug.toString()) : null; const projectDetail = projectId ? getProjectById(projectId.toString()) : null; const ref = useRef(null); @@ -87,11 +85,11 @@ export const KanBanQuickAddIssueForm: React.FC = obser }, [isOpen, reset]); const onSubmitHandler = async (formData: TIssue) => { - if (isSubmitting || !groupId || !workspaceDetail || !projectDetail || !workspaceSlug || !projectId) return; + if (isSubmitting || !workspaceSlug || !projectId) return; reset({ ...defaultValues }); - const payload = createIssuePayload(workspaceDetail, projectDetail, { + const payload = createIssuePayload(projectId.toString(), { ...(prePopulatedData ?? {}), ...formData, }); @@ -143,33 +141,6 @@ export const KanBanQuickAddIssueForm: React.FC = obser New Issue
)} - - {/* {isOpen && ( -
- - - )} - - {isOpen && ( -

- Press {"'"}Enter{"'"} to add another issue -

- )} - - {!isOpen && ( - - )} */}
); }); diff --git a/web/components/issues/issue-layouts/list/block.tsx b/web/components/issues/issue-layouts/list/block.tsx index 7c49e744c1..99138d8f9b 100644 --- a/web/components/issues/issue-layouts/list/block.tsx +++ b/web/components/issues/issue-layouts/list/block.tsx @@ -1,13 +1,13 @@ -import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; // components import { IssueProperties } from "../properties/all-properties"; +// hooks +import { useApplication, useIssueDetail, useProject } from "hooks/store"; // ui -import { Spinner, Tooltip } from "@plane/ui"; +import { Spinner, Tooltip, ControlLink } from "@plane/ui"; // types import { TIssue, IIssueDisplayProperties, TIssueMap } from "@plane/types"; import { EIssueActions } from "../types"; -import { useProject } from "hooks/store"; interface IssueBlockProps { issueId: string; @@ -20,27 +20,29 @@ interface IssueBlockProps { export const IssueBlock: React.FC = observer((props: IssueBlockProps) => { const { issuesMap, issueId, handleIssues, quickActions, displayProperties, canEditProperties } = props; - // router - const router = useRouter(); + // hooks + const { + router: { workspaceSlug, projectId }, + } = useApplication(); + const { getProjectById } = useProject(); + const { setPeekIssue } = useIssueDetail(); + const updateIssue = (issueToUpdate: TIssue) => { handleIssues(issueToUpdate, EIssueActions.UPDATE); }; + const handleIssuePeekOverview = (issue: TIssue) => + workspaceSlug && + issue && + issue.project_id && + issue.id && + setPeekIssue({ workspaceSlug, projectId: issue.project_id, issueId: issue.id }); + const issue = issuesMap[issueId]; if (!issue) return null; - const handleIssuePeekOverview = () => { - const { query } = router; - - router.push({ - pathname: router.pathname, - query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project_id }, - }); - }; - const canEditIssueProperties = canEditProperties(issue.project_id); - const { getProjectById } = useProject(); const projectDetails = getProjectById(issue.project_id); return ( @@ -55,14 +57,17 @@ export const IssueBlock: React.FC = observer((props: IssueBlock {issue?.tempId !== undefined && (
)} - -
- {issue.name} -
-
+ + handleIssuePeekOverview(issue)} + className="w-full line-clamp-1 cursor-pointer text-sm font-medium text-custom-text-100" + > + + {issue.name} + +
{!issue?.tempId ? ( diff --git a/web/components/issues/issue-layouts/list/default.tsx b/web/components/issues/issue-layouts/list/default.tsx index 9bf7cfc78e..38994215c8 100644 --- a/web/components/issues/issue-layouts/list/default.tsx +++ b/web/components/issues/issue-layouts/list/default.tsx @@ -21,7 +21,6 @@ export interface IGroupByList { issueIds: TGroupedIssues | TUnGroupedIssues | any; issuesMap: TIssueMap; group_by: string | null; - is_list?: boolean; handleIssues: (issue: TIssue, action: EIssueActions) => Promise; quickActions: (issue: TIssue) => React.ReactNode; displayProperties: IIssueDisplayProperties | undefined; @@ -45,7 +44,6 @@ const GroupByList: React.FC = (props) => { issueIds, issuesMap, group_by, - is_list = false, handleIssues, quickActions, displayProperties, @@ -70,11 +68,27 @@ const GroupByList: React.FC = (props) => { const prePopulateQuickAddData = (groupByKey: string | null, value: any) => { const defaultState = projectState.projectStates?.find((state) => state.default); - if (groupByKey === null) return { state_id: defaultState?.id }; - else { - if (groupByKey === "state") return { state: groupByKey === "state" ? value : defaultState?.id }; - else return { state_id: defaultState?.id, [groupByKey]: value }; + let preloadedData: object = { state_id: defaultState?.id }; + + if (groupByKey === null) { + preloadedData = { ...preloadedData }; + } else { + if (groupByKey === "state") { + preloadedData = { ...preloadedData, state_id: value }; + } else if (groupByKey === "priority") { + preloadedData = { ...preloadedData, priority: value }; + } else if (groupByKey === "labels" && value != "None") { + preloadedData = { ...preloadedData, label_ids: [value] }; + } else if (groupByKey === "assignees" && value != "None") { + preloadedData = { ...preloadedData, assignee_ids: [value] }; + } else if (groupByKey === "created_by") { + preloadedData = { ...preloadedData }; + } else { + preloadedData = { ...preloadedData, [groupByKey]: value }; + } } + + return preloadedData; }; const validateEmptyIssueGroups = (issues: TIssue[]) => { @@ -83,6 +97,10 @@ const GroupByList: React.FC = (props) => { return true; }; + const is_list = group_by === null ? true : false; + + const isGroupByCreatedBy = group_by === "created_by"; + return (
{list && @@ -97,7 +115,7 @@ const GroupByList: React.FC = (props) => { title={_list.name || ""} count={is_list ? issueIds?.length || 0 : issueIds?.[_list.id]?.length || 0} issuePayload={_list.payload} - disableIssueCreation={disableIssueCreation} + disableIssueCreation={disableIssueCreation || isGroupByCreatedBy} currentStore={currentStore} addIssuesToView={addIssuesToView} /> @@ -114,7 +132,7 @@ const GroupByList: React.FC = (props) => { /> )} - {enableIssueQuickAdd && !disableIssueCreation && ( + {enableIssueQuickAdd && !disableIssueCreation && !isGroupByCreatedBy && (
= observer((props // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; - // store hooks - const { currentWorkspace } = useWorkspace(); - const { currentProjectDetails } = useProject(); + // hooks + const { getProjectById } = useProject(); + + const projectDetail = (projectId && getProjectById(projectId.toString())) || undefined; const ref = useRef(null); @@ -88,11 +89,11 @@ export const ListQuickAddIssueForm: FC = observer((props }, [isOpen, reset]); const onSubmitHandler = async (formData: TIssue) => { - if (isSubmitting || !currentWorkspace || !currentProjectDetails || !workspaceSlug || !projectId) return; + if (isSubmitting || !workspaceSlug || !projectId) return; reset({ ...defaultValues }); - const payload = createIssuePayload(currentWorkspace, currentProjectDetails, { + const payload = createIssuePayload(projectId.toString(), { ...(prePopulatedData ?? {}), ...formData, }); @@ -127,12 +128,7 @@ export const ListQuickAddIssueForm: FC = observer((props onSubmit={handleSubmit(onSubmitHandler)} className="flex w-full items-center gap-x-3 border-[0.5px] border-t-0 border-custom-border-100 bg-custom-background-100 px-3" > - +
{`Press 'Enter' to add another issue`}
diff --git a/web/components/issues/issue-layouts/properties/all-properties.tsx b/web/components/issues/issue-layouts/properties/all-properties.tsx index fe05d834b1..0df4b415ed 100644 --- a/web/components/issues/issue-layouts/properties/all-properties.tsx +++ b/web/components/issues/issue-layouts/properties/all-properties.tsx @@ -141,8 +141,8 @@ export const IssueProperties: React.FC = observer((props) => { onChange={handleAssignee} disabled={isReadOnly} multiple - buttonVariant={issue.assignee_ids.length > 0 ? "transparent-without-text" : "border-without-text"} - buttonClassName={issue.assignee_ids.length > 0 ? "hover:bg-transparent px-0" : ""} + buttonVariant={issue.assignee_ids?.length > 0 ? "transparent-without-text" : "border-without-text"} + buttonClassName={issue.assignee_ids?.length > 0 ? "hover:bg-transparent px-0" : ""} />
diff --git a/web/components/issues/issue-layouts/roots/project-layout-root.tsx b/web/components/issues/issue-layouts/roots/project-layout-root.tsx index f8e428e5c0..453f331cb5 100644 --- a/web/components/issues/issue-layouts/roots/project-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/project-layout-root.tsx @@ -1,4 +1,4 @@ -import { useRouter } from "next/router"; +import { FC } from "react"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; // components @@ -10,21 +10,24 @@ import { ProjectAppliedFiltersRoot, ProjectSpreadsheetLayout, ProjectEmptyState, + IssuePeekOverview, } from "components/issues"; +// ui import { Spinner } from "@plane/ui"; -import { useIssues } from "hooks/store/use-issues"; -import { EIssuesStoreType } from "constants/issue"; // hooks +import { useApplication, useIssues } from "hooks/store"; +// constants +import { EIssuesStoreType } from "constants/issue"; -export const ProjectLayoutRoot: React.FC = observer(() => { - // router - const router = useRouter(); - const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; - +export const ProjectLayoutRoot: FC = observer(() => { + // hooks + const { + router: { workspaceSlug, projectId }, + } = useApplication(); const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT); useSWR( - workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, + workspaceSlug && projectId ? `PROJECT_ISSUES_${workspaceSlug}_${projectId}` : null, async () => { if (workspaceSlug && projectId) { await issuesFilter?.fetchFilters(workspaceSlug, projectId); @@ -40,28 +43,35 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
- {issues?.loader === "init-loader" || !issues?.groupedIssueIds ? ( + {issues?.loader === "init-loader" ? (
) : ( <> - {(issues?.groupedIssueIds ?? {}).length == 0 ? ( - - ) : ( -
- {activeLayout === "list" ? ( - - ) : activeLayout === "kanban" ? ( - - ) : activeLayout === "calendar" ? ( - - ) : activeLayout === "gantt_chart" ? ( - - ) : activeLayout === "spreadsheet" ? ( - - ) : null} + {!issues?.groupedIssueIds ? ( +
+
+ ) : ( + <> +
+ {activeLayout === "list" ? ( + + ) : activeLayout === "kanban" ? ( + + ) : activeLayout === "calendar" ? ( + + ) : activeLayout === "gantt_chart" ? ( + + ) : activeLayout === "spreadsheet" ? ( + + ) : null} +
+ + {/* peek overview */} + + )} )} diff --git a/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx b/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx index 0c2b323a21..f6b5500a62 100644 --- a/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx @@ -29,12 +29,15 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => { issuesFilter: { issueFilters, fetchFilters }, } = useIssues(EIssuesStoreType.PROJECT_VIEW); - useSWR(workspaceSlug && projectId && viewId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, async () => { - if (workspaceSlug && projectId && viewId) { - await fetchFilters(workspaceSlug, projectId, viewId); - await fetchIssues(workspaceSlug, projectId, groupedIssueIds ? "mutation" : "init-loader"); + useSWR( + workspaceSlug && projectId && viewId ? `PROJECT_VIEW_ISSUES_${workspaceSlug}_${projectId}` : null, + async () => { + if (workspaceSlug && projectId && viewId) { + await fetchFilters(workspaceSlug, projectId, viewId); + await fetchIssues(workspaceSlug, projectId, groupedIssueIds ? "mutation" : "init-loader"); + } } - }); + ); const activeLayout = issueFilters?.displayFilters?.layout; diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx index 6dbcecb8d4..89d8367f3c 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx @@ -32,7 +32,7 @@ export const SpreadsheetAssigneeColumn: React.FC = ({ issueId, onChange, disabled={disabled} multiple placeholder="Assignees" - buttonVariant={issueDetail.assignee_ids.length > 0 ? "transparent-without-text" : "transparent-with-text"} + buttonVariant={issueDetail.assignee_ids?.length > 0 ? "transparent-without-text" : "transparent-with-text"} buttonClassName="text-left" buttonContainerClassName="w-full" /> diff --git a/web/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx b/web/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx index 603276b3bb..44eb3a1987 100644 --- a/web/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx @@ -1,4 +1,5 @@ import { useEffect, useState, useRef } from "react"; +import { useRouter } from "next/router"; import { useForm } from "react-hook-form"; import { observer } from "mobx-react-lite"; import { PlusIcon } from "lucide-react"; @@ -55,6 +56,10 @@ const Inputs = (props: any) => { export const SpreadsheetQuickAddIssueForm: React.FC = observer((props) => { const { formKey, prePopulatedData, quickAddCallback, viewId } = props; + + // router + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; // store hooks const { currentWorkspace } = useWorkspace(); const { currentProjectDetails } = useProject(); @@ -148,7 +153,7 @@ export const SpreadsheetQuickAddIssueForm: React.FC = observer((props) => reset({ ...defaultValues }); - const payload = createIssuePayload(currentWorkspace, currentProjectDetails, { + const payload = createIssuePayload(currentProjectDetails.id, { ...(prePopulatedData ?? {}), ...formData, }); diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx index b86eabf543..0e5d2ba946 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx @@ -1,13 +1,7 @@ import React, { useEffect, useRef, useState } from "react"; -import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; // components -import { - IssuePeekOverview, - SpreadsheetColumnsList, - SpreadsheetIssuesColumn, - SpreadsheetQuickAddIssueForm, -} from "components/issues"; +import { SpreadsheetColumnsList, SpreadsheetIssuesColumn, SpreadsheetQuickAddIssueForm } from "components/issues"; import { Spinner, LayersIcon } from "@plane/ui"; // types import { TIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueLabel, IState } from "@plane/types"; @@ -56,9 +50,6 @@ export const SpreadsheetView: React.FC = observer((props) => { const [isScrolled, setIsScrolled] = useState(false); // refs const containerRef = useRef(null); - // router - const router = useRouter(); - const { workspaceSlug, peekIssueId, peekProjectId } = router.query; const handleScroll = () => { if (!containerRef.current) return; @@ -186,14 +177,6 @@ export const SpreadsheetView: React.FC = observer((props) => { ))} */}
- {workspaceSlug && peekIssueId && peekProjectId && ( - await handleIssues(issueToUpdate, EIssueActions.UPDATE)} - /> - )}
); }); diff --git a/web/components/issues/issue-layouts/utils.tsx b/web/components/issues/issue-layouts/utils.tsx index 866e26e750..f3f4a483e0 100644 --- a/web/components/issues/issue-layouts/utils.tsx +++ b/web/components/issues/issue-layouts/utils.tsx @@ -49,7 +49,7 @@ const getProjectColumns = (project: IProjectStore): IGroupByColumn[] | undefined id: project.id, name: project.name, Icon:
{renderEmoji(project.emoji || "")}
, - payload: { project: project.id }, + payload: { project_id: project.id }, }; }) as any; }; @@ -66,7 +66,7 @@ const getStateColumns = (projectState: IStateStore): IGroupByColumn[] | undefine
), - payload: { state: state.id }, + payload: { state_id: state.id }, })) as any; }; @@ -111,7 +111,7 @@ const getLabelsColumns = (projectLabel: ILabelRootStore) => { Icon: (
), - payload: { labels: [label.id] }, + payload: label?.id === "None" ? {} : { label_ids: [label.id] }, })); }; @@ -123,17 +123,17 @@ const getAssigneeColumns = (member: IMemberRootStore) => { if (!projectMemberIds) return; - const assigneeColumns = projectMemberIds.map((memberId) => { + const assigneeColumns: any = projectMemberIds.map((memberId) => { const member = getUserDetails(memberId); return { id: memberId, name: member?.display_name || "", Icon: , - payload: { assignees: [memberId] }, + payload: { assignee_ids: [memberId] }, }; }); - assigneeColumns.push({ id: "None", name: "None", Icon: , payload: { assignees: [""] } }); + assigneeColumns.push({ id: "None", name: "None", Icon: , payload: {} }); return assigneeColumns; }; @@ -152,7 +152,7 @@ const getCreatedByColumns = (member: IMemberRootStore) => { id: memberId, name: member?.display_name || "", Icon: , - payload: { assignees: [memberId] }, + payload: {}, }; }); }; diff --git a/web/components/issues/issue-links/link-detail.tsx b/web/components/issues/issue-links/link-detail.tsx index d00e43597f..3a5fdc2246 100644 --- a/web/components/issues/issue-links/link-detail.tsx +++ b/web/components/issues/issue-links/link-detail.tsx @@ -1,5 +1,6 @@ import { FC, useState } from "react"; // hooks +import useToast from "hooks/use-toast"; import { useIssueDetail } from "hooks/store"; // ui import { ExternalLinkIcon, Tooltip } from "@plane/ui"; @@ -9,6 +10,7 @@ import { Pencil, Trash2, LinkIcon } from "lucide-react"; import { IssueLinkCreateUpdateModal, TLinkOperationsModal } from "./create-update-link-modal"; // helpers import { calculateTimeAgo } from "helpers/date-time.helper"; +import { copyTextToClipboard } from "helpers/string.helper"; export type TIssueLinkDetail = { linkId: string; @@ -23,6 +25,8 @@ export const IssueLinkDetail: FC = (props) => { const { link: { getLinkById }, } = useIssueDetail(); + const { setToastAlert } = useToast(); + // state const [isIssueLinkModalOpen, setIsIssueLinkModalOpen] = useState(false); const toggleIssueLinkModal = (modalToggle: boolean) => setIsIssueLinkModalOpen(modalToggle); @@ -40,18 +44,23 @@ export const IssueLinkDetail: FC = (props) => { />
-
+
{ + copyTextToClipboard(linkDetail.url); + setToastAlert({ + type: "success", + title: "Link copied!", + message: "Link copied to clipboard", + }); + }} + >
- - // copyToClipboard(linkDetail.title && linkDetail.title !== "" ? linkDetail.title : linkDetail.url) - // } - > + {linkDetail.title && linkDetail.title !== "" ? linkDetail.title : linkDetail.url} diff --git a/web/components/issues/issue-links/root.tsx b/web/components/issues/issue-links/root.tsx index bd2db3d398..d4e948bb29 100644 --- a/web/components/issues/issue-links/root.tsx +++ b/web/components/issues/issue-links/root.tsx @@ -27,7 +27,7 @@ export const IssueLinkRoot: FC = (props) => { const { router: { workspaceSlug, projectId }, } = useApplication(); - const { issueId, createLink, updateLink, removeLink } = useIssueDetail(); + const { peekIssue, createLink, updateLink, removeLink } = useIssueDetail(); // state const [isIssueLinkModalOpen, setIsIssueLinkModalOpen] = useState(false); const toggleIssueLinkModal = (modalToggle: boolean) => setIsIssueLinkModalOpen(modalToggle); @@ -38,8 +38,8 @@ export const IssueLinkRoot: FC = (props) => { () => ({ create: async (data: Partial) => { try { - if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields"); - await createLink(workspaceSlug, projectId, issueId, data); + if (!workspaceSlug || !projectId || !peekIssue?.issueId) throw new Error("Missing required fields"); + await createLink(workspaceSlug, projectId, peekIssue?.issueId, data); setToastAlert({ message: "The link has been successfully created", type: "success", @@ -56,8 +56,8 @@ export const IssueLinkRoot: FC = (props) => { }, update: async (linkId: string, data: Partial) => { try { - if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields"); - await updateLink(workspaceSlug, projectId, issueId, linkId, data); + if (!workspaceSlug || !projectId || !peekIssue?.issueId) throw new Error("Missing required fields"); + await updateLink(workspaceSlug, projectId, peekIssue?.issueId, linkId, data); setToastAlert({ message: "The link has been successfully updated", type: "success", @@ -74,8 +74,8 @@ export const IssueLinkRoot: FC = (props) => { }, remove: async (linkId: string) => { try { - if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields"); - await removeLink(workspaceSlug, projectId, issueId, linkId); + if (!workspaceSlug || !projectId || !peekIssue?.issueId) throw new Error("Missing required fields"); + await removeLink(workspaceSlug, projectId, peekIssue?.issueId, linkId); setToastAlert({ message: "The link has been successfully removed", type: "success", @@ -91,7 +91,7 @@ export const IssueLinkRoot: FC = (props) => { } }, }), - [workspaceSlug, projectId, issueId, createLink, updateLink, removeLink, setToastAlert] + [workspaceSlug, projectId, peekIssue, createLink, updateLink, removeLink, setToastAlert] ); return ( diff --git a/web/components/issues/peek-overview/properties.tsx b/web/components/issues/peek-overview/properties.tsx index 852f1ac92a..48afc4cd4e 100644 --- a/web/components/issues/peek-overview/properties.tsx +++ b/web/components/issues/peek-overview/properties.tsx @@ -6,11 +6,17 @@ import { CalendarDays, Link2, Plus, Signal, Tag, Triangle, LayoutPanelTop } from import { useIssueDetail, useProject, useUser } from "hooks/store"; // ui icons import { DiceIcon, DoubleCircleIcon, UserGroupIcon, ContrastIcon } from "@plane/ui"; -import { SidebarCycleSelect, SidebarLabelSelect, SidebarModuleSelect, SidebarParentSelect } from "components/issues"; +import { + IssueLinkRoot, + SidebarCycleSelect, + SidebarLabelSelect, + SidebarModuleSelect, + SidebarParentSelect, +} from "components/issues"; import { EstimateDropdown, PriorityDropdown, ProjectMemberDropdown, StateDropdown } from "components/dropdowns"; // components import { CustomDatePicker } from "components/ui"; -import { LinkModal, LinksList } from "components/core"; +import { LinkModal } from "components/core"; // types import { TIssue, TIssuePriorities, ILinkDetails, IIssueLink } from "@plane/types"; // constants @@ -39,6 +45,9 @@ export const PeekOverviewProperties: FC = observer((pro const router = useRouter(); const { workspaceSlug, projectId } = router.query; + const uneditable = currentProjectRole ? [5, 10].includes(currentProjectRole) : false; + const isAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; + const handleState = (_state: string) => { issueUpdate({ ...issue, state_id: _state }); }; @@ -274,42 +283,8 @@ export const PeekOverviewProperties: FC = observer((pro
-
-
-
- -

Links

-
-
- {!disableUserActions && ( - - )} -
-
-
- {issue?.issue_link && issue.issue_link.length > 0 ? ( - - ) : null} -
+
+
diff --git a/web/components/issues/peek-overview/root.tsx b/web/components/issues/peek-overview/root.tsx index 12113af08b..2b06bd0dab 100644 --- a/web/components/issues/peek-overview/root.tsx +++ b/web/components/issues/peek-overview/root.tsx @@ -1,4 +1,5 @@ -import { FC, Fragment, ReactNode, useCallback, useEffect } from "react"; +import { FC, Fragment, useEffect, useState } from "react"; +// router import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; // hooks @@ -13,33 +14,28 @@ import { TIssue, IIssueLink } from "@plane/types"; // constants import { EUserProjectRoles } from "constants/project"; import { EIssuesStoreType } from "constants/issue"; -import { EIssueActions } from "../issue-layouts/types"; interface IIssuePeekOverview { - workspaceSlug: string; - projectId: string; - issueId: string; - handleIssue: (issue: Partial, action: EIssueActions) => void; isArchived?: boolean; - children?: ReactNode; } export const IssuePeekOverview: FC = observer((props) => { - const { workspaceSlug, projectId, issueId, handleIssue, children, isArchived = false } = props; + const { isArchived = false } = props; // router const router = useRouter(); - const { peekIssueId } = router.query; - // FIXME - // store hooks - // const { - // archivedIssueDetail: { - // getIssue: getArchivedIssue, - // loader: archivedIssueLoader, - // fetchPeekIssueDetails: fetchArchivedPeekIssueDetails, - // }, - // } = useMobxStore(); - + // hooks + const { currentProjectDetails } = useProject(); + const { setToastAlert } = useToast(); + const { + membership: { currentProjectRole }, + } = useUser(); const { + issues: { removeIssue: removeArchivedIssue }, + } = useIssues(EIssuesStoreType.ARCHIVED); + const { + peekIssue, + updateIssue, + removeIssue, createComment, updateComment, removeComment, @@ -53,37 +49,38 @@ export const IssuePeekOverview: FC = observer((props) => { updateLink, removeLink, issue: { getIssueById, fetchIssue }, - // loader, - setIssueId, fetchActivities, } = useIssueDetail(); - const { - issues: { removeIssue }, - } = useIssues(EIssuesStoreType.ARCHIVED); - const { - membership: { currentProjectRole }, - } = useUser(); - const { currentProjectDetails } = useProject(); - - const { setToastAlert } = useToast(); + // state + const [loader, setLoader] = useState(false); - const fetchIssueDetail = useCallback(async () => { - if (workspaceSlug && projectId && peekIssueId) { - //if (isArchived) await fetchArchivedPeekIssueDetails(workspaceSlug, projectId, peekIssueId as string); - //else - await fetchIssue(workspaceSlug, projectId, peekIssueId.toString()); + useEffect(() => { + if (peekIssue) { + setLoader(true); + fetchIssue(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId).finally(() => { + setLoader(false); + }); } - }, [fetchIssue, workspaceSlug, projectId, peekIssueId]); + }, [peekIssue, fetchIssue]); - useEffect(() => { - fetchIssueDetail(); - }, [workspaceSlug, projectId, peekIssueId, fetchIssueDetail]); + if (!peekIssue) return <>; + + const issue = getIssueById(peekIssue.issueId) || undefined; + const redirectToIssueDetail = () => { + router.push({ + pathname: `/${peekIssue.workspaceSlug}/projects/${peekIssue.projectId}/${ + isArchived ? "archived-issues" : "issues" + }/${peekIssue.issueId}`, + }); + }; const handleCopyText = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); copyUrlToClipboard( - `${workspaceSlug}/projects/${projectId}/${isArchived ? "archived-issues" : "issues"}/${peekIssueId}` + `${peekIssue.workspaceSlug}/projects/${peekIssue.projectId}/${isArchived ? "archived-issues" : "issues"}/${ + peekIssue.issueId + }` ).then(() => { setToastAlert({ type: "success", @@ -93,101 +90,81 @@ export const IssuePeekOverview: FC = observer((props) => { }); }; - const redirectToIssueDetail = () => { - router.push({ - pathname: `/${workspaceSlug}/projects/${projectId}/${isArchived ? "archived-issues" : "issues"}/${issueId}`, - }); - }; - - // const issue = isArchived ? getArchivedIssue : getIssue; - // const isLoading = isArchived ? archivedIssueLoader : loader; - - const issue = getIssueById(issueId); - const isLoading = false; - const issueUpdate = async (_data: Partial) => { - if (handleIssue) { - await handleIssue(_data, EIssueActions.UPDATE); - fetchActivities(workspaceSlug, projectId, issueId); - } + if (!issue) return; + await updateIssue(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId, _data); + fetchActivities(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId); + }; + const issueDelete = async () => { + if (!issue) return; + if (isArchived) await removeArchivedIssue(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId); + else await removeIssue(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId); }; - const issueReactionCreate = (reaction: string) => createReaction(workspaceSlug, projectId, issueId, reaction); - - const issueReactionRemove = (reaction: string) => removeReaction(workspaceSlug, projectId, issueId, reaction); - - const issueCommentCreate = (comment: any) => createComment(workspaceSlug, projectId, issueId, comment); - - const issueCommentUpdate = (comment: any) => updateComment(workspaceSlug, projectId, issueId, comment?.id, comment); + const issueReactionCreate = (reaction: string) => + createReaction(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId, reaction); + const issueReactionRemove = (reaction: string) => + removeReaction(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId, reaction); - const issueCommentRemove = (commentId: string) => removeComment(workspaceSlug, projectId, issueId, commentId); + const issueCommentCreate = (comment: any) => + createComment(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId, comment); + const issueCommentUpdate = (comment: any) => + updateComment(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId, comment?.id, comment); + const issueCommentRemove = (commentId: string) => + removeComment(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId, commentId); const issueCommentReactionCreate = (commentId: string, reaction: string) => - createCommentReaction(workspaceSlug, projectId, commentId, reaction); - + createCommentReaction(peekIssue.workspaceSlug, peekIssue.projectId, commentId, reaction); const issueCommentReactionRemove = (commentId: string, reaction: string) => - removeCommentReaction(workspaceSlug, projectId, commentId, reaction); + removeCommentReaction(peekIssue.workspaceSlug, peekIssue.projectId, commentId, reaction); - const issueSubscriptionCreate = () => createSubscription(workspaceSlug, projectId, issueId); - - const issueSubscriptionRemove = () => removeSubscription(workspaceSlug, projectId, issueId); - - const issueLinkCreate = (formData: IIssueLink) => createLink(workspaceSlug, projectId, issueId, formData); + const issueSubscriptionCreate = () => + createSubscription(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId); + const issueSubscriptionRemove = () => + removeSubscription(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId); + const issueLinkCreate = (formData: IIssueLink) => + createLink(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId, formData); const issueLinkUpdate = (formData: IIssueLink, linkId: string) => - updateLink(workspaceSlug, projectId, issueId, linkId, formData); - - const issueLinkDelete = (linkId: string) => removeLink(workspaceSlug, projectId, issueId, linkId); - - const handleDeleteIssue = async () => { - if (!issue) return; - - if (isArchived) await removeIssue(workspaceSlug, projectId, issue?.id); - // FIXME else delete... - const { query } = router; - if (query.peekIssueId) { - setIssueId(undefined); - delete query.peekIssueId; - delete query.peekProjectId; - router.push({ - pathname: router.pathname, - query: { ...query }, - }); - } - }; + updateLink(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId, linkId, formData); + const issueLinkDelete = (linkId: string) => + removeLink(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId, linkId); const userRole = currentProjectRole ?? EUserProjectRoles.GUEST; + const isLoading = !issue || loader ? true : false; return ( - - {children} - + {isLoading ? ( + <> // TODO: show the spinner + ) : ( + + )} ); }); diff --git a/web/components/issues/peek-overview/view.tsx b/web/components/issues/peek-overview/view.tsx index e598683b29..6a7737f733 100644 --- a/web/components/issues/peek-overview/view.tsx +++ b/web/components/issues/peek-overview/view.tsx @@ -1,7 +1,5 @@ -import { FC, ReactNode, useRef, useState } from "react"; -import { useRouter } from "next/router"; +import { FC, useRef, useState } from "react"; import { observer } from "mobx-react-lite"; -import useSWR from "swr"; import { MoveRight, MoveDiagonal, Bell, Link2, Trash2 } from "lucide-react"; // hooks import { useIssueDetail, useUser } from "hooks/store"; @@ -43,7 +41,6 @@ interface IIssueView { issueLinkUpdate: (formData: IIssueLink, linkId: string) => Promise; issueLinkDelete: (linkId: string) => Promise; handleDeleteIssue: () => Promise; - children: ReactNode; disableUserActions?: boolean; showCommentAccessSpecifier?: boolean; } @@ -92,7 +89,6 @@ export const IssueView: FC = observer((props) => { issueLinkUpdate, issueLinkDelete, handleDeleteIssue, - children, disableUserActions = false, showCommentAccessSpecifier = false, } = props; @@ -101,58 +97,19 @@ export const IssueView: FC = observer((props) => { const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); // ref const issuePeekOverviewRef = useRef(null); - // router - const router = useRouter(); - const { peekIssueId } = router.query; // store hooks const { - fetchSubscriptions, activity, reaction, subscription, - setIssueId, + setPeekIssue, isAnyModalOpen, isDeleteIssueModalOpen, toggleDeleteIssueModal, } = useIssueDetail(); const { currentUser } = useUser(); - const updateRoutePeekId = () => { - if (issueId != peekIssueId) { - setIssueId(issueId); - const { query } = router; - router.push({ - pathname: router.pathname, - query: { ...query, peekIssueId: issueId, peekProjectId: projectId }, - }); - } - }; - - const removeRoutePeekId = () => { - const { query } = router; - - if (query.peekIssueId) { - setIssueId(undefined); - - delete query.peekIssueId; - delete query.peekProjectId; - router.push({ - pathname: router.pathname, - query: { ...query }, - }); - } - }; - - useSWR( - workspaceSlug && projectId && issueId && peekIssueId && issueId === peekIssueId - ? `ISSUE_PEEK_OVERVIEW_SUBSCRIPTION_${workspaceSlug}_${projectId}_${peekIssueId}` - : null, - async () => { - if (workspaceSlug && projectId && issueId && peekIssueId && issueId === peekIssueId) { - await fetchSubscriptions(workspaceSlug, projectId, issueId); - } - } - ); + const removeRoutePeekId = () => setPeekIssue(undefined); const issueReactions = reaction.getReactionsByIssueId(issueId) || []; const issueActivity = activity.getActivitiesByIssueId(issueId); @@ -172,6 +129,7 @@ export const IssueView: FC = observer((props) => { onSubmit={handleDeleteIssue} /> )} + {issue && isArchived && ( = observer((props) => { onSubmit={handleDeleteIssue} /> )} -
- {children && ( -
- {children} -
- )} - {issueId === peekIssueId && ( +
+ {issueId && (
= observer((props) => {
{issue?.created_by !== currentUser?.id && !issue?.assignee_ids.includes(currentUser?.id ?? "") && - !router.pathname.includes("[archivedIssueId]") && ( + !issue?.archived_at && ( diff --git a/web/components/issues/sidebar-select/module.tsx b/web/components/issues/sidebar-select/module.tsx index ee35a58b67..235f8486b7 100644 --- a/web/components/issues/sidebar-select/module.tsx +++ b/web/components/issues/sidebar-select/module.tsx @@ -81,17 +81,17 @@ export const SidebarModuleSelect: React.FC = observer((props) => { }); // derived values - const issueModule = issueDetail?.issue_module; - const selectedModule = issueModule?.module ? getModuleById(issueModule?.module) : null; + const issueModule = (issueDetail && issueDetail?.module_id && getModuleById(issueDetail.module_id)) || undefined; + const disableSelect = disabled || isUpdating; return (
{ - value === issueModule?.module_detail.id - ? handleRemoveIssueFromModule(issueModule?.id ?? "", issueModule?.module ?? "") + value === issueDetail?.module_id + ? handleRemoveIssueFromModule(issueModule?.id ?? "", issueDetail?.module_id ?? "") : handleModuleChange ? handleModuleChange(value) : handleModuleStoreChange(value); @@ -99,7 +99,7 @@ export const SidebarModuleSelect: React.FC = observer((props) => { options={options} customButton={
- + diff --git a/web/components/issues/sidebar-select/parent.tsx b/web/components/issues/sidebar-select/parent.tsx index 8d3d71c491..47fb01b273 100644 --- a/web/components/issues/sidebar-select/parent.tsx +++ b/web/components/issues/sidebar-select/parent.tsx @@ -2,13 +2,14 @@ import React, { useState } from "react"; import { useRouter } from "next/router"; // hooks -import { useIssueDetail, useProject } from "hooks/store"; +import { useIssueDetail, useIssues, useProject } from "hooks/store"; // components import { ParentIssuesListModal } from "components/issues"; // icons import { X } from "lucide-react"; // types import { TIssue, ISearchIssueResponse } from "@plane/types"; +import { observer } from "mobx-react-lite"; type Props = { onChange: (value: string) => void; @@ -16,7 +17,7 @@ type Props = { disabled?: boolean; }; -export const SidebarParentSelect: React.FC = ({ onChange, issueDetails, disabled = false }) => { +export const SidebarParentSelect: React.FC = observer(({ onChange, issueDetails, disabled = false }) => { const [selectedParentIssue, setSelectedParentIssue] = useState(null); const { isParentIssueModalOpen, toggleParentIssueModal } = useIssueDetail(); @@ -26,6 +27,7 @@ export const SidebarParentSelect: React.FC = ({ onChange, issueDetails, d // hooks const { getProjectById } = useProject(); + const { issueMap } = useIssues(); return ( <> @@ -56,7 +58,7 @@ export const SidebarParentSelect: React.FC = ({ onChange, issueDetails, d {selectedParentIssue && issueDetails?.parent_id ? ( `${selectedParentIssue.project__identifier}-${selectedParentIssue.sequence_id}` ) : !selectedParentIssue && issueDetails?.parent_id ? ( - `${getProjectById(issueDetails.parent_id)?.identifier}-${issueDetails.parent_detail?.sequence_id}` + `${getProjectById(issueDetails.parent_id)?.identifier}-${issueMap[issueDetails.parent_id]?.sequence_id}` ) : ( Select issue )} @@ -64,4 +66,4 @@ export const SidebarParentSelect: React.FC = ({ onChange, issueDetails, d ); -}; +}); diff --git a/web/components/issues/sub-issues/issue.tsx b/web/components/issues/sub-issues/issue.tsx index 8a9f2c0ebb..57a887798a 100644 --- a/web/components/issues/sub-issues/issue.tsx +++ b/web/components/issues/sub-issues/issue.tsx @@ -1,10 +1,8 @@ -import { useRouter } from "next/router"; import React from "react"; import { ChevronDown, ChevronRight, X, Pencil, Trash, Link as LinkIcon, Loader } from "lucide-react"; // components import { SubIssuesRootList } from "./issues-list"; import { IssueProperty } from "./properties"; -import { IssuePeekOverview } from "components/issues"; // ui import { CustomMenu, Tooltip } from "@plane/ui"; // types @@ -42,7 +40,6 @@ export const SubIssues: React.FC = ({ projectId, parentIssue, issueId, - handleIssue, spacingLeft = 0, user, editable, @@ -53,9 +50,6 @@ export const SubIssues: React.FC = ({ handleIssueCrudOperation, handleUpdateIssue, }) => { - const router = useRouter(); - const { peekProjectId, peekIssueId } = router.query; - const { issue: { getIssueById }, } = useIssueDetail(); @@ -68,25 +62,8 @@ export const SubIssues: React.FC = ({ (issue?.project_id && getProjectStates(issue?.project_id)?.find((state) => issue?.state_id == state.id)) || undefined; - const handleIssuePeekOverview = () => { - const { query } = router; - - router.push({ - pathname: router.pathname, - query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project_id }, - }); - }; - return ( <> - {workspaceSlug && peekProjectId && peekIssueId && peekIssueId === issue?.id && ( - await handleUpdateIssue(issue, { ...issue, ...issueToUpdate })} - /> - )}
{issue && (
= ({ )}
-
+
) => TIssue = ( + projectId: string, formData: Partial -) => TIssue = (workspaceDetail: IWorkspace, projectDetail: IProject, formData: Partial) => { - const payload = { - archived_at: null, - assignee_details: [], - attachment_count: 0, - attachments: [], - issue_relations: [], - related_issues: [], - bridge_id: null, - completed_at: new Date(), - created_at: "", - created_by: "", - cycle: null, - cycle_id: null, - cycle_detail: null, - description: {}, - description_html: "", - description_stripped: "", - estimate_point: null, - issue_cycle: null, - issue_link: [], - issue_module: null, - label_details: [], - is_draft: false, - links_list: [], - link_count: 0, - module: null, - module_id: null, - name: "", - parent: null, - parent_detail: null, - priority: "none", - project: projectDetail.id, - project_detail: projectDetail, - sequence_id: 0, - sort_order: 0, - sprints: null, - start_date: null, - state: projectDetail.default_state, - state_detail: {} as any, - sub_issues_count: 0, - target_date: null, - updated_at: "", - updated_by: "", - workspace: workspaceDetail.id, - workspace_detail: workspaceDetail, +) => { + const payload: TIssue = { id: uuidv4(), + project_id: projectId, + // tempId is used for optimistic updates. It is not a part of the API response. tempId: uuidv4(), // to be overridden by the form data ...formData, - assignee_ids: Array.isArray(formData.assignee_ids) - ? formData.assignee_ids - : formData.assignee_ids && formData.assignee_ids !== "none" && formData.assignee_ids !== null - ? [formData.assignee_ids] - : [], - label_ids: Array.isArray(formData.label_ids) - ? formData.label_ids - : formData.label_ids && formData.label_ids !== "none" && formData.label_ids !== null - ? [formData.label_ids] - : [], } as TIssue; return payload; diff --git a/web/lib/app-provider.tsx b/web/lib/app-provider.tsx index dad6253c9d..027800cd85 100644 --- a/web/lib/app-provider.tsx +++ b/web/lib/app-provider.tsx @@ -47,7 +47,7 @@ export const AppProvider: FC = observer((props) => { - = observer((props) => { posthogHost={envConfig?.posthog_host || null} > {children} - + */} + {children} diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx index d59bf4fe02..43c23eb78d 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx @@ -30,8 +30,8 @@ const defaultValues: Partial = { state_id: "", priority: "low", target_date: new Date().toString(), - issue_cycle: null, - issue_module: null, + cycle_id: null, + module_id: null, }; // services diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index fd1a1fd6dd..d6cc2b04d4 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -26,8 +26,8 @@ const defaultValues: Partial = { // description: "", description_html: "", estimate_point: null, - issue_cycle: null, - issue_module: null, + cycle_id: null, + module_id: null, name: "", priority: "low", start_date: undefined, @@ -43,7 +43,7 @@ const IssueDetailsPage: NextPageWithLayout = observer(() => { const router = useRouter(); const { workspaceSlug, projectId, issueId: routeIssueId } = router.query; - const { issueId, fetchIssue } = useIssueDetail(); + const { peekIssue, fetchIssue } = useIssueDetail(); useEffect(() => { if (!workspaceSlug || !projectId || !routeIssueId) return; fetchIssue(workspaceSlug as string, projectId as string, routeIssueId as string); @@ -54,9 +54,9 @@ const IssueDetailsPage: NextPageWithLayout = observer(() => { mutate: mutateIssueDetails, error, } = useSWR( - workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null, - workspaceSlug && projectId && issueId - ? () => issueService.retrieve(workspaceSlug as string, projectId as string, issueId as string) + workspaceSlug && projectId && peekIssue?.issueId ? ISSUE_DETAILS(peekIssue?.issueId as string) : null, + workspaceSlug && projectId && peekIssue?.issueId + ? () => issueService.retrieve(workspaceSlug as string, projectId as string, peekIssue?.issueId as string) : null ); @@ -66,10 +66,10 @@ const IssueDetailsPage: NextPageWithLayout = observer(() => { const submitChanges = useCallback( async (formData: Partial) => { - if (!workspaceSlug || !projectId || !issueId) return; + if (!workspaceSlug || !projectId || !peekIssue?.issueId) return; mutate( - ISSUE_DETAILS(issueId as string), + ISSUE_DETAILS(peekIssue?.issueId as string), (prevData) => { if (!prevData) return prevData; @@ -85,30 +85,30 @@ const IssueDetailsPage: NextPageWithLayout = observer(() => { ...formData, }; - delete payload.related_issues; - delete payload.issue_relations; + // delete payload.related_issues; + // delete payload.issue_relations; await issueService - .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload) + .patchIssue(workspaceSlug as string, projectId as string, peekIssue?.issueId as string, payload) .then(() => { mutateIssueDetails(); - mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); + mutate(PROJECT_ISSUES_ACTIVITY(peekIssue?.issueId as string)); }) .catch((e) => { console.error(e); }); }, - [workspaceSlug, issueId, projectId, mutateIssueDetails] + [workspaceSlug, peekIssue?.issueId, projectId, mutateIssueDetails] ); useEffect(() => { if (!issueDetails) return; - mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); + mutate(PROJECT_ISSUES_ACTIVITY(peekIssue?.issueId as string)); reset({ ...issueDetails, }); - }, [issueDetails, reset, issueId]); + }, [issueDetails, reset, peekIssue?.issueId]); return ( <> @@ -123,7 +123,7 @@ const IssueDetailsPage: NextPageWithLayout = observer(() => { onClick: () => router.push(`/${workspaceSlug}/projects/${projectId}/issues`), }} /> - ) : issueDetails && projectId && issueId ? ( + ) : issueDetails && projectId && peekIssue?.issueId ? (
diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index 261ad17cca..16bc2d3ae7 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -18,7 +18,6 @@ import { AppLayout } from "layouts/app-layout"; // components import { GptAssistantPopover } from "components/core"; import { PageDetailsHeader } from "components/headers/page-details"; -import { IssuePeekOverview } from "components/issues/peek-overview"; import { EmptyState } from "components/common"; // ui import { DocumentEditorWithRef, DocumentReadOnlyEditorWithRef } from "@plane/document-editor"; @@ -49,7 +48,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { const editorRef = useRef(null); // router const router = useRouter(); - const { workspaceSlug, projectId, pageId, peekIssueId } = router.query; + const { workspaceSlug, projectId, pageId } = router.query; // store hooks const { issues: { updateIssue }, @@ -108,12 +107,6 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { } ); - const handleUpdateIssue = (issueId: string, data: Partial) => { - if (!workspaceSlug || !projectId || !currentUser) return; - - updateIssue(workspaceSlug.toString(), projectId.toString(), issueId, data); - }; - const fetchIssue = async (issueId: string) => { const issue = await issueService.retrieve(workspaceSlug as string, projectId as string, issueId as string); return issue as TIssue; @@ -523,17 +516,6 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { )}
)} - { - if (peekIssueId && typeof peekIssueId === "string") { - handleUpdateIssue(peekIssueId, issueToUpdate); - } - }} - />
) : ( diff --git a/web/store/issue/archived/filter.store.ts b/web/store/issue/archived/filter.store.ts index fc81a9e03d..aeb71d7d2b 100644 --- a/web/store/issue/archived/filter.store.ts +++ b/web/store/issue/archived/filter.store.ts @@ -158,16 +158,23 @@ export class ArchivedIssuesFilter extends IssueFilterHelperStore implements IArc _filters.displayFilters = { ..._filters.displayFilters, ...updatedDisplayFilters }; // set sub_group_by to null if group_by is set to null - if (_filters.displayFilters.group_by === null) _filters.displayFilters.sub_group_by = null; + if (_filters.displayFilters.group_by === null) { + _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same if ( _filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === _filters.displayFilters.sub_group_by - ) + ) { _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set group_by to state if layout is switched to kanban and group_by is null - if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) + if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) { _filters.displayFilters.group_by = "state"; + updatedDisplayFilters.group_by = "state"; + } runInAction(() => { Object.keys(updatedDisplayFilters).forEach((_key) => { diff --git a/web/store/issue/cycle/filter.store.ts b/web/store/issue/cycle/filter.store.ts index 007cb9a2b6..fa933c372d 100644 --- a/web/store/issue/cycle/filter.store.ts +++ b/web/store/issue/cycle/filter.store.ts @@ -145,16 +145,23 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI _filters.displayFilters = { ..._filters.displayFilters, ...updatedDisplayFilters }; // set sub_group_by to null if group_by is set to null - if (_filters.displayFilters.group_by === null) _filters.displayFilters.sub_group_by = null; + if (_filters.displayFilters.group_by === null) { + _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same if ( _filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === _filters.displayFilters.sub_group_by - ) + ) { _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set group_by to state if layout is switched to kanban and group_by is null - if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) + if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) { _filters.displayFilters.group_by = "state"; + updatedDisplayFilters.group_by = "state"; + } runInAction(() => { Object.keys(updatedDisplayFilters).forEach((_key) => { diff --git a/web/store/issue/draft/filter.store.ts b/web/store/issue/draft/filter.store.ts index 8683a32986..3e43eb1474 100644 --- a/web/store/issue/draft/filter.store.ts +++ b/web/store/issue/draft/filter.store.ts @@ -142,16 +142,23 @@ export class DraftIssuesFilter extends IssueFilterHelperStore implements IDraftI _filters.displayFilters = { ..._filters.displayFilters, ...updatedDisplayFilters }; // set sub_group_by to null if group_by is set to null - if (_filters.displayFilters.group_by === null) _filters.displayFilters.sub_group_by = null; + if (_filters.displayFilters.group_by === null) { + _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same if ( _filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === _filters.displayFilters.sub_group_by - ) + ) { _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set group_by to state if layout is switched to kanban and group_by is null - if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) + if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) { _filters.displayFilters.group_by = "state"; + updatedDisplayFilters.group_by = "state"; + } runInAction(() => { Object.keys(updatedDisplayFilters).forEach((_key) => { diff --git a/web/store/issue/helpers/issue-helper.store.ts b/web/store/issue/helpers/issue-helper.store.ts index 8c148caa8c..f54d4d7809 100644 --- a/web/store/issue/helpers/issue-helper.store.ts +++ b/web/store/issue/helpers/issue-helper.store.ts @@ -76,7 +76,10 @@ export class IssueHelperStore implements TIssueHelperStore { const state_group = this.rootStore?.stateDetails?.find((_state) => _state.id === _issue?.state_id)?.group || "None"; groupArray = [state_group]; - } else groupArray = this.getGroupArray(get(_issue, ISSUE_FILTER_DEFAULT_DATA[groupBy]), isCalendarIssues); + } else { + const groupValue = get(_issue, ISSUE_FILTER_DEFAULT_DATA[groupBy]); + groupArray = groupValue !== undefined ? this.getGroupArray(groupValue, isCalendarIssues) : []; + } for (const group of groupArray) { if (group && _issues[group]) _issues[group].push(_issue.id); @@ -116,8 +119,10 @@ export class IssueHelperStore implements TIssueHelperStore { subGroupArray = [state_group]; groupArray = [state_group]; } else { - subGroupArray = this.getGroupArray(get(_issue, ISSUE_FILTER_DEFAULT_DATA[subGroupBy])); - groupArray = this.getGroupArray(get(_issue, ISSUE_FILTER_DEFAULT_DATA[groupBy])); + const subGroupValue = get(_issue, ISSUE_FILTER_DEFAULT_DATA[subGroupBy]); + const groupValue = get(_issue, ISSUE_FILTER_DEFAULT_DATA[groupBy]); + subGroupArray = subGroupValue != undefined ? this.getGroupArray(subGroupValue) : []; + groupArray = groupValue != undefined ? this.getGroupArray(groupValue) : []; } for (const subGroup of subGroupArray) { diff --git a/web/store/issue/issue-details/activity.store.ts b/web/store/issue/issue-details/activity.store.ts index caa1eb0a3d..168a3f5400 100644 --- a/web/store/issue/issue-details/activity.store.ts +++ b/web/store/issue/issue-details/activity.store.ts @@ -49,7 +49,7 @@ export class IssueActivityStore implements IIssueActivityStore { // computed get issueActivities() { - const issueId = this.rootIssueDetailStore.issueId; + const issueId = this.rootIssueDetailStore.peekIssue?.issueId; if (!issueId) return undefined; return this.activities[issueId] ?? undefined; } diff --git a/web/store/issue/issue-details/attachment.store.ts b/web/store/issue/issue-details/attachment.store.ts index fab3e3a83f..c21c2ac1dc 100644 --- a/web/store/issue/issue-details/attachment.store.ts +++ b/web/store/issue/issue-details/attachment.store.ts @@ -62,7 +62,7 @@ export class IssueAttachmentStore implements IIssueAttachmentStore { // computed get issueAttachments() { - const issueId = this.rootIssueDetailStore.issueId; + const issueId = this.rootIssueDetailStore.peekIssue?.issueId; if (!issueId) return undefined; return this.attachments[issueId] ?? undefined; } diff --git a/web/store/issue/issue-details/link.store.ts b/web/store/issue/issue-details/link.store.ts index 50072472e2..a77ee7417f 100644 --- a/web/store/issue/issue-details/link.store.ts +++ b/web/store/issue/issue-details/link.store.ts @@ -60,7 +60,7 @@ export class IssueLinkStore implements IIssueLinkStore { // computed get issueLinks() { - const issueId = this.rootIssueDetailStore.issueId; + const issueId = this.rootIssueDetailStore.peekIssue?.issueId; if (!issueId) return undefined; return this.links[issueId] ?? undefined; } diff --git a/web/store/issue/issue-details/reaction.store.ts b/web/store/issue/issue-details/reaction.store.ts index 217b5c4471..bac47ccded 100644 --- a/web/store/issue/issue-details/reaction.store.ts +++ b/web/store/issue/issue-details/reaction.store.ts @@ -53,7 +53,7 @@ export class IssueReactionStore implements IIssueReactionStore { // computed get issueReactions() { - const issueId = this.rootIssueDetailStore.issueId; + const issueId = this.rootIssueDetailStore.peekIssue?.issueId; if (!issueId) return undefined; return this.reactions[issueId] ?? undefined; } diff --git a/web/store/issue/issue-details/relation.store.ts b/web/store/issue/issue-details/relation.store.ts index bb3a288785..f9e0ac5f03 100644 --- a/web/store/issue/issue-details/relation.store.ts +++ b/web/store/issue/issue-details/relation.store.ts @@ -68,7 +68,7 @@ export class IssueRelationStore implements IIssueRelationStore { // computed get issueRelations() { - const issueId = this.rootIssueDetailStore.issueId; + const issueId = this.rootIssueDetailStore.peekIssue?.issueId; if (!issueId) return undefined; return this.relationMap?.[issueId] ?? undefined; } diff --git a/web/store/issue/issue-details/root.store.ts b/web/store/issue/issue-details/root.store.ts index c4f5be09ae..67aa4b46f9 100644 --- a/web/store/issue/issue-details/root.store.ts +++ b/web/store/issue/issue-details/root.store.ts @@ -18,6 +18,12 @@ import { IIssueRelationStore, IssueRelationStore, IIssueRelationStoreActions } f import { TIssue, IIssueActivity, TIssueLink, TIssueRelationTypes } from "@plane/types"; +export type TPeekIssue = { + workspaceSlug: string; + projectId: string; + issueId: string; +}; + export interface IIssueDetail extends IIssueStoreActions, IIssueReactionStoreActions, @@ -30,14 +36,14 @@ export interface IIssueDetail IIssueAttachmentStoreActions, IIssueRelationStoreActions { // observables - issueId: string | undefined; + peekIssue: TPeekIssue | undefined; isIssueLinkModalOpen: boolean; isParentIssueModalOpen: boolean; isDeleteIssueModalOpen: boolean; // computed isAnyModalOpen: boolean; // actions - setIssueId: (issueId: string | undefined) => void; + setPeekIssue: (peekIssue: TPeekIssue | undefined) => void; toggleIssueLinkModal: (value: boolean) => void; toggleParentIssueModal: (value: boolean) => void; toggleDeleteIssueModal: (value: boolean) => void; @@ -57,7 +63,7 @@ export interface IIssueDetail export class IssueDetail implements IIssueDetail { // observables - issueId: string | undefined = undefined; + peekIssue: TPeekIssue | undefined = undefined; isIssueLinkModalOpen: boolean = false; isParentIssueModalOpen: boolean = false; isDeleteIssueModalOpen: boolean = false; @@ -77,14 +83,14 @@ export class IssueDetail implements IIssueDetail { constructor(rootStore: IIssueRootStore) { makeObservable(this, { // observables - issueId: observable.ref, + peekIssue: observable, isIssueLinkModalOpen: observable.ref, isParentIssueModalOpen: observable.ref, isDeleteIssueModalOpen: observable.ref, // computed isAnyModalOpen: computed, // action - setIssueId: action, + setPeekIssue: action, toggleIssueLinkModal: action, toggleParentIssueModal: action, toggleDeleteIssueModal: action, @@ -110,16 +116,14 @@ export class IssueDetail implements IIssueDetail { } // actions - setIssueId = (issueId: string | undefined) => (this.issueId = issueId); + setPeekIssue = (peekIssue: TPeekIssue | undefined) => (this.peekIssue = peekIssue); toggleIssueLinkModal = (value: boolean) => (this.isIssueLinkModalOpen = value); toggleParentIssueModal = (value: boolean) => (this.isParentIssueModalOpen = value); toggleDeleteIssueModal = (value: boolean) => (this.isDeleteIssueModalOpen = value); // issue - fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string) => { - this.issueId = issueId; - return this.issue.fetchIssue(workspaceSlug, projectId, issueId); - }; + fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string) => + this.issue.fetchIssue(workspaceSlug, projectId, issueId); updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => this.issue.updateIssue(workspaceSlug, projectId, issueId, data); removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => diff --git a/web/store/issue/issue-details/subscription.store.ts b/web/store/issue/issue-details/subscription.store.ts index ce71bf4464..02f863cbe3 100644 --- a/web/store/issue/issue-details/subscription.store.ts +++ b/web/store/issue/issue-details/subscription.store.ts @@ -46,7 +46,7 @@ export class IssueSubscriptionStore implements IIssueSubscriptionStore { if (!issueId) return undefined; const currentUserId = this.rootIssueDetail.rootIssueStore.currentUserId; if (!currentUserId) return undefined; - return this.subscriptionMap[issueId][currentUserId] ?? undefined; + return this.subscriptionMap[issueId]?.[currentUserId] ?? undefined; }; fetchSubscriptions = async (workspaceSlug: string, projectId: string, issueId: string) => { diff --git a/web/store/issue/issue_detail.store.ts b/web/store/issue/issue_detail.store.ts deleted file mode 100644 index 36e2c1b9df..0000000000 --- a/web/store/issue/issue_detail.store.ts +++ /dev/null @@ -1,757 +0,0 @@ -import { observable, action, makeObservable, runInAction, computed, autorun } from "mobx"; -// services -import { IssueService, IssueReactionService, IssueCommentService } from "services/issue"; -import { NotificationService } from "services/notification.service"; -// types -import { IIssueRootStore } from "./root.store"; -import type { TIssue, IIssueActivity, IIssueLink, ILinkDetails } from "@plane/types"; -// constants -import { groupReactionEmojis } from "constants/issue"; -import { RootStore } from "store/root.store"; - -export interface IIssueDetailStore { - loader: boolean; - error: any | null; - - peekId: string | null; - issues: { - [issueId: string]: TIssue; - }; - issueReactions: { - [issueId: string]: any; - }; - issueActivity: { - [issueId: string]: IIssueActivity[]; - }; - issueCommentReactions: { - [issueId: string]: { - [comment_id: string]: any; - }; - }; - issueSubscription: { - [issueId: string]: any; - }; - - setPeekId: (issueId: string | null) => void; - - // computed - getIssue: TIssue | null; - getIssueReactions: any | null; - getIssueActivity: IIssueActivity[] | null; - getIssueCommentReactions: any | null; - getIssueSubscription: any | null; - - // fetch issue details - fetchIssueDetails: (workspaceSlug: string, projectId: string, issueId: string) => Promise; - // deleting issue - deleteIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; - - fetchPeekIssueDetails: (workspaceSlug: string, projectId: string, issueId: string) => Promise; - - fetchIssueReactions: (workspaceSlug: string, projectId: string, issueId: string) => Promise; - createIssueReaction: (workspaceSlug: string, projectId: string, issueId: string, reaction: string) => Promise; - removeIssueReaction: (workspaceSlug: string, projectId: string, issueId: string, reaction: string) => Promise; - - createIssueLink: ( - workspaceSlug: string, - projectId: string, - issueId: string, - data: IIssueLink - ) => Promise; - updateIssueLink: ( - workspaceSlug: string, - projectId: string, - issueId: string, - linkId: string, - data: IIssueLink - ) => Promise; - deleteIssueLink: (workspaceSlug: string, projectId: string, issueId: string, linkId: string) => Promise; - - fetchIssueActivity: (workspaceSlug: string, projectId: string, issueId: string) => Promise; - createIssueComment: (workspaceSlug: string, projectId: string, issueId: string, data: any) => Promise; - updateIssueComment: ( - workspaceSlug: string, - projectId: string, - issueId: string, - commentId: string, - data: any - ) => Promise; - removeIssueComment: (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => Promise; - - fetchIssueCommentReactions: ( - workspaceSlug: string, - projectId: string, - issueId: string, - commentId: string - ) => Promise; - creationIssueCommentReaction: ( - workspaceSlug: string, - projectId: string, - issueId: string, - commentId: string, - reaction: string - ) => Promise; - removeIssueCommentReaction: ( - workspaceSlug: string, - projectId: string, - issueId: string, - commentId: string, - reaction: string - ) => Promise; - - fetchIssueSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise; - createIssueSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise; - removeIssueSubscription: (workspaceSlug: string, projectId: string, issueId: string) => Promise; -} - -export class IssueDetailStore implements IIssueDetailStore { - loader: boolean = false; - error: any | null = null; - - peekId: string | null = null; - issues: { - [issueId: string]: TIssue; - } = {}; - issueReactions: { - [issueId: string]: any; - } = {}; - issueActivity: { - [issueId: string]: IIssueActivity[]; - } = {}; - issueCommentReactions: { - [issueId: string]: any; - } = {}; - issueSubscription: { - [issueId: string]: any; - } = {}; - - // root store - issueRootStore; - rootStore; - // service - issueService; - issueReactionService; - issueCommentService; - notificationService; - - constructor(_issueRootStore: IIssueRootStore, _rootStore: RootStore) { - makeObservable(this, { - // observable - loader: observable.ref, - error: observable.ref, - - peekId: observable.ref, - issues: observable.ref, - issueReactions: observable.ref, - issueActivity: observable.ref, - issueCommentReactions: observable.ref, - issueSubscription: observable.ref, - - getIssue: computed, - getIssueReactions: computed, - getIssueActivity: computed, - getIssueCommentReactions: computed, - getIssueSubscription: computed, - - setPeekId: action, - - fetchIssueDetails: action, - deleteIssue: action, - - fetchPeekIssueDetails: action, - - fetchIssueReactions: action, - createIssueReaction: action, - removeIssueReaction: action, - - createIssueLink: action, - updateIssueLink: action, - deleteIssueLink: action, - - fetchIssueActivity: action, - createIssueComment: action, - updateIssueComment: action, - removeIssueComment: action, - - fetchIssueCommentReactions: action, - creationIssueCommentReaction: action, - removeIssueCommentReaction: action, - - fetchIssueSubscription: action, - createIssueSubscription: action, - removeIssueSubscription: action, - }); - - this.issueRootStore = _issueRootStore; - this.rootStore = _rootStore; - this.issueService = new IssueService(); - this.issueReactionService = new IssueReactionService(); - this.issueCommentService = new IssueCommentService(); - this.notificationService = new NotificationService(); - - autorun(() => { - const projectId = this.rootStore?.app.router.projectId; - const peekId = this.peekId; - - if (!projectId || !peekId) return; - - // FIXME: uncomment and fix - // const issue = this.issueRootStore.projectIssues.issues?.[projectId]?.[peekId]; - - // if (issue && issue.id) - // runInAction(() => { - // this.issues = { - // ...this.issues, - // [issue.id]: { - // ...this.issues[issue.id], - // ...issue, - // }, - // }; - // }); - }); - } - - get getIssue() { - if (!this.peekId) return null; - const _issue = this.issues[this.peekId]; - return _issue || null; - } - - get getIssueReactions() { - if (!this.peekId) return null; - const _reactions = this.issueReactions[this.peekId]; - return _reactions || null; - } - - get getIssueActivity() { - if (!this.peekId) return null; - const activity = this.issueActivity[this.peekId]; - return activity || null; - } - - get getIssueCommentReactions() { - if (!this.peekId) return null; - const _commentReactions = this.issueCommentReactions[this.peekId]; - return _commentReactions || null; - } - - get getIssueSubscription() { - if (!this.peekId) return null; - const _commentSubscription = this.issueSubscription[this.peekId]; - return _commentSubscription || null; - } - - setPeekId = (issueId: string | null) => (this.peekId = issueId); - - fetchIssueDetails = async (workspaceSlug: string, projectId: string, issueId: string) => { - try { - this.loader = true; - this.error = null; - this.peekId = issueId; - - const issueDetailsResponse = await this.issueService.retrieve(workspaceSlug, projectId, issueId); - - runInAction(() => { - this.loader = false; - this.error = null; - this.issues = { - ...this.issues, - [issueId]: issueDetailsResponse, - }; - }); - - return issueDetailsResponse; - } catch (error) { - runInAction(() => { - this.loader = false; - this.error = error; - }); - - throw error; - } - }; - - deleteIssue = async (workspaceSlug: string, projectId: string, issueId: string) => { - const newIssues = { ...this.issues }; - delete newIssues[issueId]; - - try { - runInAction(() => { - this.loader = true; - this.error = null; - this.issues = newIssues; - }); - - const user = this.rootStore.user.currentUser; - - if (!user) return; - - const response = await this.issueService.deleteIssue(workspaceSlug, projectId, issueId); - - runInAction(() => { - this.loader = false; - this.error = null; - }); - - return response; - } catch (error) { - this.fetchIssueDetails(workspaceSlug, projectId, issueId); - - runInAction(() => { - this.loader = false; - this.error = error; - }); - - return error; - } - }; - - fetchPeekIssueDetails = async (workspaceSlug: string, projectId: string, issueId: string) => { - try { - this.loader = true; - this.error = null; - - this.peekId = issueId; - - const issueDetailsResponse = await this.issueService.retrieve(workspaceSlug, projectId, issueId); - await this.fetchIssueReactions(workspaceSlug, projectId, issueId); - await this.fetchIssueActivity(workspaceSlug, projectId, issueId); - - runInAction(() => { - this.loader = false; - this.error = null; - this.issues = { - ...this.issues, - [issueId]: issueDetailsResponse, - }; - }); - - return issueDetailsResponse; - } catch (error) { - runInAction(() => { - this.loader = false; - this.error = error; - }); - - throw error; - } - }; - - // reactions - fetchIssueReactions = async (workspaceSlug: string, projectId: string, issueId: string) => { - try { - const _reactions = await this.issueReactionService.listIssueReactions(workspaceSlug, projectId, issueId); - - const _issueReactions = { - ...this.issueReactions, - [issueId]: groupReactionEmojis(_reactions), - }; - - runInAction(() => { - this.issueReactions = _issueReactions; - }); - } catch (error) { - console.warn("error creating the issue reaction", error); - throw error; - } - }; - createIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reaction: string) => { - let _currentReactions = this.getIssueReactions; - - try { - const _reaction = await this.issueReactionService.createIssueReaction(workspaceSlug, projectId, issueId, { - reaction, - }); - - _currentReactions = { - ..._currentReactions, - [reaction]: [..._currentReactions[reaction], { ..._reaction }], - }; - - runInAction(() => { - this.issueReactions = { - ...this.issueReactions, - [issueId]: _currentReactions, - }; - }); - } catch (error) { - runInAction(() => { - this.issueReactions = { - ...this.issueReactions, - [issueId]: _currentReactions, - }; - }); - console.warn("error creating the issue reaction", error); - throw error; - } - }; - removeIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reaction: string) => { - let _currentReactions = this.getIssueReactions; - - try { - const user = this.rootStore.user.currentUser; - - if (user) { - _currentReactions = { - ..._currentReactions, - [reaction]: [..._currentReactions[reaction].filter((r: any) => r.actor !== user.id)], - }; - - runInAction(() => { - this.issueReactions = { - ...this.issueReactions, - [issueId]: _currentReactions, - }; - }); - - await this.issueReactionService.deleteIssueReaction(workspaceSlug, projectId, issueId, reaction); - } - } catch (error) { - runInAction(() => { - this.issueReactions = { - ...this.issueReactions, - [issueId]: _currentReactions, - }; - }); - console.warn("error removing the issue reaction", error); - throw error; - } - }; - - fetchIssueActivity = async (workspaceSlug: string, projectId: string, issueId: string) => { - try { - const issueActivityResponse = await this.issueService.getIssueActivities(workspaceSlug, projectId, issueId); - - const _issueComments = { - ...this.issueActivity, - [issueId]: [...issueActivityResponse], - }; - - runInAction(() => { - this.issueActivity = _issueComments; - }); - } catch (error) { - console.warn("error creating the issue comment", error); - throw error; - } - }; - - // comments - createIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, data: any) => { - try { - const _issueCommentResponse = await this.issueCommentService.createIssueComment( - workspaceSlug, - projectId, - issueId, - data - ); - - const _issueComments = { - ...this.issueActivity, - [issueId]: [...this.issueActivity[issueId], _issueCommentResponse], - }; - - runInAction(() => { - this.issueActivity = _issueComments; - }); - } catch (error) { - console.warn("error creating the issue comment", error); - throw error; - } - }; - - updateIssueComment = async ( - workspaceSlug: string, - projectId: string, - issueId: string, - commentId: string, - data: any - ) => { - try { - const _issueCommentResponse = await this.issueCommentService.patchIssueComment( - workspaceSlug, - projectId, - issueId, - commentId, - data - ); - - const _issueComments = { - ...this.issueActivity, - [issueId]: this.issueActivity[issueId].map((comment) => - comment.id === commentId ? _issueCommentResponse : comment - ), - }; - - runInAction(() => { - this.issueActivity = _issueComments; - }); - } catch (error) { - console.warn("error updating the issue comment", error); - throw error; - } - }; - - removeIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => { - try { - const _issueComments = { - ...this.issueActivity, - [issueId]: this.issueActivity[issueId]?.filter((comment) => comment.id != commentId), - }; - - await this.issueCommentService.deleteIssueComment(workspaceSlug, projectId, issueId, commentId); - - runInAction(() => { - this.issueActivity = _issueComments; - }); - } catch (error) { - console.warn("error removing the issue comment", error); - throw error; - } - }; - - // comment reactions - fetchIssueCommentReactions = async (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => { - try { - const _reactions = await this.issueReactionService.listIssueCommentReactions(workspaceSlug, projectId, commentId); - - const _issueCommentReactions = { - ...this.issueCommentReactions, - [issueId]: { - ...this.issueCommentReactions[issueId], - [commentId]: groupReactionEmojis(_reactions), - }, - }; - - runInAction(() => { - this.issueCommentReactions = _issueCommentReactions; - }); - } catch (error) { - console.warn("error removing the issue comment", error); - throw error; - } - }; - - creationIssueCommentReaction = async ( - workspaceSlug: string, - projectId: string, - issueId: string, - commentId: string, - reaction: string - ) => { - let _currentReactions = this.getIssueCommentReactions; - _currentReactions = _currentReactions && commentId ? _currentReactions?.[commentId] : null; - - try { - const _reaction = await this.issueReactionService.createIssueCommentReaction( - workspaceSlug, - projectId, - commentId, - { - reaction, - } - ); - - _currentReactions = { - ..._currentReactions, - [reaction]: [..._currentReactions?.[reaction], { ..._reaction }], - }; - - const _issueCommentReactions = { - ...this.issueCommentReactions, - [issueId]: { - ...this.issueCommentReactions[issueId], - [commentId]: _currentReactions, - }, - }; - - runInAction(() => { - this.issueCommentReactions = _issueCommentReactions; - }); - } catch (error) { - console.warn("error removing the issue comment", error); - throw error; - } - }; - - removeIssueCommentReaction = async ( - workspaceSlug: string, - projectId: string, - issueId: string, - commentId: string, - reaction: string - ) => { - let _currentReactions = this.getIssueCommentReactions; - _currentReactions = _currentReactions && commentId ? _currentReactions?.[commentId] : null; - - try { - const user = this.rootStore.user.currentUser; - - if (user) { - _currentReactions = { - ..._currentReactions, - [reaction]: [..._currentReactions?.[reaction].filter((r: any) => r.actor !== user.id)], - }; - - const _issueCommentReactions = { - ...this.issueCommentReactions, - [issueId]: { - ...this.issueCommentReactions[issueId], - [commentId]: _currentReactions, - }, - }; - - runInAction(() => { - this.issueCommentReactions = _issueCommentReactions; - }); - - await this.issueReactionService.deleteIssueCommentReaction(workspaceSlug, projectId, commentId, reaction); - } - } catch (error) { - console.warn("error removing the issue comment", error); - throw error; - } - }; - - createIssueLink = async (workspaceSlug: string, projectId: string, issueId: string, data: IIssueLink) => { - try { - const response = await this.issueService.createIssueLink(workspaceSlug, projectId, issueId, data); - - runInAction(() => { - this.issues = { - ...this.issues, - [issueId]: { - ...this.issues[issueId], - issue_link: [response, ...this.issues[issueId].issue_link], - }, - }; - }); - - return response; - } catch (error) { - console.error("Failed to create link in store", error); - - this.fetchIssueDetails(workspaceSlug, projectId, issueId); - - runInAction(() => { - this.error = error; - }); - - throw error; - } - }; - - updateIssueLink = async ( - workspaceSlug: string, - projectId: string, - issueId: string, - linkId: string, - data: IIssueLink - ) => { - try { - const response = await this.issueService.updateIssueLink(workspaceSlug, projectId, issueId, linkId, data); - - runInAction(() => { - this.issues = { - ...this.issues, - [issueId]: { - ...this.issues[issueId], - issue_link: this.issues[issueId].issue_link.map((link: any) => (link.id === linkId ? response : link)), - }, - }; - }); - - return response; - } catch (error) { - console.error("Failed to update link in issue store", error); - - this.fetchIssueDetails(workspaceSlug, projectId, issueId); - - runInAction(() => { - this.error = error; - }); - - throw error; - } - }; - - deleteIssueLink = async (workspaceSlug: string, projectId: string, issueId: string, linkId: string) => { - try { - runInAction(() => { - this.issues = { - ...this.issues, - [issueId]: { - ...this.issues[issueId], - issue_link: this.issues[issueId].issue_link.filter((link: any) => link.id !== linkId), - }, - }; - }); - await this.issueService.deleteIssueLink(workspaceSlug, projectId, issueId, linkId); - } catch (error) { - console.error("Failed to delete link in issue store", error); - - runInAction(() => { - this.error = error; - }); - - throw error; - } - }; - - // subscriptions - fetchIssueSubscription = async (workspaceSlug: string, projectId: string, issueId: string) => { - try { - const _subscription = await this.notificationService.getIssueNotificationSubscriptionStatus( - workspaceSlug, - projectId, - issueId - ); - - const _issueSubscription = { - ...this.issueSubscription, - [issueId]: _subscription, - }; - - runInAction(() => { - this.issueSubscription = _issueSubscription; - }); - } catch (error) { - console.warn("error fetching the issue subscription", error); - throw error; - } - }; - createIssueSubscription = async (workspaceSlug: string, projectId: string, issueId: string) => { - try { - await this.notificationService.subscribeToIssueNotifications(workspaceSlug, projectId, issueId); - - const _issueSubscription = { - ...this.issueSubscription, - [issueId]: { subscribed: true }, - }; - - runInAction(() => { - this.issueSubscription = _issueSubscription; - }); - } catch (error) { - console.warn("error creating the issue subscription", error); - throw error; - } - }; - removeIssueSubscription = async (workspaceSlug: string, projectId: string, issueId: string) => { - try { - const _issueSubscription = { - ...this.issueSubscription, - [issueId]: { subscribed: false }, - }; - - runInAction(() => { - this.issueSubscription = _issueSubscription; - }); - - await this.notificationService.unsubscribeFromIssueNotifications(workspaceSlug, projectId, issueId); - } catch (error) { - console.warn("error removing the issue subscription", error); - throw error; - } - }; -} diff --git a/web/store/issue/issue_kanban_view.store.ts b/web/store/issue/issue_kanban_view.store.ts index 5d0b13d22c..3664ad22d5 100644 --- a/web/store/issue/issue_kanban_view.store.ts +++ b/web/store/issue/issue_kanban_view.store.ts @@ -40,7 +40,7 @@ export class IssueKanBanViewStore implements IIssueKanBanViewStore { get canUserDragDrop() { return true; - if (this.rootStore.issueDetail.issueId) return false; + if (this.rootStore.issueDetail.peekIssue?.issueId) return false; // FIXME: uncomment and fix // if ( // this.rootStore?.issueFilter?.userDisplayFilters?.order_by && diff --git a/web/store/issue/module/filter.store.ts b/web/store/issue/module/filter.store.ts index ab7d98b3fc..7819ad6e08 100644 --- a/web/store/issue/module/filter.store.ts +++ b/web/store/issue/module/filter.store.ts @@ -145,16 +145,23 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul _filters.displayFilters = { ..._filters.displayFilters, ...updatedDisplayFilters }; // set sub_group_by to null if group_by is set to null - if (_filters.displayFilters.group_by === null) _filters.displayFilters.sub_group_by = null; + if (_filters.displayFilters.group_by === null) { + _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same if ( _filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === _filters.displayFilters.sub_group_by - ) + ) { _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set group_by to state if layout is switched to kanban and group_by is null - if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) + if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) { _filters.displayFilters.group_by = "state"; + updatedDisplayFilters.group_by = "state"; + } runInAction(() => { Object.keys(updatedDisplayFilters).forEach((_key) => { diff --git a/web/store/issue/profile/filter.store.ts b/web/store/issue/profile/filter.store.ts index b1c7567d5f..e14b7179d8 100644 --- a/web/store/issue/profile/filter.store.ts +++ b/web/store/issue/profile/filter.store.ts @@ -150,16 +150,23 @@ export class ProfileIssuesFilter extends IssueFilterHelperStore implements IProf _filters.displayFilters = { ..._filters.displayFilters, ...updatedDisplayFilters }; // set sub_group_by to null if group_by is set to null - if (_filters.displayFilters.group_by === null) _filters.displayFilters.sub_group_by = null; + if (_filters.displayFilters.group_by === null) { + _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same if ( _filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === _filters.displayFilters.sub_group_by - ) + ) { _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set group_by to state if layout is switched to kanban and group_by is null - if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) + if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) { _filters.displayFilters.group_by = "state"; + updatedDisplayFilters.group_by = "state"; + } runInAction(() => { Object.keys(updatedDisplayFilters).forEach((_key) => { diff --git a/web/store/issue/project-views/filter.store.ts b/web/store/issue/project-views/filter.store.ts index a162d5dd5c..5d0ec332b4 100644 --- a/web/store/issue/project-views/filter.store.ts +++ b/web/store/issue/project-views/filter.store.ts @@ -146,16 +146,23 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I _filters.displayFilters = { ..._filters.displayFilters, ...updatedDisplayFilters }; // set sub_group_by to null if group_by is set to null - if (_filters.displayFilters.group_by === null) _filters.displayFilters.sub_group_by = null; + if (_filters.displayFilters.group_by === null) { + _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same if ( _filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === _filters.displayFilters.sub_group_by - ) + ) { _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set group_by to state if layout is switched to kanban and group_by is null - if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) + if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) { _filters.displayFilters.group_by = "state"; + updatedDisplayFilters.group_by = "state"; + } runInAction(() => { Object.keys(updatedDisplayFilters).forEach((_key) => { diff --git a/web/store/issue/project/filter.store.ts b/web/store/issue/project/filter.store.ts index e1e8aa8bd8..2b47e41872 100644 --- a/web/store/issue/project/filter.store.ts +++ b/web/store/issue/project/filter.store.ts @@ -142,16 +142,23 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj _filters.displayFilters = { ..._filters.displayFilters, ...updatedDisplayFilters }; // set sub_group_by to null if group_by is set to null - if (_filters.displayFilters.group_by === null) _filters.displayFilters.sub_group_by = null; + if (_filters.displayFilters.group_by === null) { + _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same if ( _filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === _filters.displayFilters.sub_group_by - ) + ) { _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set group_by to state if layout is switched to kanban and group_by is null - if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) + if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) { _filters.displayFilters.group_by = "state"; + updatedDisplayFilters.group_by = "state"; + } runInAction(() => { Object.keys(updatedDisplayFilters).forEach((_key) => { diff --git a/web/store/issue/workspace/filter.store.ts b/web/store/issue/workspace/filter.store.ts index 4fbf840508..3f6aa97b9e 100644 --- a/web/store/issue/workspace/filter.store.ts +++ b/web/store/issue/workspace/filter.store.ts @@ -157,16 +157,23 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo _filters.displayFilters = { ..._filters.displayFilters, ...updatedDisplayFilters }; // set sub_group_by to null if group_by is set to null - if (_filters.displayFilters.group_by === null) _filters.displayFilters.sub_group_by = null; + if (_filters.displayFilters.group_by === null) { + _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same if ( _filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === _filters.displayFilters.sub_group_by - ) + ) { _filters.displayFilters.sub_group_by = null; + updatedDisplayFilters.sub_group_by = null; + } // set group_by to state if layout is switched to kanban and group_by is null - if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) + if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) { _filters.displayFilters.group_by = "state"; + updatedDisplayFilters.group_by = "state"; + } runInAction(() => { Object.keys(updatedDisplayFilters).forEach((_key) => {