UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: каркас вкладки внешние контуры

This commit is contained in:
DCCONSTRUCTIONS 2026-04-18 20:20:28 +03:00
parent a59f09e2f0
commit c76f519488
9 changed files with 229 additions and 18 deletions

View File

@ -0,0 +1,21 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { Outlet } from "react-router";
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectExternalContoursHeader } from "@/plane-web/components/projects/external-contours/header";
export default function ProjectExternalContoursLayout() {
return (
<>
<AppHeader header={<ProjectExternalContoursHeader />} />
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}

View File

@ -0,0 +1,60 @@
/**
* 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 { TransferIcon } from "@plane/propel/icons";
import { PageHead } from "@/components/core/page-title";
import { useProject } from "@/hooks/store/use-project";
import type { Route } from "./+types/page";
function ProjectExternalContoursPage(_props: Route.ComponentProps) {
const { t } = useTranslation();
const { currentProjectDetails } = useProject();
const pageTitle = currentProjectDetails?.name
? t("external_contours_page.page_label", { workspace: currentProjectDetails.name })
: t("external_contours_page.page_label", { workspace: "NODE.DC" });
return (
<div className="flex h-full flex-col">
<PageHead title={pageTitle} />
<div className="mx-auto flex w-full max-w-5xl flex-col gap-6 px-5 py-6 md:px-8">
<section className="rounded-xl border border-subtle bg-surface-1 p-6">
<div className="flex items-start gap-4">
<div className="flex size-11 shrink-0 items-center justify-center rounded-lg bg-accent-primary/10 text-accent-primary">
<TransferIcon className="h-5 w-5 fill-current" />
</div>
<div className="flex flex-col gap-2">
<h1 className="text-2xl font-semibold text-primary">{t("external_contours_page.empty_state.title")}</h1>
<p className="max-w-3xl text-sm text-secondary">
{t("external_contours_page.empty_state.description")}
</p>
</div>
</div>
</section>
<section className="grid gap-4 lg:grid-cols-2">
<div className="rounded-xl border border-dashed border-subtle bg-surface-1 p-6">
<p className="mb-2 text-base font-semibold text-primary">
{t("external_contours_page.empty_state.request_title")}
</p>
<p className="text-sm text-secondary">{t("external_contours_page.empty_state.request_description")}</p>
</div>
<div className="rounded-xl border border-dashed border-subtle bg-surface-1 p-6">
<p className="mb-2 text-base font-semibold text-primary">
{t("external_contours_page.empty_state.list_title")}
</p>
<p className="text-sm text-secondary">{t("external_contours_page.empty_state.list_description")}</p>
</div>
</section>
</div>
</div>
);
}
export default observer(ProjectExternalContoursPage);

View File

@ -216,6 +216,13 @@ export const coreRoutes: RouteConfigEntry[] = [
"./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/intake/page.tsx"
),
]),
// External contours list
layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/external-contours/layout.tsx", [
route(
":workspaceSlug/projects/:projectId/external-contours",
"./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/external-contours/page.tsx"
),
]),
]),
// Project Archives - Issues, Cycles, Modules

View File

@ -7,7 +7,7 @@
import { useMemo, useCallback } from "react";
// plane imports
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { CycleIcon, IntakeIcon, ModuleIcon, PageIcon, ViewsIcon, WorkItemsIcon } from "@plane/propel/icons";
import { CycleIcon, IntakeIcon, ModuleIcon, PageIcon, TransferIcon, ViewsIcon, WorkItemsIcon } from "@plane/propel/icons";
import type { EUserProjectRoles, IPartialProject } from "@plane/types";
import type { TNavigationItem } from "@/components/navigation/tab-navigation-root";
@ -42,6 +42,16 @@ export const useNavigationItems = ({
shouldRender: true,
sortOrder: 1,
},
{
i18n_key: "sidebar.external_contours",
key: "external_contours",
name: "External contours",
href: `/${workspaceSlug}/projects/${projectId}/external-contours`,
icon: TransferIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: true,
sortOrder: 2,
},
{
i18n_key: "sidebar.cycles",
key: "cycles",
@ -50,7 +60,7 @@ export const useNavigationItems = ({
icon: CycleIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
shouldRender: !!project?.cycle_view,
sortOrder: 2,
sortOrder: 3,
},
{
i18n_key: "sidebar.modules",
@ -60,7 +70,7 @@ export const useNavigationItems = ({
icon: ModuleIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
shouldRender: !!project?.module_view,
sortOrder: 3,
sortOrder: 4,
},
{
i18n_key: "sidebar.views",
@ -70,7 +80,7 @@ export const useNavigationItems = ({
icon: ViewsIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: !!project?.issue_views_view,
sortOrder: 4,
sortOrder: 5,
},
{
i18n_key: "sidebar.pages",
@ -80,7 +90,7 @@ export const useNavigationItems = ({
icon: PageIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: !!project?.page_view,
sortOrder: 5,
sortOrder: 6,
},
{
i18n_key: "sidebar.intake",
@ -90,7 +100,7 @@ export const useNavigationItems = ({
icon: IntakeIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: !!project?.inbox_view,
sortOrder: 6,
sortOrder: 7,
},
],
[project]

View File

@ -0,0 +1,39 @@
/**
* 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 { useParams } from "next/navigation";
import { useTranslation } from "@plane/i18n";
import { TransferIcon } from "@plane/propel/icons";
import { Breadcrumbs, Header } from "@plane/ui";
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
export const ProjectExternalContoursHeader = observer(function ProjectExternalContoursHeader() {
const { workspaceSlug, projectId } = useParams();
const { t } = useTranslation();
return (
<Header>
<Header.LeftItem>
<Breadcrumbs>
<CommonProjectBreadcrumbs workspaceSlug={workspaceSlug?.toString()} projectId={projectId?.toString()} />
<Breadcrumbs.Item
component={
<BreadcrumbLink
label={t("external_contours_page.title")}
href={`/${workspaceSlug}/projects/${projectId}/external-contours/`}
icon={<TransferIcon className="h-4 w-4 text-tertiary" />}
isLast
/>
}
isLast
/>
</Breadcrumbs>
</Header.LeftItem>
</Header>
);
});

View File

@ -6,7 +6,7 @@
// plane imports
import { EUserPermissions, EProjectFeatureKey } from "@plane/constants";
import { CycleIcon, IntakeIcon, ModuleIcon, PageIcon, ViewsIcon, WorkItemsIcon } from "@plane/propel/icons";
import { CycleIcon, IntakeIcon, ModuleIcon, PageIcon, TransferIcon, ViewsIcon, WorkItemsIcon } from "@plane/propel/icons";
// components
import type { TNavigationItem } from "@/components/workspace/sidebar/project-navigation";
@ -31,6 +31,16 @@ export const getProjectFeatureNavigation = (
shouldRender: true,
sortOrder: 1,
},
{
i18n_key: "sidebar.external_contours",
key: "external_contours",
name: "External contours",
href: `/${workspaceSlug}/projects/${projectId}/external-contours`,
icon: TransferIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: true,
sortOrder: 2,
},
{
i18n_key: "sidebar.cycles",
key: EProjectFeatureKey.CYCLES,
@ -39,7 +49,7 @@ export const getProjectFeatureNavigation = (
icon: CycleIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
shouldRender: project.cycle_view,
sortOrder: 2,
sortOrder: 3,
},
{
i18n_key: "sidebar.modules",
@ -49,7 +59,7 @@ export const getProjectFeatureNavigation = (
icon: ModuleIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
shouldRender: project.module_view,
sortOrder: 3,
sortOrder: 4,
},
{
i18n_key: "sidebar.views",
@ -59,7 +69,7 @@ export const getProjectFeatureNavigation = (
icon: ViewsIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: project.issue_views_view,
sortOrder: 4,
sortOrder: 5,
},
{
i18n_key: "sidebar.pages",
@ -69,7 +79,7 @@ export const getProjectFeatureNavigation = (
icon: PageIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: project.page_view,
sortOrder: 5,
sortOrder: 6,
},
{
i18n_key: "sidebar.intake",
@ -79,6 +89,6 @@ export const getProjectFeatureNavigation = (
icon: IntakeIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: project.inbox_view,
sortOrder: 6,
sortOrder: 7,
},
];

View File

@ -10,7 +10,7 @@ import Link from "next/link";
import { useParams, usePathname } from "next/navigation";
import { EUserPermissionsLevel, EUserPermissions } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { CycleIcon, IntakeIcon, ModuleIcon, PageIcon, ViewsIcon, WorkItemsIcon } from "@plane/propel/icons";
import { CycleIcon, IntakeIcon, ModuleIcon, PageIcon, TransferIcon, ViewsIcon, WorkItemsIcon } from "@plane/propel/icons";
import type { EUserProjectRoles } from "@plane/types";
// plane ui
// components
@ -80,6 +80,16 @@ export const ProjectNavigation = observer(function ProjectNavigation(props: TPro
shouldRender: true,
sortOrder: 1,
},
{
i18n_key: "sidebar.external_contours",
key: "external_contours",
name: "External contours",
href: `/${workspaceSlug}/projects/${projectId}/external-contours`,
icon: TransferIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: true,
sortOrder: 2,
},
{
i18n_key: "sidebar.cycles",
key: "cycles",
@ -88,7 +98,7 @@ export const ProjectNavigation = observer(function ProjectNavigation(props: TPro
icon: CycleIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
shouldRender: project?.cycle_view ?? false,
sortOrder: 2,
sortOrder: 3,
},
{
i18n_key: "sidebar.modules",
@ -98,7 +108,7 @@ export const ProjectNavigation = observer(function ProjectNavigation(props: TPro
icon: ModuleIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
shouldRender: project?.module_view ?? false,
sortOrder: 3,
sortOrder: 4,
},
{
i18n_key: "sidebar.views",
@ -108,7 +118,7 @@ export const ProjectNavigation = observer(function ProjectNavigation(props: TPro
icon: ViewsIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: project?.issue_views_view ?? false,
sortOrder: 4,
sortOrder: 5,
},
{
i18n_key: "sidebar.pages",
@ -118,7 +128,7 @@ export const ProjectNavigation = observer(function ProjectNavigation(props: TPro
icon: PageIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: project?.page_view ?? false,
sortOrder: 5,
sortOrder: 6,
},
{
i18n_key: "sidebar.intake",
@ -128,7 +138,7 @@ export const ProjectNavigation = observer(function ProjectNavigation(props: TPro
icon: IntakeIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: project?.inbox_view ?? false,
sortOrder: 6,
sortOrder: 7,
},
],
[project]

View File

@ -5,6 +5,27 @@
*/
export default {
sidebar: {
projects: "Projects",
pages: "Pages",
new_work_item: "New work item",
home: "Home",
your_work: "Your work",
inbox: "Inbox",
workspace: "Workspace",
views: "Views",
analytics: "Analytics",
work_items: "Work items",
external_contours: "External contours",
cycles: "Cycles",
modules: "Modules",
intake: "Intake",
drafts: "Drafts",
favorites: "Favorites",
pro: "Pro",
upgrade: "Upgrade",
stickies: "Stickies",
},
submit: "Submit",
cancel: "Cancel",
loading: "Loading",
@ -217,6 +238,7 @@ export default {
modules: "Modules",
pages: "Pages",
intake: "Intake",
external_contours: "External contours",
time_tracking: "Time Tracking",
work_management: "Work management",
projects_and_issues: "Projects and work items",
@ -258,6 +280,21 @@ export default {
you_can_see_here_if_someone_invites_you_to_a_workspace: "You can see here if someone invites you to a workspace",
back_to_home: "Back to home",
workspace_name: "workspace-name",
external_contours_page: {
page_label: "{workspace} - External contours",
title: "External contours",
empty_state: {
title: "External contours module is ready for the next stage",
description:
"This screen will host the cross-project request form, the sent requests list, and status pills for each routed task.",
request_title: "Send to an external contour",
request_description:
"The next stage will add the form for selecting the target project, assignee, priority, due date, and description.",
list_title: "Sent requests",
list_description:
"This area will show sent cross-project requests with their current status, a link to the target task, and later synchronization.",
},
},
deactivate_your_account: "Deactivate your account",
deactivate_your_account_description:
"Once deactivated, you can't be assigned work items and be billed for your workspace. To reactivate your account, you will need an invite to a workspace at this email address.",

View File

@ -16,6 +16,7 @@ export default {
views: "Представления",
analytics: "Аналитика",
work_items: "Внутренний контур",
external_contours: "Внешние контуры",
cycles: "Циклы",
modules: "Модули",
intake: "Предложения",
@ -389,6 +390,7 @@ export default {
modules: "Модули",
pages: "Страницы",
intake: "Предложения",
external_contours: "Внешние контуры",
time_tracking: "Учет времени",
work_management: "Управление рабочими элементами",
projects_and_issues: "Проекты и рабочие элементы",
@ -434,6 +436,21 @@ export default {
you_can_see_here_if_someone_invites_you_to_a_workspace: "Здесь отображаются приглашения в рабочие пространства",
back_to_home: "Вернуться на главную",
workspace_name: "название-рабочего-пространства",
external_contours_page: {
page_label: "{workspace} - Внешние контуры",
title: "Внешние контуры",
empty_state: {
title: "Модуль внешних контуров подготовлен",
description:
"Здесь появятся форма отправки задачи в другой проект, список отправленных запросов и их статусные маркеры.",
request_title: "Отправка во внешний контур",
request_description:
"На следующем этапе здесь будет форма выбора целевого проекта, исполнителя, приоритета, срока и описания.",
list_title: "Отправленные запросы",
list_description:
"Здесь будет список межпроектных запросов с текущим статусом, ссылкой на целевую задачу и дальнейшей синхронизацией.",
},
},
deactivate_your_account: "Деактивировать ваш аккаунт",
deactivate_your_account_description:
"После деактивации вы не сможете получать рабочие элементы и оплачивать рабочее пространство. Для реактивации потребуется новое приглашение.",