ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: отправка во внешний контур и source-side список

This commit is contained in:
DCCONSTRUCTIONS
2026-04-18 21:47:29 +03:00
parent 390bcdbf38
commit fb33f093de
28 changed files with 911 additions and 386 deletions
@@ -0,0 +1,15 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useContext } from "react";
import { StoreContext } from "@/lib/store-context";
import type { IProjectExternalContoursStore } from "@/store/external-contours/project-external-contours.store";
export const useProjectExternalContours = (): IProjectExternalContoursStore => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useProjectExternalContours must be used within StoreProvider");
return context.projectExternalContours;
};
@@ -0,0 +1,53 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { API_BASE_URL } from "@plane/constants";
import type { TExternalContourRequest, TExternalContourRequestResponse, TIssue } from "@plane/types";
import { APIService } from "@/services/api.service";
export class ExternalContourService extends APIService {
constructor() {
super(API_BASE_URL);
}
async list(workspaceSlug: string, projectId: string): Promise<TExternalContourRequestResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/external-contours/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async retrieve(workspaceSlug: string, projectId: string, requestId: string): Promise<TExternalContourRequest> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/external-contours/${requestId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async create(
workspaceSlug: string,
projectId: string,
data: Partial<TIssue> & { target_project_id?: string | null }
): Promise<TExternalContourRequest> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/external-contours/`, {
target_project_id: data.target_project_id,
issue: {
name: data.name,
description_html: data.description_html,
priority: data.priority,
assignee_ids: data.assignee_ids,
label_ids: data.label_ids,
target_date: data.target_date || null,
},
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -0,0 +1 @@
export * from "./external-contour.service";
@@ -0,0 +1,182 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { set } from "lodash-es";
import { action, computed, makeObservable, observable, runInAction } from "mobx";
import type { TExternalContourRequest, TInboxIssueCurrentTab, TIssue } from "@plane/types";
import { EInboxIssueCurrentTab } from "@plane/types";
import { ExternalContourService } from "@/services/external-contours";
import type { CoreRootStore } from "../root.store";
type TLoader = "init-loading" | "mutation-loading" | "issue-loading" | undefined;
export interface IProjectExternalContoursStore {
currentProjectId: string;
currentTab: TInboxIssueCurrentTab;
error: { message: string; status: "init-error" } | undefined;
loader: TLoader;
requestIds: string[];
requests: Record<string, TExternalContourRequest>;
createRequest: (
workspaceSlug: string,
projectId: string,
data: Partial<TIssue> & { target_project_id?: string | null }
) => Promise<TExternalContourRequest | undefined>;
fetchRequestById: (workspaceSlug: string, projectId: string, requestId: string) => Promise<TExternalContourRequest | undefined>;
fetchRequests: (workspaceSlug: string, projectId: string, tab?: TInboxIssueCurrentTab) => Promise<void>;
getIsRequestAvailable: (requestId: string) => boolean;
getRequestById: (requestId: string) => TExternalContourRequest | undefined;
handleCurrentTab: (workspaceSlug: string, projectId: string, tab: TInboxIssueCurrentTab) => Promise<void>;
openRequestIds: string[];
closedRequestIds: string[];
filteredRequestIds: string[];
upsertRequests: (requests: TExternalContourRequest[]) => void;
updateRequestIssue: (requestId: string, issueData: Partial<TExternalContourRequest["issue"]>) => void;
}
export class ProjectExternalContoursStore implements IProjectExternalContoursStore {
currentProjectId = "";
currentTab: TInboxIssueCurrentTab = EInboxIssueCurrentTab.OPEN;
error: { message: string; status: "init-error" } | undefined = undefined;
loader: TLoader = "init-loading";
requestIds: string[] = [];
requests: Record<string, TExternalContourRequest> = {};
externalContourService;
constructor(_store: CoreRootStore) {
makeObservable(this, {
currentProjectId: observable.ref,
currentTab: observable.ref,
error: observable.ref,
loader: observable.ref,
requestIds: observable.shallow,
requests: observable,
openRequestIds: computed,
closedRequestIds: computed,
filteredRequestIds: computed,
fetchRequests: action,
fetchRequestById: action,
createRequest: action,
handleCurrentTab: action,
upsertRequests: action,
updateRequestIssue: action,
});
this.externalContourService = new ExternalContourService();
}
get openRequestIds() {
return this.requestIds.filter((requestId) => this.requests[requestId]?.status === "open");
}
get closedRequestIds() {
return this.requestIds.filter((requestId) => this.requests[requestId]?.status === "closed");
}
get filteredRequestIds() {
return this.currentTab === EInboxIssueCurrentTab.CLOSED ? this.closedRequestIds : this.openRequestIds;
}
getRequestById = (requestId: string) => this.requests[requestId];
getIsRequestAvailable = (requestId: string) => this.requestIds.includes(requestId);
upsertRequests = (requests: TExternalContourRequest[]) => {
requests.forEach((request) => {
set(this.requests, request.id, request);
if (!this.requestIds.includes(request.id)) this.requestIds.push(request.id);
});
this.requestIds = this.requestIds.sort((left, right) => {
const leftUpdatedAt = this.requests[left]?.updated_at || "";
const rightUpdatedAt = this.requests[right]?.updated_at || "";
return rightUpdatedAt.localeCompare(leftUpdatedAt);
});
};
handleCurrentTab = async (workspaceSlug: string, projectId: string, tab: TInboxIssueCurrentTab) => {
this.currentTab = tab;
await this.fetchRequests(workspaceSlug, projectId, tab);
};
fetchRequests = async (workspaceSlug: string, projectId: string, tab = this.currentTab) => {
this.loader = "init-loading";
this.error = undefined;
this.currentProjectId = projectId;
this.currentTab = tab;
try {
const response = await this.externalContourService.list(workspaceSlug, projectId);
runInAction(() => {
this.requestIds = [];
this.requests = {};
this.upsertRequests(response.results || []);
this.loader = undefined;
});
} catch (error: any) {
runInAction(() => {
this.loader = undefined;
this.error = {
message: error?.error || "Не удалось загрузить внешние контуры",
status: "init-error",
};
});
}
};
fetchRequestById = async (workspaceSlug: string, projectId: string, requestId: string) => {
this.loader = "issue-loading";
try {
const request = await this.externalContourService.retrieve(workspaceSlug, projectId, requestId);
runInAction(() => {
this.upsertRequests([request]);
this.loader = undefined;
});
return request;
} catch (error) {
runInAction(() => {
this.loader = undefined;
});
return undefined;
}
};
createRequest = async (workspaceSlug: string, projectId: string, data: Partial<TIssue> & { target_project_id?: string | null }) => {
this.loader = "mutation-loading";
try {
const request = await this.externalContourService.create(workspaceSlug, projectId, data);
runInAction(() => {
this.upsertRequests([request]);
this.currentTab = EInboxIssueCurrentTab.OPEN;
this.loader = undefined;
});
return request;
} catch (error) {
runInAction(() => {
this.loader = undefined;
});
throw error;
}
};
updateRequestIssue = (requestId: string, issueData: Partial<TExternalContourRequest["issue"]>) => {
if (!this.requests[requestId]) return;
const nextStatus =
issueData.state_detail?.group !== undefined
? ["completed", "cancelled"].includes(issueData.state_detail.group)
? "closed"
: "open"
: this.requests[requestId].status;
this.requests[requestId] = {
...this.requests[requestId],
issue: {
...this.requests[requestId].issue,
...issueData,
},
status: nextStatus,
};
};
}
@@ -31,6 +31,8 @@ import type { IEditorAssetStore } from "./editor/asset.store";
import { EditorAssetStore } from "./editor/asset.store";
import type { IProjectEstimateStore } from "./estimates/project-estimate.store";
import { ProjectEstimateStore } from "./estimates/project-estimate.store";
import type { IProjectExternalContoursStore } from "./external-contours/project-external-contours.store";
import { ProjectExternalContoursStore } from "./external-contours/project-external-contours.store";
import type { IFavoriteStore } from "./favorite.store";
import { FavoriteStore } from "./favorite.store";
import type { IGlobalViewStore } from "./global-view.store";
@@ -93,6 +95,7 @@ export class CoreRootStore {
instance: IInstanceStore;
user: IUserStore;
projectInbox: IProjectInboxStore;
projectExternalContours: IProjectExternalContoursStore;
projectEstimate: IProjectEstimateStore;
multipleSelect: IMultipleSelectStore;
workspaceNotification: IWorkspaceNotificationStore;
@@ -123,6 +126,7 @@ export class CoreRootStore {
this.dashboard = new DashboardStore(this);
this.multipleSelect = new MultipleSelectStore();
this.projectInbox = new ProjectInboxStore(this);
this.projectExternalContours = new ProjectExternalContoursStore(this);
this.projectPages = new ProjectPageStore(this as unknown as RootStore);
this.projectEstimate = new ProjectEstimateStore(this);
this.workspaceNotification = new WorkspaceNotificationStore(this);
@@ -156,6 +160,7 @@ export class CoreRootStore {
this.label = new LabelStore(this);
this.dashboard = new DashboardStore(this);
this.projectInbox = new ProjectInboxStore(this);
this.projectExternalContours = new ProjectExternalContoursStore(this);
this.projectPages = new ProjectPageStore(this as unknown as RootStore);
this.multipleSelect = new MultipleSelectStore();
this.projectEstimate = new ProjectEstimateStore(this);