forked from github/plane
chore: inbox issue update
This commit is contained in:
parent
aeb2c0383b
commit
42f2d346bf
@ -3,15 +3,17 @@ import Router, { 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 { useForm } from "react-hook-form";
|
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
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { IssueDescriptionForm, IssueDetailsSidebar, IssueReaction } from "components/issues";
|
import { IssueDescriptionForm, IssueDetailsSidebar, IssueReaction } from "components/issues";
|
||||||
import { InboxIssueActivity } from "components/inbox";
|
import { InboxIssueActivity } from "components/inbox";
|
||||||
|
// hooks
|
||||||
|
import useReloadConfirmations from "hooks/use-reload-confirmation";
|
||||||
// ui
|
// ui
|
||||||
import { Loader } from "@plane/ui";
|
import { Loader, StateGroupIcon } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
||||||
// types
|
// types
|
||||||
@ -30,11 +32,18 @@ export const InboxMainContent: React.FC = observer(() => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, inboxId, inboxIssueId } = router.query;
|
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 user = userStore.currentUser;
|
||||||
const userRole = userStore.currentProjectRole;
|
const userRole = userStore.currentProjectRole;
|
||||||
|
|
||||||
|
const { setShowAlert } = useReloadConfirmations();
|
||||||
|
|
||||||
const { reset, control, watch } = useForm<IIssue>({
|
const { reset, control, watch } = useForm<IIssue>({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
});
|
});
|
||||||
@ -54,6 +63,9 @@ export const InboxMainContent: React.FC = observer(() => {
|
|||||||
|
|
||||||
const issuesList = inboxId ? inboxIssuesStore.inboxIssues[inboxId.toString()] : undefined;
|
const issuesList = inboxId ? inboxIssuesStore.inboxIssues[inboxId.toString()] : undefined;
|
||||||
const issueDetails = inboxIssueId ? inboxIssueDetailsStore.issueDetails[inboxIssueId.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(
|
const submitChanges = useCallback(
|
||||||
async (formData: Partial<IInboxIssue>) => {
|
async (formData: Partial<IInboxIssue>) => {
|
||||||
@ -124,6 +136,17 @@ export const InboxMainContent: React.FC = observer(() => {
|
|||||||
});
|
});
|
||||||
}, [issueDetails, reset, inboxIssueId]);
|
}, [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;
|
const issueStatus = issueDetails?.issue_inbox[0].status;
|
||||||
|
|
||||||
if (!inboxIssueId)
|
if (!inboxIssueId)
|
||||||
@ -214,8 +237,36 @@ export const InboxMainContent: React.FC = observer(() => {
|
|||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<IssueDescriptionForm
|
<IssueDescriptionForm
|
||||||
|
setShowAlert={setShowAlert}
|
||||||
workspaceSlug={workspaceSlug as string}
|
workspaceSlug={workspaceSlug as string}
|
||||||
issue={{
|
issue={{
|
||||||
name: issueDetails.name,
|
name: issueDetails.name,
|
||||||
|
@ -11,6 +11,7 @@ import { IIssue } from "types";
|
|||||||
// services
|
// services
|
||||||
import { FileService } from "services/file.service";
|
import { FileService } from "services/file.service";
|
||||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
export interface IssueDescriptionFormValues {
|
export interface IssueDescriptionFormValues {
|
||||||
name: string;
|
name: string;
|
||||||
@ -36,9 +37,14 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
|
|||||||
// states
|
// states
|
||||||
const [characterLimit, setCharacterLimit] = useState(false);
|
const [characterLimit, setCharacterLimit] = useState(false);
|
||||||
|
|
||||||
|
// router
|
||||||
|
const router = useRouter();
|
||||||
|
const { inboxId } = router.query;
|
||||||
|
|
||||||
// mobx store
|
// mobx store
|
||||||
const {
|
const {
|
||||||
projectIssues: { setIsSubmitting },
|
projectIssues: { setIsSubmitting: PIsetIsSubmitting },
|
||||||
|
inboxIssueDetails: { setIsSubmitting: IIsetIsSubmitting },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
const editorSuggestion = useEditorSuggestions();
|
const editorSuggestion = useEditorSuggestions();
|
||||||
@ -89,6 +95,8 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
|
|||||||
handleSubmit(handleDescriptionFormSubmit)();
|
handleSubmit(handleDescriptionFormSubmit)();
|
||||||
}, 1500);
|
}, 1500);
|
||||||
|
|
||||||
|
const setIsSubmitting = inboxId ? IIsetIsSubmitting : PIsetIsSubmitting;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
@ -87,7 +87,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
const userRole = userStore.currentProjectRole;
|
const userRole = userStore.currentProjectRole;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, issueId } = router.query;
|
const { workspaceSlug, projectId, issueId, inboxIssueId } = router.query;
|
||||||
|
|
||||||
const { isEstimateActive } = useEstimateOption();
|
const { isEstimateActive } = useEstimateOption();
|
||||||
|
|
||||||
@ -273,13 +273,15 @@ 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="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">
|
<div className="flex items-center justify-between px-5 pb-3">
|
||||||
<div className="flex items-center gap-x-2">
|
<div className="flex items-center gap-x-2">
|
||||||
{currentIssueState && (
|
{currentIssueState ? (
|
||||||
<StateGroupIcon
|
<StateGroupIcon
|
||||||
className="h-4 w-4"
|
className="h-4 w-4"
|
||||||
stateGroup={currentIssueState.group}
|
stateGroup={currentIssueState.group}
|
||||||
color={currentIssueState.color}
|
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">
|
<h4 className="text-lg text-custom-text-300 font-medium">
|
||||||
{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id}
|
{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id}
|
||||||
</h4>
|
</h4>
|
||||||
|
@ -50,6 +50,7 @@ const IssueDetailsPage: NextPageWithLayout = observer(() => {
|
|||||||
projectIssues: { isSubmitting, setIsSubmitting },
|
projectIssues: { isSubmitting, setIsSubmitting },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
|
// hooks
|
||||||
const { setShowAlert } = useReloadConfirmations();
|
const { setShowAlert } = useReloadConfirmations();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -5,6 +5,7 @@ import { RootStore } from "../root";
|
|||||||
import { InboxService } from "services/inbox.service";
|
import { InboxService } from "services/inbox.service";
|
||||||
// types
|
// types
|
||||||
import { IInboxIssue, IIssue, TInboxStatus } from "types";
|
import { IInboxIssue, IIssue, TInboxStatus } from "types";
|
||||||
|
import { TIssueUpdateStatus } from "store/issues/types";
|
||||||
// constants
|
// constants
|
||||||
import { INBOX_ISSUE_SOURCE } from "constants/inbox";
|
import { INBOX_ISSUE_SOURCE } from "constants/inbox";
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ export interface IInboxIssueDetailsStore {
|
|||||||
// states
|
// states
|
||||||
loader: boolean;
|
loader: boolean;
|
||||||
error: any | null;
|
error: any | null;
|
||||||
|
isSubmitting: TIssueUpdateStatus; // inbox issue update status;
|
||||||
|
|
||||||
// observables
|
// observables
|
||||||
issueDetails: {
|
issueDetails: {
|
||||||
@ -46,12 +48,14 @@ export interface IInboxIssueDetailsStore {
|
|||||||
data: TInboxStatus
|
data: TInboxStatus
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
deleteIssue: (workspaceSlug: string, projectId: string, inboxId: string, issueId: string) => Promise<void>;
|
deleteIssue: (workspaceSlug: string, projectId: string, inboxId: string, issueId: string) => Promise<void>;
|
||||||
|
setIsSubmitting: (status: TIssueUpdateStatus) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InboxIssueDetailsStore implements IInboxIssueDetailsStore {
|
export class InboxIssueDetailsStore implements IInboxIssueDetailsStore {
|
||||||
// states
|
// states
|
||||||
loader: boolean = false;
|
loader: boolean = false;
|
||||||
error: any | null = null;
|
error: any | null = null;
|
||||||
|
isSubmitting: TIssueUpdateStatus = "saved";
|
||||||
|
|
||||||
// observables
|
// observables
|
||||||
issueDetails: { [issueId: string]: IInboxIssue } = {};
|
issueDetails: { [issueId: string]: IInboxIssue } = {};
|
||||||
@ -67,6 +71,7 @@ export class InboxIssueDetailsStore implements IInboxIssueDetailsStore {
|
|||||||
// states
|
// states
|
||||||
loader: observable.ref,
|
loader: observable.ref,
|
||||||
error: observable.ref,
|
error: observable.ref,
|
||||||
|
isSubmitting: observable.ref,
|
||||||
|
|
||||||
// observables
|
// observables
|
||||||
issueDetails: observable.ref,
|
issueDetails: observable.ref,
|
||||||
@ -76,12 +81,17 @@ export class InboxIssueDetailsStore implements IInboxIssueDetailsStore {
|
|||||||
createIssue: action,
|
createIssue: action,
|
||||||
updateIssueStatus: action,
|
updateIssueStatus: action,
|
||||||
deleteIssue: action,
|
deleteIssue: action,
|
||||||
|
setIsSubmitting: action,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rootStore = _rootStore;
|
this.rootStore = _rootStore;
|
||||||
this.inboxService = new InboxService();
|
this.inboxService = new InboxService();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsSubmitting = (status: TIssueUpdateStatus) => {
|
||||||
|
this.isSubmitting = status;
|
||||||
|
};
|
||||||
|
|
||||||
fetchIssueDetails = async (workspaceSlug: string, projectId: string, inboxId: string, issueId: string) => {
|
fetchIssueDetails = async (workspaceSlug: string, projectId: string, inboxId: string, issueId: string) => {
|
||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
@ -155,6 +165,7 @@ export class InboxIssueDetailsStore implements IInboxIssueDetailsStore {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
|
this.isSubmitting = "submitting";
|
||||||
this.issueDetails = {
|
this.issueDetails = {
|
||||||
...this.issueDetails,
|
...this.issueDetails,
|
||||||
[issueId]: updatedIssue,
|
[issueId]: updatedIssue,
|
||||||
@ -170,6 +181,7 @@ export class InboxIssueDetailsStore implements IInboxIssueDetailsStore {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await this.inboxService.patchInboxIssue(workspaceSlug, projectId, inboxId, issueId, { issue: data });
|
await this.inboxService.patchInboxIssue(workspaceSlug, projectId, inboxId, issueId, { issue: data });
|
||||||
|
this.isSubmitting = "submitted";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.error = error;
|
this.error = error;
|
||||||
|
@ -6,13 +6,21 @@ 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, TIssueUpdateStatus } 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;
|
isSubmitting: TIssueUpdateStatus; // update issue status
|
||||||
issues: { [project_id: string]: IIssueResponse } | undefined;
|
issues: { [project_id: string]: IIssueResponse } | undefined;
|
||||||
// computed
|
// computed
|
||||||
getIssues: IIssueResponse | undefined;
|
getIssues: IIssueResponse | undefined;
|
||||||
|
Loading…
Reference in New Issue
Block a user