Compare commits

...

10 Commits

Author SHA1 Message Date
LAKHAN BAHETI
b96917a9f4 formatting 2023-12-04 22:16:41 +05:30
LAKHAN BAHETI
b0a383c457 refactor: update complete in description form 2023-12-04 22:10:42 +05:30
LAKHAN BAHETI
fb5983e23e Merge branch 'develop' of https://github.com/makeplane/plane into fix/description_field 2023-12-04 21:58:36 +05:30
LAKHAN BAHETI
6d1a99846d refactor: useEffect placement 2023-12-04 21:56:39 +05:30
LAKHAN BAHETI
aecb9d8619 chore: archived issue update status 2023-12-04 21:36:24 +05:30
LAKHAN BAHETI
42f2d346bf chore: inbox issue update 2023-12-04 21:09:59 +05:30
LAKHAN BAHETI
aeb2c0383b style: transition 2023-12-04 18:53:55 +05:30
LAKHAN BAHETI
fa4e87238a fix: alert box state 2023-12-04 18:47:24 +05:30
LAKHAN BAHETI
85d295b21d chore: issue update status in detail page 2023-12-04 18:35:44 +05:30
LAKHAN BAHETI
9d7b0c6daa chore: issue update status in peek view 2023-12-04 17:19:39 +05:30
11 changed files with 227 additions and 56 deletions

View File

@@ -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
@@ -31,11 +33,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<IIssue>({
defaultValues,
});
@@ -55,6 +64,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<IInboxIssue>) => {
@@ -217,6 +229,33 @@ export const InboxMainContent: React.FC = observer(() => {
</>
) : null}
</div>
<div className="flex items-center mb-5">
{!currentIssueState ? (
<StateGroupIcon className="h-4 w-4 mr-3" stateGroup="backlog" color="#ff7700" />
) : (
<StateGroupIcon
className="h-4 w-4 mr-3"
stateGroup={currentIssueState.group}
color={currentIssueState.color}
/>
)}
<h4 className="text-lg text-custom-text-300 font-medium mr-4">
{issueDetails?.project_detail?.identifier}-{issueDetails?.sequence_id}
</h4>
<div
className={`flex transition-all duration-300 items-center gap-x-2 ${
inboxIssueDetailsStore.isSubmitting === "saved" ? "fadeOut" : "fadeIn"
}`}
>
{inboxIssueDetailsStore.isSubmitting !== "submitted" &&
inboxIssueDetailsStore.isSubmitting !== "saved" && (
<RefreshCw className="h-4 w-4 stroke-custom-text-300" />
)}
<span className="text-sm text-custom-text-300">
{inboxIssueDetailsStore.isSubmitting === "submitting" ? "Saving..." : "Saved"}
</span>
</div>
</div>
<div>
<IssueDescriptionForm
workspaceSlug={workspaceSlug as string}

View File

@@ -1,8 +1,9 @@
import { ChangeEvent, FC, useCallback, useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import useReloadConfirmations from "hooks/use-reload-confirmation";
import debounce from "lodash/debounce";
import useReloadConfirmations from "hooks/use-reload-confirmation";
// components
import { TextArea } from "@plane/ui";
import { RichTextEditor } from "@plane/rich-text-editor";
@@ -11,6 +12,7 @@ import { IIssue } from "types";
// services
import { FileService } from "services/file.service";
import useEditorSuggestions from "hooks/use-editor-suggestions";
import { useRouter } from "next/router";
export interface IssueDescriptionFormValues {
name: string;
@@ -33,11 +35,20 @@ const fileService = new FileService();
export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
const { issue, handleFormSubmit, workspaceSlug, isAllowed } = props;
// states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
const [characterLimit, setCharacterLimit] = useState(false);
const { setShowAlert } = useReloadConfirmations();
// router
const router = useRouter();
const { inboxId } = router.query;
// mobx store
const {
projectIssues: { setIsSubmitting: PIsetIsSubmitting, isSubmitting: PIisSubmitting },
inboxIssueDetails: { setIsSubmitting: IIsetIsSubmitting, isSubmitting: IIisSubmitting },
} = useMobxStore();
// hooks
const { setShowAlert } = useReloadConfirmations();
const editorSuggestion = useEditorSuggestions();
const {
@@ -73,17 +84,6 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
[handleFormSubmit]
);
useEffect(() => {
if (isSubmitting === "submitted") {
setShowAlert(false);
setTimeout(async () => {
setIsSubmitting("saved");
}, 2000);
} else if (isSubmitting === "submitting") {
setShowAlert(true);
}
}, [isSubmitting, setShowAlert]);
// reset form values
useEffect(() => {
if (!issue) return;
@@ -94,9 +94,23 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
}, [issue, reset]);
const debouncedFormSave = debounce(async () => {
handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted"));
handleSubmit(handleDescriptionFormSubmit)();
}, 1500);
const setIsSubmitting = inboxId ? IIsetIsSubmitting : PIsetIsSubmitting;
const isSubmitting = inboxId ? IIisSubmitting : PIisSubmitting;
useEffect(() => {
if (isSubmitting === "submitted") {
setShowAlert(false);
setTimeout(async () => {
setIsSubmitting("saved");
}, 2000);
} else if (isSubmitting === "submitting") {
setShowAlert(true);
}
}, [isSubmitting, setShowAlert, setIsSubmitting]);
return (
<div className="relative">
<div className="relative">
@@ -166,13 +180,6 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (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>
);

View File

@@ -1,5 +1,7 @@
import { ChangeEvent, FC, useCallback, useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useMobxStore } from "lib/mobx/store-provider";
import useReloadConfirmations from "hooks/use-reload-confirmation";
// packages
import { RichTextEditor } from "@plane/rich-text-editor";
// components
@@ -7,13 +9,12 @@ import { TextArea } from "@plane/ui";
import { IssueReaction } from "./reactions";
// hooks
import debounce from "lodash/debounce";
import useReloadConfirmations from "hooks/use-reload-confirmation";
import useEditorSuggestions from "hooks/use-editor-suggestions";
// types
import { IIssue } from "types";
// services
import { FileService } from "services/file.service";
import { useMobxStore } from "lib/mobx/store-provider";
import { EUserWorkspaceRoles } from "constants/workspace";
const fileService = new FileService();
@@ -31,12 +32,15 @@ interface IPeekOverviewIssueDetails {
export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) => {
const { workspaceSlug, issue, issueReactions, user, issueUpdate, issueReactionCreate, issueReactionRemove } = props;
// store
const { user: userStore } = useMobxStore();
const {
user: userStore,
projectIssues: { isSubmitting, setIsSubmitting },
} = 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();
const editorSuggestions = useEditorSuggestions();
@@ -76,7 +80,7 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
}, [issueTitleCurrentValue, localTitleValue]);
const debouncedFormSave = debounce(async () => {
handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted"));
handleSubmit(handleDescriptionFormSubmit)();
}, 1500);
useEffect(() => {
@@ -172,13 +176,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>
<IssueReaction
issueReactions={issueReactions}

View File

@@ -2,7 +2,7 @@ import { FC, ReactNode, useState } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { MoveRight, MoveDiagonal, Bell, Link2, Trash2 } from "lucide-react";
import { MoveRight, MoveDiagonal, Bell, Link2, Trash2, RefreshCw } from "lucide-react";
// components
import { PeekOverviewIssueDetails } from "./issue-detail";
import { PeekOverviewProperties } from "./properties";
@@ -14,6 +14,7 @@ import { DeleteArchivedIssueModal } from "../delete-archived-issue-modal";
import { IIssue } from "types";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import useReloadConfirmations from "hooks/use-reload-confirmation";
interface IIssueView {
workspaceSlug: string;
@@ -89,11 +90,17 @@ export const IssueView: FC<IIssueView> = observer((props) => {
const router = useRouter();
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 [deleteIssueModal, setDeleteIssueModal] = useState(false);
const { setShowAlert } = useReloadConfirmations();
const updateRoutePeekId = () => {
if (issueId != peekIssueId) {
issueDetailStore.setPeekId(issueId);
@@ -235,12 +242,24 @@ export const IssueView: FC<IIssueView> = observer((props) => {
{issueSubscription && issueSubscription.subscribed ? "Unsubscribe" : "Subscribe"}
</Button>
)}
<div
className={`flex items-center gap-x-2 transition-all duration-300 ${
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}>
<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>
{!disableUserActions && (
<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>
)}
</div>

View File

@@ -8,6 +8,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { IssueService, IssueCommentService } from "services/issue";
// hooks
import useToast from "hooks/use-toast";
import useReloadConfirmations from "hooks/use-reload-confirmation";
// components
import {
AddComment,
@@ -19,9 +20,9 @@ import {
} from "components/issues";
import { SubIssuesRoot } from "./sub-issues";
// ui
import { CustomMenu, LayersIcon } from "@plane/ui";
import { CustomMenu, LayersIcon, StateGroupIcon } from "@plane/ui";
// icons
import { MinusCircle } from "lucide-react";
import { MinusCircle, RefreshCw } from "lucide-react";
// types
import { IIssue, IIssueComment } from "types";
// fetch-keys
@@ -44,13 +45,24 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
// hooks
const { setToastAlert } = useToast();
const { setShowAlert } = useReloadConfirmations();
const { user: userStore, project: projectStore } = useMobxStore();
const {
user: userStore,
project: projectStore,
projectState: { states },
projectIssues: { isSubmitting, setIsSubmitting },
} = 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,
workspaceSlug && projectId && issueDetails?.parent
@@ -165,6 +177,31 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
</CustomMenu>
</div>
) : null}
<div className="flex items-center mb-5">
{currentIssueState && (
<StateGroupIcon
className="h-4 w-4 mr-3"
stateGroup={currentIssueState.group}
color={currentIssueState.color}
/>
)}
<h4 className="text-lg text-custom-text-300 font-medium mr-4">
{issueDetails?.project_detail?.identifier}-{issueDetails?.sequence_id}
</h4>
<div
className={`flex transition-all duration-300 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>
</div>
<IssueDescriptionForm
workspaceSlug={workspaceSlug as string}
issue={issueDetails}

View File

@@ -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<Props> = observer((props) => {
const [linkModal, setLinkModal] = useState(false);
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<linkDetails | null>(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<Props> = observer((props) => {
const isAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER;
const currentIssueState = projectId
? states[projectId.toString()]?.find((s) => s.id === issueDetail?.state)
: undefined;
return (
<>
<LinkModal
@@ -266,9 +273,21 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
)}
<div className="h-full w-full flex flex-col divide-y-2 divide-custom-border-200 overflow-hidden">
<div className="flex items-center justify-between px-5 pb-3">
<h4 className="text-sm font-medium">
{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id}
</h4>
<div className="flex items-center gap-x-2">
{currentIssueState ? (
<StateGroupIcon
className="h-4 w-4"
stateGroup={currentIssueState.group}
color={currentIssueState.color}
/>
) : inboxIssueId ? (
<StateGroupIcon className="h-4 w-4" stateGroup="backlog" color="#ff7700" />
) : null}
<h4 className="text-lg text-custom-text-300 font-medium">
{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id}
</h4>
</div>
<div className="flex flex-wrap items-center gap-2">
{issueDetail?.created_by !== user?.id &&
!issueDetail?.assignees.includes(user?.id ?? "") &&

View File

@@ -2,10 +2,13 @@ import { useCallback, useEffect, useState, ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
import { useForm } from "react-hook-form";
import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite";
// services
import { IssueService, IssueArchiveService } from "services/issue";
// hooks
import useToast from "hooks/use-toast";
import useReloadConfirmations from "hooks/use-reload-confirmation";
// layouts
import { AppLayout } from "layouts/app-layout";
// components
@@ -37,7 +40,7 @@ const defaultValues: Partial<IIssue> = {
const issueService = new IssueService();
const issueArchiveService = new IssueArchiveService();
const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
const ArchivedIssueDetailsPage: NextPageWithLayout = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, archivedIssueId } = router.query;
@@ -45,6 +48,11 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
const [isRestoring, setIsRestoring] = useState(false);
// hooks
const { setToastAlert } = useToast();
const { setShowAlert } = useReloadConfirmations();
// mobx stores
const {
projectIssues: { isSubmitting, setIsSubmitting },
} = useMobxStore();
const { data: issueDetails, mutate: mutateIssueDetails } = useSWR<IIssue | undefined>(
workspaceSlug && projectId && archivedIssueId ? ISSUE_DETAILS(archivedIssueId as string) : null,
@@ -65,7 +73,7 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
const submitChanges = useCallback(
async (formData: Partial<IIssue>) => {
if (!workspaceSlug || !projectId || !archivedIssueId) return;
setIsSubmitting("submitting");
mutate<IIssue>(
ISSUE_DETAILS(archivedIssueId as string),
(prevData) => {
@@ -88,6 +96,7 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
.then(() => {
mutateIssueDetails();
mutate(PROJECT_ISSUES_ACTIVITY(archivedIssueId as string));
setIsSubmitting("submitted");
})
.catch((e) => {
console.error(e);
@@ -184,7 +193,7 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
)}
</>
);
};
});
ArchivedIssueDetailsPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@@ -2,6 +2,8 @@ import React, { useCallback, useEffect, ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
import { useForm } from "react-hook-form";
import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite";
// services
import { IssueService } from "services/issue";
// layouts
@@ -36,11 +38,16 @@ const defaultValues: Partial<IIssue> = {
// services
const issueService = new IssueService();
const IssueDetailsPage: NextPageWithLayout = () => {
const IssueDetailsPage: NextPageWithLayout = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
// mobx store
const {
projectIssues: { isSubmitting, setIsSubmitting },
} = useMobxStore();
const {
data: issueDetails,
mutate: mutateIssueDetails,
@@ -60,6 +67,7 @@ const IssueDetailsPage: NextPageWithLayout = () => {
async (formData: Partial<IIssue>) => {
if (!workspaceSlug || !projectId || !issueId) return;
setIsSubmitting("submitting");
mutate<IIssue>(
ISSUE_DETAILS(issueId as string),
(prevData) => {
@@ -85,6 +93,7 @@ const IssueDetailsPage: NextPageWithLayout = () => {
.then(() => {
mutateIssueDetails();
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
setIsSubmitting("submitted");
})
.catch((e) => {
console.error(e);
@@ -147,7 +156,7 @@ const IssueDetailsPage: NextPageWithLayout = () => {
)}
</>
);
};
});
IssueDetailsPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@@ -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<void>;
deleteIssue: (workspaceSlug: string, projectId: string, inboxId: string, issueId: string) => Promise<void>;
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;

View File

@@ -6,12 +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 } 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; // update issue status
issues: { [project_id: string]: IIssueResponse } | undefined;
// computed
getIssues: IIssueResponse | undefined;
@@ -22,12 +31,14 @@ export interface IProjectIssuesStore {
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<IIssue>) => Promise<IIssue>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<IIssue>;
quickAddIssue: (workspaceSlug: string, projectId: string, data: IIssue) => Promise<IIssue>;
setIsSubmitting: (value: TIssueUpdateStatus) => void;
viewFlags: ViewFlags;
}
export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssuesStore {
loader: TLoader = "init-loader";
isSubmitting: TIssueUpdateStatus = "saved";
issues: { [project_id: string]: IIssueResponse } | undefined = undefined;
// root store
rootStore;
@@ -48,6 +59,7 @@ export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssues
// observable
loader: observable.ref,
issues: observable.ref,
isSubmitting: observable.ref,
// computed
getIssues: computed,
getIssuesIds: computed,
@@ -57,6 +69,7 @@ export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssues
updateIssue: action,
removeIssue: action,
quickAddIssue: action,
setIsSubmitting: action,
});
this.rootStore = _rootStore;
@@ -72,6 +85,10 @@ export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssues
});
}
setIsSubmitting = (status: TIssueUpdateStatus) => {
this.isSubmitting = status;
};
get getIssues() {
const projectId = this.rootStore?.project.projectId;
if (!projectId || !this.issues || !this.issues[projectId]) return undefined;
@@ -157,11 +174,14 @@ export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssues
_issues[projectId][issueId] = { ..._issues[projectId][issueId], ...data };
runInAction(() => {
this.isSubmitting = "submitting";
this.issues = _issues;
});
const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data);
this.isSubmitting = "submitted";
return response;
} catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation");

View File

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