mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
[WEB-999] chore: updated UI improvements and workflow updates in the project inbox (#4180)
* chore: snoozed filter in the issue inbox filter * chore: navigating to the next or previous issue when we accept, decline, or duplicate the issue in inbox * chore: Implemented state, label, assignee and target_date in the inbox issue description and Implemented issue edit confirmation once we click accept the inbox issue * chore: removed logs * chore: inbox issue create response * chore: update inbox issue response * chore: updated inbox issue accept workflow and added issue properties in inbox issue create modal * chore: resolved build errors and upgraded lucide react * chore: updated inbox issue store hook * chore: code cleanup and removed validation for inbox description * fix: renamed the variable isLoading to loader in project-inbox store * fix: updated set function for issue property update --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
parent
a44a032683
commit
20b0edeaa6
@ -24,6 +24,7 @@ from plane.db.models import (
|
|||||||
State,
|
State,
|
||||||
IssueLink,
|
IssueLink,
|
||||||
IssueAttachment,
|
IssueAttachment,
|
||||||
|
Project,
|
||||||
ProjectMember,
|
ProjectMember,
|
||||||
)
|
)
|
||||||
from plane.app.serializers import (
|
from plane.app.serializers import (
|
||||||
@ -239,41 +240,75 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# create an issue
|
# create an issue
|
||||||
issue = Issue.objects.create(
|
project = Project.objects.get(pk=project_id)
|
||||||
name=request.data.get("issue", {}).get("name"),
|
serializer = IssueCreateSerializer(
|
||||||
description=request.data.get("issue", {}).get("description", {}),
|
data=request.data.get("issue"),
|
||||||
description_html=request.data.get("issue", {}).get(
|
context={
|
||||||
"description_html", "<p></p>"
|
"project_id": project_id,
|
||||||
),
|
"workspace_id": project.workspace_id,
|
||||||
priority=request.data.get("issue", {}).get("priority", "low"),
|
"default_assignee_id": project.default_assignee_id,
|
||||||
project_id=project_id,
|
},
|
||||||
state=state,
|
|
||||||
)
|
)
|
||||||
|
if serializer.is_valid():
|
||||||
# Create an Issue Activity
|
serializer.save()
|
||||||
issue_activity.delay(
|
# Create an Issue Activity
|
||||||
type="issue.activity.created",
|
issue_activity.delay(
|
||||||
requested_data=json.dumps(request.data, cls=DjangoJSONEncoder),
|
type="issue.activity.created",
|
||||||
actor_id=str(request.user.id),
|
requested_data=json.dumps(request.data, cls=DjangoJSONEncoder),
|
||||||
issue_id=str(issue.id),
|
actor_id=str(request.user.id),
|
||||||
project_id=str(project_id),
|
issue_id=str(serializer.data["id"]),
|
||||||
current_instance=None,
|
project_id=str(project_id),
|
||||||
epoch=int(timezone.now().timestamp()),
|
current_instance=None,
|
||||||
notification=True,
|
epoch=int(timezone.now().timestamp()),
|
||||||
origin=request.META.get("HTTP_ORIGIN"),
|
notification=True,
|
||||||
)
|
origin=request.META.get("HTTP_ORIGIN"),
|
||||||
inbox_id = Inbox.objects.filter(
|
)
|
||||||
workspace__slug=slug, project_id=project_id
|
inbox_id = Inbox.objects.filter(
|
||||||
).first()
|
workspace__slug=slug, project_id=project_id
|
||||||
# create an inbox issue
|
).first()
|
||||||
inbox_issue = InboxIssue.objects.create(
|
# create an inbox issue
|
||||||
inbox_id=inbox_id.id,
|
inbox_issue = InboxIssue.objects.create(
|
||||||
project_id=project_id,
|
inbox_id=inbox_id.id,
|
||||||
issue=issue,
|
project_id=project_id,
|
||||||
source=request.data.get("source", "in-app"),
|
issue_id=serializer.data["id"],
|
||||||
)
|
source=request.data.get("source", "in-app"),
|
||||||
serializer = InboxIssueDetailSerializer(inbox_issue)
|
)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
inbox_issue = (
|
||||||
|
InboxIssue.objects.select_related("issue")
|
||||||
|
.prefetch_related(
|
||||||
|
"issue__labels",
|
||||||
|
"issue__assignees",
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
label_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"issue__labels__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(issue__labels__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
assignee_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"issue__assignees__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(issue__assignees__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
inbox_id=inbox_id.id,
|
||||||
|
issue_id=serializer.data["id"],
|
||||||
|
project_id=project_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
serializer = InboxIssueDetailSerializer(inbox_issue)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
return Response(
|
||||||
|
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
def partial_update(self, request, slug, project_id, issue_id):
|
def partial_update(self, request, slug, project_id, issue_id):
|
||||||
inbox_id = Inbox.objects.filter(
|
inbox_id = Inbox.objects.filter(
|
||||||
@ -395,6 +430,42 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
issue.state = state
|
issue.state = state
|
||||||
issue.save()
|
issue.save()
|
||||||
|
|
||||||
|
inbox_issue = (
|
||||||
|
InboxIssue.objects.select_related("issue")
|
||||||
|
.prefetch_related(
|
||||||
|
"issue__labels",
|
||||||
|
"issue__assignees",
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
label_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"issue__labels__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(issue__labels__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value(
|
||||||
|
[],
|
||||||
|
output_field=ArrayField(UUIDField()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
assignee_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"issue__assignees__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(issue__assignees__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value(
|
||||||
|
[],
|
||||||
|
output_field=ArrayField(UUIDField()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
inbox_id=inbox_id.id,
|
||||||
|
issue_id=serializer.data["id"],
|
||||||
|
project_id=project_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
serializer = InboxIssueDetailSerializer(inbox_issue).data
|
serializer = InboxIssueDetailSerializer(inbox_issue).data
|
||||||
return Response(serializer, status=status.HTTP_200_OK)
|
return Response(serializer, status=status.HTTP_200_OK)
|
||||||
return Response(
|
return Response(
|
||||||
|
@ -6,7 +6,7 @@ import { Plus, RefreshCcw } from "lucide-react";
|
|||||||
import { Breadcrumbs, Button, LayersIcon } from "@plane/ui";
|
import { Breadcrumbs, Button, LayersIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink } from "@/components/common";
|
||||||
import { CreateInboxIssueModal } from "@/components/inbox";
|
import { InboxIssueCreateEditModalRoot } from "@/components/inbox";
|
||||||
import { ProjectLogo } from "@/components/project";
|
import { ProjectLogo } from "@/components/project";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject, useProjectInbox } from "@/hooks/store";
|
import { useProject, useProjectInbox } from "@/hooks/store";
|
||||||
@ -16,10 +16,10 @@ export const ProjectInboxHeader: FC = observer(() => {
|
|||||||
const [createIssueModal, setCreateIssueModal] = useState(false);
|
const [createIssueModal, setCreateIssueModal] = useState(false);
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { currentProjectDetails } = useProject();
|
const { currentProjectDetails } = useProject();
|
||||||
const { isLoading } = useProjectInbox();
|
const { loader } = useProjectInbox();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
|
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
|
||||||
@ -51,7 +51,7 @@ export const ProjectInboxHeader: FC = observer(() => {
|
|||||||
/>
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
|
|
||||||
{isLoading === "pagination-loading" && (
|
{loader === "pagination-loading" && (
|
||||||
<div className="flex items-center gap-1.5 text-custom-text-300">
|
<div className="flex items-center gap-1.5 text-custom-text-300">
|
||||||
<RefreshCcw className="h-3.5 w-3.5 animate-spin" />
|
<RefreshCcw className="h-3.5 w-3.5 animate-spin" />
|
||||||
<p className="text-sm">Syncing...</p>
|
<p className="text-sm">Syncing...</p>
|
||||||
@ -60,9 +60,16 @@ export const ProjectInboxHeader: FC = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{currentProjectDetails?.inbox_view && (
|
{currentProjectDetails?.inbox_view && workspaceSlug && projectId && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CreateInboxIssueModal isOpen={createIssueModal} onClose={() => setCreateIssueModal(false)} />
|
<InboxIssueCreateEditModalRoot
|
||||||
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
|
projectId={projectId.toString()}
|
||||||
|
modalState={createIssueModal}
|
||||||
|
handleModalClose={() => setCreateIssueModal(false)}
|
||||||
|
issue={undefined}
|
||||||
|
/>
|
||||||
|
|
||||||
<Button variant="primary" prependIcon={<Plus />} size="sm" onClick={() => setCreateIssueModal(true)}>
|
<Button variant="primary" prependIcon={<Plus />} size="sm" onClick={() => setCreateIssueModal(true)}>
|
||||||
Add Issue
|
Add Issue
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
import { FC, useCallback, useEffect, useState } from "react";
|
import { FC, useCallback, useEffect, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { ChevronDown, ChevronUp, Clock, ExternalLink, FileStack, Link, Trash2 } from "lucide-react";
|
import {
|
||||||
|
CircleCheck,
|
||||||
|
CircleX,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronUp,
|
||||||
|
Clock,
|
||||||
|
ExternalLink,
|
||||||
|
FileStack,
|
||||||
|
Link,
|
||||||
|
Trash2,
|
||||||
|
} from "lucide-react";
|
||||||
import { Button, ControlLink, CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
|
import { Button, ControlLink, CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import {
|
import {
|
||||||
AcceptIssueModal,
|
|
||||||
DeclineIssueModal,
|
DeclineIssueModal,
|
||||||
DeleteInboxIssueModal,
|
DeleteInboxIssueModal,
|
||||||
|
InboxIssueCreateEditModalRoot,
|
||||||
InboxIssueSnoozeModal,
|
InboxIssueSnoozeModal,
|
||||||
InboxIssueStatus,
|
InboxIssueStatus,
|
||||||
SelectDuplicateInboxIssueModal,
|
SelectDuplicateInboxIssueModal,
|
||||||
@ -39,7 +49,7 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
const [declineIssueModal, setDeclineIssueModal] = useState(false);
|
const [declineIssueModal, setDeclineIssueModal] = useState(false);
|
||||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||||
// store
|
// store
|
||||||
const { deleteInboxIssue, inboxIssuesArray } = useProjectInbox();
|
const { currentTab, deleteInboxIssue, inboxIssuesArray } = useProjectInbox();
|
||||||
const {
|
const {
|
||||||
currentUser,
|
currentUser,
|
||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
@ -60,25 +70,50 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
|
|
||||||
const issueLink = `${workspaceSlug}/projects/${issue?.project_id}/issues/${currentInboxIssueId}`;
|
const issueLink = `${workspaceSlug}/projects/${issue?.project_id}/issues/${currentInboxIssueId}`;
|
||||||
|
|
||||||
|
const redirectIssue = (): string | undefined => {
|
||||||
|
let nextOrPreviousIssueId: string | undefined = undefined;
|
||||||
|
const currentIssueIndex = inboxIssuesArray.findIndex((i) => i.issue.id === currentInboxIssueId);
|
||||||
|
if (inboxIssuesArray[currentIssueIndex + 1])
|
||||||
|
nextOrPreviousIssueId = inboxIssuesArray[currentIssueIndex + 1].issue.id;
|
||||||
|
else if (inboxIssuesArray[currentIssueIndex - 1])
|
||||||
|
nextOrPreviousIssueId = inboxIssuesArray[currentIssueIndex - 1].issue.id;
|
||||||
|
else nextOrPreviousIssueId = undefined;
|
||||||
|
return nextOrPreviousIssueId;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRedirection = (nextOrPreviousIssueId: string | undefined) => {
|
||||||
|
if (nextOrPreviousIssueId)
|
||||||
|
router.push(
|
||||||
|
`/${workspaceSlug}/projects/${projectId}/inbox?currentTab=${currentTab}&inboxIssueId=${nextOrPreviousIssueId}`
|
||||||
|
);
|
||||||
|
else router.push(`/${workspaceSlug}/projects/${projectId}/inbox?currentTab=${currentTab}`);
|
||||||
|
};
|
||||||
|
|
||||||
const handleInboxIssueAccept = async () => {
|
const handleInboxIssueAccept = async () => {
|
||||||
|
const nextOrPreviousIssueId = redirectIssue();
|
||||||
await inboxIssue?.updateInboxIssueStatus(EInboxIssueStatus.ACCEPTED);
|
await inboxIssue?.updateInboxIssueStatus(EInboxIssueStatus.ACCEPTED);
|
||||||
setAcceptIssueModal(false);
|
setAcceptIssueModal(false);
|
||||||
|
handleRedirection(nextOrPreviousIssueId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInboxIssueDecline = async () => {
|
const handleInboxIssueDecline = async () => {
|
||||||
|
const nextOrPreviousIssueId = redirectIssue();
|
||||||
await inboxIssue?.updateInboxIssueStatus(EInboxIssueStatus.DECLINED);
|
await inboxIssue?.updateInboxIssueStatus(EInboxIssueStatus.DECLINED);
|
||||||
setDeclineIssueModal(false);
|
setDeclineIssueModal(false);
|
||||||
|
handleRedirection(nextOrPreviousIssueId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInboxSIssueSnooze = async (date: Date) => {
|
||||||
|
const nextOrPreviousIssueId = redirectIssue();
|
||||||
|
await inboxIssue?.updateInboxIssueSnoozeTill(date);
|
||||||
|
setIsSnoozeDateModalOpen(false);
|
||||||
|
handleRedirection(nextOrPreviousIssueId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInboxIssueDuplicate = async (issueId: string) => {
|
const handleInboxIssueDuplicate = async (issueId: string) => {
|
||||||
await inboxIssue?.updateInboxIssueDuplicateTo(issueId);
|
await inboxIssue?.updateInboxIssueDuplicateTo(issueId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInboxSIssueSnooze = async (date: Date) => {
|
|
||||||
await inboxIssue?.updateInboxIssueSnoozeTill(date);
|
|
||||||
setIsSnoozeDateModalOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInboxIssueDelete = async () => {
|
const handleInboxIssueDelete = async () => {
|
||||||
if (!inboxIssue || !currentInboxIssueId) return;
|
if (!inboxIssue || !currentInboxIssueId) return;
|
||||||
await deleteInboxIssue(workspaceSlug, projectId, currentInboxIssueId).finally(() => {
|
await deleteInboxIssue(workspaceSlug, projectId, currentInboxIssueId).finally(() => {
|
||||||
@ -143,10 +178,12 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
onSubmit={handleInboxIssueDuplicate}
|
onSubmit={handleInboxIssueDuplicate}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AcceptIssueModal
|
<InboxIssueCreateEditModalRoot
|
||||||
data={inboxIssue?.issue}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
isOpen={acceptIssueModal}
|
projectId={projectId.toString()}
|
||||||
onClose={() => setAcceptIssueModal(false)}
|
modalState={acceptIssueModal}
|
||||||
|
handleModalClose={() => setAcceptIssueModal(false)}
|
||||||
|
issue={inboxIssue?.issue}
|
||||||
onSubmit={handleInboxIssueAccept}
|
onSubmit={handleInboxIssueAccept}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -156,14 +193,12 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
onClose={() => setDeclineIssueModal(false)}
|
onClose={() => setDeclineIssueModal(false)}
|
||||||
onSubmit={handleInboxIssueDecline}
|
onSubmit={handleInboxIssueDecline}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteInboxIssueModal
|
<DeleteInboxIssueModal
|
||||||
data={inboxIssue?.issue}
|
data={inboxIssue?.issue}
|
||||||
isOpen={deleteIssueModal}
|
isOpen={deleteIssueModal}
|
||||||
onClose={() => setDeleteIssueModal(false)}
|
onClose={() => setDeleteIssueModal(false)}
|
||||||
onSubmit={handleInboxIssueDelete}
|
onSubmit={handleInboxIssueDelete}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<InboxIssueSnoozeModal
|
<InboxIssueSnoozeModal
|
||||||
isOpen={isSnoozeDateModalOpen}
|
isOpen={isSnoozeDateModalOpen}
|
||||||
handleClose={() => setIsSnoozeDateModalOpen(false)}
|
handleClose={() => setIsSnoozeDateModalOpen(false)}
|
||||||
@ -206,7 +241,13 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{canMarkAsAccepted && (
|
{canMarkAsAccepted && (
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<Button variant="neutral-primary" size="sm" onClick={() => setAcceptIssueModal(true)}>
|
<Button
|
||||||
|
variant="neutral-primary"
|
||||||
|
size="sm"
|
||||||
|
prependIcon={<CircleCheck className="w-3 h-3" />}
|
||||||
|
className="text-green-500 border-0.5 border-green-500 bg-green-500/20 focus:bg-green-500/20 focus:text-green-500 hover:bg-green-500/40 bg-opacity-20"
|
||||||
|
onClick={() => setAcceptIssueModal(true)}
|
||||||
|
>
|
||||||
Accept
|
Accept
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -214,7 +255,13 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
|
|
||||||
{canMarkAsDeclined && (
|
{canMarkAsDeclined && (
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<Button variant="neutral-primary" size="sm" onClick={() => setDeclineIssueModal(true)}>
|
<Button
|
||||||
|
variant="neutral-primary"
|
||||||
|
size="sm"
|
||||||
|
prependIcon={<CircleX className="w-3 h-3" />}
|
||||||
|
className="text-red-500 border-0.5 border-red-500 bg-red-500/20 focus:bg-red-500/20 focus:text-red-500 hover:bg-red-500/40 bg-opacity-20"
|
||||||
|
onClick={() => setDeclineIssueModal(true)}
|
||||||
|
>
|
||||||
Decline
|
Decline
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,7 +21,7 @@ type Props = {
|
|||||||
duplicateIssueDetails: TInboxDuplicateIssueDetails | undefined;
|
duplicateIssueDetails: TInboxDuplicateIssueDetails | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InboxIssueProperties: React.FC<Props> = observer((props) => {
|
export const InboxIssueContentProperties: React.FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, issue, issueOperations, isEditable, duplicateIssueDetails } = props;
|
const { workspaceSlug, projectId, issue, issueOperations, isEditable, duplicateIssueDetails } = props;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -2,9 +2,9 @@ import { Dispatch, SetStateAction, useEffect, useMemo } from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { TIssue } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
import { Loader, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { InboxIssueProperties } from "@/components/inbox/content";
|
import { InboxIssueContentProperties } from "@/components/inbox/content";
|
||||||
import {
|
import {
|
||||||
IssueDescriptionInput,
|
IssueDescriptionInput,
|
||||||
IssueTitleInput,
|
IssueTitleInput,
|
||||||
@ -13,7 +13,7 @@ import {
|
|||||||
TIssueOperations,
|
TIssueOperations,
|
||||||
} from "@/components/issues";
|
} from "@/components/issues";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useUser } from "@/hooks/store";
|
import { useEventTracker, useProjectInbox, useUser } from "@/hooks/store";
|
||||||
import useReloadConfirmations from "@/hooks/use-reload-confirmation";
|
import useReloadConfirmations from "@/hooks/use-reload-confirmation";
|
||||||
// store types
|
// store types
|
||||||
import { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
|
import { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
|
||||||
@ -36,6 +36,7 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
|||||||
const { currentUser } = useUser();
|
const { currentUser } = useUser();
|
||||||
const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting");
|
const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting");
|
||||||
const { captureIssueEvent } = useEventTracker();
|
const { captureIssueEvent } = useEventTracker();
|
||||||
|
const { loader } = useProjectInbox();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSubmitting === "submitted") {
|
if (isSubmitting === "submitted") {
|
||||||
@ -126,16 +127,22 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
|||||||
value={issue.name}
|
value={issue.name}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IssueDescriptionInput
|
{loader === "issue-loading" ? (
|
||||||
workspaceSlug={workspaceSlug}
|
<Loader className="min-h-[6rem] rounded-md border border-custom-border-200">
|
||||||
projectId={issue.project_id}
|
<Loader.Item width="100%" height="140px" />
|
||||||
issueId={issue.id}
|
</Loader>
|
||||||
swrIssueDescription={swrIssueDescription}
|
) : (
|
||||||
initialValue={issue.description_html ?? "<p></p>"}
|
<IssueDescriptionInput
|
||||||
disabled={!isEditable}
|
workspaceSlug={workspaceSlug}
|
||||||
issueOperations={issueOperations}
|
projectId={issue.project_id}
|
||||||
setIsSubmitting={(value) => setIsSubmitting(value)}
|
issueId={issue.id}
|
||||||
/>
|
swrIssueDescription={swrIssueDescription}
|
||||||
|
initialValue={issue.description_html ?? "<p></p>"}
|
||||||
|
disabled={!isEditable}
|
||||||
|
issueOperations={issueOperations}
|
||||||
|
setIsSubmitting={(value) => setIsSubmitting(value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{currentUser && (
|
{currentUser && (
|
||||||
<IssueReaction
|
<IssueReaction
|
||||||
@ -147,7 +154,7 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<InboxIssueProperties
|
<InboxIssueContentProperties
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
issue={issue}
|
issue={issue}
|
||||||
|
147
web/components/inbox/modals/create-edit-modal/create-root.tsx
Normal file
147
web/components/inbox/modals/create-edit-modal/create-root.tsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import { FC, useCallback, useRef, useState } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { EditorRefApi } from "@plane/rich-text-editor";
|
||||||
|
import { TIssue } from "@plane/types";
|
||||||
|
import { Button, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
|
// components
|
||||||
|
import {
|
||||||
|
InboxIssueTitle,
|
||||||
|
InboxIssueDescription,
|
||||||
|
InboxIssueProperties,
|
||||||
|
} from "@/components/inbox/modals/create-edit-modal";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_CREATED } from "@/constants/event-tracker";
|
||||||
|
// helpers
|
||||||
|
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||||
|
// hooks
|
||||||
|
import { useEventTracker, useProjectInbox, useWorkspace } from "@/hooks/store";
|
||||||
|
|
||||||
|
type TInboxIssueCreateRoot = {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
handleModalClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultIssueData: Partial<TIssue> = {
|
||||||
|
id: undefined,
|
||||||
|
name: "",
|
||||||
|
description_html: "",
|
||||||
|
priority: "none",
|
||||||
|
state_id: "",
|
||||||
|
label_ids: [],
|
||||||
|
assignee_ids: [],
|
||||||
|
start_date: renderFormattedPayloadDate(new Date()),
|
||||||
|
target_date: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props) => {
|
||||||
|
const { workspaceSlug, projectId, handleModalClose } = props;
|
||||||
|
const router = useRouter();
|
||||||
|
// refs
|
||||||
|
const descriptionEditorRef = useRef<EditorRefApi>(null);
|
||||||
|
// hooks
|
||||||
|
const { captureIssueEvent } = useEventTracker();
|
||||||
|
const { createInboxIssue } = useProjectInbox();
|
||||||
|
const { getWorkspaceBySlug } = useWorkspace();
|
||||||
|
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id;
|
||||||
|
// states
|
||||||
|
const [createMore, setCreateMore] = useState<boolean>(false);
|
||||||
|
const [formSubmitting, setFormSubmitting] = useState(false);
|
||||||
|
const [formData, setFormData] = useState<Partial<TIssue>>(defaultIssueData);
|
||||||
|
const handleFormData = useCallback(
|
||||||
|
<T extends keyof Partial<TIssue>>(issueKey: T, issueValue: Partial<TIssue>[T]) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[issueKey]: issueValue,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[formData]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFormSubmit = async () => {
|
||||||
|
const payload: Partial<TIssue> = {
|
||||||
|
name: formData.name || "",
|
||||||
|
description_html: formData.description_html || "<p></p>",
|
||||||
|
priority: formData.priority || "none",
|
||||||
|
state_id: formData.state_id || "",
|
||||||
|
label_ids: formData.label_ids || [],
|
||||||
|
assignee_ids: formData.assignee_ids || [],
|
||||||
|
target_date: formData.target_date || null,
|
||||||
|
};
|
||||||
|
setFormSubmitting(true);
|
||||||
|
|
||||||
|
await createInboxIssue(workspaceSlug, projectId, payload)
|
||||||
|
.then((res) => {
|
||||||
|
if (!createMore) {
|
||||||
|
router.push(`/${workspaceSlug}/projects/${projectId}/inbox/?currentTab=open&inboxIssueId=${res?.issue?.id}`);
|
||||||
|
handleModalClose();
|
||||||
|
} else {
|
||||||
|
descriptionEditorRef?.current?.clearEditor();
|
||||||
|
setFormData(defaultIssueData);
|
||||||
|
}
|
||||||
|
captureIssueEvent({
|
||||||
|
eventName: ISSUE_CREATED,
|
||||||
|
payload: {
|
||||||
|
...formData,
|
||||||
|
state: "SUCCESS",
|
||||||
|
element: "Inbox page",
|
||||||
|
},
|
||||||
|
path: router.pathname,
|
||||||
|
});
|
||||||
|
setToast({
|
||||||
|
type: TOAST_TYPE.SUCCESS,
|
||||||
|
title: `${TOAST_TYPE.SUCCESS}!`,
|
||||||
|
message: "Issue created successfully.",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
captureIssueEvent({
|
||||||
|
eventName: ISSUE_CREATED,
|
||||||
|
payload: {
|
||||||
|
...formData,
|
||||||
|
state: "FAILED",
|
||||||
|
element: "Inbox page",
|
||||||
|
},
|
||||||
|
path: router.pathname,
|
||||||
|
});
|
||||||
|
setToast({
|
||||||
|
type: TOAST_TYPE.ERROR,
|
||||||
|
title: `${TOAST_TYPE.ERROR}!`,
|
||||||
|
message: "Some error occurred. Please try again.",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setFormSubmitting(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!workspaceSlug || !projectId || !workspaceId) return <></>;
|
||||||
|
return (
|
||||||
|
<div className="relative space-y-4">
|
||||||
|
<InboxIssueTitle data={formData} handleData={handleFormData} />
|
||||||
|
<InboxIssueDescription
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
|
projectId={projectId}
|
||||||
|
workspaceId={workspaceId}
|
||||||
|
data={formData}
|
||||||
|
handleData={handleFormData}
|
||||||
|
editorRef={descriptionEditorRef}
|
||||||
|
/>
|
||||||
|
<InboxIssueProperties projectId={projectId} data={formData} handleData={handleFormData} />
|
||||||
|
<div className="relative flex justify-between items-center gap-3">
|
||||||
|
<div className="flex cursor-pointer items-center gap-1" onClick={() => setCreateMore((prevData) => !prevData)}>
|
||||||
|
<span className="text-xs">Create more</span>
|
||||||
|
<ToggleSwitch value={createMore} onChange={() => {}} size="md" />
|
||||||
|
</div>
|
||||||
|
<div className="relative flex items-center gap-3">
|
||||||
|
<Button variant="neutral-primary" size="sm" type="button" onClick={handleModalClose}>
|
||||||
|
Discard
|
||||||
|
</Button>
|
||||||
|
<Button variant="primary" size="sm" type="button" loading={formSubmitting} onClick={handleFormSubmit}>
|
||||||
|
{formSubmitting ? "Adding Issue..." : "Add Issue"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
147
web/components/inbox/modals/create-edit-modal/edit-root.tsx
Normal file
147
web/components/inbox/modals/create-edit-modal/edit-root.tsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import { FC, useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { EditorRefApi } from "@plane/rich-text-editor";
|
||||||
|
import { TIssue } from "@plane/types";
|
||||||
|
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
|
// components
|
||||||
|
import {
|
||||||
|
InboxIssueTitle,
|
||||||
|
InboxIssueDescription,
|
||||||
|
InboxIssueProperties,
|
||||||
|
} from "@/components/inbox/modals/create-edit-modal";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_UPDATED } from "@/constants/event-tracker";
|
||||||
|
// helpers
|
||||||
|
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||||
|
// hooks
|
||||||
|
import { useEventTracker, useInboxIssues, useWorkspace } from "@/hooks/store";
|
||||||
|
|
||||||
|
type TInboxIssueEditRoot = {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
issueId: string;
|
||||||
|
issue: Partial<TIssue>;
|
||||||
|
handleModalClose: () => void;
|
||||||
|
onSubmit?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InboxIssueEditRoot: FC<TInboxIssueEditRoot> = observer((props) => {
|
||||||
|
const { workspaceSlug, projectId, issueId, issue, handleModalClose, onSubmit } = props;
|
||||||
|
const router = useRouter();
|
||||||
|
// refs
|
||||||
|
const descriptionEditorRef = useRef<EditorRefApi>(null);
|
||||||
|
// hooks
|
||||||
|
const { captureIssueEvent } = useEventTracker();
|
||||||
|
const { updateProjectIssue } = useInboxIssues(issueId);
|
||||||
|
const { getWorkspaceBySlug } = useWorkspace();
|
||||||
|
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id;
|
||||||
|
// states
|
||||||
|
const [formSubmitting, setFormSubmitting] = useState(false);
|
||||||
|
const [formData, setFormData] = useState<Partial<TIssue> | undefined>(undefined);
|
||||||
|
const handleFormData = useCallback(
|
||||||
|
<T extends keyof Partial<TIssue>>(issueKey: T, issueValue: Partial<TIssue>[T]) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[issueKey]: issueValue,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[formData]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (formData?.id != issue?.id)
|
||||||
|
setFormData({
|
||||||
|
id: issue?.id || undefined,
|
||||||
|
name: issue?.name ?? "",
|
||||||
|
description_html: issue?.description_html ?? "<p></p>",
|
||||||
|
priority: issue?.priority ?? "none",
|
||||||
|
state_id: issue?.state_id ?? "",
|
||||||
|
label_ids: issue?.label_ids ?? [],
|
||||||
|
assignee_ids: issue?.assignee_ids ?? [],
|
||||||
|
start_date: renderFormattedPayloadDate(issue?.start_date) ?? "",
|
||||||
|
target_date: renderFormattedPayloadDate(issue?.target_date) ?? "",
|
||||||
|
});
|
||||||
|
}, [issue, formData]);
|
||||||
|
|
||||||
|
const handleFormSubmit = async () => {
|
||||||
|
const payload: Partial<TIssue> = {
|
||||||
|
name: formData?.name || "",
|
||||||
|
description_html: formData?.description_html || "<p></p>",
|
||||||
|
priority: formData?.priority || "none",
|
||||||
|
state_id: formData?.state_id || "",
|
||||||
|
label_ids: formData?.label_ids || [],
|
||||||
|
assignee_ids: formData?.assignee_ids || [],
|
||||||
|
start_date: formData?.start_date || undefined,
|
||||||
|
target_date: formData?.target_date || undefined,
|
||||||
|
cycle_id: formData?.cycle_id || "",
|
||||||
|
module_ids: formData?.module_ids || [],
|
||||||
|
estimate_point: formData?.estimate_point || undefined,
|
||||||
|
parent_id: formData?.parent_id || "",
|
||||||
|
};
|
||||||
|
setFormSubmitting(true);
|
||||||
|
|
||||||
|
onSubmit && (await onSubmit());
|
||||||
|
await updateProjectIssue(payload)
|
||||||
|
.then(async () => {
|
||||||
|
captureIssueEvent({
|
||||||
|
eventName: ISSUE_UPDATED,
|
||||||
|
payload: {
|
||||||
|
...formData,
|
||||||
|
state: "SUCCESS",
|
||||||
|
element: "Inbox page",
|
||||||
|
},
|
||||||
|
path: router.pathname,
|
||||||
|
});
|
||||||
|
setToast({
|
||||||
|
type: TOAST_TYPE.SUCCESS,
|
||||||
|
title: `${TOAST_TYPE.SUCCESS}!`,
|
||||||
|
message: "Issue created successfully.",
|
||||||
|
});
|
||||||
|
descriptionEditorRef?.current?.clearEditor();
|
||||||
|
handleModalClose();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
captureIssueEvent({
|
||||||
|
eventName: ISSUE_UPDATED,
|
||||||
|
payload: {
|
||||||
|
...formData,
|
||||||
|
state: "FAILED",
|
||||||
|
element: "Inbox page",
|
||||||
|
},
|
||||||
|
path: router.pathname,
|
||||||
|
});
|
||||||
|
setToast({
|
||||||
|
type: TOAST_TYPE.ERROR,
|
||||||
|
title: `${TOAST_TYPE.ERROR}!`,
|
||||||
|
message: "Some error occurred. Please try again.",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setFormSubmitting(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!workspaceSlug || !projectId || !workspaceId || !formData) return <></>;
|
||||||
|
return (
|
||||||
|
<div className="relative space-y-4">
|
||||||
|
<InboxIssueTitle data={formData} handleData={handleFormData} />
|
||||||
|
<InboxIssueDescription
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
|
projectId={projectId}
|
||||||
|
workspaceId={workspaceId}
|
||||||
|
data={formData}
|
||||||
|
handleData={handleFormData}
|
||||||
|
editorRef={descriptionEditorRef}
|
||||||
|
/>
|
||||||
|
<InboxIssueProperties projectId={projectId} data={formData} handleData={handleFormData} isVisible />
|
||||||
|
<div className="relative flex justify-end items-center gap-3">
|
||||||
|
<Button variant="neutral-primary" size="sm" type="button" onClick={handleModalClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button variant="primary" size="sm" type="button" loading={formSubmitting} onClick={handleFormSubmit}>
|
||||||
|
{formSubmitting ? "Adding..." : "Add to project"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
6
web/components/inbox/modals/create-edit-modal/index.ts
Normal file
6
web/components/inbox/modals/create-edit-modal/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export * from "./modal";
|
||||||
|
export * from "./create-root";
|
||||||
|
export * from "./edit-root";
|
||||||
|
export * from "./issue-title";
|
||||||
|
export * from "./issue-description";
|
||||||
|
export * from "./issue-properties";
|
@ -0,0 +1,47 @@
|
|||||||
|
import { FC, RefObject } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { EditorRefApi } from "@plane/rich-text-editor";
|
||||||
|
import { TIssue } from "@plane/types";
|
||||||
|
import { Loader } from "@plane/ui";
|
||||||
|
// components
|
||||||
|
import { RichTextEditor } from "@/components/editor/rich-text-editor/rich-text-editor";
|
||||||
|
// hooks
|
||||||
|
import { useProjectInbox } from "@/hooks/store";
|
||||||
|
|
||||||
|
type TInboxIssueDescription = {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
data: Partial<TIssue>;
|
||||||
|
handleData: (issueKey: keyof Partial<TIssue>, issueValue: Partial<TIssue>[keyof Partial<TIssue>]) => void;
|
||||||
|
editorRef: RefObject<EditorRefApi>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: have to implement GPT Assistance
|
||||||
|
export const InboxIssueDescription: FC<TInboxIssueDescription> = observer((props) => {
|
||||||
|
const { workspaceSlug, projectId, workspaceId, data, handleData, editorRef } = props;
|
||||||
|
// hooks
|
||||||
|
const { loader } = useProjectInbox();
|
||||||
|
|
||||||
|
if (loader === "issue-loading")
|
||||||
|
return (
|
||||||
|
<Loader className="min-h-[6rem] rounded-md border border-custom-border-200">
|
||||||
|
<Loader.Item width="100%" height="140px" />
|
||||||
|
</Loader>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<RichTextEditor
|
||||||
|
initialValue={!data?.description_html || data?.description_html === "" ? "<p></p>" : data?.description_html}
|
||||||
|
ref={editorRef}
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
|
workspaceId={workspaceId}
|
||||||
|
projectId={projectId}
|
||||||
|
dragDropEnabled={false}
|
||||||
|
onChange={(_description: object, description_html: string) => {
|
||||||
|
handleData("description_html", description_html);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,186 @@
|
|||||||
|
import { FC, useState } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { LayoutPanelTop } from "lucide-react";
|
||||||
|
import { ISearchIssueResponse, TIssue } from "@plane/types";
|
||||||
|
// components
|
||||||
|
import {
|
||||||
|
CycleDropdown,
|
||||||
|
DateDropdown,
|
||||||
|
EstimateDropdown,
|
||||||
|
ModuleDropdown,
|
||||||
|
PriorityDropdown,
|
||||||
|
MemberDropdown,
|
||||||
|
StateDropdown,
|
||||||
|
} from "@/components/dropdowns";
|
||||||
|
import { ParentIssuesListModal } from "@/components/issues";
|
||||||
|
import { IssueLabelSelect } from "@/components/issues/select";
|
||||||
|
// helpers
|
||||||
|
import { renderFormattedPayloadDate, getDate } from "@/helpers/date-time.helper";
|
||||||
|
// hooks
|
||||||
|
import { useEstimate } from "@/hooks/store";
|
||||||
|
|
||||||
|
type TInboxIssueProperties = {
|
||||||
|
projectId: string;
|
||||||
|
data: Partial<TIssue>;
|
||||||
|
handleData: (issueKey: keyof Partial<TIssue>, issueValue: Partial<TIssue>[keyof Partial<TIssue>]) => void;
|
||||||
|
isVisible?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props) => {
|
||||||
|
const { projectId, data, handleData, isVisible = false } = props;
|
||||||
|
// hooks
|
||||||
|
const { areEstimatesEnabledForProject } = useEstimate();
|
||||||
|
// states
|
||||||
|
const [parentIssueModalOpen, setParentIssueModalOpen] = useState(false);
|
||||||
|
const [selectedParentIssue, setSelectedParentIssue] = useState<ISearchIssueResponse | undefined>(undefined);
|
||||||
|
true;
|
||||||
|
|
||||||
|
const startDate = data?.start_date;
|
||||||
|
const targetDate = data?.target_date;
|
||||||
|
|
||||||
|
const minDate = getDate(startDate);
|
||||||
|
minDate?.setDate(minDate.getDate());
|
||||||
|
|
||||||
|
const maxDate = getDate(targetDate);
|
||||||
|
maxDate?.setDate(maxDate.getDate());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative flex flex-wrap gap-2 items-center">
|
||||||
|
{/* state */}
|
||||||
|
<div className="h-7">
|
||||||
|
<StateDropdown
|
||||||
|
value={data?.state_id || ""}
|
||||||
|
onChange={(stateId) => handleData("state_id", stateId)}
|
||||||
|
projectId={projectId}
|
||||||
|
buttonVariant="border-with-text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* priority */}
|
||||||
|
<div className="h-7">
|
||||||
|
<PriorityDropdown
|
||||||
|
value={data?.priority || "none"}
|
||||||
|
onChange={(priority) => handleData("priority", priority)}
|
||||||
|
buttonVariant="border-with-text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Assignees */}
|
||||||
|
<div className="h-7">
|
||||||
|
<MemberDropdown
|
||||||
|
projectId={projectId}
|
||||||
|
value={data?.assignee_ids || []}
|
||||||
|
onChange={(assigneeIds) => handleData("assignee_ids", assigneeIds)}
|
||||||
|
buttonVariant={(data?.assignee_ids || [])?.length > 0 ? "transparent-without-text" : "border-with-text"}
|
||||||
|
buttonClassName={(data?.assignee_ids || [])?.length > 0 ? "hover:bg-transparent" : ""}
|
||||||
|
placeholder="Assignees"
|
||||||
|
multiple
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* labels */}
|
||||||
|
<div className="h-7">
|
||||||
|
<IssueLabelSelect
|
||||||
|
createLabelEnabled={false}
|
||||||
|
setIsOpen={() => {}}
|
||||||
|
value={data?.label_ids || []}
|
||||||
|
onChange={(labelIds) => handleData("label_ids", labelIds)}
|
||||||
|
projectId={projectId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* start date */}
|
||||||
|
{isVisible && (
|
||||||
|
<div className="h-7">
|
||||||
|
<DateDropdown
|
||||||
|
value={data?.start_date || null}
|
||||||
|
onChange={(date) => (date ? handleData("start_date", renderFormattedPayloadDate(date)) : null)}
|
||||||
|
buttonVariant="border-with-text"
|
||||||
|
minDate={minDate ?? undefined}
|
||||||
|
placeholder="Start date"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* due date */}
|
||||||
|
<div className="h-7">
|
||||||
|
<DateDropdown
|
||||||
|
value={data?.target_date || null}
|
||||||
|
onChange={(date) => (date ? handleData("target_date", renderFormattedPayloadDate(date)) : null)}
|
||||||
|
buttonVariant="border-with-text"
|
||||||
|
minDate={minDate ?? undefined}
|
||||||
|
placeholder="Due date"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* cycle */}
|
||||||
|
{isVisible && (
|
||||||
|
<div className="h-7">
|
||||||
|
<CycleDropdown
|
||||||
|
value={data?.cycle_id || ""}
|
||||||
|
onChange={(cycleId) => handleData("cycle_id", cycleId)}
|
||||||
|
projectId={projectId}
|
||||||
|
placeholder="Cycle"
|
||||||
|
buttonVariant="border-with-text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* module */}
|
||||||
|
{isVisible && (
|
||||||
|
<div className="h-7">
|
||||||
|
<ModuleDropdown
|
||||||
|
value={data?.module_ids || []}
|
||||||
|
onChange={(moduleIds) => handleData("module_ids", moduleIds)}
|
||||||
|
projectId={projectId}
|
||||||
|
placeholder="Modules"
|
||||||
|
buttonVariant="border-with-text"
|
||||||
|
multiple
|
||||||
|
showCount
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* estimate */}
|
||||||
|
{isVisible && areEstimatesEnabledForProject(projectId) && (
|
||||||
|
<div className="h-7">
|
||||||
|
<EstimateDropdown
|
||||||
|
value={data?.estimate_point || null}
|
||||||
|
onChange={(estimatePoint) => handleData("estimate_point", estimatePoint)}
|
||||||
|
projectId={projectId}
|
||||||
|
buttonVariant="border-with-text"
|
||||||
|
placeholder="Estimate"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* add parent */}
|
||||||
|
{isVisible && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex cursor-pointer items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2 py-1.5 text-xs hover:bg-custom-background-80"
|
||||||
|
onClick={() => setParentIssueModalOpen(true)}
|
||||||
|
>
|
||||||
|
<LayoutPanelTop className="h-3 w-3 flex-shrink-0" />
|
||||||
|
<span className="whitespace-nowrap">
|
||||||
|
{selectedParentIssue
|
||||||
|
? `${selectedParentIssue.project__identifier}-${selectedParentIssue.sequence_id}`
|
||||||
|
: `Add parent`}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<ParentIssuesListModal
|
||||||
|
isOpen={parentIssueModalOpen}
|
||||||
|
handleClose={() => setParentIssueModalOpen(false)}
|
||||||
|
onChange={(issue) => {
|
||||||
|
handleData("parent_id", issue?.id);
|
||||||
|
setSelectedParentIssue(issue);
|
||||||
|
}}
|
||||||
|
projectId={projectId}
|
||||||
|
issueId={data?.id}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,27 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { TIssue } from "@plane/types";
|
||||||
|
import { Input } from "@plane/ui";
|
||||||
|
|
||||||
|
type TInboxIssueTitle = {
|
||||||
|
data: Partial<TIssue>;
|
||||||
|
handleData: (issueKey: keyof Partial<TIssue>, issueValue: Partial<TIssue>[keyof Partial<TIssue>]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InboxIssueTitle: FC<TInboxIssueTitle> = observer((props) => {
|
||||||
|
const { data, handleData } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative flex flex-wrap gap-2 items-center">
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
value={data?.name}
|
||||||
|
onChange={(e) => handleData("name", e.target.value)}
|
||||||
|
placeholder="Title"
|
||||||
|
className="w-full resize-none text-xl"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
84
web/components/inbox/modals/create-edit-modal/modal.tsx
Normal file
84
web/components/inbox/modals/create-edit-modal/modal.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { FC, Fragment } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { Transition, Dialog } from "@headlessui/react";
|
||||||
|
import { TIssue } from "@plane/types";
|
||||||
|
// components
|
||||||
|
import { InboxIssueCreateRoot, InboxIssueEditRoot } from "@/components/inbox/modals/create-edit-modal";
|
||||||
|
// hooks
|
||||||
|
import { useProject } from "@/hooks/store";
|
||||||
|
|
||||||
|
type TInboxIssueCreateEditModalRoot = {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
modalState: boolean;
|
||||||
|
handleModalClose: () => void;
|
||||||
|
issue: Partial<TIssue> | undefined;
|
||||||
|
onSubmit?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InboxIssueCreateEditModalRoot: FC<TInboxIssueCreateEditModalRoot> = observer((props) => {
|
||||||
|
const { workspaceSlug, projectId, modalState, handleModalClose, issue, onSubmit } = props;
|
||||||
|
// hooks
|
||||||
|
const { currentProjectDetails } = useProject();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Transition.Root show={modalState} as={Fragment}>
|
||||||
|
<Dialog as="div" className="relative z-20" onClose={handleModalClose}>
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0"
|
||||||
|
enterTo="opacity-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
|
||||||
|
</Transition.Child>
|
||||||
|
|
||||||
|
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||||
|
<div className="my-10 flex items-center justify-center p-4 text-center sm:p-0 md:my-20">
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
>
|
||||||
|
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 p-5 text-left shadow-custom-shadow-md transition-all w-full lg:max-w-4xl">
|
||||||
|
{issue && issue?.id ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-xl font-medium text-custom-text-100">
|
||||||
|
Move {currentProjectDetails?.identifier}-{issue?.sequence_id} to project issues
|
||||||
|
</h3>
|
||||||
|
<InboxIssueEditRoot
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
|
projectId={projectId}
|
||||||
|
issueId={issue.id}
|
||||||
|
issue={issue}
|
||||||
|
handleModalClose={handleModalClose}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-xl font-medium text-custom-text-100">Create Inbox Issue</h3>
|
||||||
|
<InboxIssueCreateRoot
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
|
projectId={projectId}
|
||||||
|
handleModalClose={handleModalClose}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Transition.Child>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</Transition.Root>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -1,5 +1,5 @@
|
|||||||
export * from "./accept-issue-modal";
|
export * from "./accept-issue-modal";
|
||||||
export * from "./create-issue-modal";
|
export * from "./create-edit-modal";
|
||||||
export * from "./decline-issue-modal";
|
export * from "./decline-issue-modal";
|
||||||
export * from "./delete-issue-modal";
|
export * from "./delete-issue-modal";
|
||||||
export * from "./select-duplicate";
|
export * from "./select-duplicate";
|
||||||
|
@ -21,7 +21,7 @@ type TInboxIssueRoot = {
|
|||||||
export const InboxIssueRoot: FC<TInboxIssueRoot> = observer((props) => {
|
export const InboxIssueRoot: FC<TInboxIssueRoot> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, inboxIssueId, inboxAccessible } = props;
|
const { workspaceSlug, projectId, inboxIssueId, inboxAccessible } = props;
|
||||||
// hooks
|
// hooks
|
||||||
const { isLoading, error, fetchInboxIssues } = useProjectInbox();
|
const { loader, error, fetchInboxIssues } = useProjectInbox();
|
||||||
|
|
||||||
useSWR(
|
useSWR(
|
||||||
inboxAccessible && workspaceSlug && projectId ? `PROJECT_INBOX_ISSUES_${workspaceSlug}_${projectId}` : null,
|
inboxAccessible && workspaceSlug && projectId ? `PROJECT_INBOX_ISSUES_${workspaceSlug}_${projectId}` : null,
|
||||||
@ -35,7 +35,7 @@ export const InboxIssueRoot: FC<TInboxIssueRoot> = observer((props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// loader
|
// loader
|
||||||
if (isLoading === "init-loading")
|
if (loader === "init-loading")
|
||||||
return (
|
return (
|
||||||
<div className="relative flex w-full h-full flex-col">
|
<div className="relative flex w-full h-full flex-col">
|
||||||
<InboxLayoutLoader />
|
<InboxLayoutLoader />
|
||||||
|
@ -42,7 +42,7 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
|
|||||||
const {
|
const {
|
||||||
currentTab,
|
currentTab,
|
||||||
handleCurrentTab,
|
handleCurrentTab,
|
||||||
isLoading,
|
loader,
|
||||||
inboxIssuesArray,
|
inboxIssuesArray,
|
||||||
inboxIssuePaginationInfo,
|
inboxIssuePaginationInfo,
|
||||||
fetchInboxPaginationIssues,
|
fetchInboxPaginationIssues,
|
||||||
@ -100,7 +100,7 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
|
|||||||
|
|
||||||
<InboxIssueAppliedFilters />
|
<InboxIssueAppliedFilters />
|
||||||
|
|
||||||
{isLoading != undefined && isLoading === "filter-loading" && !inboxIssuePaginationInfo?.next_page_results ? (
|
{loader != undefined && loader === "filter-loading" && !inboxIssuePaginationInfo?.next_page_results ? (
|
||||||
<InboxSidebarLoader />
|
<InboxSidebarLoader />
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
|
@ -649,6 +649,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
buttonVariant="border-with-text"
|
buttonVariant="border-with-text"
|
||||||
tabIndex={getTabIndex("estimate_point")}
|
tabIndex={getTabIndex("estimate_point")}
|
||||||
|
placeholder="Estimate"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -20,10 +20,11 @@ type Props = {
|
|||||||
label?: JSX.Element;
|
label?: JSX.Element;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
tabIndex?: number;
|
tabIndex?: number;
|
||||||
|
createLabelEnabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueLabelSelect: React.FC<Props> = observer((props) => {
|
export const IssueLabelSelect: React.FC<Props> = observer((props) => {
|
||||||
const { setIsOpen, value, onChange, projectId, label, disabled = false, tabIndex } = props;
|
const { setIsOpen, value, onChange, projectId, label, disabled = false, tabIndex, createLabelEnabled = true } = props;
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
@ -221,14 +222,16 @@ export const IssueLabelSelect: React.FC<Props> = observer((props) => {
|
|||||||
) : (
|
) : (
|
||||||
<p className="text-custom-text-400 italic py-1 px-1.5">Loading...</p>
|
<p className="text-custom-text-400 italic py-1 px-1.5">Loading...</p>
|
||||||
)}
|
)}
|
||||||
<button
|
{createLabelEnabled && (
|
||||||
type="button"
|
<button
|
||||||
className="flex items-center gap-2 w-full select-none rounded px-1 py-2 hover:bg-custom-background-80"
|
type="button"
|
||||||
onClick={() => setIsOpen(true)}
|
className="flex items-center gap-2 w-full select-none rounded px-1 py-2 hover:bg-custom-background-80"
|
||||||
>
|
onClick={() => setIsOpen(true)}
|
||||||
<Plus className="h-3 w-3" aria-hidden="true" />
|
>
|
||||||
<span className="whitespace-nowrap">Create new label</span>
|
<Plus className="h-3 w-3" aria-hidden="true" />
|
||||||
</button>
|
<span className="whitespace-nowrap">Create new label</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Combobox.Options>
|
</Combobox.Options>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { StoreContext } from "contexts/store-context";
|
import { StoreContext } from "contexts/store-context";
|
||||||
|
import { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
|
||||||
|
|
||||||
export const useInboxIssues = (inboxIssueId: string) => {
|
export const useInboxIssues = (inboxIssueId: string): IInboxIssueStore => {
|
||||||
const context = useContext(StoreContext);
|
const context = useContext(StoreContext);
|
||||||
if (context === undefined) throw new Error("useInboxIssues must be used within StoreProvider");
|
if (context === undefined) throw new Error("useInboxIssues must be used within StoreProvider");
|
||||||
return context.projectInbox.getIssueInboxByIssueId(inboxIssueId) || {};
|
return context.projectInbox.getIssueInboxByIssueId(inboxIssueId);
|
||||||
};
|
};
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lucide-react": "^0.294.0",
|
"lucide-react": "^0.368.0",
|
||||||
"mobx": "^6.10.0",
|
"mobx": "^6.10.0",
|
||||||
"mobx-react": "^9.1.0",
|
"mobx-react": "^9.1.0",
|
||||||
"mobx-utils": "^6.0.8",
|
"mobx-utils": "^6.0.8",
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import clone from "lodash/clone";
|
||||||
import set from "lodash/set";
|
import set from "lodash/set";
|
||||||
import { makeObservable, observable, runInAction, action } from "mobx";
|
import { makeObservable, observable, runInAction, action } from "mobx";
|
||||||
import { TIssue, TInboxIssue, TInboxIssueStatus, TInboxDuplicateIssueDetails } from "@plane/types";
|
import { TIssue, TInboxIssue, TInboxIssueStatus, TInboxDuplicateIssueDetails } from "@plane/types";
|
||||||
@ -5,6 +6,7 @@ import { TIssue, TInboxIssue, TInboxIssueStatus, TInboxDuplicateIssueDetails } f
|
|||||||
import { EInboxIssueStatus } from "@/helpers/inbox.helper";
|
import { EInboxIssueStatus } from "@/helpers/inbox.helper";
|
||||||
// services
|
// services
|
||||||
import { InboxIssueService } from "@/services/inbox";
|
import { InboxIssueService } from "@/services/inbox";
|
||||||
|
import { IssueService } from "@/services/issue";
|
||||||
// root store
|
// root store
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
|
|
||||||
@ -22,6 +24,8 @@ export interface IInboxIssueStore {
|
|||||||
updateInboxIssueDuplicateTo: (issueId: string) => Promise<void>; // connecting the inbox issue to the project existing issue
|
updateInboxIssueDuplicateTo: (issueId: string) => Promise<void>; // connecting the inbox issue to the project existing issue
|
||||||
updateInboxIssueSnoozeTill: (date: Date) => Promise<void>; // snooze the issue
|
updateInboxIssueSnoozeTill: (date: Date) => Promise<void>; // snooze the issue
|
||||||
updateIssue: (issue: Partial<TIssue>) => Promise<void>; // updating the issue
|
updateIssue: (issue: Partial<TIssue>) => Promise<void>; // updating the issue
|
||||||
|
updateProjectIssue: (issue: Partial<TIssue>) => Promise<void>; // updating the issue
|
||||||
|
fetchIssueActivity: () => Promise<void>; // fetching the issue activity
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InboxIssueStore implements IInboxIssueStore {
|
export class InboxIssueStore implements IInboxIssueStore {
|
||||||
@ -38,6 +42,7 @@ export class InboxIssueStore implements IInboxIssueStore {
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
// services
|
// services
|
||||||
inboxIssueService;
|
inboxIssueService;
|
||||||
|
issueService;
|
||||||
|
|
||||||
constructor(workspaceSlug: string, projectId: string, data: TInboxIssue, private store: RootStore) {
|
constructor(workspaceSlug: string, projectId: string, data: TInboxIssue, private store: RootStore) {
|
||||||
this.id = data.id;
|
this.id = data.id;
|
||||||
@ -51,6 +56,7 @@ export class InboxIssueStore implements IInboxIssueStore {
|
|||||||
this.projectId = projectId;
|
this.projectId = projectId;
|
||||||
// services
|
// services
|
||||||
this.inboxIssueService = new InboxIssueService();
|
this.inboxIssueService = new InboxIssueService();
|
||||||
|
this.issueService = new IssueService();
|
||||||
// observable variables should be defined after the initialization of the values
|
// observable variables should be defined after the initialization of the values
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
id: observable,
|
id: observable,
|
||||||
@ -65,6 +71,8 @@ export class InboxIssueStore implements IInboxIssueStore {
|
|||||||
updateInboxIssueDuplicateTo: action,
|
updateInboxIssueDuplicateTo: action,
|
||||||
updateInboxIssueSnoozeTill: action,
|
updateInboxIssueSnoozeTill: action,
|
||||||
updateIssue: action,
|
updateIssue: action,
|
||||||
|
updateProjectIssue: action,
|
||||||
|
fetchIssueActivity: action,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,12 +80,13 @@ export class InboxIssueStore implements IInboxIssueStore {
|
|||||||
const previousData: Partial<TInboxIssue> = {
|
const previousData: Partial<TInboxIssue> = {
|
||||||
status: this.status,
|
status: this.status,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!this.issue.id) return;
|
if (!this.issue.id) return;
|
||||||
set(this, "status", status);
|
const inboxIssue = await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, {
|
||||||
await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, {
|
|
||||||
status: status,
|
status: status,
|
||||||
});
|
});
|
||||||
|
runInAction(() => set(this, "status", inboxIssue?.status));
|
||||||
} catch {
|
} catch {
|
||||||
runInAction(() => set(this, "status", previousData.status));
|
runInAction(() => set(this, "status", previousData.status));
|
||||||
}
|
}
|
||||||
@ -85,20 +94,19 @@ export class InboxIssueStore implements IInboxIssueStore {
|
|||||||
|
|
||||||
updateInboxIssueDuplicateTo = async (issueId: string) => {
|
updateInboxIssueDuplicateTo = async (issueId: string) => {
|
||||||
const inboxStatus = EInboxIssueStatus.DUPLICATE;
|
const inboxStatus = EInboxIssueStatus.DUPLICATE;
|
||||||
|
|
||||||
const previousData: Partial<TInboxIssue> = {
|
const previousData: Partial<TInboxIssue> = {
|
||||||
status: this.status,
|
status: this.status,
|
||||||
duplicate_to: this.duplicate_to,
|
duplicate_to: this.duplicate_to,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!this.issue.id) return;
|
if (!this.issue.id) return;
|
||||||
set(this, "status", inboxStatus);
|
|
||||||
set(this, "duplicate_to", issueId);
|
|
||||||
const issueResponse = await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, {
|
const issueResponse = await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, {
|
||||||
status: inboxStatus,
|
status: inboxStatus,
|
||||||
duplicate_to: issueId,
|
duplicate_to: issueId,
|
||||||
});
|
});
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
|
this.status = issueResponse.status;
|
||||||
this.duplicate_to = issueResponse.duplicate_to;
|
this.duplicate_to = issueResponse.duplicate_to;
|
||||||
this.duplicate_issue_detail = issueResponse.duplicate_issue_detail;
|
this.duplicate_issue_detail = issueResponse.duplicate_issue_detail;
|
||||||
});
|
});
|
||||||
@ -112,19 +120,21 @@ export class InboxIssueStore implements IInboxIssueStore {
|
|||||||
|
|
||||||
updateInboxIssueSnoozeTill = async (date: Date) => {
|
updateInboxIssueSnoozeTill = async (date: Date) => {
|
||||||
const inboxStatus = EInboxIssueStatus.SNOOZED;
|
const inboxStatus = EInboxIssueStatus.SNOOZED;
|
||||||
|
|
||||||
const previousData: Partial<TInboxIssue> = {
|
const previousData: Partial<TInboxIssue> = {
|
||||||
status: this.status,
|
status: this.status,
|
||||||
snoozed_till: this.snoozed_till,
|
snoozed_till: this.snoozed_till,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!this.issue.id) return;
|
if (!this.issue.id) return;
|
||||||
set(this, "status", inboxStatus);
|
const issueResponse = await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, {
|
||||||
set(this, "snoozed_till", date);
|
|
||||||
await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, {
|
|
||||||
status: inboxStatus,
|
status: inboxStatus,
|
||||||
snoozed_till: new Date(date),
|
snoozed_till: new Date(date),
|
||||||
});
|
});
|
||||||
|
runInAction(() => {
|
||||||
|
this.status = issueResponse?.status;
|
||||||
|
this.snoozed_till = issueResponse?.snoozed_till ? new Date(issueResponse.snoozed_till) : undefined;
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
set(this, "status", previousData.status);
|
set(this, "status", previousData.status);
|
||||||
@ -134,21 +144,49 @@ export class InboxIssueStore implements IInboxIssueStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
updateIssue = async (issue: Partial<TIssue>) => {
|
updateIssue = async (issue: Partial<TIssue>) => {
|
||||||
const inboxIssue = this.issue;
|
const inboxIssue = clone(this.issue);
|
||||||
try {
|
try {
|
||||||
if (!this.issue.id) return;
|
if (!this.issue.id) return;
|
||||||
Object.keys(issue).forEach((key) => {
|
Object.keys(issue).forEach((key) => {
|
||||||
const issueKey = key as keyof TIssue;
|
const issueKey = key as keyof TIssue;
|
||||||
set(inboxIssue, issueKey, issue[issueKey]);
|
set(this.issue, issueKey, issue[issueKey]);
|
||||||
});
|
});
|
||||||
await this.inboxIssueService.updateIssue(this.workspaceSlug, this.projectId, this.issue.id, issue);
|
await this.inboxIssueService.updateIssue(this.workspaceSlug, this.projectId, this.issue.id, issue);
|
||||||
// fetching activity
|
// fetching activity
|
||||||
await this.store.issue.issueDetail.fetchActivities(this.workspaceSlug, this.projectId, this.issue.id);
|
this.fetchIssueActivity();
|
||||||
} catch {
|
} catch {
|
||||||
Object.keys(issue).forEach((key) => {
|
Object.keys(issue).forEach((key) => {
|
||||||
const issueKey = key as keyof TIssue;
|
const issueKey = key as keyof TIssue;
|
||||||
set(inboxIssue, issueKey, inboxIssue[issueKey]);
|
set(this.issue, issueKey, inboxIssue[issueKey]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateProjectIssue = async (issue: Partial<TIssue>) => {
|
||||||
|
const inboxIssue = clone(this.issue);
|
||||||
|
try {
|
||||||
|
if (!this.issue.id) return;
|
||||||
|
Object.keys(issue).forEach((key) => {
|
||||||
|
const issueKey = key as keyof TIssue;
|
||||||
|
set(this.issue, issueKey, issue[issueKey]);
|
||||||
|
});
|
||||||
|
await this.issueService.patchIssue(this.workspaceSlug, this.projectId, this.issue.id, issue);
|
||||||
|
// fetching activity
|
||||||
|
this.fetchIssueActivity();
|
||||||
|
} catch {
|
||||||
|
Object.keys(issue).forEach((key) => {
|
||||||
|
const issueKey = key as keyof TIssue;
|
||||||
|
set(this.issue, issueKey, inboxIssue[issueKey]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchIssueActivity = async () => {
|
||||||
|
try {
|
||||||
|
if (!this.issue.id) return;
|
||||||
|
await this.store.issue.issueDetail.fetchActivities(this.workspaceSlug, this.projectId, this.issue.id);
|
||||||
|
} catch {
|
||||||
|
console.error("Failed to fetch issue activity");
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ type TLoader =
|
|||||||
|
|
||||||
export interface IProjectInboxStore {
|
export interface IProjectInboxStore {
|
||||||
currentTab: TInboxIssueCurrentTab;
|
currentTab: TInboxIssueCurrentTab;
|
||||||
isLoading: TLoader;
|
loader: TLoader;
|
||||||
error: { message: string; status: "init-error" | "pagination-error" } | undefined;
|
error: { message: string; status: "init-error" | "pagination-error" } | undefined;
|
||||||
currentInboxProjectId: string;
|
currentInboxProjectId: string;
|
||||||
inboxFilters: Partial<TInboxIssueFilter>;
|
inboxFilters: Partial<TInboxIssueFilter>;
|
||||||
@ -42,7 +42,7 @@ export interface IProjectInboxStore {
|
|||||||
getAppliedFiltersCount: number;
|
getAppliedFiltersCount: number;
|
||||||
inboxIssuesArray: IInboxIssueStore[];
|
inboxIssuesArray: IInboxIssueStore[];
|
||||||
// helper actions
|
// helper actions
|
||||||
getIssueInboxByIssueId: (issueId: string) => IInboxIssueStore | undefined;
|
getIssueInboxByIssueId: (issueId: string) => IInboxIssueStore;
|
||||||
inboxIssueSorting: (issues: IInboxIssueStore[]) => IInboxIssueStore[];
|
inboxIssueSorting: (issues: IInboxIssueStore[]) => IInboxIssueStore[];
|
||||||
inboxIssueQueryParams: (
|
inboxIssueQueryParams: (
|
||||||
inboxFilters: Partial<TInboxIssueFilter>,
|
inboxFilters: Partial<TInboxIssueFilter>,
|
||||||
@ -70,7 +70,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
PER_PAGE_COUNT = 10;
|
PER_PAGE_COUNT = 10;
|
||||||
// observables
|
// observables
|
||||||
currentTab: TInboxIssueCurrentTab = EInboxIssueCurrentTab.OPEN;
|
currentTab: TInboxIssueCurrentTab = EInboxIssueCurrentTab.OPEN;
|
||||||
isLoading: TLoader = "init-loading";
|
loader: TLoader = "init-loading";
|
||||||
error: { message: string; status: "init-error" | "pagination-error" } | undefined = undefined;
|
error: { message: string; status: "init-error" | "pagination-error" } | undefined = undefined;
|
||||||
currentInboxProjectId: string = "";
|
currentInboxProjectId: string = "";
|
||||||
inboxFilters: Partial<TInboxIssueFilter> = {
|
inboxFilters: Partial<TInboxIssueFilter> = {
|
||||||
@ -88,7 +88,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
constructor(private store: RootStore) {
|
constructor(private store: RootStore) {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
currentTab: observable.ref,
|
currentTab: observable.ref,
|
||||||
isLoading: observable.ref,
|
loader: observable.ref,
|
||||||
error: observable,
|
error: observable,
|
||||||
currentInboxProjectId: observable.ref,
|
currentInboxProjectId: observable.ref,
|
||||||
inboxFilters: observable,
|
inboxFilters: observable,
|
||||||
@ -123,17 +123,17 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get inboxIssuesArray() {
|
get inboxIssuesArray() {
|
||||||
|
let appliedFilters =
|
||||||
|
this.currentTab === EInboxIssueCurrentTab.OPEN
|
||||||
|
? [EInboxIssueStatus.PENDING, EInboxIssueStatus.SNOOZED]
|
||||||
|
: [EInboxIssueStatus.ACCEPTED, EInboxIssueStatus.DECLINED, EInboxIssueStatus.DUPLICATE];
|
||||||
|
appliedFilters = appliedFilters.filter((filter) => this.inboxFilters?.status?.includes(filter));
|
||||||
return this.inboxIssueSorting(
|
return this.inboxIssueSorting(
|
||||||
Object.values(this.inboxIssues || {}).filter((inbox) =>
|
Object.values(this.inboxIssues || {}).filter((inbox) => appliedFilters.includes(inbox.status))
|
||||||
(this.currentTab === EInboxIssueCurrentTab.OPEN
|
|
||||||
? [EInboxIssueStatus.PENDING, EInboxIssueStatus.SNOOZED]
|
|
||||||
: [EInboxIssueStatus.ACCEPTED, EInboxIssueStatus.DECLINED, EInboxIssueStatus.DUPLICATE]
|
|
||||||
).includes(inbox.status)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getIssueInboxByIssueId = computedFn((issueId: string) => this.inboxIssues?.[issueId] || undefined);
|
getIssueInboxByIssueId = computedFn((issueId: string) => this.inboxIssues?.[issueId]);
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
inboxIssueSorting = (issues: IInboxIssueStore[]) => {
|
inboxIssueSorting = (issues: IInboxIssueStore[]) => {
|
||||||
@ -252,9 +252,9 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
set(this, ["inboxIssues"], {});
|
set(this, ["inboxIssues"], {});
|
||||||
set(this, ["inboxIssuePaginationInfo"], undefined);
|
set(this, ["inboxIssuePaginationInfo"], undefined);
|
||||||
}
|
}
|
||||||
if (Object.keys(this.inboxIssues).length === 0) this.isLoading = "init-loading";
|
if (Object.keys(this.inboxIssues).length === 0) this.loader = "init-loading";
|
||||||
else this.isLoading = "mutation-loading";
|
else this.loader = "mutation-loading";
|
||||||
if (loadingType) this.isLoading = loadingType;
|
if (loadingType) this.loader = loadingType;
|
||||||
|
|
||||||
const queryParams = this.inboxIssueQueryParams(
|
const queryParams = this.inboxIssueQueryParams(
|
||||||
this.inboxFilters,
|
this.inboxFilters,
|
||||||
@ -265,7 +265,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
const { results, ...paginationInfo } = await this.inboxIssueService.list(workspaceSlug, projectId, queryParams);
|
const { results, ...paginationInfo } = await this.inboxIssueService.list(workspaceSlug, projectId, queryParams);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.isLoading = undefined;
|
this.loader = undefined;
|
||||||
set(this, "inboxIssuePaginationInfo", paginationInfo);
|
set(this, "inboxIssuePaginationInfo", paginationInfo);
|
||||||
if (results && results.length > 0)
|
if (results && results.length > 0)
|
||||||
results.forEach((value: TInboxIssue) => {
|
results.forEach((value: TInboxIssue) => {
|
||||||
@ -279,7 +279,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching the inbox issues", error);
|
console.error("Error fetching the inbox issues", error);
|
||||||
this.isLoading = undefined;
|
this.loader = undefined;
|
||||||
this.error = {
|
this.error = {
|
||||||
message: "Error fetching the inbox issues please try again later.",
|
message: "Error fetching the inbox issues please try again later.",
|
||||||
status: "init-error",
|
status: "init-error",
|
||||||
@ -301,7 +301,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
(this.inboxIssuePaginationInfo?.total_results &&
|
(this.inboxIssuePaginationInfo?.total_results &&
|
||||||
this.inboxIssuesArray.length < this.inboxIssuePaginationInfo?.total_results))
|
this.inboxIssuesArray.length < this.inboxIssuePaginationInfo?.total_results))
|
||||||
) {
|
) {
|
||||||
this.isLoading = "pagination-loading";
|
this.loader = "pagination-loading";
|
||||||
|
|
||||||
const queryParams = this.inboxIssueQueryParams(
|
const queryParams = this.inboxIssueQueryParams(
|
||||||
this.inboxFilters,
|
this.inboxFilters,
|
||||||
@ -312,7 +312,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
const { results, ...paginationInfo } = await this.inboxIssueService.list(workspaceSlug, projectId, queryParams);
|
const { results, ...paginationInfo } = await this.inboxIssueService.list(workspaceSlug, projectId, queryParams);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.isLoading = undefined;
|
this.loader = undefined;
|
||||||
set(this, "inboxIssuePaginationInfo", paginationInfo);
|
set(this, "inboxIssuePaginationInfo", paginationInfo);
|
||||||
if (results && results.length > 0)
|
if (results && results.length > 0)
|
||||||
results.forEach((value: TInboxIssue) => {
|
results.forEach((value: TInboxIssue) => {
|
||||||
@ -327,7 +327,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
} else set(this, ["inboxIssuePaginationInfo", "next_page_results"], false);
|
} else set(this, ["inboxIssuePaginationInfo", "next_page_results"], false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching the inbox issues", error);
|
console.error("Error fetching the inbox issues", error);
|
||||||
this.isLoading = undefined;
|
this.loader = undefined;
|
||||||
this.error = {
|
this.error = {
|
||||||
message: "Error fetching the paginated inbox issues please try again later.",
|
message: "Error fetching the paginated inbox issues please try again later.",
|
||||||
status: "pagination-error",
|
status: "pagination-error",
|
||||||
@ -348,7 +348,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
inboxIssueId: string
|
inboxIssueId: string
|
||||||
): Promise<TInboxIssue> => {
|
): Promise<TInboxIssue> => {
|
||||||
try {
|
try {
|
||||||
this.isLoading = "issue-loading";
|
this.loader = "issue-loading";
|
||||||
const inboxIssue = await this.inboxIssueService.retrieve(workspaceSlug, projectId, inboxIssueId);
|
const inboxIssue = await this.inboxIssueService.retrieve(workspaceSlug, projectId, inboxIssueId);
|
||||||
const issueId = inboxIssue?.issue?.id || undefined;
|
const issueId = inboxIssue?.issue?.id || undefined;
|
||||||
|
|
||||||
@ -362,12 +362,12 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
await this.store.issue.issueDetail.fetchActivities(workspaceSlug, projectId, issueId);
|
await this.store.issue.issueDetail.fetchActivities(workspaceSlug, projectId, issueId);
|
||||||
// fetching comments
|
// fetching comments
|
||||||
await this.store.issue.issueDetail.fetchComments(workspaceSlug, projectId, issueId);
|
await this.store.issue.issueDetail.fetchComments(workspaceSlug, projectId, issueId);
|
||||||
this.isLoading = undefined;
|
this.loader = undefined;
|
||||||
}
|
}
|
||||||
return inboxIssue;
|
return inboxIssue;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching the inbox issue with inbox issue id");
|
console.error("Error fetching the inbox issue with inbox issue id");
|
||||||
this.isLoading = undefined;
|
this.loader = undefined;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
27
yarn.lock
27
yarn.lock
@ -5874,6 +5874,11 @@ lucide-react@^0.309.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.309.0.tgz#7369893cb4b074a0a0b1d3acdc6fd9a8bdb5add1"
|
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.309.0.tgz#7369893cb4b074a0a0b1d3acdc6fd9a8bdb5add1"
|
||||||
integrity sha512-zNVPczuwFrCfksZH3zbd1UDE6/WYhYAdbe2k7CImVyPAkXLgIwbs6eXQ4loigqDnUFjyFYCI5jZ1y10Kqal0dg==
|
integrity sha512-zNVPczuwFrCfksZH3zbd1UDE6/WYhYAdbe2k7CImVyPAkXLgIwbs6eXQ4loigqDnUFjyFYCI5jZ1y10Kqal0dg==
|
||||||
|
|
||||||
|
lucide-react@^0.368.0:
|
||||||
|
version "0.368.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.368.0.tgz#3c0ee63f4f7d30ae63b621b2b8f04f9e409ee6e7"
|
||||||
|
integrity sha512-soryVrCjheZs8rbXKdINw9B8iPi5OajBJZMJ1HORig89ljcOcEokKKAgGbg3QWxSXel7JwHOfDFUdDHAKyUAMw==
|
||||||
|
|
||||||
magic-string@^0.25.0, magic-string@^0.25.7:
|
magic-string@^0.25.0, magic-string@^0.25.7:
|
||||||
version "0.25.9"
|
version "0.25.9"
|
||||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
|
||||||
@ -7815,8 +7820,16 @@ streamx@^2.15.0:
|
|||||||
fast-fifo "^1.1.0"
|
fast-fifo "^1.1.0"
|
||||||
queue-tick "^1.0.1"
|
queue-tick "^1.0.1"
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
name string-width-cjs
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
dependencies:
|
||||||
|
emoji-regex "^8.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
|
string-width@^4.1.0:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@ -7892,8 +7905,14 @@ stringify-object@^3.3.0:
|
|||||||
is-obj "^1.0.1"
|
is-obj "^1.0.1"
|
||||||
is-regexp "^1.0.0"
|
is-regexp "^1.0.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||||
name strip-ansi-cjs
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.1"
|
||||||
|
|
||||||
|
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
Loading…
Reference in New Issue
Block a user