diff --git a/web/components/inbox/main-content.tsx b/web/components/inbox/main-content.tsx index 5d7863196..193f59263 100644 --- a/web/components/inbox/main-content.tsx +++ b/web/components/inbox/main-content.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect } from "react"; +import { useCallback, useEffect, useState } from "react"; import Router, { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; @@ -8,10 +8,10 @@ import { AlertTriangle, CheckCircle2, Clock, Copy, ExternalLink, Inbox, XCircle // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { IssueDescriptionForm, IssueDetailsSidebar, IssueReaction } from "components/issues"; +import { IssueDescriptionForm, IssueDetailsSidebar, IssueReaction, IssueUpdateStatus } from "components/issues"; import { InboxIssueActivity } from "components/inbox"; // ui -import { Loader } from "@plane/ui"; +import { Loader, StateGroupIcon } from "@plane/ui"; // helpers import { renderShortDateWithYearFormat } from "helpers/date-time.helper"; // types @@ -31,7 +31,15 @@ export const InboxMainContent: React.FC = observer(() => { const router = useRouter(); const { workspaceSlug, projectId, inboxId, inboxIssueId } = router.query; - const { inboxIssues: inboxIssuesStore, inboxIssueDetails: inboxIssueDetailsStore, user: userStore } = useMobxStore(); + // states + const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); + + const { + inboxIssues: inboxIssuesStore, + inboxIssueDetails: inboxIssueDetailsStore, + user: userStore, + projectState: { states }, + } = useMobxStore(); const user = userStore.currentUser; const userRole = userStore.currentProjectRole; @@ -55,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) => { @@ -217,8 +228,20 @@ export const InboxMainContent: React.FC = observer(() => { ) : null} +
+ {currentIssueState && ( + + )} + +
setIsSubmitting(value)} + isSubmitting={isSubmitting} workspaceSlug={workspaceSlug as string} issue={{ name: issueDetails.name, diff --git a/web/components/issues/description-form.tsx b/web/components/issues/description-form.tsx index 43600533b..ef26e22d8 100644 --- a/web/components/issues/description-form.tsx +++ b/web/components/issues/description-form.tsx @@ -26,14 +26,15 @@ export interface IssueDetailsProps { workspaceSlug: string; handleFormSubmit: (value: IssueDescriptionFormValues) => Promise; isAllowed: boolean; + isSubmitting: "submitting" | "submitted" | "saved"; + setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void; } const fileService = new FileService(); export const IssueDescriptionForm: FC = (props) => { - const { issue, handleFormSubmit, workspaceSlug, isAllowed } = props; + const { issue, handleFormSubmit, workspaceSlug, isAllowed, isSubmitting, setIsSubmitting } = props; // states - const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); const [characterLimit, setCharacterLimit] = useState(false); const { setShowAlert } = useReloadConfirmations(); @@ -166,13 +167,6 @@ export const IssueDescriptionForm: FC = (props) => { /> )} /> -
- {isSubmitting === "submitting" ? "Saving..." : "Saved"} -
); diff --git a/web/components/issues/index.ts b/web/components/issues/index.ts index 0cf3c8bda..f8f0ba003 100644 --- a/web/components/issues/index.ts +++ b/web/components/issues/index.ts @@ -15,6 +15,7 @@ export * from "./sidebar"; export * from "./label"; export * from "./issue-reaction"; export * from "./confirm-issue-discard"; +export * from "./issue-update-status"; // draft issue export * from "./draft-issue-form"; diff --git a/web/components/issues/issue-peek-overview/issue-detail.tsx b/web/components/issues/issue-peek-overview/issue-detail.tsx index fafdccb71..3e90c8b8d 100644 --- a/web/components/issues/issue-peek-overview/issue-detail.tsx +++ b/web/components/issues/issue-peek-overview/issue-detail.tsx @@ -26,16 +26,27 @@ interface IPeekOverviewIssueDetails { issueUpdate: (issue: Partial) => void; issueReactionCreate: (reaction: string) => void; issueReactionRemove: (reaction: string) => void; + isSubmitting: "submitting" | "submitted" | "saved"; + setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void; } export const PeekOverviewIssueDetails: FC = (props) => { - const { workspaceSlug, issue, issueReactions, user, issueUpdate, issueReactionCreate, issueReactionRemove } = props; + const { + workspaceSlug, + issue, + issueReactions, + user, + issueUpdate, + issueReactionCreate, + issueReactionRemove, + isSubmitting, + setIsSubmitting, + } = props; // store const { user: userStore } = useMobxStore(); const { currentProjectRole } = userStore; const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; // states - const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); const [characterLimit, setCharacterLimit] = useState(false); // hooks const { setShowAlert } = useReloadConfirmations(); @@ -172,13 +183,6 @@ export const PeekOverviewIssueDetails: FC = (props) = /> )} /> -
- {isSubmitting === "submitting" ? "Saving..." : "Saved"} -
= observer((props) => { const [peekMode, setPeekMode] = useState("side-peek"); const [deleteIssueModal, setDeleteIssueModal] = useState(false); + const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); const updateRoutePeekId = () => { if (issueId != peekIssueId) { @@ -216,33 +216,35 @@ export const IssueView: FC = observer((props) => { )} - -
- {issue?.created_by !== user?.id && - !issue?.assignees.includes(user?.id ?? "") && - !router.pathname.includes("[archivedIssueId]") && ( - - )} - - {!disableUserActions && ( - + )} + - )} + {!disableUserActions && ( + + )} +
@@ -261,6 +263,8 @@ export const IssueView: FC = observer((props) => {
)} setIsSubmitting(value)} + isSubmitting={isSubmitting} workspaceSlug={workspaceSlug} issue={issue} issueUpdate={issueUpdate} @@ -295,6 +299,8 @@ export const IssueView: FC = observer((props) => {
setIsSubmitting(value)} + isSubmitting={isSubmitting} workspaceSlug={workspaceSlug} issue={issue} issueReactions={issueReactions} diff --git a/web/components/issues/issue-update-status.tsx b/web/components/issues/issue-update-status.tsx new file mode 100644 index 000000000..e6852936e --- /dev/null +++ b/web/components/issues/issue-update-status.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import { RefreshCw } from "lucide-react"; +// types +import { IIssue } from "types"; + +type Props = { + isSubmitting: "submitting" | "submitted" | "saved"; + issueDetail?: IIssue; +}; + +export const IssueUpdateStatus: React.FC = (props) => { + const { isSubmitting, issueDetail } = props; + return ( + <> + {issueDetail && ( +

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

+ )} +
+ {isSubmitting !== "submitted" && isSubmitting !== "saved" && ( + + )} + {isSubmitting === "submitting" ? "Saving..." : "Saved"} +
+ + ); +}; diff --git a/web/components/issues/main-content.tsx b/web/components/issues/main-content.tsx index 7ea0e7cfe..bd18cb73b 100644 --- a/web/components/issues/main-content.tsx +++ b/web/components/issues/main-content.tsx @@ -2,6 +2,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import useSWR, { mutate } from "swr"; +import { MinusCircle } from "lucide-react"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // services @@ -16,12 +17,12 @@ import { IssueAttachments, IssueDescriptionForm, IssueReaction, + IssueUpdateStatus, } from "components/issues"; +import { useState } from "react"; import { SubIssuesRoot } from "./sub-issues"; // ui -import { CustomMenu, LayersIcon } from "@plane/ui"; -// icons -import { MinusCircle } from "lucide-react"; +import { CustomMenu, LayersIcon, StateGroupIcon } from "@plane/ui"; // types import { IIssue, IIssueComment } from "types"; // fetch-keys @@ -41,15 +42,25 @@ const issueCommentService = new IssueCommentService(); export const IssueMainContent: React.FC = observer((props) => { const { issueDetails, submitChanges, uneditable = false } = props; + // states + const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); + const router = useRouter(); const { workspaceSlug, projectId, issueId } = router.query; const { setToastAlert } = useToast(); - const { user: userStore, project: projectStore } = useMobxStore(); + const { + user: userStore, + project: projectStore, + projectState: { states }, + } = useMobxStore(); const user = userStore.currentUser ?? undefined; const userRole = userStore.currentProjectRole; const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : undefined; + const currentIssueState = projectId + ? states[projectId.toString()]?.find((s) => s.id === issueDetails.state) + : undefined; const { data: siblingIssues } = useSWR( workspaceSlug && projectId && issueDetails?.parent ? SUB_ISSUES(issueDetails.parent) : null, @@ -165,7 +176,19 @@ export const IssueMainContent: React.FC = observer((props) => {
) : null} +
+ {currentIssueState && ( + + )} + +
setIsSubmitting(value)} + isSubmitting={isSubmitting} workspaceSlug={workspaceSlug as string} issue={issueDetails} handleFormSubmit={submitChanges} diff --git a/web/components/issues/sidebar.tsx b/web/components/issues/sidebar.tsx index d6681cb4a..b0315304d 100644 --- a/web/components/issues/sidebar.tsx +++ b/web/components/issues/sidebar.tsx @@ -33,7 +33,7 @@ import { import { CustomDatePicker } from "components/ui"; // icons import { Bell, CalendarDays, LinkIcon, Plus, Signal, Tag, Trash2, Triangle, User2 } from "lucide-react"; -import { Button, ContrastIcon, DiceIcon, DoubleCircleIcon, UserGroupIcon } from "@plane/ui"; +import { Button, ContrastIcon, DiceIcon, DoubleCircleIcon, StateGroupIcon, UserGroupIcon } from "@plane/ui"; // helpers import { copyTextToClipboard } from "helpers/string.helper"; // types @@ -80,12 +80,15 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { const [linkModal, setLinkModal] = useState(false); const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState(null); - const { user: userStore } = useMobxStore(); + const { + user: userStore, + projectState: { states }, + } = useMobxStore(); const user = userStore.currentUser; const userRole = userStore.currentProjectRole; const router = useRouter(); - const { workspaceSlug, projectId, issueId } = router.query; + const { workspaceSlug, projectId, issueId, inboxIssueId } = router.query; const { isEstimateActive } = useEstimateOption(); @@ -248,6 +251,10 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { const isAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER; + const currentIssueState = projectId + ? states[projectId.toString()]?.find((s) => s.id === issueDetail?.state) + : undefined; + return ( <> = observer((props) => { )}
-

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

+
+ {currentIssueState ? ( + + ) : inboxIssueId ? ( + + ) : null} +

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

+
{issueDetail?.created_by !== user?.id && !issueDetail?.assignees.includes(user?.id ?? "") &&