diff --git a/web/components/issues/sub-issues/issue-list-item.tsx b/web/components/issues/sub-issues/issue-list-item.tsx index 033473470..c6b87411d 100644 --- a/web/components/issues/sub-issues/issue-list-item.tsx +++ b/web/components/issues/sub-issues/issue-list-item.tsx @@ -32,11 +32,11 @@ export const IssueListItem: React.FC = observer((props) => { workspaceSlug, projectId, parentIssueId, + issueId, spacingLeft = 10, disabled, handleIssueCrudState, subIssueOperations, - issueId, } = props; const { @@ -81,12 +81,12 @@ export const IssueListItem: React.FC = observer((props) => {
{ - if (!subIssueHelpers.issue_visibility.includes(issue.id)) { - setSubIssueHelpers(parentIssueId, "preview_loader", issue.id); - await subIssueOperations.fetchSubIssues(workspaceSlug, projectId, issue.id); - setSubIssueHelpers(parentIssueId, "preview_loader", issue.id); + if (!subIssueHelpers.issue_visibility.includes(issueId)) { + setSubIssueHelpers(parentIssueId, "preview_loader", issueId); + await subIssueOperations.fetchSubIssues(workspaceSlug, projectId, issueId); + setSubIssueHelpers(parentIssueId, "preview_loader", issueId); } - setSubIssueHelpers(parentIssueId, "issue_visibility", issue.id); + setSubIssueHelpers(parentIssueId, "issue_visibility", issueId); }} > {subIssueHelpers.issue_visibility.includes(issue.id) ? ( @@ -136,7 +136,7 @@ export const IssueListItem: React.FC = observer((props) => {
{disabled && ( - handleIssueCrudState("update", parentIssueId, issue)}> + handleIssueCrudState("update", parentIssueId, { ...issue })}>
Edit issue diff --git a/web/components/issues/sub-issues/issues-list.tsx b/web/components/issues/sub-issues/issues-list.tsx index eaa3e098e..ad09938cb 100644 --- a/web/components/issues/sub-issues/issues-list.tsx +++ b/web/components/issues/sub-issues/issues-list.tsx @@ -34,16 +34,13 @@ export const IssueList: FC = observer((props) => { } = props; // hooks const { - subIssues: { subIssuesByIssueId, subIssueHelpersByIssueId }, + subIssues: { subIssuesByIssueId }, } = useIssueDetail(); const subIssueIds = subIssuesByIssueId(parentIssueId); - const subIssueHelpers = subIssueHelpersByIssueId(parentIssueId); return ( <> - {subIssueHelpers.preview_loader.includes(parentIssueId) ? "Loading..." : null} -
{subIssueIds && subIssueIds.length > 0 && @@ -53,11 +50,11 @@ export const IssueList: FC = observer((props) => { workspaceSlug={workspaceSlug} projectId={projectId} parentIssueId={parentIssueId} + issueId={issueId} spacingLeft={spacingLeft} disabled={disabled} handleIssueCrudState={handleIssueCrudState} subIssueOperations={subIssueOperations} - issueId={issueId} /> ))} diff --git a/web/components/issues/sub-issues/properties.tsx b/web/components/issues/sub-issues/properties.tsx index 64a832c9e..3a205aea2 100644 --- a/web/components/issues/sub-issues/properties.tsx +++ b/web/components/issues/sub-issues/properties.tsx @@ -31,9 +31,16 @@ export const IssueProperty: React.FC = (props) => { value={issue.state_id} projectId={issue.project_id} onChange={(val) => - subIssueOperations.updateSubIssue(workspaceSlug, issue.project_id, parentIssueId, issueId, { - state_id: val, - }) + subIssueOperations.updateSubIssue( + workspaceSlug, + issue.project_id, + parentIssueId, + issueId, + { + state_id: val, + }, + { ...issue } + ) } disabled={!disabled} buttonVariant="border-with-text" diff --git a/web/components/issues/sub-issues/root.tsx b/web/components/issues/sub-issues/root.tsx index 967df8541..409865d9a 100644 --- a/web/components/issues/sub-issues/root.tsx +++ b/web/components/issues/sub-issues/root.tsx @@ -1,7 +1,6 @@ import { FC, useMemo, useState } from "react"; import { observer } from "mobx-react-lite"; import { Plus, ChevronRight, ChevronDown, Loader } from "lucide-react"; -import useSWR from "swr"; // hooks import { useIssueDetail } from "hooks/store"; import useToast from "hooks/use-toast"; @@ -34,7 +33,9 @@ export type TSubIssueOperations = { projectId: string, parentIssueId: string, issueId: string, - data: Partial + issueData: Partial, + oldIssue?: Partial, + fromModal?: boolean ) => Promise; removeSubIssue: (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => Promise; deleteSubIssue: (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => Promise; @@ -84,18 +85,6 @@ export const SubIssuesRoot: FC = observer((props) => { }, }); - useSWR( - workspaceSlug && projectId && parentIssueId - ? `ISSUE_DETAIL_SUB_ISSUES_${workspaceSlug}_${projectId}_${parentIssueId}` - : null, - async () => { - workspaceSlug && - projectId && - parentIssueId && - (await subIssueOperations.fetchSubIssues(workspaceSlug, projectId, parentIssueId)); - } - ); - const handleIssueCrudState = ( key: "create" | "existing" | "update" | "delete", _parentIssueId: string | null, @@ -155,11 +144,13 @@ export const SubIssuesRoot: FC = observer((props) => { projectId: string, parentIssueId: string, issueId: string, - data: Partial + issueData: Partial, + oldIssue: Partial = {}, + fromModal: boolean = false ) => { try { setSubIssueHelpers(parentIssueId, "issue_loader", issueId); - await updateSubIssue(workspaceSlug, projectId, parentIssueId, issueId, data); + await updateSubIssue(workspaceSlug, projectId, parentIssueId, issueId, issueData, oldIssue, fromModal); setToastAlert({ type: "success", title: "Sub-issue updated successfully", @@ -231,7 +222,14 @@ export const SubIssuesRoot: FC = observer((props) => {
setSubIssueHelpers(`${parentIssueId}_root`, "issue_visibility", parentIssueId)} + onClick={async () => { + if (!subIssueHelpers.issue_visibility.includes(parentIssueId)) { + setSubIssueHelpers(`${parentIssueId}_root`, "preview_loader", parentIssueId); + await subIssueOperations.fetchSubIssues(workspaceSlug, projectId, parentIssueId); + setSubIssueHelpers(`${parentIssueId}_root`, "preview_loader", parentIssueId); + } + setSubIssueHelpers(`${parentIssueId}_root`, "issue_visibility", parentIssueId); + }} >
{subIssueHelpers.preview_loader.includes(parentIssueId) ? ( @@ -395,7 +393,15 @@ export const SubIssuesRoot: FC = observer((props) => { }} data={issueCrudState?.update?.issue ?? undefined} onSubmit={async (_issue: TIssue) => { - await subIssueOperations.updateSubIssue(workspaceSlug, projectId, parentIssueId, _issue.id, _issue); + await subIssueOperations.updateSubIssue( + workspaceSlug, + projectId, + parentIssueId, + _issue.id, + _issue, + issueCrudState?.update?.issue, + true + ); }} /> diff --git a/web/store/issue/issue-details/issue.store.ts b/web/store/issue/issue-details/issue.store.ts index ba429b6dd..bc34af0f6 100644 --- a/web/store/issue/issue-details/issue.store.ts +++ b/web/store/issue/issue-details/issue.store.ts @@ -89,6 +89,9 @@ export class IssueStore implements IIssueStore { // fetch issue relations this.rootIssueDetailStore.relation.fetchRelations(workspaceSlug, projectId, issueId); + // fetching states + this.rootIssueDetailStore.rootIssueStore.state.fetchProjectStates(workspaceSlug, projectId); + return issue; } catch (error) { throw error; diff --git a/web/store/issue/issue-details/root.store.ts b/web/store/issue/issue-details/root.store.ts index 21f66adee..0fedd99bb 100644 --- a/web/store/issue/issue-details/root.store.ts +++ b/web/store/issue/issue-details/root.store.ts @@ -183,8 +183,10 @@ export class IssueDetail implements IIssueDetail { projectId: string, parentIssueId: string, issueId: string, - data: Partial - ) => this.subIssues.updateSubIssue(workspaceSlug, projectId, parentIssueId, issueId, data); + issueData: Partial, + oldIssue?: Partial, + fromModal?: boolean + ) => this.subIssues.updateSubIssue(workspaceSlug, projectId, parentIssueId, issueId, issueData, oldIssue, fromModal); removeSubIssue = async (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => this.subIssues.removeSubIssue(workspaceSlug, projectId, parentIssueId, issueId); deleteSubIssue = async (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => diff --git a/web/store/issue/issue-details/sub_issues.store.ts b/web/store/issue/issue-details/sub_issues.store.ts index a1bd3f818..cfa1be12e 100644 --- a/web/store/issue/issue-details/sub_issues.store.ts +++ b/web/store/issue/issue-details/sub_issues.store.ts @@ -28,7 +28,9 @@ export interface IIssueSubIssuesStoreActions { projectId: string, parentIssueId: string, issueId: string, - data: Partial + issueData: Partial, + oldIssue?: Partial, + fromModal?: boolean ) => Promise; removeSubIssue: (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => Promise; deleteSubIssue: (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => Promise; @@ -100,11 +102,9 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore { setSubIssueHelpers = (parentIssueId: string, key: TSubIssueHelpersKeys, value: string) => { if (!parentIssueId || !key || !value) return; - update(this.subIssueHelpers, [parentIssueId, key], (subIssueHelpers: string[]) => { - if (!subIssueHelpers || subIssueHelpers.length <= 0) return [value]; - else if (subIssueHelpers.includes(value)) pull(subIssueHelpers, value); - else concat(subIssueHelpers, value); - return subIssueHelpers; + update(this.subIssueHelpers, [parentIssueId, key], (_subIssueHelpers: string[] = []) => { + if (_subIssueHelpers.includes(value)) return pull(_subIssueHelpers, value); + return concat(_subIssueHelpers, value); }); }; @@ -169,16 +169,60 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore { projectId: string, parentIssueId: string, issueId: string, - data: Partial + issueData: Partial, + oldIssue: Partial = {}, + fromModal: boolean = false ) => { try { - await this.rootIssueDetailStore.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); + if (!fromModal) + await this.rootIssueDetailStore.rootIssueStore.projectIssues.updateIssue( + workspaceSlug, + projectId, + issueId, + issueData + ); - if (data.hasOwnProperty("parent_id") && data.parent_id !== parentIssueId) { + // parent update + if (issueData.hasOwnProperty("parent_id") && issueData.parent_id !== oldIssue.parent_id) { runInAction(() => { - pull(this.subIssues[parentIssueId], issueId); + if (oldIssue.parent_id) pull(this.subIssues[oldIssue.parent_id], issueId); + if (issueData.parent_id) + set(this.subIssues, [issueData.parent_id], concat(this.subIssues[issueData.parent_id], issueId)); }); } + + // state update + if (issueData.hasOwnProperty("state_id") && issueData.state_id !== oldIssue.state_id) { + let oldIssueStateGroup: string | undefined = undefined; + let issueStateGroup: string | undefined = undefined; + + if (oldIssue.state_id) { + const state = this.rootIssueDetailStore.rootIssueStore.state.getStateById(oldIssue.state_id); + if (state?.group) oldIssueStateGroup = state.group; + } + + if (issueData.state_id) { + const state = this.rootIssueDetailStore.rootIssueStore.state.getStateById(issueData.state_id); + if (state?.group) issueStateGroup = state.group; + } + + if (oldIssueStateGroup && issueStateGroup && issueStateGroup !== oldIssueStateGroup) { + runInAction(() => { + if (oldIssueStateGroup) + update(this.subIssuesStateDistribution, [parentIssueId, oldIssueStateGroup], (stateDistribution) => { + if (!stateDistribution) return; + return pull(stateDistribution, issueId); + }); + + if (issueStateGroup) + update(this.subIssuesStateDistribution, [parentIssueId, issueStateGroup], (stateDistribution) => { + if (!stateDistribution) return [issueId]; + return concat(stateDistribution, issueId); + }); + }); + } + } + return; } catch (error) { throw error; @@ -191,6 +235,23 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore { parent_id: null, }); + const issue = this.rootIssueDetailStore.issue.getIssueById(issueId); + if (issue && issue.state_id) { + let issueStateGroup: string | undefined = undefined; + const state = this.rootIssueDetailStore.rootIssueStore.state.getStateById(issue.state_id); + if (state?.group) issueStateGroup = state.group; + + if (issueStateGroup) { + runInAction(() => { + if (issueStateGroup) + update(this.subIssuesStateDistribution, [parentIssueId, issueStateGroup], (stateDistribution) => { + if (!stateDistribution) return; + return pull(stateDistribution, issueId); + }); + }); + } + } + runInAction(() => { pull(this.subIssues[parentIssueId], issueId); }); @@ -205,6 +266,23 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore { try { await this.rootIssueDetailStore.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); + const issue = this.rootIssueDetailStore.issue.getIssueById(issueId); + if (issue && issue.state_id) { + let issueStateGroup: string | undefined = undefined; + const state = this.rootIssueDetailStore.rootIssueStore.state.getStateById(issue.state_id); + if (state?.group) issueStateGroup = state.group; + + if (issueStateGroup) { + runInAction(() => { + if (issueStateGroup) + update(this.subIssuesStateDistribution, [parentIssueId, issueStateGroup], (stateDistribution) => { + if (!stateDistribution) return; + return pull(stateDistribution, issueId); + }); + }); + } + } + runInAction(() => { pull(this.subIssues[parentIssueId], issueId); }); diff --git a/web/store/issue/root.store.ts b/web/store/issue/root.store.ts index 46e4b67c6..04f46c280 100644 --- a/web/store/issue/root.store.ts +++ b/web/store/issue/root.store.ts @@ -2,6 +2,7 @@ import { autorun, makeObservable, observable } from "mobx"; import isEmpty from "lodash/isEmpty"; // root store import { RootStore } from "../root.store"; +import { IStateStore, StateStore } from "../state.store"; // issues data store import { IState } from "@plane/types"; import { IIssueStore, IssueStore } from "./issue.store"; @@ -39,6 +40,8 @@ export interface IIssueRootStore { issues: IIssueStore; + state: IStateStore; + issueDetail: IIssueDetail; workspaceIssuesFilter: IWorkspaceIssuesFilter; @@ -86,6 +89,8 @@ export class IssueRootStore implements IIssueRootStore { issues: IIssueStore; + state: IStateStore; + issueDetail: IIssueDetail; workspaceIssuesFilter: IWorkspaceIssuesFilter; @@ -151,6 +156,8 @@ export class IssueRootStore implements IIssueRootStore { this.issues = new IssueStore(); + this.state = new StateStore(rootStore); + this.issueDetail = new IssueDetail(this); this.workspaceIssuesFilter = new WorkspaceIssuesFilter(this); diff --git a/web/store/state.store.ts b/web/store/state.store.ts index eee6c1096..783a82ee2 100644 --- a/web/store/state.store.ts +++ b/web/store/state.store.ts @@ -1,5 +1,5 @@ import { makeObservable, observable, computed, action, runInAction } from "mobx"; -import { computedFn } from "mobx-utils" +import { computedFn } from "mobx-utils"; import groupBy from "lodash/groupBy"; import set from "lodash/set"; // store