ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: frontend read-layer и первый экран двусторонней доски внешних контуров

This commit is contained in:
DCCONSTRUCTIONS
2026-04-20 20:49:09 +03:00
parent 0184ff9a32
commit 8bf6f2a510
15 changed files with 603 additions and 69 deletions
@@ -102,8 +102,6 @@ export const NotificationsRoot = observer(function NotificationsRoot({ workspace
</div>
) : (
<ExternalContoursContentRoot
setIsMobileSidebar={() => {}}
isMobileSidebar={false}
workspaceSlug={workspace_slug}
projectId={project_id}
inboxIssueId={issue_id}
@@ -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 { IProjectExternalContoursBoardStore } from "@/store/external-contours/project-external-contours-board.store";
export const useProjectExternalContoursBoard = (): IProjectExternalContoursBoardStore => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useProjectExternalContoursBoard must be used within StoreProvider");
return context.projectExternalContoursBoard;
};
@@ -6,6 +6,8 @@
import { API_BASE_URL } from "@plane/constants";
import type {
TExternalContourBoardFilter,
TExternalContourBoardResponse,
TExternalContourRequest,
TExternalContourRequestResponse,
TExternalContourTargetOptions,
@@ -27,6 +29,26 @@ export class ExternalContourService extends APIService {
});
}
async listBoard(
workspaceSlug: string,
projectId: string,
filters: Partial<TExternalContourBoardFilter> = {}
): Promise<TExternalContourBoardResponse> {
const params = Object.fromEntries(
Object.entries(filters).flatMap(([key, value]) => {
if (value === undefined || value === null || value === "") return [];
if (Array.isArray(value)) return [[key, value.join(",")]];
return [[key, String(value)]];
})
);
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/external-contours/board/`, { params })
.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)
@@ -35,6 +57,14 @@ export class ExternalContourService extends APIService {
});
}
async retrieveBoardItem(workspaceSlug: string, projectId: string, requestId: string): Promise<TExternalContourRequest> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/external-contours/board-items/${requestId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async updateRequest(
workspaceSlug: string,
projectId: string,
@@ -0,0 +1,158 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { action, computed, makeObservable, observable, runInAction } from "mobx";
import type {
TExternalContourBoardDirection,
TExternalContourBoardFilter,
TExternalContourBoardSorting,
TExternalContourRequest,
TInboxIssueCurrentTab,
} from "@plane/types";
import { EInboxIssueCurrentTab } from "@plane/types";
import { ExternalContourService } from "@/services/external-contours";
import type { CoreRootStore } from "../root.store";
type TLoader = "init-loading" | undefined;
export interface IProjectExternalContoursBoardStore {
currentProjectId: string;
currentTab: TInboxIssueCurrentTab;
error: { message: string; status: "init-error" } | undefined;
filters: Partial<TExternalContourBoardFilter>;
items: Record<string, TExternalContourRequest>;
loader: TLoader;
sorting: TExternalContourBoardSorting;
columnIdsMap: Record<TExternalContourBoardDirection, string[]>;
columnCountMap: Record<TExternalContourBoardDirection, number>;
tabCountMap: Record<TInboxIssueCurrentTab, number>;
fetchBoard: (workspaceSlug: string, projectId: string, tab?: TInboxIssueCurrentTab) => Promise<void>;
getColumnRequestIds: (direction: TExternalContourBoardDirection) => string[];
getColumnTotalCount: (direction: TExternalContourBoardDirection) => number;
getRequestById: (requestId: string) => TExternalContourRequest | undefined;
handleCurrentTab: (workspaceSlug: string, projectId: string, tab: TInboxIssueCurrentTab) => Promise<void>;
hasAnyItems: boolean;
upsertBoardItems: (items: TExternalContourRequest[]) => void;
}
export class ProjectExternalContoursBoardStore implements IProjectExternalContoursBoardStore {
currentProjectId = "";
currentTab: TInboxIssueCurrentTab = EInboxIssueCurrentTab.OPEN;
error: { message: string; status: "init-error" } | undefined = undefined;
filters: Partial<TExternalContourBoardFilter> = { status: [EInboxIssueCurrentTab.OPEN] };
items: Record<string, TExternalContourRequest> = {};
loader: TLoader = "init-loading";
sorting: TExternalContourBoardSorting = { order_by: "updated_at", sort_by: "desc" };
columnIdsMap: Record<TExternalContourBoardDirection, string[]> = {
outgoing: [],
incoming: [],
};
columnCountMap: Record<TExternalContourBoardDirection, number> = {
outgoing: 0,
incoming: 0,
};
tabCountMap: Record<TInboxIssueCurrentTab, number> = {
[EInboxIssueCurrentTab.OPEN]: 0,
[EInboxIssueCurrentTab.CLOSED]: 0,
};
externalContourService;
constructor(private store: CoreRootStore) {
makeObservable(this, {
currentProjectId: observable.ref,
currentTab: observable.ref,
error: observable.ref,
filters: observable.ref,
items: observable,
loader: observable.ref,
sorting: observable.ref,
columnIdsMap: observable,
columnCountMap: observable,
tabCountMap: observable,
hasAnyItems: computed,
fetchBoard: action,
handleCurrentTab: action,
upsertBoardItems: action,
});
this.externalContourService = new ExternalContourService();
}
get hasAnyItems() {
return this.columnIdsMap.outgoing.length > 0 || this.columnIdsMap.incoming.length > 0;
}
getRequestById = (requestId: string) => this.items[requestId];
getColumnRequestIds = (direction: TExternalContourBoardDirection) => this.columnIdsMap[direction] ?? [];
getColumnTotalCount = (direction: TExternalContourBoardDirection) => this.columnCountMap[direction] ?? 0;
upsertBoardItems = (items: TExternalContourRequest[]) => {
items.forEach((request) => {
this.items[request.id] = request;
});
this.store.projectExternalContours.upsertRequests(items);
};
handleCurrentTab = async (workspaceSlug: string, projectId: string, tab: TInboxIssueCurrentTab) => {
this.currentProjectId = projectId;
this.currentTab = tab;
this.filters = {
...this.filters,
status: [tab],
};
await this.fetchBoard(workspaceSlug, projectId, tab);
};
fetchBoard = async (workspaceSlug: string, projectId: string, tab = this.currentTab) => {
this.loader = "init-loading";
this.error = undefined;
this.currentProjectId = projectId;
this.currentTab = tab;
this.filters = {
...this.filters,
status: [tab],
};
try {
const response = await this.externalContourService.listBoard(workspaceSlug, projectId, {
status: tab,
});
runInAction(() => {
this.items = {};
this.columnIdsMap = { outgoing: [], incoming: [] };
this.columnCountMap = { outgoing: 0, incoming: 0 };
this.filters = response.filters || { status: [tab] };
this.sorting = response.sorting || { order_by: "updated_at", sort_by: "desc" };
response.columns.forEach((column) => {
this.columnIdsMap[column.key] = column.results.map((request) => request.id);
this.columnCountMap[column.key] = column.total_count;
this.upsertBoardItems(column.results);
});
this.tabCountMap = {
...this.tabCountMap,
[tab]: response.columns.reduce((total, column) => total + column.total_count, 0),
};
this.loader = undefined;
});
} catch (error: any) {
runInAction(() => {
this.loader = undefined;
this.error = {
message: error?.error || "Не удалось загрузить доску внешних контуров",
status: "init-error",
};
});
}
};
}
@@ -215,7 +215,7 @@ export class ProjectExternalContoursStore implements IProjectExternalContoursSto
fetchRequestById = async (workspaceSlug: string, projectId: string, requestId: string) => {
this.loader = "issue-loading";
try {
const request = await this.externalContourService.retrieve(workspaceSlug, projectId, requestId);
const request = await this.externalContourService.retrieveBoardItem(workspaceSlug, projectId, requestId);
runInAction(() => {
this.upsertRequests([request]);
this.loader = undefined;
@@ -32,6 +32,8 @@ 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 type { IProjectExternalContoursBoardStore } from "./external-contours/project-external-contours-board.store";
import { ProjectExternalContoursBoardStore } from "./external-contours/project-external-contours-board.store";
import { ProjectExternalContoursStore } from "./external-contours/project-external-contours.store";
import type { IFavoriteStore } from "./favorite.store";
import { FavoriteStore } from "./favorite.store";
@@ -95,6 +97,7 @@ export class CoreRootStore {
instance: IInstanceStore;
user: IUserStore;
projectInbox: IProjectInboxStore;
projectExternalContoursBoard: IProjectExternalContoursBoardStore;
projectExternalContours: IProjectExternalContoursStore;
projectEstimate: IProjectEstimateStore;
multipleSelect: IMultipleSelectStore;
@@ -127,6 +130,7 @@ export class CoreRootStore {
this.multipleSelect = new MultipleSelectStore();
this.projectInbox = new ProjectInboxStore(this);
this.projectExternalContours = new ProjectExternalContoursStore(this);
this.projectExternalContoursBoard = new ProjectExternalContoursBoardStore(this);
this.projectPages = new ProjectPageStore(this as unknown as RootStore);
this.projectEstimate = new ProjectEstimateStore(this);
this.workspaceNotification = new WorkspaceNotificationStore(this);
@@ -161,6 +165,7 @@ export class CoreRootStore {
this.dashboard = new DashboardStore(this);
this.projectInbox = new ProjectInboxStore(this);
this.projectExternalContours = new ProjectExternalContoursStore(this);
this.projectExternalContoursBoard = new ProjectExternalContoursBoardStore(this);
this.projectPages = new ProjectPageStore(this as unknown as RootStore);
this.multipleSelect = new MultipleSelectStore();
this.projectEstimate = new ProjectEstimateStore(this);