diff --git a/web/components/inbox/main-content.tsx b/web/components/inbox/main-content.tsx index 02ead34cb..7356e9e7b 100644 --- a/web/components/inbox/main-content.tsx +++ b/web/components/inbox/main-content.tsx @@ -3,15 +3,17 @@ import Router, { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; import { useForm } from "react-hook-form"; -import { AlertTriangle, CheckCircle2, Clock, Copy, ExternalLink, Inbox, XCircle } from "lucide-react"; +import { AlertTriangle, CheckCircle2, Clock, Copy, ExternalLink, Inbox, RefreshCw, XCircle } from "lucide-react"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components import { IssueDescriptionForm, IssueDetailsSidebar, IssueReaction } from "components/issues"; import { InboxIssueActivity } from "components/inbox"; +// hooks +import useReloadConfirmations from "hooks/use-reload-confirmation"; // ui -import { Loader } from "@plane/ui"; +import { Loader, StateGroupIcon } from "@plane/ui"; // helpers import { renderShortDateWithYearFormat } from "helpers/date-time.helper"; // types @@ -30,11 +32,18 @@ export const InboxMainContent: React.FC = observer(() => { const router = useRouter(); const { workspaceSlug, projectId, inboxId, inboxIssueId } = router.query; - const { inboxIssues: inboxIssuesStore, inboxIssueDetails: inboxIssueDetailsStore, user: userStore } = useMobxStore(); + const { + inboxIssues: inboxIssuesStore, + inboxIssueDetails: inboxIssueDetailsStore, + user: userStore, + projectState: { states }, + } = useMobxStore(); const user = userStore.currentUser; const userRole = userStore.currentProjectRole; + const { setShowAlert } = useReloadConfirmations(); + const { reset, control, watch } = useForm({ defaultValues, }); @@ -54,6 +63,9 @@ export const InboxMainContent: React.FC = observer(() => { const issuesList = inboxId ? inboxIssuesStore.inboxIssues[inboxId.toString()] : undefined; const issueDetails = inboxIssueId ? inboxIssueDetailsStore.issueDetails[inboxIssueId.toString()] : undefined; + const currentIssueState = projectId + ? states[projectId.toString()]?.find((s) => s.id === issueDetails?.state) + : undefined; const submitChanges = useCallback( async (formData: Partial) => { @@ -124,6 +136,17 @@ export const InboxMainContent: React.FC = observer(() => { }); }, [issueDetails, reset, inboxIssueId]); + useEffect(() => { + if (inboxIssueDetailsStore.isSubmitting === "submitted") { + setShowAlert(false); + setTimeout(async () => { + inboxIssueDetailsStore.setIsSubmitting("saved"); + }, 2000); + } else if (inboxIssueDetailsStore.isSubmitting === "submitting") { + setShowAlert(true); + } + }, [inboxIssueDetailsStore.isSubmitting, setShowAlert]); + const issueStatus = issueDetails?.issue_inbox[0].status; if (!inboxIssueId) @@ -214,8 +237,36 @@ export const InboxMainContent: React.FC = observer(() => { ) : null} +
+ {!currentIssueState ? ( + + ) : ( + + )} +

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

+
+ {inboxIssueDetailsStore.isSubmitting !== "submitted" && + inboxIssueDetailsStore.isSubmitting !== "saved" && ( + + )} + + {inboxIssueDetailsStore.isSubmitting === "submitting" ? "Saving..." : "Saved"} + +
+
= (props) => { - const { issue, handleFormSubmit, workspaceSlug, isAllowed,setShowAlert } = props; + const { issue, handleFormSubmit, workspaceSlug, isAllowed, setShowAlert } = props; // states const [characterLimit, setCharacterLimit] = useState(false); + // router + const router = useRouter(); + const { inboxId } = router.query; + // mobx store const { - projectIssues: { setIsSubmitting }, + projectIssues: { setIsSubmitting: PIsetIsSubmitting }, + inboxIssueDetails: { setIsSubmitting: IIsetIsSubmitting }, } = useMobxStore(); const editorSuggestion = useEditorSuggestions(); @@ -89,6 +95,8 @@ export const IssueDescriptionForm: FC = (props) => { handleSubmit(handleDescriptionFormSubmit)(); }, 1500); + const setIsSubmitting = inboxId ? IIsetIsSubmitting : PIsetIsSubmitting; + return (
diff --git a/web/components/issues/sidebar.tsx b/web/components/issues/sidebar.tsx index 22818fda6..a1a78c7dd 100644 --- a/web/components/issues/sidebar.tsx +++ b/web/components/issues/sidebar.tsx @@ -87,7 +87,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { const userRole = userStore.currentProjectRole; const router = useRouter(); - const { workspaceSlug, projectId, issueId } = router.query; + const { workspaceSlug, projectId, issueId, inboxIssueId } = router.query; const { isEstimateActive } = useEstimateOption(); @@ -273,13 +273,15 @@ export const IssueDetailsSidebar: React.FC = observer((props) => {
- {currentIssueState && ( + {currentIssueState ? ( - )} + ) : inboxIssueId ? ( + + ) : null}

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

diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index 1d441da30..b648953b9 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -50,6 +50,7 @@ const IssueDetailsPage: NextPageWithLayout = observer(() => { projectIssues: { isSubmitting, setIsSubmitting }, } = useMobxStore(); + // hooks const { setShowAlert } = useReloadConfirmations(); const { diff --git a/web/store/inbox/inbox_issue_detail.store.ts b/web/store/inbox/inbox_issue_detail.store.ts index 25653ab09..7e74f624f 100644 --- a/web/store/inbox/inbox_issue_detail.store.ts +++ b/web/store/inbox/inbox_issue_detail.store.ts @@ -5,6 +5,7 @@ import { RootStore } from "../root"; import { InboxService } from "services/inbox.service"; // types import { IInboxIssue, IIssue, TInboxStatus } from "types"; +import { TIssueUpdateStatus } from "store/issues/types"; // constants import { INBOX_ISSUE_SOURCE } from "constants/inbox"; @@ -12,6 +13,7 @@ export interface IInboxIssueDetailsStore { // states loader: boolean; error: any | null; + isSubmitting: TIssueUpdateStatus; // inbox issue update status; // observables issueDetails: { @@ -46,12 +48,14 @@ export interface IInboxIssueDetailsStore { data: TInboxStatus ) => Promise; deleteIssue: (workspaceSlug: string, projectId: string, inboxId: string, issueId: string) => Promise; + setIsSubmitting: (status: TIssueUpdateStatus) => void; } export class InboxIssueDetailsStore implements IInboxIssueDetailsStore { // states loader: boolean = false; error: any | null = null; + isSubmitting: TIssueUpdateStatus = "saved"; // observables issueDetails: { [issueId: string]: IInboxIssue } = {}; @@ -67,6 +71,7 @@ export class InboxIssueDetailsStore implements IInboxIssueDetailsStore { // states loader: observable.ref, error: observable.ref, + isSubmitting: observable.ref, // observables issueDetails: observable.ref, @@ -76,12 +81,17 @@ export class InboxIssueDetailsStore implements IInboxIssueDetailsStore { createIssue: action, updateIssueStatus: action, deleteIssue: action, + setIsSubmitting: action, }); this.rootStore = _rootStore; this.inboxService = new InboxService(); } + setIsSubmitting = (status: TIssueUpdateStatus) => { + this.isSubmitting = status; + }; + fetchIssueDetails = async (workspaceSlug: string, projectId: string, inboxId: string, issueId: string) => { try { runInAction(() => { @@ -155,6 +165,7 @@ export class InboxIssueDetailsStore implements IInboxIssueDetailsStore { try { runInAction(() => { + this.isSubmitting = "submitting"; this.issueDetails = { ...this.issueDetails, [issueId]: updatedIssue, @@ -170,6 +181,7 @@ export class InboxIssueDetailsStore implements IInboxIssueDetailsStore { }); await this.inboxService.patchInboxIssue(workspaceSlug, projectId, inboxId, issueId, { issue: data }); + this.isSubmitting = "submitted"; } catch (error) { runInAction(() => { this.error = error; diff --git a/web/store/issues/project-issues/project/issue.store.ts b/web/store/issues/project-issues/project/issue.store.ts index b1c0ee8b5..a87e3c1d0 100644 --- a/web/store/issues/project-issues/project/issue.store.ts +++ b/web/store/issues/project-issues/project/issue.store.ts @@ -6,13 +6,21 @@ import { IssueService } from "services/issue/issue.service"; // types import { TIssueGroupByOptions } from "types"; import { IIssue } from "types/issues"; -import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues, ViewFlags, TIssueUpdateStatus } from "../../types"; +import { + IIssueResponse, + TLoader, + IGroupedIssues, + ISubGroupedIssues, + TUnGroupedIssues, + ViewFlags, + TIssueUpdateStatus, +} from "../../types"; import { RootStore } from "store/root"; export interface IProjectIssuesStore { // observable loader: TLoader; - isSubmitting: TIssueUpdateStatus; + isSubmitting: TIssueUpdateStatus; // update issue status issues: { [project_id: string]: IIssueResponse } | undefined; // computed getIssues: IIssueResponse | undefined;