ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: frontend read-layer и первый экран двусторонней доски внешних контуров
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { TExternalContourBoardDirection, TInboxIssueCurrentTab } from "@plane/types";
|
||||
import { useProjectExternalContoursBoard } from "@/hooks/store/use-project-external-contours-board";
|
||||
import { ExternalContoursBoardItem } from "./board-item";
|
||||
import { ExternalContoursEmptyState } from "./empty-state";
|
||||
|
||||
type Props = {
|
||||
currentTab: TInboxIssueCurrentTab;
|
||||
direction: TExternalContourBoardDirection;
|
||||
projectId: string;
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export const ExternalContoursBoardColumn = observer(function ExternalContoursBoardColumn(props: Props) {
|
||||
const { currentTab, direction, projectId, workspaceSlug } = props;
|
||||
const { t } = useTranslation();
|
||||
const { getColumnRequestIds, getColumnTotalCount, getRequestById } = useProjectExternalContoursBoard();
|
||||
const requestIds = getColumnRequestIds(direction);
|
||||
const totalCount = getColumnTotalCount(direction);
|
||||
|
||||
const title =
|
||||
direction === "outgoing"
|
||||
? t("external_contours_page.board.columns.outgoing")
|
||||
: t("external_contours_page.board.columns.incoming");
|
||||
|
||||
const emptyTitle =
|
||||
direction === "outgoing"
|
||||
? t("external_contours_page.board.empty.outgoing_title")
|
||||
: t("external_contours_page.board.empty.incoming_title");
|
||||
|
||||
const emptyDescription =
|
||||
direction === "outgoing"
|
||||
? t("external_contours_page.board.empty.outgoing_description")
|
||||
: t("external_contours_page.board.empty.incoming_description");
|
||||
|
||||
return (
|
||||
<section className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-[28px] bg-surface-2/30">
|
||||
<div className="flex items-center justify-between gap-3 border-b border-subtle/40 px-5 py-4">
|
||||
<div className="text-15 font-semibold text-primary">{title}</div>
|
||||
<div className="rounded-full bg-white/5 px-2 py-1 text-12 font-semibold text-secondary">{totalCount}</div>
|
||||
</div>
|
||||
|
||||
<div className="vertical-scrollbar scrollbar-md min-h-0 flex-1 overflow-y-auto p-4">
|
||||
{requestIds.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
{requestIds.map((requestId) => {
|
||||
const request = getRequestById(requestId);
|
||||
if (!request) return null;
|
||||
|
||||
return (
|
||||
<ExternalContoursBoardItem
|
||||
key={requestId}
|
||||
currentTab={currentTab}
|
||||
direction={direction}
|
||||
projectId={projectId}
|
||||
request={request}
|
||||
workspaceSlug={workspaceSlug}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-full min-h-[18rem] items-center justify-center">
|
||||
<ExternalContoursEmptyState title={emptyTitle} description={emptyDescription} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import Link from "next/link";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { PriorityIcon } from "@plane/propel/icons";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { cn, renderFormattedDate } from "@plane/utils";
|
||||
import type { TExternalContourBoardDirection, TExternalContourRequest, TInboxIssueCurrentTab } from "@plane/types";
|
||||
import { ExternalContourStatePill } from "./state-pill";
|
||||
|
||||
type Props = {
|
||||
currentTab: TInboxIssueCurrentTab;
|
||||
direction: TExternalContourBoardDirection;
|
||||
projectId: string;
|
||||
request: TExternalContourRequest;
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export const ExternalContoursBoardItem = observer(function ExternalContoursBoardItem(props: Props) {
|
||||
const { currentTab, direction, projectId, request, workspaceSlug } = props;
|
||||
const { t } = useTranslation();
|
||||
const issue = request.issue;
|
||||
const requester = request.requested_by?.display_name || request.requested_by_name || issue.created_by_detail?.display_name || "NODE.DC";
|
||||
const requesterAvatar = issue.created_by_detail?.avatar_url || "";
|
||||
const counterpartContourName =
|
||||
direction === "outgoing"
|
||||
? request.target_project?.name || request.target_project_name || issue.project_detail?.name
|
||||
: request.source_project?.name || request.source_project_name;
|
||||
const assigneeDetails = issue.assignee_details?.slice(0, 2) ?? [];
|
||||
const lastUpdatedAt = issue.updated_at || request.updated_at;
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`/${workspaceSlug}/projects/${projectId}/external-contours?currentTab=${currentTab}&inboxIssueId=${request.id}`}
|
||||
className="block"
|
||||
>
|
||||
<div className="nodedc-external-card relative flex min-h-[13rem] flex-col gap-4 px-5 py-5 transition-all hover:bg-white/5">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<Avatar src={requesterAvatar} name={requester} size="md" />
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-[15px] leading-5 font-semibold text-primary">{requester}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{request.has_unread_updates && <span className="size-2 rounded-full bg-accent-primary" />}
|
||||
<ExternalContourStatePill request={request} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="truncate pl-10 text-[12px] font-medium leading-4 text-secondary">
|
||||
{counterpartContourName || t("common.none")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 items-center justify-center px-3 text-center">
|
||||
<h3 className="line-clamp-3 w-full text-center text-16 leading-7 font-semibold text-primary">{issue.name}</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
{assigneeDetails.length > 0 ? (
|
||||
assigneeDetails.map((assignee, index) => (
|
||||
<div key={assignee.id} className={cn(index > 0 && "-ml-2")}>
|
||||
<Avatar src={assignee.avatar_url || ""} name={assignee.display_name || "NODE.DC"} size="md" />
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="text-11 text-placeholder">{t("external_contours_page.list.unassigned")}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="rounded-full bg-white/6 px-3 py-1.5 text-12 text-secondary">{renderFormattedDate(lastUpdatedAt ?? "")}</div>
|
||||
{issue.priority && issue.priority !== "none" && (
|
||||
<div className="nodedc-external-priority-inline flex items-center justify-center">
|
||||
<PriorityIcon priority={issue.priority} className="h-3.5 w-3.5" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { TInboxIssueCurrentTab } from "@plane/types";
|
||||
import { EInboxIssueCurrentTab } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
import { useProjectExternalContoursBoard } from "@/hooks/store/use-project-external-contours-board";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { ExternalContoursBoardColumn } from "./board-column";
|
||||
|
||||
type Props = {
|
||||
projectId: 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) {
|
||||
const { projectId, workspaceSlug } = props;
|
||||
const { t } = useTranslation();
|
||||
const router = useAppRouter();
|
||||
const { currentTab, loader, tabCountMap, handleCurrentTab } = useProjectExternalContoursBoard();
|
||||
|
||||
return (
|
||||
<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="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>
|
||||
|
||||
{loader === "init-loading" ? (
|
||||
<div className="flex flex-1 items-center justify-center text-13 text-secondary">{t("loading")}...</div>
|
||||
) : (
|
||||
<div className="grid min-h-0 flex-1 grid-cols-1 gap-5 xl:grid-cols-2">
|
||||
<ExternalContoursBoardColumn
|
||||
currentTab={currentTab}
|
||||
direction="outgoing"
|
||||
projectId={projectId}
|
||||
workspaceSlug={workspaceSlug}
|
||||
/>
|
||||
<ExternalContoursBoardColumn
|
||||
currentTab={currentTab}
|
||||
direction="incoming"
|
||||
projectId={projectId}
|
||||
workspaceSlug={workspaceSlug}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -20,16 +20,15 @@ type Props = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
inboxIssueId: string;
|
||||
isMobileSidebar: boolean;
|
||||
setIsMobileSidebar: (value: boolean) => void;
|
||||
};
|
||||
|
||||
export const ExternalContoursContentRoot = observer(function ExternalContoursContentRoot(props: Props) {
|
||||
const { workspaceSlug, projectId, inboxIssueId, isMobileSidebar, setIsMobileSidebar } = props;
|
||||
const { workspaceSlug, projectId, inboxIssueId } = props;
|
||||
const router = useAppRouter();
|
||||
const [isSubmitting, setIsSubmitting] = useState<TNameDescriptionLoader>("saved");
|
||||
const [isDetailResolved, setIsDetailResolved] = useState(false);
|
||||
const { data: currentUser } = useUser();
|
||||
const { currentTab, fetchRequestById, getRequestById, getIsRequestAvailable } = useProjectExternalContours();
|
||||
const { currentTab, fetchRequestById, getRequestById } = useProjectExternalContours();
|
||||
const contourRequest = getRequestById(inboxIssueId);
|
||||
const issue = contourRequest?.issue;
|
||||
const targetProjectId = issue?.project_id || projectId;
|
||||
@@ -38,20 +37,24 @@ export const ExternalContoursContentRoot = observer(function ExternalContoursCon
|
||||
targetProjectId && getProjectRoleByWorkspaceSlugAndProjectId(workspaceSlug, targetProjectId) !== undefined
|
||||
);
|
||||
|
||||
const isIssueAvailable = getIsRequestAvailable(inboxIssueId?.toString() || "");
|
||||
|
||||
useEffect(() => {
|
||||
if (!isIssueAvailable && inboxIssueId) {
|
||||
if (isDetailResolved && !contourRequest && inboxIssueId) {
|
||||
router.replace(`/${workspaceSlug}/projects/${projectId}/external-contours?currentTab=${currentTab}`);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isIssueAvailable]);
|
||||
}, [contourRequest, currentTab, inboxIssueId, isDetailResolved, projectId, router, workspaceSlug]);
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && projectId && inboxIssueId
|
||||
? `PROJECT_EXTERNAL_CONTOUR_DETAIL_${workspaceSlug}_${projectId}_${inboxIssueId}`
|
||||
: null,
|
||||
workspaceSlug && projectId && inboxIssueId ? () => fetchRequestById(workspaceSlug, projectId, inboxIssueId) : null,
|
||||
workspaceSlug && projectId && inboxIssueId
|
||||
? async () => {
|
||||
const request = await fetchRequestById(workspaceSlug, projectId, inboxIssueId);
|
||||
setIsDetailResolved(true);
|
||||
return request;
|
||||
}
|
||||
: null,
|
||||
{
|
||||
revalidateOnFocus: !hasDirectTargetAccess,
|
||||
revalidateIfStale: !hasDirectTargetAccess,
|
||||
@@ -81,8 +84,6 @@ export const ExternalContoursContentRoot = observer(function ExternalContoursCon
|
||||
<div className="relative flex h-full w-full flex-col overflow-hidden pt-6">
|
||||
<div className="z-[11] min-h-[52px] flex-shrink-0">
|
||||
<ExternalContoursIssueActionsHeader
|
||||
setIsMobileSidebar={setIsMobileSidebar}
|
||||
isMobileSidebar={isMobileSidebar}
|
||||
workspaceSlug={workspaceSlug}
|
||||
sourceProjectId={projectId}
|
||||
contourRequest={contourRequest}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { PanelLeft } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { CheckCircleFilledIcon, ChevronDownIcon, ChevronUpIcon, CloseCircleFilledIcon, LinkIcon, NewTabIcon } from "@plane/propel/icons";
|
||||
@@ -28,8 +27,6 @@ type Props = {
|
||||
contourRequest: TExternalContourRequest;
|
||||
hasDirectTargetAccess: boolean;
|
||||
isSubmitting: TNameDescriptionLoader;
|
||||
isMobileSidebar: boolean;
|
||||
setIsMobileSidebar: (value: boolean) => void;
|
||||
};
|
||||
|
||||
export const ExternalContoursIssueActionsHeader = observer(function ExternalContoursIssueActionsHeader(props: Props) {
|
||||
@@ -39,8 +36,6 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||
contourRequest,
|
||||
hasDirectTargetAccess,
|
||||
isSubmitting,
|
||||
isMobileSidebar,
|
||||
setIsMobileSidebar,
|
||||
} = props;
|
||||
const { t } = useTranslation();
|
||||
const router = useAppRouter();
|
||||
@@ -50,13 +45,17 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||
|
||||
const issue = contourRequest.issue;
|
||||
const currentRequestId = contourRequest.id;
|
||||
const canReviewClosedRequest = contourRequest.status === "closed" && contourRequest.source_decision !== "accepted";
|
||||
const hasRelativeNavigation = !!currentRequestId && filteredRequestIds.includes(currentRequestId);
|
||||
const canReviewClosedRequest =
|
||||
contourRequest.capabilities?.can_source_decide ??
|
||||
(contourRequest.status === "closed" && contourRequest.source_decision !== "accepted");
|
||||
const isSourceAccepted = contourRequest.source_decision === "accepted";
|
||||
|
||||
const redirectToRelativeIssue = useCallback(
|
||||
(direction: "next" | "prev") => {
|
||||
if (!filteredRequestIds || !currentRequestId) return;
|
||||
if (!filteredRequestIds || !currentRequestId || !hasRelativeNavigation || filteredRequestIds.length <= 1) return;
|
||||
const currentIssueIndex = filteredRequestIds.findIndex((requestId) => requestId === currentRequestId);
|
||||
if (currentIssueIndex === -1) return;
|
||||
const nextIssueIndex =
|
||||
direction === "next"
|
||||
? (currentIssueIndex + 1) % filteredRequestIds.length
|
||||
@@ -65,7 +64,7 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||
if (!nextIssueId) return;
|
||||
router.push(`/${workspaceSlug}/projects/${sourceProjectId}/external-contours?currentTab=${currentTab}&inboxIssueId=${nextIssueId}`);
|
||||
},
|
||||
[currentRequestId, currentTab, filteredRequestIds, router, sourceProjectId, workspaceSlug]
|
||||
[currentRequestId, currentTab, filteredRequestIds, hasRelativeNavigation, router, sourceProjectId, workspaceSlug]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -152,6 +151,7 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||
type="button"
|
||||
aria-label="Previous request"
|
||||
onClick={() => redirectToRelativeIssue("prev")}
|
||||
disabled={!hasRelativeNavigation || filteredRequestIds.length <= 1}
|
||||
className="nodedc-external-icon-button"
|
||||
>
|
||||
<ChevronUpIcon className="size-3.5" />
|
||||
@@ -160,6 +160,7 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||
type="button"
|
||||
aria-label="Next request"
|
||||
onClick={() => redirectToRelativeIssue("next")}
|
||||
disabled={!hasRelativeNavigation || filteredRequestIds.length <= 1}
|
||||
className="nodedc-external-icon-button"
|
||||
>
|
||||
<ChevronDownIcon className="size-3.5" />
|
||||
@@ -207,10 +208,6 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
|
||||
</Row>
|
||||
|
||||
<Header className="justify-start lg:hidden">
|
||||
<PanelLeft
|
||||
onClick={() => setIsMobileSidebar(!isMobileSidebar)}
|
||||
className={`my-auto mr-2 h-4 w-4 flex-shrink-0 ${isMobileSidebar ? "text-accent-primary" : "text-secondary"}`}
|
||||
/>
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<ExternalContourStatePill request={contourRequest} />
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
|
||||
@@ -4,18 +4,15 @@
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { PanelLeft } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TransferIcon } from "@plane/propel/icons";
|
||||
import type { TInboxIssueCurrentTab } from "@plane/types";
|
||||
import { EInboxIssueCurrentTab } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
import { useProjectExternalContoursBoard } from "@/hooks/store/use-project-external-contours-board";
|
||||
import { useProjectExternalContours } from "@/hooks/store/use-project-external-contours";
|
||||
import { ExternalContoursBoardRoot } from "./board-root";
|
||||
import { ExternalContoursContentRoot } from "./content-root";
|
||||
import { ExternalContoursEmptyState } from "./empty-state";
|
||||
import { ExternalContoursSidebar } from "./sidebar";
|
||||
|
||||
type TExternalContoursRoot = {
|
||||
workspaceSlug: string;
|
||||
@@ -26,10 +23,16 @@ type TExternalContoursRoot = {
|
||||
|
||||
export const ExternalContoursRoot = observer(function ExternalContoursRoot(props: TExternalContoursRoot) {
|
||||
const { workspaceSlug, projectId, inboxIssueId, navigationTab } = props;
|
||||
const [isMobileSidebar, setIsMobileSidebar] = useState(true);
|
||||
const { t } = useTranslation();
|
||||
const { loader, error, currentTab, currentProjectId, requestIds, handleCurrentTab, fetchRequests } =
|
||||
useProjectExternalContours();
|
||||
const {
|
||||
error: boardError,
|
||||
currentProjectId: boardProjectId,
|
||||
currentTab: boardCurrentTab,
|
||||
fetchBoard,
|
||||
handleCurrentTab: handleBoardCurrentTab,
|
||||
loader: boardLoader,
|
||||
} = useProjectExternalContoursBoard();
|
||||
|
||||
useEffect(() => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
@@ -56,7 +59,24 @@ export const ExternalContoursRoot = observer(function ExternalContoursRoot(props
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [workspaceSlug, projectId, navigationTab]);
|
||||
|
||||
if (error && error?.status === "init-error") {
|
||||
useEffect(() => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const resolvedTab = navigationTab || EInboxIssueCurrentTab.OPEN;
|
||||
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
|
||||
}, [workspaceSlug, projectId, navigationTab]);
|
||||
|
||||
if (error && error?.status === "init-error" && !!inboxIssueId) {
|
||||
return (
|
||||
<div className="relative flex h-full w-full flex-col items-center justify-center gap-3">
|
||||
<TransferIcon className="size-[60px]" strokeWidth={1.5} />
|
||||
@@ -65,50 +85,26 @@ export const ExternalContoursRoot = observer(function ExternalContoursRoot(props
|
||||
);
|
||||
}
|
||||
|
||||
if (boardError && boardError?.status === "init-error" && !inboxIssueId) {
|
||||
return (
|
||||
<div className="relative flex h-full w-full flex-col items-center justify-center gap-3">
|
||||
<TransferIcon className="size-[60px]" strokeWidth={1.5} />
|
||||
<div className="text-secondary">{boardError?.message}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!inboxIssueId && (
|
||||
<div className="flex h-12 w-full items-center border-b border-subtle px-4 lg:hidden">
|
||||
<PanelLeft
|
||||
onClick={() => setIsMobileSidebar(!isMobileSidebar)}
|
||||
className={cn("h-4 w-4", isMobileSidebar ? "text-accent-primary" : "text-secondary")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex h-full w-full overflow-hidden bg-surface-1 pt-2">
|
||||
<div
|
||||
className={cn(
|
||||
"absolute top-[50px] bottom-0 z-10 w-full flex-shrink-0 bg-surface-1 transition-all lg:!relative lg:!top-0 lg:w-2/6",
|
||||
isMobileSidebar ? "translate-x-0" : "-translate-x-full lg:!translate-x-0"
|
||||
)}
|
||||
>
|
||||
<ExternalContoursSidebar
|
||||
setIsMobileSidebar={setIsMobileSidebar}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
inboxIssueId={inboxIssueId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{inboxIssueId ? (
|
||||
<ExternalContoursContentRoot
|
||||
setIsMobileSidebar={setIsMobileSidebar}
|
||||
isMobileSidebar={isMobileSidebar}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
inboxIssueId={inboxIssueId.toString()}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full w-full flex-col overflow-hidden px-8">
|
||||
<div className="hidden h-20 shrink-0 lg:block" />
|
||||
<div className="flex min-h-0 flex-1 items-center justify-center">
|
||||
<ExternalContoursEmptyState
|
||||
compact
|
||||
title={t("external_contours_page.empty_state.detail_title")}
|
||||
description={t("external_contours_page.empty_state.detail_description")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ExternalContoursBoardRoot workspaceSlug={workspaceSlug.toString()} projectId={projectId.toString()} />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user