chore: issue update status in peek view

This commit is contained in:
LAKHAN BAHETI 2023-12-04 17:19:39 +05:30
parent fa990ed444
commit 9d7b0c6daa
4 changed files with 67 additions and 29 deletions

View File

@ -25,19 +25,32 @@ interface IPeekOverviewIssueDetails {
issueUpdate: (issue: Partial<IIssue>) => void; issueUpdate: (issue: Partial<IIssue>) => void;
issueReactionCreate: (reaction: string) => void; issueReactionCreate: (reaction: string) => void;
issueReactionRemove: (reaction: string) => void; issueReactionRemove: (reaction: string) => void;
setShowAlert: (value: boolean) => void;
} }
export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) => { export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) => {
const { workspaceSlug, issue, issueReactions, user, issueUpdate, issueReactionCreate, issueReactionRemove } = props; const {
workspaceSlug,
issue,
issueReactions,
user,
issueUpdate,
issueReactionCreate,
issueReactionRemove,
setShowAlert,
} = props;
// store // store
const { user: userStore } = useMobxStore(); const {
user: userStore,
projectIssues: { isSubmitting, setIsSubmitting },
} = useMobxStore();
const { currentProjectRole } = userStore; const { currentProjectRole } = userStore;
const isAllowed = [15, 20].includes(currentProjectRole || 0); const isAllowed = [15, 20].includes(currentProjectRole || 0);
// states // states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
const [characterLimit, setCharacterLimit] = useState(false); const [characterLimit, setCharacterLimit] = useState(false);
// hooks // hooks
const { setShowAlert } = useReloadConfirmations();
const editorSuggestions = useEditorSuggestions(); const editorSuggestions = useEditorSuggestions();
const { const {
@ -75,20 +88,9 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
}, [issueTitleCurrentValue, localTitleValue]); }, [issueTitleCurrentValue, localTitleValue]);
const debouncedFormSave = debounce(async () => { const debouncedFormSave = debounce(async () => {
handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted")); handleSubmit(handleDescriptionFormSubmit)();
}, 1500); }, 1500);
useEffect(() => {
if (isSubmitting === "submitted") {
setShowAlert(false);
setTimeout(async () => {
setIsSubmitting("saved");
}, 2000);
} else if (isSubmitting === "submitting") {
setShowAlert(true);
}
}, [isSubmitting, setShowAlert]);
// reset form values // reset form values
useEffect(() => { useEffect(() => {
if (!issue) return; if (!issue) return;
@ -171,13 +173,6 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
/> />
)} )}
/> />
<div
className={`absolute right-5 bottom-5 text-xs text-custom-text-200 border border-custom-border-400 rounded-xl w-[6.5rem] py-1 z-10 flex items-center justify-center ${
isSubmitting === "saved" ? "fadeOut" : "fadeIn"
}`}
>
{isSubmitting === "submitting" ? "Saving..." : "Saved"}
</div>
</div> </div>
<IssueReaction <IssueReaction
issueReactions={issueReactions} issueReactions={issueReactions}

View File

@ -1,8 +1,8 @@
import { FC, ReactNode, useState } from "react"; import { FC, ReactNode, useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import useSWR from "swr"; import useSWR from "swr";
import { MoveRight, MoveDiagonal, Bell, Link2, Trash2 } from "lucide-react"; import { MoveRight, MoveDiagonal, Bell, Link2, Trash2, RefreshCw } from "lucide-react";
// components // components
import { PeekOverviewIssueDetails } from "./issue-detail"; import { PeekOverviewIssueDetails } from "./issue-detail";
import { PeekOverviewProperties } from "./properties"; import { PeekOverviewProperties } from "./properties";
@ -14,6 +14,7 @@ import { DeleteArchivedIssueModal } from "../delete-archived-issue-modal";
import { IIssue } from "types"; import { IIssue } from "types";
// hooks // hooks
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import useReloadConfirmations from "hooks/use-reload-confirmation";
interface IIssueView { interface IIssueView {
workspaceSlug: string; workspaceSlug: string;
@ -89,11 +90,17 @@ export const IssueView: FC<IIssueView> = observer((props) => {
const router = useRouter(); const router = useRouter();
const { peekIssueId } = router.query as { peekIssueId: string }; const { peekIssueId } = router.query as { peekIssueId: string };
const { user: userStore, issueDetail: issueDetailStore } = useMobxStore(); const {
user: userStore,
issueDetail: issueDetailStore,
projectIssues: { isSubmitting, setIsSubmitting },
} = useMobxStore();
const [peekMode, setPeekMode] = useState<TPeekModes>("side-peek"); const [peekMode, setPeekMode] = useState<TPeekModes>("side-peek");
const [deleteIssueModal, setDeleteIssueModal] = useState(false); const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const { setShowAlert } = useReloadConfirmations();
const updateRoutePeekId = () => { const updateRoutePeekId = () => {
if (issueId != peekIssueId) { if (issueId != peekIssueId) {
issueDetailStore.setPeekId(issueId); issueDetailStore.setPeekId(issueId);
@ -136,6 +143,17 @@ export const IssueView: FC<IIssueView> = observer((props) => {
const currentMode = peekOptions.find((m) => m.key === peekMode); const currentMode = peekOptions.find((m) => m.key === peekMode);
useEffect(() => {
if (isSubmitting === "submitted") {
setShowAlert(false);
setTimeout(async () => {
setIsSubmitting("saved");
}, 2000);
} else if (isSubmitting === "submitting") {
setShowAlert(true);
}
}, [isSubmitting, setShowAlert]);
return ( return (
<> <>
{issue && !isArchived && ( {issue && !isArchived && (
@ -235,12 +253,20 @@ export const IssueView: FC<IIssueView> = observer((props) => {
{issueSubscription && issueSubscription.subscribed ? "Unsubscribe" : "Subscribe"} {issueSubscription && issueSubscription.subscribed ? "Unsubscribe" : "Subscribe"}
</Button> </Button>
)} )}
<div className={`flex items-center gap-x-2 ${isSubmitting === "saved" ? "fadeOut" : "fadeIn"}`}>
{isSubmitting !== "submitted" && isSubmitting !== "saved" && (
<RefreshCw className="h-4 w-4 stroke-custom-text-300" />
)}
<span className="text-sm text-custom-text-300">
{isSubmitting === "submitting" ? "Saving..." : "Saved"}
</span>
</div>
<button onClick={handleCopyText}> <button onClick={handleCopyText}>
<Link2 className="h-4 w-4 text-custom-text-400 hover:text-custom-text-200 -rotate-45" /> <Link2 className="h-4 w-4 text-custom-text-300 hover:text-custom-text-200 -rotate-45" />
</button> </button>
{!disableUserActions && ( {!disableUserActions && (
<button onClick={() => setDeleteIssueModal(true)}> <button onClick={() => setDeleteIssueModal(true)}>
<Trash2 className="h-4 w-4 text-custom-text-400 hover:text-custom-text-200" /> <Trash2 className="h-4 w-4 text-custom-text-300 hover:text-custom-text-200" />
</button> </button>
)} )}
</div> </div>
@ -261,6 +287,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
<div className="absolute top-0 left-0 h-full min-h-full w-full z-[9] flex items-center justify-center bg-custom-background-100 opacity-60" /> <div className="absolute top-0 left-0 h-full min-h-full w-full z-[9] flex items-center justify-center bg-custom-background-100 opacity-60" />
)} )}
<PeekOverviewIssueDetails <PeekOverviewIssueDetails
setShowAlert={setShowAlert}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
issue={issue} issue={issue}
issueUpdate={issueUpdate} issueUpdate={issueUpdate}
@ -295,6 +322,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
<div className="relative w-full h-full space-y-6 p-4 py-5 overflow-auto"> <div className="relative w-full h-full space-y-6 p-4 py-5 overflow-auto">
<div className={isArchived ? "pointer-events-none" : ""}> <div className={isArchived ? "pointer-events-none" : ""}>
<PeekOverviewIssueDetails <PeekOverviewIssueDetails
setShowAlert={setShowAlert}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
issue={issue} issue={issue}
issueReactions={issueReactions} issueReactions={issueReactions}

View File

@ -6,12 +6,13 @@ import { IssueService } from "services/issue/issue.service";
// types // types
import { TIssueGroupByOptions } from "types"; import { TIssueGroupByOptions } from "types";
import { IIssue } from "types/issues"; import { IIssue } from "types/issues";
import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues, ViewFlags } from "../../types"; import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues, ViewFlags, TIssueUpdateStatus } from "../../types";
import { RootStore } from "store/root"; import { RootStore } from "store/root";
export interface IProjectIssuesStore { export interface IProjectIssuesStore {
// observable // observable
loader: TLoader; loader: TLoader;
isSubmitting: TIssueUpdateStatus;
issues: { [project_id: string]: IIssueResponse } | undefined; issues: { [project_id: string]: IIssueResponse } | undefined;
// computed // computed
getIssues: IIssueResponse | undefined; getIssues: IIssueResponse | undefined;
@ -22,12 +23,14 @@ export interface IProjectIssuesStore {
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<IIssue>) => Promise<IIssue>; updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<IIssue>) => Promise<IIssue>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<IIssue>; removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<IIssue>;
quickAddIssue: (workspaceSlug: string, projectId: string, data: IIssue) => Promise<IIssue>; quickAddIssue: (workspaceSlug: string, projectId: string, data: IIssue) => Promise<IIssue>;
setIsSubmitting: (value: TIssueUpdateStatus) => void;
viewFlags: ViewFlags; viewFlags: ViewFlags;
} }
export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssuesStore { export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssuesStore {
loader: TLoader = "init-loader"; loader: TLoader = "init-loader";
isSubmitting: TIssueUpdateStatus = "saved";
issues: { [project_id: string]: IIssueResponse } | undefined = undefined; issues: { [project_id: string]: IIssueResponse } | undefined = undefined;
// root store // root store
rootStore; rootStore;
@ -48,6 +51,7 @@ export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssues
// observable // observable
loader: observable.ref, loader: observable.ref,
issues: observable.ref, issues: observable.ref,
isSubmitting: observable.ref,
// computed // computed
getIssues: computed, getIssues: computed,
getIssuesIds: computed, getIssuesIds: computed,
@ -57,6 +61,7 @@ export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssues
updateIssue: action, updateIssue: action,
removeIssue: action, removeIssue: action,
quickAddIssue: action, quickAddIssue: action,
setIsSubmitting: action,
}); });
this.rootStore = _rootStore; this.rootStore = _rootStore;
@ -72,6 +77,10 @@ export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssues
}); });
} }
setIsSubmitting = (value: "submitting" | "submitted" | "saved") => {
this.isSubmitting = value;
};
get getIssues() { get getIssues() {
const projectId = this.rootStore?.project.projectId; const projectId = this.rootStore?.project.projectId;
if (!projectId || !this.issues || !this.issues[projectId]) return undefined; if (!projectId || !this.issues || !this.issues[projectId]) return undefined;
@ -157,11 +166,14 @@ export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssues
_issues[projectId][issueId] = { ..._issues[projectId][issueId], ...data }; _issues[projectId][issueId] = { ..._issues[projectId][issueId], ...data };
runInAction(() => { runInAction(() => {
this.isSubmitting = "submitting";
this.issues = _issues; this.issues = _issues;
}); });
const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data);
this.isSubmitting = "submitted";
return response; return response;
} catch (error) { } catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation"); this.fetchIssues(workspaceSlug, projectId, "mutation");

View File

@ -31,3 +31,6 @@ export interface ViewFlags {
enableIssueCreation: boolean; enableIssueCreation: boolean;
enableInlineEditing: boolean; enableInlineEditing: boolean;
} }
// issue update status
export type TIssueUpdateStatus = "saved" | "submitting" | "submitted";