From 71b2884b570c260118ab8a8f16656d011f819bf8 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 23 Jun 2023 13:19:26 +0530 Subject: [PATCH] chore: route to issue after creating it (#1359) * chore: navigate to newly created inbox issue * refactor: inbox * fix: hide ai modal after issue creation * chore: hide action buttons after acting upon them * chore: add icon to inbox status * chore: update inbox status colors --- .../app/components/inbox/filters-dropdown.tsx | 126 +++--- .../components/inbox/inbox-action-headers.tsx | 415 ++++++++++-------- .../app/components/inbox/inbox-issue-card.tsx | 179 ++++---- .../components/inbox/inbox-main-content.tsx | 114 +++-- apps/app/components/issues/form.tsx | 2 + apps/app/components/issues/modal.tsx | 4 + apps/app/constants/inbox.ts | 15 + apps/app/pages/[workspaceSlug]/index.tsx | 2 +- .../projects/[projectId]/inbox/[inboxId].tsx | 222 +--------- apps/app/services/inbox.service.ts | 2 +- 10 files changed, 517 insertions(+), 564 deletions(-) diff --git a/apps/app/components/inbox/filters-dropdown.tsx b/apps/app/components/inbox/filters-dropdown.tsx index b567baed0..1b6af608c 100644 --- a/apps/app/components/inbox/filters-dropdown.tsx +++ b/apps/app/components/inbox/filters-dropdown.tsx @@ -1,63 +1,81 @@ +// hooks +import useInboxView from "hooks/use-inbox-view"; // ui import { MultiLevelDropdown } from "components/ui"; // icons import { getPriorityIcon } from "components/icons"; -// types -import { IInboxFilterOptions } from "types"; // constants import { PRIORITIES } from "constants/project"; import { INBOX_STATUS } from "constants/inbox"; -type Props = { - filters: Partial; - onSelect: (option: any) => void; - direction?: "left" | "right"; - height?: "sm" | "md" | "rg" | "lg"; -}; +export const FiltersDropdown: React.FC = () => { + const { filters, setFilters, filtersLength } = useInboxView(); -export const FiltersDropdown: React.FC = ({ filters, onSelect, direction, height }) => ( - ({ - id: priority ?? "none", - label: ( -
- {getPriorityIcon(priority)} {priority ?? "None"} -
- ), - value: { - key: "priority", - value: priority, - }, - selected: filters?.priority?.includes(priority ?? "none"), - })), - ], - }, - { - id: "inbox_status", - label: "Status", - value: INBOX_STATUS.map((status) => status.value), - children: [ - ...INBOX_STATUS.map((status) => ({ - id: status.key, - label: status.label, - value: { - key: "inbox_status", - value: status.value, - }, - selected: filters?.inbox_status?.includes(status.value), - })), - ], - }, - ]} - /> -); + return ( +
+ { + const key = option.key as keyof typeof filters; + + const valueExists = (filters[key] as any[])?.includes(option.value); + + if (valueExists) { + setFilters({ + [option.key]: ((filters[key] ?? []) as any[])?.filter((val) => val !== option.value), + }); + } else { + setFilters({ + [option.key]: [...((filters[key] ?? []) as any[]), option.value], + }); + } + }} + direction="right" + height="rg" + options={[ + { + id: "priority", + label: "Priority", + value: PRIORITIES, + children: [ + ...PRIORITIES.map((priority) => ({ + id: priority ?? "none", + label: ( +
+ {getPriorityIcon(priority)} {priority ?? "None"} +
+ ), + value: { + key: "priority", + value: priority, + }, + selected: filters?.priority?.includes(priority ?? "none"), + })), + ], + }, + { + id: "inbox_status", + label: "Status", + value: INBOX_STATUS.map((status) => status.value), + children: [ + ...INBOX_STATUS.map((status) => ({ + id: status.key, + label: status.label, + value: { + key: "inbox_status", + value: status.value, + }, + selected: filters?.inbox_status?.includes(status.value), + })), + ], + }, + ]} + /> + {filtersLength > 0 && ( +
+ {filtersLength} +
+ )} +
+ ); +}; diff --git a/apps/app/components/inbox/inbox-action-headers.tsx b/apps/app/components/inbox/inbox-action-headers.tsx index 5ceaa8f2c..5702d560b 100644 --- a/apps/app/components/inbox/inbox-action-headers.tsx +++ b/apps/app/components/inbox/inbox-action-headers.tsx @@ -2,17 +2,27 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/router"; +import { mutate } from "swr"; + // react-datepicker import DatePicker from "react-datepicker"; // headless ui import { Popover } from "@headlessui/react"; // contexts import { useProjectMyMembership } from "contexts/project-member.context"; +// services +import inboxServices from "services/inbox.service"; // hooks import useInboxView from "hooks/use-inbox-view"; import useUserAuth from "hooks/use-user-auth"; +import useToast from "hooks/use-toast"; // components -import { FiltersDropdown } from "components/inbox"; +import { + DeclineIssueModal, + DeleteIssueModal, + FiltersDropdown, + SelectDuplicateInboxIssueModal, +} from "components/inbox"; // ui import { PrimaryButton, SecondaryButton } from "components/ui"; // icons @@ -26,47 +36,84 @@ import { TrashIcon, } from "@heroicons/react/24/outline"; // types -import type { IInboxIssue } from "types"; - -type Props = { - issueCount: number; - currentIssueIndex: number; - issue?: IInboxIssue; - onAccept: () => Promise; - onDecline: () => void; - onMarkAsDuplicate: () => void; - onSnooze: (date: Date | string) => void; - onDelete: () => void; -}; - -export const InboxActionHeader: React.FC = (props) => { - const { - issueCount, - currentIssueIndex, - onAccept, - onDecline, - onMarkAsDuplicate, - onSnooze, - onDelete, - issue, - } = props; +import type { IInboxIssueDetail, TInboxStatus } from "types"; +// fetch-keys +import { INBOX_ISSUE_DETAILS } from "constants/fetch-keys"; +export const InboxActionHeader = () => { const [isAccepting, setIsAccepting] = useState(false); const [date, setDate] = useState(new Date()); + const [selectDuplicateIssue, setSelectDuplicateIssue] = useState(false); + const [declineIssueModal, setDeclineIssueModal] = useState(false); + const [deleteIssueModal, setDeleteIssueModal] = useState(false); const router = useRouter(); - const { inboxIssueId } = router.query; + const { workspaceSlug, projectId, inboxId, inboxIssueId } = router.query; - const { memberRole } = useProjectMyMembership(); - const { filters, setFilters, filtersLength } = useInboxView(); const { user } = useUserAuth(); + const { memberRole } = useProjectMyMembership(); + const { issues: inboxIssues, mutate: mutateInboxIssues } = useInboxView(); + const { setToastAlert } = useToast(); + + const markInboxStatus = async (data: TInboxStatus) => { + if (!workspaceSlug || !projectId || !inboxId || !inboxIssueId) return; + + mutate( + INBOX_ISSUE_DETAILS(inboxId as string, inboxIssueId as string), + (prevData) => { + if (!prevData) return prevData; + + return { + ...prevData, + issue_inbox: [{ ...prevData.issue_inbox[0], ...data }], + }; + }, + false + ); + mutateInboxIssues( + (prevData) => + (prevData ?? []).map((i) => + i.bridge_id === inboxIssueId + ? { ...i, issue_inbox: [{ ...i.issue_inbox[0], ...data }] } + : i + ), + false + ); + + await inboxServices + .markInboxStatus( + workspaceSlug.toString(), + projectId.toString(), + inboxId.toString(), + inboxIssues?.find((inboxIssue) => inboxIssue.bridge_id === inboxIssueId)?.bridge_id!, + data, + user + ) + .catch(() => + setToastAlert({ + type: "error", + title: "Error!", + message: "Something went wrong while updating inbox status. Please try again.", + }) + ) + .finally(() => { + mutate(INBOX_ISSUE_DETAILS(inboxId as string, inboxIssueId as string)); + mutateInboxIssues(); + }); + }; const handleAcceptIssue = () => { setIsAccepting(true); - onAccept().finally(() => setIsAccepting(false)); + markInboxStatus({ + status: 1, + }).finally(() => setIsAccepting(false)); }; + const issue = inboxIssues?.find((issue) => issue.bridge_id === inboxIssueId); + const currentIssueIndex = + inboxIssues?.findIndex((issue) => issue.bridge_id === inboxIssueId) ?? 0; + useEffect(() => { if (!issue?.issue_inbox[0].snoozed_till) return; @@ -82,163 +129,165 @@ export const InboxActionHeader: React.FC = (props) => { tomorrow.setDate(today.getDate() + 1); return ( -
-
-
- -

Inbox

-
-
- { - const key = option.key as keyof typeof filters; - - const valueExists = (filters[key] as any[])?.includes(option.value); - - if (valueExists) { - setFilters({ - [option.key]: ((filters[key] ?? []) as any[])?.filter( - (val) => val !== option.value - ), - }); - } else { - setFilters({ - [option.key]: [...((filters[key] ?? []) as any[]), option.value], - }); - } - }} - direction="right" - height="rg" - /> - {filtersLength > 0 && ( -
- {filtersLength} -
- )} -
-
- {inboxIssueId && ( -
-
- - -
- {currentIssueIndex + 1}/{issueCount} -
+ <> + setSelectDuplicateIssue(false)} + value={ + inboxIssues?.find((inboxIssue) => inboxIssue.bridge_id === inboxIssueId)?.issue_inbox[0] + .duplicate_to + } + onSubmit={(dupIssueId: string) => { + markInboxStatus({ + status: 2, + duplicate_to: dupIssueId, + }).finally(() => setSelectDuplicateIssue(false)); + }} + /> + setDeclineIssueModal(false)} + data={inboxIssues?.find((i) => i.bridge_id === inboxIssueId)} + onSubmit={async () => { + await markInboxStatus({ + status: -1, + }).finally(() => setDeclineIssueModal(false)); + }} + /> + setDeleteIssueModal(false)} + data={inboxIssues?.find((i) => i.bridge_id === inboxIssueId)} + /> +
+
+
+ +

Inbox

-
- {isAllowed && ( -
+
+ {inboxIssueId && ( +
+
+ + +
+ {currentIssueIndex + 1}/{inboxIssues?.length ?? 0} +
+
+
+ {isAllowed && (issueStatus === 0 || issueStatus === -2) && ( +
+ + + + + Snooze + + + + {({ close }) => ( +
+ { + if (!val) return; + setDate(val); + }} + dateFormat="dd-MM-yyyy" + minDate={tomorrow} + inline + /> + { + close(); + markInboxStatus({ + status: 0, + snoozed_till: new Date(date), + }); + }} + > + Snooze + +
+ )} +
+
+
+ )} + {isAllowed && issueStatus === -2 && ( +
+ setSelectDuplicateIssue(true)} > - - - Snooze - - - - {({ close }) => ( -
- { - if (!val) return; - setDate(val); - }} - dateFormat="dd-MM-yyyy" - minDate={tomorrow} - inline - /> - { - close(); - onSnooze(date); - }} - > - Snooze - -
- )} -
- -
- )} - {isAllowed && ( -
- - - Mark as duplicate - - - - {isAccepting ? "Accepting..." : "Accept"} - - - - Decline - -
- )} - {(isAllowed || user?.id === issue?.created_by) && ( -
- - - Delete - -
- )} + + Mark as duplicate + +
+ )} + {isAllowed && (issueStatus === 0 || issueStatus === -2) && ( +
+ + + {isAccepting ? "Accepting..." : "Accept"} + +
+ )} + {isAllowed && issueStatus === -2 && ( +
+ setDeclineIssueModal(true)} + > + + Decline + +
+ )} + {(isAllowed || user?.id === issue?.created_by) && ( +
+ setDeleteIssueModal(true)} + > + + Delete + +
+ )} +
-
- )} -
+ )} +
+ ); }; diff --git a/apps/app/components/inbox/inbox-issue-card.tsx b/apps/app/components/inbox/inbox-issue-card.tsx index 814f221a5..072647ae3 100644 --- a/apps/app/components/inbox/inbox-issue-card.tsx +++ b/apps/app/components/inbox/inbox-issue-card.tsx @@ -4,13 +4,21 @@ import Link from "next/link"; // ui import { Tooltip } from "components/ui"; // icons -import { getPriorityIcon, getStateGroupIcon } from "components/icons"; -import { CalendarDaysIcon, ClockIcon } from "@heroicons/react/24/outline"; +import { getPriorityIcon } from "components/icons"; +import { + CalendarDaysIcon, + CheckCircleIcon, + ClockIcon, + DocumentDuplicateIcon, + ExclamationTriangleIcon, + XCircleIcon, +} from "@heroicons/react/24/outline"; // helpers import { renderShortNumericDateFormat } from "helpers/date-time.helper"; -import { addSpaceIfCamelCase } from "helpers/string.helper"; // types import type { IInboxIssue } from "types"; +// constants +import { INBOX_STATUS } from "constants/inbox"; type Props = { issue: IInboxIssue; @@ -30,93 +38,88 @@ export const InboxIssueCard: React.FC = (props) => { href={`/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}?inboxIssueId=${issue.bridge_id}`} > - -
-
-

- {issue.project_detail?.identifier}-{issue.sequence_id} -

-
{issue.name}
-
-
- -
- {getStateGroupIcon( - issue.state_detail?.group ?? "backlog", - "14", - "14", - issue.state_detail?.color - )} - {issue.state_detail?.name ?? "Triage"} -
-
- -
- {getPriorityIcon( - issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None", - "text-sm" - )} -
-
- -
- - {renderShortNumericDateFormat(issue.created_at ?? "")} -
-
- {issue.issue_inbox[0].snoozed_till && ( -
- - - Snoozed till {renderShortNumericDateFormat(issue.issue_inbox[0].snoozed_till)} - -
- )} -
+
+

+ {issue.project_detail?.identifier}-{issue.sequence_id} +

+
{issue.name}
- +
+ +
+ {getPriorityIcon( + issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None", + "text-sm" + )} +
+
+ +
+ + {renderShortNumericDateFormat(issue.created_at ?? "")} +
+
+
+
s.value === issueStatus)?.textColor + }`} + > + {issueStatus === -2 ? ( + <> + + Pending + + ) : issueStatus === -1 ? ( + <> + + Declined + + ) : issueStatus === 0 ? ( + <> + + + {new Date(issue.issue_inbox[0].snoozed_till ?? "") < new Date() + ? "Snoozed date passed" + : "Snoozed"} + + + ) : issueStatus === 1 ? ( + <> + + Accepted + + ) : ( + <> + + Duplicate + + )} +
+
); diff --git a/apps/app/components/inbox/inbox-main-content.tsx b/apps/app/components/inbox/inbox-main-content.tsx index 83948495c..d03368944 100644 --- a/apps/app/components/inbox/inbox-main-content.tsx +++ b/apps/app/components/inbox/inbox-main-content.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect } from "react"; -import { useRouter } from "next/router"; +import Router, { useRouter } from "next/router"; import useSWR, { mutate } from "swr"; @@ -29,6 +29,7 @@ import { ClockIcon, DocumentDuplicateIcon, ExclamationTriangleIcon, + InboxIcon, XCircleIcon, } from "@heroicons/react/24/outline"; // helpers @@ -37,6 +38,8 @@ import { renderShortNumericDateFormat } from "helpers/date-time.helper"; import type { IInboxIssue, IIssue } from "types"; // fetch-keys import { INBOX_ISSUES, INBOX_ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; +// constants +import { INBOX_STATUS } from "constants/inbox"; const defaultValues = { name: "", @@ -55,7 +58,7 @@ export const InboxMainContent: React.FC = () => { const { user } = useUserAuth(); const { memberRole } = useProjectMyMembership(); - const { params } = useInboxView(); + const { params, issues: inboxIssues } = useInboxView(); const { reset, control, watch } = useForm({ defaultValues, @@ -76,17 +79,6 @@ export const InboxMainContent: React.FC = () => { : null ); - useEffect(() => { - if (!issueDetails || !inboxIssueId) return; - - reset({ - ...issueDetails, - assignees_list: - issueDetails.assignees_list ?? (issueDetails.assignee_details ?? []).map((user) => user.id), - labels_list: issueDetails.labels_list ?? issueDetails.labels, - }); - }, [issueDetails, reset, inboxIssueId]); - const submitChanges = useCallback( async (formData: Partial) => { if (!workspaceSlug || !projectId || !inboxIssueId || !inboxId || !issueDetails) return; @@ -144,7 +136,86 @@ export const InboxMainContent: React.FC = () => { ] ); + const onKeyDown = useCallback( + (e: KeyboardEvent) => { + if (!inboxIssues || !inboxIssueId) return; + + const currentIssueIndex = inboxIssues.findIndex((issue) => issue.bridge_id === inboxIssueId); + + switch (e.key) { + case "ArrowUp": + Router.push({ + pathname: `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}`, + query: { + inboxIssueId: + currentIssueIndex === 0 + ? inboxIssues[inboxIssues.length - 1].bridge_id + : inboxIssues[currentIssueIndex - 1].bridge_id, + }, + }); + break; + case "ArrowDown": + Router.push({ + pathname: `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}`, + query: { + inboxIssueId: + currentIssueIndex === inboxIssues.length - 1 + ? inboxIssues[0].bridge_id + : inboxIssues[currentIssueIndex + 1].bridge_id, + }, + }); + break; + default: + break; + } + }, + [workspaceSlug, projectId, inboxIssueId, inboxId, inboxIssues] + ); + + useEffect(() => { + document.addEventListener("keydown", onKeyDown); + + return () => { + document.removeEventListener("keydown", onKeyDown); + }; + }, [onKeyDown]); + + useEffect(() => { + if (!issueDetails || !inboxIssueId) return; + + reset({ + ...issueDetails, + assignees_list: + issueDetails.assignees_list ?? (issueDetails.assignee_details ?? []).map((user) => user.id), + labels_list: issueDetails.labels_list ?? issueDetails.labels, + }); + }, [issueDetails, reset, inboxIssueId]); + const issueStatus = issueDetails?.issue_inbox[0].status; + const inboxStatusDetails = INBOX_STATUS.find((s) => s.value === issueStatus); + + if (!inboxIssueId) + return ( +
+
+
+ + {inboxIssues && inboxIssues.length > 0 ? ( + + {inboxIssues?.length} issues found. Select an issue from the sidebar to view its + details. + + ) : ( + + No issues found. Use{" "} +
C
shortcut to + create a new issue +
+ )} +
+
+
+ ); return ( <> @@ -153,19 +224,10 @@ export const InboxMainContent: React.FC = () => {
{issueStatus === -2 ? ( @@ -266,6 +328,4 @@ export const InboxMainContent: React.FC = () => { )} ); - - return null; }; diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index 5db688283..cc5cae3f1 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -163,6 +163,8 @@ export const IssueForm: FC = ({ const handleCreateUpdateIssue = async (formData: Partial) => { await handleFormSubmit(formData); + setGptAssistantModal(false); + reset({ ...defaultValues, project: projectId, diff --git a/apps/app/components/issues/modal.tsx b/apps/app/components/issues/modal.tsx index 5142c6a32..22a275fee 100644 --- a/apps/app/components/issues/modal.tsx +++ b/apps/app/components/issues/modal.tsx @@ -188,6 +188,10 @@ export const CreateUpdateIssueModal: React.FC = ({ message: "Issue created successfully.", }); + router.push( + `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}?inboxIssueId=${res.issue_inbox[0].id}` + ); + mutate(INBOX_ISSUES(inboxId.toString(), inboxParams)); }) .catch(() => { diff --git a/apps/app/constants/inbox.ts b/apps/app/constants/inbox.ts index 9bbe8230a..3d7e8a054 100644 --- a/apps/app/constants/inbox.ts +++ b/apps/app/constants/inbox.ts @@ -3,26 +3,41 @@ export const INBOX_STATUS = [ key: "pending", label: "Pending", value: -2, + textColor: "text-yellow-500", + bgColor: "bg-yellow-500/10", + borderColor: "border-yellow-500", }, { key: "declined", label: "Declined", value: -1, + textColor: "text-red-500", + bgColor: "bg-red-500/10", + borderColor: "border-red-500", }, { key: "snoozed", label: "Snoozed", value: 0, + textColor: "text-brand-secondary", + bgColor: "bg-gray-500/10", + borderColor: "border-gray-500", }, { key: "accepted", label: "Accepted", value: 1, + textColor: "text-green-500", + bgColor: "bg-green-500/10", + borderColor: "border-green-500", }, { key: "duplicate", label: "Duplicate", value: 2, + textColor: "text-brand-secondary", + bgColor: "bg-gray-500/10", + borderColor: "border-gray-500", }, ]; diff --git a/apps/app/pages/[workspaceSlug]/index.tsx b/apps/app/pages/[workspaceSlug]/index.tsx index 233fd9b8a..3c75890f6 100644 --- a/apps/app/pages/[workspaceSlug]/index.tsx +++ b/apps/app/pages/[workspaceSlug]/index.tsx @@ -49,7 +49,7 @@ const WorkspacePage: NextPage = () => { )}
-
+

Plane is open source, support us by starring us on GitHub.

diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx index 140e704ed..dc301e0d8 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx @@ -1,29 +1,13 @@ -import { useState, useEffect, useCallback } from "react"; +import { useRouter } from "next/router"; -import Router, { useRouter } from "next/router"; - -import useSWR, { mutate } from "swr"; - -// services -import inboxServices from "services/inbox.service"; -import projectService from "services/project.service"; // hooks -import useInboxView from "hooks/use-inbox-view"; -import useUserAuth from "hooks/use-user-auth"; -import useToast from "hooks/use-toast"; +import useProjectDetails from "hooks/use-project-details"; // layouts import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; // contexts import { InboxViewContextProvider } from "contexts/inbox-view-context"; // components -import { - InboxActionHeader, - InboxMainContent, - SelectDuplicateInboxIssueModal, - DeclineIssueModal, - DeleteIssueModal, - IssuesListSidebar, -} from "components/inbox"; +import { InboxActionHeader, InboxMainContent, IssuesListSidebar } from "components/inbox"; // helper import { truncateText } from "helpers/string.helper"; // ui @@ -31,123 +15,14 @@ import { PrimaryButton } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // icons import { PlusIcon } from "@heroicons/react/24/outline"; -import { InboxIcon } from "components/icons"; // types -import { IInboxIssueDetail, TInboxStatus } from "types"; import type { NextPage } from "next"; -// fetch-keys -import { INBOX_ISSUE_DETAILS, PROJECT_DETAILS } from "constants/fetch-keys"; const ProjectInbox: NextPage = () => { - const [selectDuplicateIssue, setSelectDuplicateIssue] = useState(false); - const [declineIssueModal, setDeclineIssueModal] = useState(false); - const [deleteIssueModal, setDeleteIssueModal] = useState(false); - const router = useRouter(); - const { workspaceSlug, projectId, inboxId, inboxIssueId } = router.query; + const { workspaceSlug } = router.query; - const { user } = useUserAuth(); - const { issues: inboxIssues, mutate: mutateInboxIssues } = useInboxView(); - const { setToastAlert } = useToast(); - - const { data: projectDetails } = useSWR( - workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, - workspaceSlug && projectId - ? () => projectService.getProject(workspaceSlug as string, projectId as string) - : null - ); - - const onKeyDown = useCallback( - (e: KeyboardEvent) => { - if (!inboxIssues || !inboxIssueId) return; - - const currentIssueIndex = inboxIssues.findIndex((issue) => issue.bridge_id === inboxIssueId); - - switch (e.key) { - case "ArrowUp": - Router.push({ - pathname: `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}`, - query: { - inboxIssueId: - currentIssueIndex === 0 - ? inboxIssues[inboxIssues.length - 1].bridge_id - : inboxIssues[currentIssueIndex - 1].bridge_id, - }, - }); - break; - case "ArrowDown": - Router.push({ - pathname: `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}`, - query: { - inboxIssueId: - currentIssueIndex === inboxIssues.length - 1 - ? inboxIssues[0].bridge_id - : inboxIssues[currentIssueIndex + 1].bridge_id, - }, - }); - - break; - default: - break; - } - }, - [workspaceSlug, projectId, inboxIssueId, inboxId, inboxIssues] - ); - - useEffect(() => { - document.addEventListener("keydown", onKeyDown); - - return () => { - document.removeEventListener("keydown", onKeyDown); - }; - }, [onKeyDown]); - - const markInboxStatus = async (data: TInboxStatus) => { - if (!workspaceSlug || !projectId || !inboxId || !inboxIssueId) return; - - mutate( - INBOX_ISSUE_DETAILS(inboxId as string, inboxIssueId as string), - (prevData) => { - if (!prevData) return prevData; - - return { - ...prevData, - issue_inbox: [{ ...prevData.issue_inbox[0], ...data }], - }; - }, - false - ); - mutateInboxIssues( - (prevData) => - (prevData ?? []).map((i) => - i.bridge_id === inboxIssueId - ? { ...i, issue_inbox: [{ ...i.issue_inbox[0], ...data }] } - : i - ), - false - ); - - await inboxServices - .markInboxStatus( - workspaceSlug.toString(), - projectId.toString(), - inboxId.toString(), - inboxIssues?.find((inboxIssue) => inboxIssue.bridge_id === inboxIssueId)?.bridge_id!, - data, - user - ) - .catch(() => - setToastAlert({ - type: "error", - title: "Error!", - message: "Something went wrong while updating inbox status. Please try again.", - }) - ) - .finally(() => { - mutate(INBOX_ISSUE_DETAILS(inboxId as string, inboxIssueId as string)); - mutateInboxIssues(); - }); - }; + const { projectDetails } = useProjectDetails(); return ( @@ -175,88 +50,15 @@ const ProjectInbox: NextPage = () => {
} > - <> - setSelectDuplicateIssue(false)} - value={ - inboxIssues?.find((inboxIssue) => inboxIssue.bridge_id === inboxIssueId) - ?.issue_inbox[0].duplicate_to - } - onSubmit={(dupIssueId: string) => { - markInboxStatus({ - status: 2, - duplicate_to: dupIssueId, - }).finally(() => setSelectDuplicateIssue(false)); - }} - /> - setDeclineIssueModal(false)} - data={inboxIssues?.find((i) => i.bridge_id === inboxIssueId)} - onSubmit={async () => { - await markInboxStatus({ - status: -1, - }).finally(() => setDeclineIssueModal(false)); - }} - /> - setDeleteIssueModal(false)} - data={inboxIssues?.find((i) => i.bridge_id === inboxIssueId)} - /> -
- issue.bridge_id === inboxIssueId)} - currentIssueIndex={ - inboxIssues?.findIndex((issue) => issue.bridge_id === inboxIssueId) ?? 0 - } - issueCount={inboxIssues?.length ?? 0} - onAccept={() => - markInboxStatus({ - status: 1, - }) - } - onDecline={() => setDeclineIssueModal(true)} - onMarkAsDuplicate={() => setSelectDuplicateIssue(true)} - onSnooze={(date) => { - markInboxStatus({ - status: 0, - snoozed_till: new Date(date), - }); - }} - onDelete={() => setDeleteIssueModal(true)} - /> -
- -
- {inboxIssueId ? ( - - ) : ( -
-
-
- - {inboxIssues && inboxIssues.length > 0 ? ( - - {inboxIssues?.length} issues found. Select an issue from the sidebar to - view its details. - - ) : ( - - No issues found. Use{" "} -
C
{" "} - shortcut to create a new issue -
- )} -
-
-
- )} -
+
+ +
+ +
+
- +
); diff --git a/apps/app/services/inbox.service.ts b/apps/app/services/inbox.service.ts index 9ee20923c..61949c877 100644 --- a/apps/app/services/inbox.service.ts +++ b/apps/app/services/inbox.service.ts @@ -162,7 +162,7 @@ class InboxServices extends APIService { inboxId: string, data: any, user: ICurrentUserResponse | undefined - ): Promise { + ): Promise { return this.post( `/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/`, data