ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: отказ от open-closed табов и стабилизация фильтров внешних контуров
This commit is contained in:
parent
c6645bb4fc
commit
91906e917e
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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 }));
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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 () => {
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue