ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: отказ от open-closed табов и стабилизация фильтров внешних контуров

This commit is contained in:
DCCONSTRUCTIONS 2026-04-21 08:32:20 +03:00
parent c6645bb4fc
commit 91906e917e
10 changed files with 38 additions and 129 deletions

View File

@ -6,20 +6,19 @@
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import type { TExternalContourBoardDirection, TInboxIssueCurrentTab } from "@plane/types"; import type { TExternalContourBoardDirection } from "@plane/types";
import { useProjectExternalContoursBoard } from "@/hooks/store/use-project-external-contours-board"; import { useProjectExternalContoursBoard } from "@/hooks/store/use-project-external-contours-board";
import { ExternalContoursBoardItem } from "./board-item"; import { ExternalContoursBoardItem } from "./board-item";
import { ExternalContoursEmptyState } from "./empty-state"; import { ExternalContoursEmptyState } from "./empty-state";
type Props = { type Props = {
currentTab: TInboxIssueCurrentTab;
direction: TExternalContourBoardDirection; direction: TExternalContourBoardDirection;
projectId: string; projectId: string;
workspaceSlug: string; workspaceSlug: string;
}; };
export const ExternalContoursBoardColumn = observer(function ExternalContoursBoardColumn(props: Props) { export const ExternalContoursBoardColumn = observer(function ExternalContoursBoardColumn(props: Props) {
const { currentTab, direction, projectId, workspaceSlug } = props; const { direction, projectId, workspaceSlug } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const { getColumnRequestIds, getColumnTotalCount, getRequestById } = useProjectExternalContoursBoard(); const { getColumnRequestIds, getColumnTotalCount, getRequestById } = useProjectExternalContoursBoard();
const requestIds = getColumnRequestIds(direction); const requestIds = getColumnRequestIds(direction);
@ -57,7 +56,6 @@ export const ExternalContoursBoardColumn = observer(function ExternalContoursBoa
return ( return (
<ExternalContoursBoardItem <ExternalContoursBoardItem
key={requestId} key={requestId}
currentTab={currentTab}
direction={direction} direction={direction}
projectId={projectId} projectId={projectId}
request={request} request={request}

View File

@ -431,14 +431,7 @@ const buildOptionsWithSelectedFallback = (
}; };
visibleRequests.forEach(upsertOption); visibleRequests.forEach(upsertOption);
cachedRequests.forEach(upsertOption);
if (selectedIds.length > 0) {
cachedRequests.forEach((request) => {
const option = getOption(request);
if (!option?.id || !selectedIds.includes(option.id) || optionMap.has(option.id)) return;
optionMap.set(option.id, option);
});
}
return sortFilterOptions(Array.from(optionMap.values())).map((option) => ({ return sortFilterOptions(Array.from(optionMap.values())).map((option) => ({
data: option, data: option,
@ -526,19 +519,7 @@ const getAssigneeOptions = (
}; };
visibleRequests.forEach(upsertAssignees); visibleRequests.forEach(upsertAssignees);
cachedRequests.forEach(upsertAssignees);
if (selectedIds.length > 0) {
cachedRequests.forEach((request) => {
request.issue.assignee_details?.forEach((assignee) => {
if (!assignee?.id || !selectedIds.includes(assignee.id) || assigneeMap.has(assignee.id)) return;
assigneeMap.set(assignee.id, {
id: assignee.id,
label: assignee.display_name || "NODE.DC",
avatarUrl: assignee.avatar_url || "",
});
});
});
}
return sortFilterOptions(Array.from(assigneeMap.values())).map((option) => ({ data: option, value: option.id })); return sortFilterOptions(Array.from(assigneeMap.values())).map((option) => ({ data: option, value: option.id }));
}; };

View File

@ -16,7 +16,6 @@ import type {
IState, IState,
TExternalContourBoardDirection, TExternalContourBoardDirection,
TExternalContourRequest, TExternalContourRequest,
TInboxIssueCurrentTab,
TIssue, TIssue,
} from "@plane/types"; } from "@plane/types";
import { Avatar } from "@plane/ui"; import { Avatar } from "@plane/ui";
@ -37,7 +36,6 @@ import { useUserPermissions } from "@/hooks/store/user";
import { IssueService } from "@/services/issue/issue.service"; import { IssueService } from "@/services/issue/issue.service";
type Props = { type Props = {
currentTab: TInboxIssueCurrentTab;
direction: TExternalContourBoardDirection; direction: TExternalContourBoardDirection;
projectId: string; projectId: string;
request: TExternalContourRequest; request: TExternalContourRequest;
@ -78,7 +76,7 @@ const resolveRequestStatus = (issue: TExternalContourRequest["issue"], fallbackS
}; };
export const ExternalContoursBoardItem = observer(function ExternalContoursBoardItem(props: Props) { export const ExternalContoursBoardItem = observer(function ExternalContoursBoardItem(props: Props) {
const { currentTab, direction, projectId, request, workspaceSlug } = props; const { direction, projectId, request, workspaceSlug } = props;
const router = useAppRouter(); const router = useAppRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const { t } = useTranslation(); const { t } = useTranslation();
@ -86,7 +84,6 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
const { getProjectRoleByWorkspaceSlugAndProjectId } = useUserPermissions(); const { getProjectRoleByWorkspaceSlugAndProjectId } = useUserPermissions();
const { getStateById, getProjectStateIds } = useProjectState(); const { getStateById, getProjectStateIds } = useProjectState();
const { const {
currentTab: boardCurrentTab,
fetchBoard, fetchBoard,
upsertBoardItems, upsertBoardItems,
} = useProjectExternalContoursBoard(); } = useProjectExternalContoursBoard();
@ -116,7 +113,7 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
direction === "incoming" && !!targetProjectId && projectRole !== undefined && projectRole !== EUserPermissions.GUEST; direction === "incoming" && !!targetProjectId && projectRole !== undefined && projectRole !== EUserPermissions.GUEST;
const canEditSourceRequest = direction === "outgoing" && !!request.capabilities?.can_edit_request && !!targetProjectId; const canEditSourceRequest = direction === "outgoing" && !!request.capabilities?.can_edit_request && !!targetProjectId;
const canEditCard = canEditTargetIssue || canEditSourceRequest; const canEditCard = canEditTargetIssue || canEditSourceRequest;
const requestLink = `/${workspaceSlug}/projects/${projectId}/external-contours?currentTab=${currentTab}&inboxIssueId=${request.id}`; const requestLink = `/${workspaceSlug}/projects/${projectId}/external-contours?inboxIssueId=${request.id}`;
const targetOptions = getTargetOptionsByProjectId(targetProjectId); const targetOptions = getTargetOptionsByProjectId(targetProjectId);
const sourceStateMap = useMemo( const sourceStateMap = useMemo(
() => buildSourceStateMap(targetOptions?.states, targetProjectId), () => buildSourceStateMap(targetOptions?.states, targetProjectId),
@ -145,7 +142,7 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
}; };
const syncBoardAfterMutation = async () => { const syncBoardAfterMutation = async () => {
await fetchBoard(workspaceSlug, projectId, boardCurrentTab ?? currentTab); await fetchBoard(workspaceSlug, projectId);
}; };
const ensureSourceOptions = async () => { const ensureSourceOptions = async () => {

View File

@ -6,11 +6,8 @@
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import type { TInboxIssueCurrentTab } from "@plane/types";
import { EInboxIssueCurrentTab } from "@plane/types";
import { cn } from "@plane/utils"; import { cn } from "@plane/utils";
import { useProjectExternalContoursBoard } from "@/hooks/store/use-project-external-contours-board"; import { useProjectExternalContoursBoard } from "@/hooks/store/use-project-external-contours-board";
import { useAppRouter } from "@/hooks/use-app-router";
import { ExternalContoursBoardFiltersRow } from "./board-filters-row"; import { ExternalContoursBoardFiltersRow } from "./board-filters-row";
import { ExternalContoursBoardColumn } from "./board-column"; import { ExternalContoursBoardColumn } from "./board-column";
@ -19,51 +16,14 @@ type Props = {
workspaceSlug: string; workspaceSlug: string;
}; };
const tabNavigationOptions: { key: TInboxIssueCurrentTab; i18nLabel: string }[] = [
{ key: EInboxIssueCurrentTab.OPEN, i18nLabel: "external_contours_page.tabs.open" },
{ key: EInboxIssueCurrentTab.CLOSED, i18nLabel: "external_contours_page.tabs.closed" },
];
export const ExternalContoursBoardRoot = observer(function ExternalContoursBoardRoot(props: Props) { export const ExternalContoursBoardRoot = observer(function ExternalContoursBoardRoot(props: Props) {
const { projectId, workspaceSlug } = props; const { projectId, workspaceSlug } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const router = useAppRouter(); const { hasAnyItems, isFiltering, loader } = useProjectExternalContoursBoard();
const { currentTab, hasAnyItems, isFiltering, loader, tabCountMap, handleCurrentTab } = useProjectExternalContoursBoard();
return ( return (
<div className="flex h-full min-h-0 flex-col overflow-hidden px-8 pb-6"> <div className="flex h-full min-h-0 flex-col overflow-hidden px-8 pb-6">
<div className="flex shrink-0 items-center gap-2 py-4"> <div className="shrink-0 py-4">
<div className="nodedc-filter-row-shell flex items-center gap-2 p-1">
{tabNavigationOptions.map((option) => {
const count = tabCountMap[option.key] ?? 0;
return (
<button
type="button"
key={option.key}
data-active={currentTab === option.key}
className={cn("nodedc-external-tab flex min-w-[10rem] items-center justify-center gap-2 text-13 font-medium transition-all")}
onClick={() => {
if (currentTab === option.key) return;
void handleCurrentTab(workspaceSlug, projectId, option.key);
router.push(`/${workspaceSlug}/projects/${projectId}/external-contours?currentTab=${option.key}`);
}}
>
<div>{t(option.i18nLabel)}</div>
<div
className={cn(
"rounded-full px-1.5 py-0.5 text-11 font-semibold",
currentTab === option.key ? "bg-accent-primary/15 text-accent-primary" : "bg-white/5 text-secondary"
)}
>
{count}
</div>
</button>
);
})}
</div>
</div>
<div className="shrink-0 pb-4">
<ExternalContoursBoardFiltersRow workspaceSlug={workspaceSlug} projectId={projectId} /> <ExternalContoursBoardFiltersRow workspaceSlug={workspaceSlug} projectId={projectId} />
</div> </div>
@ -84,13 +44,11 @@ export const ExternalContoursBoardRoot = observer(function ExternalContoursBoard
)} )}
> >
<ExternalContoursBoardColumn <ExternalContoursBoardColumn
currentTab={currentTab}
direction="outgoing" direction="outgoing"
projectId={projectId} projectId={projectId}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
/> />
<ExternalContoursBoardColumn <ExternalContoursBoardColumn
currentTab={currentTab}
direction="incoming" direction="incoming"
projectId={projectId} projectId={projectId}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}

View File

@ -35,7 +35,7 @@ export const ExternalContoursContentRoot = observer(function ExternalContoursCon
const [isSubmitting, setIsSubmitting] = useState<TNameDescriptionLoader>("saved"); const [isSubmitting, setIsSubmitting] = useState<TNameDescriptionLoader>("saved");
const [isDetailResolved, setIsDetailResolved] = useState(false); const [isDetailResolved, setIsDetailResolved] = useState(false);
const { data: currentUser } = useUser(); const { data: currentUser } = useUser();
const { currentTab, fetchRequestById, getRequestById } = useProjectExternalContours(); const { fetchRequestById, getRequestById } = useProjectExternalContours();
const contourRequest = getRequestById(inboxIssueId); const contourRequest = getRequestById(inboxIssueId);
const issue = contourRequest?.issue; const issue = contourRequest?.issue;
const targetProjectId = issue?.project_id || projectId; const targetProjectId = issue?.project_id || projectId;
@ -46,10 +46,10 @@ export const ExternalContoursContentRoot = observer(function ExternalContoursCon
useEffect(() => { useEffect(() => {
if (isDetailResolved && !contourRequest && inboxIssueId) { if (isDetailResolved && !contourRequest && inboxIssueId) {
router.replace(`/${workspaceSlug}/projects/${projectId}/external-contours?currentTab=${currentTab}`); router.replace(`/${workspaceSlug}/projects/${projectId}/external-contours`);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [contourRequest, currentTab, inboxIssueId, isDetailResolved, projectId, router, workspaceSlug]); }, [contourRequest, inboxIssueId, isDetailResolved, projectId, router, workspaceSlug]);
useSWR( useSWR(
workspaceSlug && projectId && inboxIssueId workspaceSlug && projectId && inboxIssueId
@ -96,7 +96,7 @@ export const ExternalContoursContentRoot = observer(function ExternalContoursCon
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
embedIssue={embedIssue} embedIssue={embedIssue}
embedRemoveCurrentNotification={embedRemoveCurrentNotification} embedRemoveCurrentNotification={embedRemoveCurrentNotification}
onClose={() => router.replace(`/${workspaceSlug}/projects/${projectId}/external-contours?currentTab=${currentTab}`)} onClose={() => router.replace(`/${workspaceSlug}/projects/${projectId}/external-contours`)}
> >
<ExternalContoursIssueMainContent <ExternalContoursIssueMainContent
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}

View File

@ -12,7 +12,6 @@ import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button"; import { Button } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { TIssue } from "@plane/types"; import type { TIssue } from "@plane/types";
import { EInboxIssueCurrentTab } from "@plane/types";
import { ToggleSwitch } from "@plane/ui"; import { ToggleSwitch } from "@plane/ui";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useProjectExternalContours } from "@/hooks/store/use-project-external-contours"; import { useProjectExternalContours } from "@/hooks/store/use-project-external-contours";
@ -110,7 +109,7 @@ export const ExternalContoursCreateRoot = observer(function ExternalContoursCrea
} }
if (createdRequest?.id) { if (createdRequest?.id) {
router.push(`/${workspaceSlug}/projects/${projectId}/external-contours?currentTab=${EInboxIssueCurrentTab.OPEN}&inboxIssueId=${createdRequest.id}`); router.push(`/${workspaceSlug}/projects/${projectId}/external-contours?inboxIssueId=${createdRequest.id}`);
} }
} catch (error: any) { } catch (error: any) {
setToast({ setToast({

View File

@ -23,7 +23,6 @@ import {
} from "@plane/propel/icons"; } from "@plane/propel/icons";
import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { TExternalContourRequest, TNameDescriptionLoader } from "@plane/types"; import type { TExternalContourRequest, TNameDescriptionLoader } from "@plane/types";
import { EInboxIssueCurrentTab } from "@plane/types";
import { ControlLink, CustomSelect, Header, Row, Tooltip } from "@plane/ui"; import { ControlLink, CustomSelect, Header, Row, Tooltip } from "@plane/ui";
import { copyUrlToClipboard, generateWorkItemLink } from "@plane/utils"; import { copyUrlToClipboard, generateWorkItemLink } from "@plane/utils";
import { NameDescriptionUpdateStatus } from "@/components/issues/issue-update-status"; import { NameDescriptionUpdateStatus } from "@/components/issues/issue-update-status";
@ -68,7 +67,6 @@ type Props = {
removeRoutePeekId: () => void; removeRoutePeekId: () => void;
peekMode: TExternalContourPeekMode; peekMode: TExternalContourPeekMode;
setPeekMode: (value: TExternalContourPeekMode) => void; setPeekMode: (value: TExternalContourPeekMode) => void;
currentTab: TInboxIssueCurrentTab;
embedIssue?: boolean; embedIssue?: boolean;
}; };
@ -82,20 +80,18 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
removeRoutePeekId, removeRoutePeekId,
peekMode, peekMode,
setPeekMode, setPeekMode,
currentTab,
embedIssue = false, embedIssue = false,
} = props; } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const router = useAppRouter(); const router = useAppRouter();
const [isDeclineModalOpen, setIsDeclineModalOpen] = useState(false); const [isDeclineModalOpen, setIsDeclineModalOpen] = useState(false);
const { decideRequest, filteredRequestIds, handleCurrentTab, loader } = useProjectExternalContours(); const { decideRequest, filteredRequestIds, loader } = useProjectExternalContours();
const { currentTab: boardCurrentTab, columnIdsMap } = useProjectExternalContoursBoard(); const { columnIdsMap } = useProjectExternalContoursBoard();
const { getProjectById } = useProject(); const { getProjectById } = useProject();
const issue = contourRequest.issue; const issue = contourRequest.issue;
const currentRequestId = contourRequest.id; const currentRequestId = contourRequest.id;
const boardRequestIds = const boardRequestIds = [...(columnIdsMap.outgoing ?? []), ...(columnIdsMap.incoming ?? [])];
boardCurrentTab === currentTab ? [...(columnIdsMap.outgoing ?? []), ...(columnIdsMap.incoming ?? [])] : [];
const relativeRequestIds = boardRequestIds.includes(currentRequestId) ? boardRequestIds : filteredRequestIds; const relativeRequestIds = boardRequestIds.includes(currentRequestId) ? boardRequestIds : filteredRequestIds;
const currentMode = PEEK_OPTIONS.find((mode) => mode.key === peekMode); const currentMode = PEEK_OPTIONS.find((mode) => mode.key === peekMode);
const hasRelativeNavigation = !!currentRequestId && relativeRequestIds.includes(currentRequestId); const hasRelativeNavigation = !!currentRequestId && relativeRequestIds.includes(currentRequestId);
@ -115,9 +111,9 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
: (currentIssueIndex - 1 + relativeRequestIds.length) % relativeRequestIds.length; : (currentIssueIndex - 1 + relativeRequestIds.length) % relativeRequestIds.length;
const nextIssueId = relativeRequestIds[nextIssueIndex]; const nextIssueId = relativeRequestIds[nextIssueIndex];
if (!nextIssueId) return; if (!nextIssueId) return;
router.push(`/${workspaceSlug}/projects/${sourceProjectId}/external-contours?currentTab=${currentTab}&inboxIssueId=${nextIssueId}`); router.push(`/${workspaceSlug}/projects/${sourceProjectId}/external-contours?inboxIssueId=${nextIssueId}`);
}, },
[currentRequestId, currentTab, hasRelativeNavigation, relativeRequestIds, router, sourceProjectId, workspaceSlug] [currentRequestId, hasRelativeNavigation, relativeRequestIds, router, sourceProjectId, workspaceSlug]
); );
useEffect(() => { useEffect(() => {
@ -131,8 +127,7 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
}, [redirectToRelativeIssue]); }, [redirectToRelativeIssue]);
const targetProjectIdentifier = issue.project_detail?.identifier || getProjectById(issue.project_id || "")?.identifier; const targetProjectIdentifier = issue.project_detail?.identifier || getProjectById(issue.project_id || "")?.identifier;
const requestTab = contourRequest.status === "closed" ? EInboxIssueCurrentTab.CLOSED : EInboxIssueCurrentTab.OPEN; const requestLink = `/${workspaceSlug}/projects/${sourceProjectId}/external-contours?inboxIssueId=${contourRequest.id}`;
const requestLink = `/${workspaceSlug}/projects/${sourceProjectId}/external-contours?currentTab=${requestTab}&inboxIssueId=${contourRequest.id}`;
const workItemLink = generateWorkItemLink({ const workItemLink = generateWorkItemLink({
workspaceSlug: workspaceSlug?.toString(), workspaceSlug: workspaceSlug?.toString(),
projectId: issue.project_id, projectId: issue.project_id,
@ -183,10 +178,7 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
await decideRequest(workspaceSlug, sourceProjectId, contourRequest.id, action, comment); await decideRequest(workspaceSlug, sourceProjectId, contourRequest.id, action, comment);
if (action === "decline") { if (action === "decline") {
setIsDeclineModalOpen(false); setIsDeclineModalOpen(false);
await handleCurrentTab(workspaceSlug, sourceProjectId, EInboxIssueCurrentTab.OPEN); router.push(`/${workspaceSlug}/projects/${sourceProjectId}/external-contours?inboxIssueId=${contourRequest.id}`);
router.push(
`/${workspaceSlug}/projects/${sourceProjectId}/external-contours?currentTab=${EInboxIssueCurrentTab.OPEN}&inboxIssueId=${contourRequest.id}`
);
} }
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,

View File

@ -9,7 +9,6 @@ import { useCallback, useEffect, useRef, useState, type MouseEvent as ReactMouse
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { cn } from "@plane/utils"; import { cn } from "@plane/utils";
import type { TExternalContourRequest, TNameDescriptionLoader } from "@plane/types"; import type { TExternalContourRequest, TNameDescriptionLoader } from "@plane/types";
import { useProjectExternalContours } from "@/hooks/store/use-project-external-contours";
import useKeypress from "@/hooks/use-keypress"; import useKeypress from "@/hooks/use-keypress";
import usePeekOverviewOutsideClickDetector from "@/hooks/use-peek-overview-outside-click"; import usePeekOverviewOutsideClickDetector from "@/hooks/use-peek-overview-outside-click";
import { ExternalContoursIssueActionsHeader, type TExternalContourPeekMode } from "./issue-header"; import { ExternalContoursIssueActionsHeader, type TExternalContourPeekMode } from "./issue-header";
@ -55,7 +54,6 @@ export const ExternalContoursPeekShell = observer(function ExternalContoursPeekS
const issuePeekOverviewRef = useRef<HTMLDivElement>(null); const issuePeekOverviewRef = useRef<HTMLDivElement>(null);
const initialPeekWidthRef = useRef<number>(0); const initialPeekWidthRef = useRef<number>(0);
const initialMouseXRef = useRef<number>(0); const initialMouseXRef = useRef<number>(0);
const { currentTab } = useProjectExternalContours();
const removeRoutePeekId = useCallback(() => { const removeRoutePeekId = useCallback(() => {
if (embedIssue) { if (embedIssue) {
@ -190,7 +188,6 @@ export const ExternalContoursPeekShell = observer(function ExternalContoursPeekS
removeRoutePeekId={removeRoutePeekId} removeRoutePeekId={removeRoutePeekId}
peekMode={peekMode} peekMode={peekMode}
setPeekMode={setPeekMode} setPeekMode={setPeekMode}
currentTab={currentTab}
embedIssue={embedIssue} embedIssue={embedIssue}
/> />
<div className="vertical-scrollbar relative scrollbar-md h-full w-full overflow-hidden overflow-y-auto"> <div className="vertical-scrollbar relative scrollbar-md h-full w-full overflow-hidden overflow-y-auto">

View File

@ -28,9 +28,7 @@ export const ExternalContoursRoot = observer(function ExternalContoursRoot(props
const { const {
error: boardError, error: boardError,
currentProjectId: boardProjectId, currentProjectId: boardProjectId,
currentTab: boardCurrentTab,
fetchBoard, fetchBoard,
handleCurrentTab: handleBoardCurrentTab,
loader: boardLoader, loader: boardLoader,
} = useProjectExternalContoursBoard(); } = useProjectExternalContoursBoard();
@ -61,20 +59,11 @@ export const ExternalContoursRoot = observer(function ExternalContoursRoot(props
useEffect(() => { useEffect(() => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
if (boardProjectId === projectId && boardLoader === "init-loading") return;
const resolvedTab = navigationTab || EInboxIssueCurrentTab.OPEN; void fetchBoard(workspaceSlug.toString(), projectId.toString());
const hasProjectChanged = boardProjectId && boardProjectId !== projectId;
if (boardProjectId === projectId && boardCurrentTab === resolvedTab && boardLoader === "init-loading") return;
if (hasProjectChanged || boardCurrentTab !== resolvedTab) {
void handleBoardCurrentTab(workspaceSlug, projectId, resolvedTab);
return;
}
void fetchBoard(workspaceSlug.toString(), projectId.toString(), resolvedTab);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [workspaceSlug, projectId, navigationTab]); }, [workspaceSlug, projectId]);
if (error && error?.status === "init-error" && !!inboxIssueId) { if (error && error?.status === "init-error" && !!inboxIssueId) {
return ( return (

View File

@ -59,7 +59,7 @@ export class ProjectExternalContoursBoardStore implements IProjectExternalContou
currentProjectId = ""; currentProjectId = "";
currentTab: TInboxIssueCurrentTab = EInboxIssueCurrentTab.OPEN; currentTab: TInboxIssueCurrentTab = EInboxIssueCurrentTab.OPEN;
error: { message: string; status: "init-error" } | undefined = undefined; error: { message: string; status: "init-error" } | undefined = undefined;
filters: Partial<TExternalContourBoardFilter> = { status: [EInboxIssueCurrentTab.OPEN] }; filters: Partial<TExternalContourBoardFilter> = {};
items: Record<string, TExternalContourRequest> = {}; items: Record<string, TExternalContourRequest> = {};
loader: TLoader = "init-loading"; loader: TLoader = "init-loading";
sorting: TExternalContourBoardSorting = DEFAULT_SORTING; sorting: TExternalContourBoardSorting = DEFAULT_SORTING;
@ -145,10 +145,6 @@ export class ProjectExternalContoursBoardStore implements IProjectExternalContou
handleCurrentTab = async (workspaceSlug: string, projectId: string, tab: TInboxIssueCurrentTab) => { handleCurrentTab = async (workspaceSlug: string, projectId: string, tab: TInboxIssueCurrentTab) => {
this.currentTab = tab; this.currentTab = tab;
this.filters = sanitizeBoardFilters({
...this.filters,
status: [tab],
});
await this.fetchBoard(workspaceSlug, projectId, tab); await this.fetchBoard(workspaceSlug, projectId, tab);
}; };
@ -160,30 +156,26 @@ export class ProjectExternalContoursBoardStore implements IProjectExternalContou
this.filters = sanitizeBoardFilters({ this.filters = sanitizeBoardFilters({
...this.filters, ...this.filters,
...filters, ...filters,
status: [this.currentTab],
}); });
await this.fetchBoard(workspaceSlug, projectId, this.currentTab); await this.fetchBoard(workspaceSlug, projectId);
}; };
updateSorting = async (workspaceSlug: string, projectId: string, sorting: TExternalContourBoardSorting) => { updateSorting = async (workspaceSlug: string, projectId: string, sorting: TExternalContourBoardSorting) => {
this.sorting = sorting; this.sorting = sorting;
await this.fetchBoard(workspaceSlug, projectId, this.currentTab); await this.fetchBoard(workspaceSlug, projectId);
}; };
clearFilters = async (workspaceSlug: string, projectId: string) => { clearFilters = async (workspaceSlug: string, projectId: string) => {
this.filters = { status: [this.currentTab] }; this.filters = {};
this.sorting = DEFAULT_SORTING; this.sorting = DEFAULT_SORTING;
await this.fetchBoard(workspaceSlug, projectId, this.currentTab); await this.fetchBoard(workspaceSlug, projectId);
}; };
fetchBoard = async (workspaceSlug: string, projectId: string, tab = this.currentTab) => { fetchBoard = async (workspaceSlug: string, projectId: string, tab = this.currentTab) => {
const hasProjectChanged = !!this.currentProjectId && this.currentProjectId !== projectId; const hasProjectChanged = !!this.currentProjectId && this.currentProjectId !== projectId;
const isInitialLoad = this.hydratedProjectId !== projectId; const isInitialLoad = this.hydratedProjectId !== projectId;
const nextFilters = sanitizeBoardFilters({ const nextFilters = sanitizeBoardFilters(hasProjectChanged ? {} : this.filters);
...(hasProjectChanged ? {} : this.filters),
status: [tab],
});
const nextSorting = hasProjectChanged ? DEFAULT_SORTING : this.sorting; const nextSorting = hasProjectChanged ? DEFAULT_SORTING : this.sorting;
const requestId = ++this.lastIssuedRequestId; const requestId = ++this.lastIssuedRequestId;
@ -213,16 +205,22 @@ export class ProjectExternalContoursBoardStore implements IProjectExternalContou
this.filters = sanitizeBoardFilters(response.filters || nextFilters); this.filters = sanitizeBoardFilters(response.filters || nextFilters);
this.sorting = response.sorting || nextSorting; this.sorting = response.sorting || nextSorting;
this.hydratedProjectId = projectId; this.hydratedProjectId = projectId;
let openCount = 0;
let closedCount = 0;
response.columns.forEach((column) => { response.columns.forEach((column) => {
this.columnIdsMap[column.key] = column.results.map((request) => request.id); this.columnIdsMap[column.key] = column.results.map((request) => request.id);
this.columnCountMap[column.key] = column.total_count; this.columnCountMap[column.key] = column.total_count;
column.results.forEach((request) => {
if (request.status === EInboxIssueCurrentTab.CLOSED) closedCount += 1;
else openCount += 1;
});
this.upsertBoardItems(column.results); this.upsertBoardItems(column.results);
}); });
this.tabCountMap = { this.tabCountMap = {
...this.tabCountMap, [EInboxIssueCurrentTab.OPEN]: openCount,
[tab]: response.columns.reduce((total, column) => total + column.total_count, 0), [EInboxIssueCurrentTab.CLOSED]: closedCount,
}; };
this.loader = undefined; this.loader = undefined;