diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8deac7d..e8fc1b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,16 @@ Versioning policy: see [CONTRIBUTING.md](CONTRIBUTING.md#versioning).
## [Unreleased]
+## [2.4.2] — 2026-06-30
+
+### Fixed
+- **Редактор снова может работать со справочниками и реестром.** У пользователя с ролью **Редактор** в меню был только «Профиль», а страница управления **Компаниями** была закрыта (admin-only гард), хотя бэкенд с 2.3.0 разрешает редактору создавать компании. Теперь:
+ - в меню редактора (и наблюдателя) есть пункт **«Реестр»**, а у редактора — ещё и **«Компании»**;
+ - страница **«Компании»** доступна редактору: он может **создавать и просматривать** компании; **редактирование и архивирование — только администратор** (колонка действий скрыта для не-админов — зеркалит RBAC бэкенда: `POST /assets` = редактор+админ, `PATCH`/архив = админ);
+ - **Типы документов** остаются только у администратора (тип задаёт глобальное расписание уведомлений) — редактор по-прежнему выбирает из готовых типов при создании документа.
+
+> Только фронтенд: бэкенд, схема БД и публичный контракт не менялись (бэкенд уже разрешал редактору создавать компании). PATCH согласно [политике версионирования](CONTRIBUTING.md#versioning) — устранение дефекта доступа.
+
## [2.4.1] — 2026-06-30
### Fixed
diff --git a/web/package.json b/web/package.json
index d6ee6c4..72856c4 100644
--- a/web/package.json
+++ b/web/package.json
@@ -1,6 +1,6 @@
{
"name": "@lotsman/web",
- "version": "2.4.1",
+ "version": "2.4.2",
"license": "BUSL-1.1",
"private": true,
"type": "module",
diff --git a/web/src/app/router.tsx b/web/src/app/router.tsx
index f17f1d7..85c4510 100644
--- a/web/src/app/router.tsx
+++ b/web/src/app/router.tsx
@@ -182,8 +182,10 @@ function GuardedAdminUsers() {
function GuardedAdminAssets() {
return (
- {/* biome-ignore lint/a11y/useValidAriaRole: role is a RoleGuard custom prop, not an HTML attribute */}
-
+ {/* Companies are managed by editors too (create + view); the page itself
+ gates edit/archive to admins. biome-ignore lint/a11y/useValidAriaRole:
+ role is a RoleGuard custom prop, not an HTML attribute */}
+
diff --git a/web/src/features/auth/RoleGuard.tsx b/web/src/features/auth/RoleGuard.tsx
index 07bb2bb..b918dc1 100644
--- a/web/src/features/auth/RoleGuard.tsx
+++ b/web/src/features/auth/RoleGuard.tsx
@@ -9,9 +9,8 @@
* navigating to /admin/users gets a 403 from the API, not a confusing redirect.
*
* Usage:
- *
- *
- *
+ * …
+ * …
*/
import type * as React from "react";
@@ -19,7 +18,8 @@ import { useAuth } from "./AuthProvider";
import type { UserRole } from "./types";
interface RoleGuardProps {
- role: UserRole;
+ /** A single allowed role, or any-of a list of allowed roles. */
+ role: UserRole | UserRole[];
children: React.ReactNode;
/** Optional fallback rendered when role does not match */
fallback?: React.ReactNode;
@@ -29,7 +29,8 @@ export function RoleGuard({ role, children, fallback = null }: RoleGuardProps) {
const { claims } = useAuth();
if (!claims) return null;
- if (claims.role !== role) return <>{fallback}>;
+ const allowed = Array.isArray(role) ? role : [role];
+ if (!allowed.includes(claims.role)) return <>{fallback}>;
return <>{children}>;
}
diff --git a/web/src/pages/admin/assets/AssetsPage.tsx b/web/src/pages/admin/assets/AssetsPage.tsx
index 7dae9e6..eacc57e 100644
--- a/web/src/pages/admin/assets/AssetsPage.tsx
+++ b/web/src/pages/admin/assets/AssetsPage.tsx
@@ -2,8 +2,10 @@
// Copyright (c) 2026 Maximilian Kaufmann. See LICENSE (Business Source License 1.1).
/**
- * AssetsPage — admin-only (RoleGuard) management of partner companies.
- * US-12..US-15: list, create, edit, archive assets.
+ * AssetsPage — management of partner companies (Компании).
+ * Editors and admins can list + create; editing and archiving are admin-only
+ * (the «Действия» column is hidden for non-admins, mirroring the backend RBAC:
+ * POST /assets = editor+admin, PATCH/archive = admin). US-12..US-15.
*/
import {
@@ -16,6 +18,7 @@ import { format, parseISO } from "date-fns";
import { ru } from "date-fns/locale";
import { Archive, Pencil, Plus, RefreshCw } from "lucide-react";
import * as React from "react";
+import { useAuth } from "@/features/auth/AuthProvider";
import {
useArchiveAsset,
useAssets,
@@ -45,6 +48,9 @@ export function AssetsPage() {
};
}, [q]);
+ const { claims } = useAuth();
+ const isAdmin = claims?.role === "admin";
+
const { data, isLoading, isError, refetch } = useAssets(debouncedQ ? { q: debouncedQ } : {});
const createMutation = useCreateAsset();
const patchMutation = usePatchAsset();
@@ -84,32 +90,38 @@ export function AssetsPage() {
),
}),
- columnHelper.display({
- id: "actions",
- header: "Действия",
- cell: ({ row }) => (
-
-
-
-
- ),
- }),
+ // Edit + archive are admin-only — hide the «Действия» column for editors,
+ // who can create + view companies but not mutate existing ones.
+ ...(isAdmin
+ ? [
+ columnHelper.display({
+ id: "actions",
+ header: "Действия",
+ cell: ({ row }) => (
+
+
+
+
+ ),
+ }),
+ ]
+ : []),
],
- [],
+ [isAdmin],
);
const table = useReactTable({
diff --git a/web/src/shared/layout/Header.tsx b/web/src/shared/layout/Header.tsx
index 18d593b..cdc3b7f 100644
--- a/web/src/shared/layout/Header.tsx
+++ b/web/src/shared/layout/Header.tsx
@@ -3,8 +3,10 @@
import { Link } from "@tanstack/react-router";
import {
+ Building2,
Calendar,
ChevronDown,
+ ClipboardList,
Compass,
LogOut,
Radio,
@@ -150,6 +152,32 @@ function UserMenu() {
+ {/* Registry — primary workspace, available to all roles */}
+ {!isSuperAdmin && (
+ setOpen(false)}
+ className={cn(
+ "flex w-full items-center gap-2 px-3 py-2 text-sm",
+ "hover:bg-accent focus-visible:outline-none focus-visible:bg-accent",
+ )}
+ >
+
+ {t("nav.registry")}
+
+ )}
+
{/* Profile link */}
+ {/* Companies — editors and admins manage companies (create + view) */}
+ {(claims.role === "admin" || claims.role === "editor") && (
+ setOpen(false)}
+ className={cn(
+ "flex w-full items-center gap-2 px-3 py-2 text-sm",
+ "hover:bg-accent focus-visible:outline-none focus-visible:bg-accent",
+ )}
+ >
+
+ {t("nav.admin_assets")}
+
+ )}
+
{/* Admin links — admin only (NOT super_admin) */}
{claims.role === "admin" && (
<>
@@ -179,18 +223,6 @@ function UserMenu() {
{t("nav.admin_users")}
- setOpen(false)}
- className={cn(
- "flex w-full items-center gap-2 px-3 py-2 text-sm",
- "hover:bg-accent focus-visible:outline-none focus-visible:bg-accent",
- )}
- >
-
- {t("nav.admin_assets")}
-