♻️ feat(ui)!: «Настройки» одним экраном — Организация, Группы, AI-провайдер, Интеграции, Профиль (§8.6)

Вертикальные секции вместо отдельных разделов: «Организация» и
«Интеграции» упразднены (старые URL ведут в настройки), IntegrationsPage
разобран на IntegrationsSection по роду, добавлено управление группами
(создание одним полем, переименование, удаление). «Ещё» в сайдбаре:
Обзор, Обзор поддержки, Знания, Использование AI, Аудит. Сотруднику —
только профильная часть (тема, акцент, безопасность).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
AndreyandClaude Fable 5 committed 2026-09-05 01:40:54 +03:00
1 parent 45605e9a45
commit ba405bb32b
16 files changed
+345 -86

No files matched your search

+1 -1
View File
@@ -35,7 +35,7 @@ describe("role-based navigation (SPEC-HUB-0031 §3)", () => {
expect(canAccess(user, "employees")).toBe(true);
expect(canAccess(user, "agents")).toBe(true);
expect(canAccess(user, "aiKnowledge")).toBe(true);
expect(canAccess(user, "administrationOrganization")).toBe(true);
expect(canAccess(user, "administrationAudit")).toBe(true);
expect(defaultRoute(user)).toBe("command");
}
});
@@ -23,8 +23,8 @@ export function AdministrationPage({
const page = useAdministration({ section, user, onUserUpdated });
const header = (
<PageHeader
title="Администрирование"
text="Настройки организации и журнал действий"
title="Аудит"
text="Журнал действий в организации"
/>
);
@@ -4,7 +4,6 @@ import { administrationSection } from "./model";
describe("administration model", () => {
it("maps administration routes to their sections", () => {
expect(administrationSection("administrationOrganization")).toBe("organization");
expect(administrationSection("administrationAudit")).toBe("audit");
});
});
@@ -1,10 +1,7 @@
import type { RouteKey, SessionUser } from "../../types";
import { hasCapability } from "../../auth/access";
export type AdministrationRoute = Extract<
RouteKey,
"administrationOrganization" | "administrationAudit"
>;
export type AdministrationRoute = Extract<RouteKey, "administrationAudit">;
export type AdministrationSection = "organization" | "audit";
@@ -2,23 +2,22 @@ import { Modal } from "antd";
import { useCallback, useEffect, useState } from "react";
import { api } from "../../api/client";
import { EmptyState, LoadingState, PageHeader } from "../../shared/ui";
import { Button, UnderlineTabs } from "../../shared/ui-controls";
import { EmptyState, LoadingState } from "../../shared/ui";
import { Button } from "../../shared/ui-controls";
import { ConnectionsTable } from "./ConnectionsTable";
import { IntegrationForm } from "./IntegrationForm";
import { KIND_LABEL, type Integration, type IntegrationKind } from "./model";
import type { Integration, IntegrationKind } from "./model";
import { ProvidersTable } from "./ProvidersTable";
// Страница «Интеграции» (SPEC-HUB-0025 §2): два таба по родам — подключения
// (MESSENGER) и LLM-провайдеры; кнопка создания контекстна активному табу.
type FormState = { initial: Integration | null; kind: IntegrationKind } | null;
// Секция интеграций одного рода — блок экрана «Настройки» (SPEC-HUB-0031
// §8.6): подключения (MESSENGER) и AI-провайдеры (LLM_PROVIDER) — отдельные
// вертикальные секции, отдельный раздел «Интеграции» упразднён.
export function IntegrationsPage() {
export function IntegrationsSection({ kind }: { kind: IntegrationKind }) {
const [items, setItems] = useState<Integration[]>([]);
const [tab, setTab] = useState<IntegrationKind>("MESSENGER");
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const [form, setForm] = useState<FormState>(null);
const [form, setForm] = useState<{ initial: Integration | null } | null>(null);
const [testingId, setTestingId] = useState<number | null>(null);
const [deleting, setDeleting] = useState<Integration | null>(null);
const [deletingError, setDeletingError] = useState<string | null>(null);
@@ -28,13 +27,13 @@ export function IntegrationsPage() {
setError(false);
try {
const payload = await api<{ items: Integration[] }>("/api/v1/integrations/");
setItems(payload.items);
setItems(payload.items.filter((item) => item.kind === kind));
} catch {
setError(true);
} finally {
setLoading(false);
}
}, []);
}, [kind]);
useEffect(() => {
void load();
@@ -66,11 +65,6 @@ export function IntegrationsPage() {
}
}
function startDelete(integration: Integration) {
setDeletingError(null);
setDeleting(integration);
}
async function confirmDelete() {
if (!deleting) return;
try {
@@ -82,56 +76,36 @@ export function IntegrationsPage() {
}
}
const connections = items.filter((item) => item.kind === "MESSENGER");
const providers = items.filter((item) => item.kind === "LLM_PROVIDER");
const isConnections = tab === "MESSENGER";
const shown = isConnections ? connections : providers;
const isConnections = kind === "MESSENGER";
const rowHandlers = {
testingId,
onTest: test,
onEdit: (item: Integration) => setForm({ initial: item, kind: item.kind }),
onEdit: (item: Integration) => setForm({ initial: item }),
onToggleActive: toggleActive,
onDelete: startDelete,
onDelete: (item: Integration) => { setDeletingError(null); setDeleting(item); },
};
const header = (
<PageHeader
title="Интеграции"
text="Подключения мессенджеров, почты и LLM-провайдеры · заводятся и проверяются здесь"
action={
<Button variant="primary" icon="plus" onClick={() => setForm({ initial: null, kind: tab })}>
{isConnections ? "Добавить подключение" : "Добавить провайдера"}
</Button>
}
/>
);
if (loading) return <div className="integrations-page">{header}<LoadingState /></div>;
if (error) return <div className="integrations-page">{header}<EmptyState title="Не удалось загрузить интеграции" /></div>;
if (loading) return <LoadingState />;
if (error) return <EmptyState title="Не удалось загрузить интеграции" />;
return (
<div className="integrations-page">
{header}
<UnderlineTabs
className="integrations-tabs"
items={[
{ key: "MESSENGER", label: KIND_LABEL.MESSENGER, count: connections.length },
{ key: "LLM_PROVIDER", label: KIND_LABEL.LLM_PROVIDER, count: providers.length },
]}
value={tab}
onChange={setTab}
/>
{shown.length === 0 ? (
<div className="integrations-section">
<div className="integrations-section-actions">
<Button variant="primary" icon="plus" onClick={() => setForm({ initial: null })}>
{isConnections ? "Добавить подключение" : "Добавить провайдера"}
</Button>
</div>
{items.length === 0 ? (
<EmptyState title={isConnections ? "Подключений пока нет. Добавьте бота, почту или Web-виджет." : "Провайдеров пока нет. Добавьте OpenRouter или Custom endpoint."} />
) : isConnections ? (
<ConnectionsTable items={connections} {...rowHandlers} />
<ConnectionsTable items={items} {...rowHandlers} />
) : (
<ProvidersTable items={providers} {...rowHandlers} />
<ProvidersTable items={items} {...rowHandlers} />
)}
{form && (
<IntegrationForm
initial={form.initial}
kind={form.kind}
kind={kind}
onClose={() => setForm(null)}
onSaved={() => { setForm(null); void load(); }}
/>
@@ -0,0 +1,114 @@
import { Modal } from "antd";
import { useState } from "react";
import { api } from "../../api/client";
import { Icon } from "../../shared/icons";
import { Button } from "../../shared/ui-controls";
import type { EmployeeGroup } from "../../types";
// Секция «Группы» экрана настроек (SPEC-HUB-0031 §8.4/§8.6): список с числом
// участников, создание — одно поле, переименование и удаление. Состав группы
// редактируется на карточке сотрудника.
export function GroupsSettingsCard({ groups, reload }: { groups: EmployeeGroup[]; reload: () => void }) {
const [name, setName] = useState("");
const [creating, setCreating] = useState(false);
const [errorText, setErrorText] = useState("");
const [renaming, setRenaming] = useState<EmployeeGroup | null>(null);
const [renameValue, setRenameValue] = useState("");
const [deleting, setDeleting] = useState<EmployeeGroup | null>(null);
const [modalError, setModalError] = useState("");
async function create() {
const trimmed = name.trim();
if (!trimmed || creating) return;
setCreating(true);
setErrorText("");
try {
await api("/api/v1/company/groups/", { method: "POST", body: JSON.stringify({ name: trimmed }) });
setName("");
reload();
} catch (caught) {
setErrorText(caught instanceof Error ? caught.message : "Не удалось создать группу");
} finally {
setCreating(false);
}
}
async function rename() {
if (!renaming) return;
setModalError("");
try {
await api(`/api/v1/company/groups/${renaming.id}/`, { method: "PATCH", body: JSON.stringify({ name: renameValue.trim() }) });
setRenaming(null);
reload();
} catch (caught) {
setModalError(caught instanceof Error ? caught.message : "Не удалось переименовать");
}
}
async function remove() {
if (!deleting) return;
setModalError("");
try {
await api(`/api/v1/company/groups/${deleting.id}/`, { method: "DELETE" });
setDeleting(null);
reload();
} catch (caught) {
setModalError(caught instanceof Error ? caught.message : "Не удалось удалить группу");
}
}
return (
<div className="profile-card">
<h3>Группы</h3>
<p className="settings-section-note">Видимость диалогов и охват чата. Состав группы — на карточке сотрудника.</p>
<div className="settings-groups-list">
{groups.length === 0 && <p className="settings-section-note">Групп пока нет — все видят все диалоги.</p>}
{groups.map((group) => (
<div className="settings-group-row" key={group.id}>
<i className="chat-scope-dot" />
<strong>{group.name}</strong>
<small>{group.memberCount} чел.</small>
<button aria-label="Переименовать" type="button" onClick={() => { setModalError(""); setRenaming(group); setRenameValue(group.name); }}><Icon name="edit" size={14} /></button>
<button aria-label="Удалить" type="button" onClick={() => { setModalError(""); setDeleting(group); }}><Icon name="trash" size={14} /></button>
</div>
))}
</div>
<div className="settings-group-create">
<input
placeholder="Название новой группы"
value={name}
onChange={(event) => setName(event.target.value)}
onKeyDown={(event) => { if (event.key === "Enter") void create(); }}
/>
<Button variant="secondary" icon="plus" disabled={!name.trim() || creating} onClick={() => void create()}>Создать</Button>
</div>
{errorText && <div className="settings-section-error">{errorText}</div>}
{renaming && (
<Modal open title="Переименовать группу" onCancel={() => setRenaming(null)} footer={null} destroyOnClose>
<div className="integration-form">
<input className="settings-modal-input" value={renameValue} onChange={(event) => setRenameValue(event.target.value)} />
{modalError && <div className="integration-form-error">{modalError}</div>}
<div className="integration-form-actions">
<Button variant="secondary" onClick={() => setRenaming(null)}>Отмена</Button>
<Button variant="primary" disabled={!renameValue.trim()} onClick={() => void rename()}>Сохранить</Button>
</div>
</div>
</Modal>
)}
{deleting && (
<Modal open title="Удалить группу?" onCancel={() => setDeleting(null)} footer={null} destroyOnClose>
<div className="integration-form">
<p>{`«${deleting.name}» будет удалена; её диалоги станут «Без группы».`}</p>
{modalError && <div className="integration-form-error">{modalError}</div>}
<div className="integration-form-actions">
<Button variant="secondary" onClick={() => setDeleting(null)}>Отмена</Button>
<Button variant="danger-outline" onClick={() => void remove()}>Удалить</Button>
</div>
</div>
</Modal>
)}
</div>
);
}
@@ -1,21 +1,90 @@
import type { SessionUser } from "../../types";
import type { ReactNode } from "react";
import { isManager } from "../../auth/access";
import type { EmployeeGroup, SessionUser } from "../../types";
import { OrganizationSettingsForm } from "../administration/OrganizationSettingsForm";
import { canManageSettings } from "../administration/model";
import { useAdministration } from "../administration/useAdministration";
import { IntegrationsSection } from "../integrations/IntegrationsSection";
import { ProfileAppearanceCard } from "../profile/ProfileAppearanceCard";
import { ProfileNotificationsCard } from "../profile/ProfileNotificationsCard";
import { ProfilePasswordForm } from "../profile/ProfilePasswordForm";
import { ProfileSessionsCard } from "../profile/ProfileSessionsCard";
import { ProfileTotpCard } from "../profile/ProfileTotpCard";
import { useProfilePage } from "../profile/useProfilePage";
import { EmptyState, LoadingState } from "../../shared/ui";
import { GroupsSettingsCard } from "./GroupsSettingsCard";
export function SettingsPage({ user, onUserUpdated, reload }: { user: SessionUser; onUserUpdated: (user: SessionUser) => void; reload: () => void }) {
// «Настройки» (SPEC-HUB-0031 §8.6): один экран с вертикальными секциями —
// Организация · Группы · AI-провайдер · Интеграции · Профиль. Отдельные
// разделы «Организация» и «Интеграции» упразднены. Сотруднику доступна
// только профильная часть (тема, акцент, безопасность).
export function SettingsPage({ user, onUserUpdated, reload, groups = [] }: { user: SessionUser; onUserUpdated: (user: SessionUser) => void; reload: () => void; groups?: EmployeeGroup[] }) {
const profilePage = useProfilePage({ user, onUserUpdated, reload });
const manager = isManager(user);
return (
<div className="profile-stack">
const profileSections = (
<>
<ProfileAppearanceCard user={user} onUserUpdated={onUserUpdated} />
<ProfilePasswordForm passwords={profilePage.passwords} message={profilePage.passwordMessage} mismatch={profilePage.passwordMismatch} ready={profilePage.passwordReady} saving={profilePage.savingPassword} setPasswords={profilePage.setPasswords} onSubmit={profilePage.updatePassword} />
<ProfileNotificationsCard />
<ProfileTotpCard user={user} message={profilePage.totpMessage} saving={profilePage.savingTotp} onToggle={profilePage.toggleTotp} />
<ProfileSessionsCard message={profilePage.sessionsMessage} revoking={profilePage.revokingSessions} onRevoke={profilePage.revokeOtherSessions} />
</>
);
if (!manager) {
return <div className="profile-stack">{profileSections}</div>;
}
return (
<div className="profile-stack settings-screen">
<SettingsSection title="Организация">
<OrganizationSection user={user} onUserUpdated={onUserUpdated} />
</SettingsSection>
<SettingsSection title="Группы">
<GroupsSettingsCard groups={groups} reload={reload} />
</SettingsSection>
<SettingsSection title="AI-провайдер" note="BYOK: ключи вашей организации для ответов агентов и расшифровки голосовых">
<IntegrationsSection kind="LLM_PROVIDER" />
</SettingsSection>
<SettingsSection title="Интеграции" note="Боты, почта и Web-виджет — точки входа диалогов">
<IntegrationsSection kind="MESSENGER" />
</SettingsSection>
<SettingsSection title="Профиль">{profileSections}</SettingsSection>
</div>
);
}
function SettingsSection({ title, note, children }: { title: string; note?: string; children: ReactNode }) {
return (
<section className="settings-section">
<header className="settings-section-head">
<h2>{title}</h2>
{note && <span>{note}</span>}
</header>
{children}
</section>
);
}
function OrganizationSection({ user, onUserUpdated }: { user: SessionUser; onUserUpdated: (user: SessionUser) => void }) {
const page = useAdministration({ section: "organization", user, onUserUpdated });
if (page.loading) return <LoadingState />;
if (!page.organization) return <EmptyState title={page.error || "Не удалось загрузить настройки"} />;
return (
<OrganizationSettingsForm
organization={page.organization}
canManage={canManageSettings(user)}
saving={page.saving}
message={page.message}
error={page.error}
timezones={page.timezones}
onChange={page.setOrganization}
onSave={() => void page.save()}
onUploadLogo={(file) => void page.uploadLogo(file)}
onRemoveLogo={() => void page.removeLogo()}
/>
);
}
@@ -0,0 +1,110 @@
/* Экран «Настройки» (SPEC-HUB-0031 §8.6): вертикальные секции. */
.settings-section {
display: flex;
flex-direction: column;
gap: 12px;
}
.settings-section-head {
display: flex;
align-items: baseline;
gap: 10px;
margin-top: 8px;
}
.settings-section-head h2 {
margin: 0;
color: var(--n-1);
font-size: 16px;
font-weight: 700;
}
.settings-section-head span {
color: var(--n-4);
font-size: 12.5px;
}
.settings-section-note {
margin: 0;
color: var(--n-4);
font-size: 12.5px;
}
.settings-section-error {
color: var(--error);
font-size: 12.5px;
}
.settings-groups-list {
display: flex;
flex-direction: column;
}
.settings-group-row {
display: flex;
align-items: center;
gap: 9px;
padding: 8px 2px;
border-bottom: 1px solid var(--n-9);
}
.settings-group-row strong {
color: var(--n-1);
font-size: 13.5px;
font-weight: 600;
}
.settings-group-row small {
flex: 1;
color: var(--n-4);
font-size: 12px;
}
.settings-group-row button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
border: 0;
border-radius: 7px;
color: var(--n-4);
background: transparent;
}
.settings-group-row button:hover {
color: var(--n-2);
background: var(--n-9);
}
.settings-group-create {
display: flex;
gap: 8px;
}
.settings-group-create input,
.settings-modal-input {
flex: 1;
height: 34px;
padding: 0 11px;
border: 1px solid var(--n-7);
border-radius: 8px;
color: var(--n-1);
background: var(--surface-card);
font-size: 13px;
}
.settings-modal-input {
width: 100%;
}
.integrations-section {
display: flex;
flex-direction: column;
gap: 10px;
}
.integrations-section-actions {
display: flex;
justify-content: flex-end;
}
@@ -26,7 +26,7 @@ type Step = {
const STEPS: Step[] = [
{ key: "agentCreated", label: "Создать агента", action: "Создать агента", route: "agents", icon: "robot" },
{ key: "connectionBound", label: "Подключить точку входа", action: "Подключить", route: "integrations", icon: "plug" },
{ key: "connectionBound", label: "Подключить точку входа", action: "Подключить", route: "settings", icon: "plug" },
{ key: "employeeInvited", label: "Пригласить сотрудников", action: "Пригласить", route: "employees", icon: "team" },
];
@@ -6,7 +6,6 @@ import { KnowledgeDetailPage } from "../features/ai/knowledge/KnowledgeDetailPag
import { KnowledgeCreatePage } from "../features/ai/knowledge/KnowledgeCreatePage";
import { KnowledgePage } from "../features/ai/knowledge/KnowledgePage";
import { CommandCenter } from "../features/command/CommandCenter";
import { IntegrationsPage } from "../features/integrations/IntegrationsPage";
import { EmployeeDetailPage, EmployeesPage } from "../features/employees/EmployeesPage";
import { ProfilePage } from "../features/profile/ProfilePage";
import { SettingsPage } from "../features/settings/SettingsPage";
@@ -36,9 +35,8 @@ export function ShellRouteContent({ chatScope, setChatScope, chatCounters, chatS
{route === "employeeDetail" && currentEmployee && <EmployeeDetailPage groups={data.groups} employee={currentEmployee} reload={reload} setRoute={setRoute} user={user} />}
{route === "employeeDetail" && !currentEmployee && <EmployeesPage groups={data.groups} employees={data.employees} reload={reload} openEmployee={openEmployee} setRoute={setRoute} user={user} />}
{route === "profile" && <ProfilePage user={user} onUserUpdated={onUserUpdated} reload={reload} onLogout={onLogout} />}
{route === "settings" && <SettingsPage user={user} onUserUpdated={onUserUpdated} reload={reload} />}
{(route === "administrationOrganization"
|| route === "administrationAudit") && (
{route === "settings" && <SettingsPage user={user} onUserUpdated={onUserUpdated} reload={reload} groups={data.groups} />}
{route === "administrationAudit" && (
<AdministrationPage
route={route}
user={user}
@@ -71,7 +69,7 @@ export function ShellRouteContent({ chatScope, setChatScope, chatCounters, chatS
canManageConnections={hasCapability(user, "integrations.manage")}
openAgents={() => setRoute("agents")}
openKnowledge={openKnowledge}
openIntegrations={() => setRoute("integrations")}
openIntegrations={() => setRoute("settings")}
setRoute={setRoute}
onLoaded={onAgentLoaded}
/>
@@ -79,7 +77,6 @@ export function ShellRouteContent({ chatScope, setChatScope, chatCounters, chatS
{route === "aiKnowledge" && <KnowledgePage openAgent={openAgent} openKnowledge={openKnowledge} setRoute={setRoute} user={user} />}
{route === "aiKnowledgeCreate" && <KnowledgeCreatePage openKnowledge={openKnowledge} setRoute={setRoute} />}
{route === "aiKnowledgeDetail" && <KnowledgeDetailPage agents={data.agents} canManage={hasCapability(user, "ai.manage")} knowledgeId={selectedKnowledgeId} openAgent={openAgent} setRoute={setRoute} onLoaded={onAgentLoaded} />}
{route === "integrations" && <IntegrationsPage />}
{route === "supportPortals" && <Suspense fallback={<LoadingState />}><SupportPortalsPage user={user} openPortal={openSupportPortal} /></Suspense>}
{route === "supportPortalDetail" && <Suspense fallback={<LoadingState />}><SupportPortalDetailPage portalId={selectedSupportPortalId} products={data.products} user={user} openPortals={() => setRoute("supportPortals")} /></Suspense>}
</>
-2
View File
@@ -44,8 +44,6 @@ const SERVICE_ITEMS: SidebarNavSectionItem[] = [
{ activeRoutes: ["supportOverview"], key: "supportOverview", label: "Обзор поддержки" },
{ activeRoutes: ["aiKnowledge", "aiKnowledgeCreate", "aiKnowledgeDetail"], key: "aiKnowledge", label: "Знания" },
{ activeRoutes: ["aiUsage"], key: "aiUsage", label: "Использование AI" },
{ activeRoutes: ["integrations"], key: "integrations", label: "Интеграции" },
{ activeRoutes: ["administrationOrganization"], key: "administrationOrganization", label: "Организация" },
{ activeRoutes: ["administrationAudit"], key: "administrationAudit", label: "Аудит" },
];
+9 -6
View File
@@ -76,28 +76,31 @@ describe("chat route", () => {
});
describe("administration route", () => {
it("keeps the old URL as an organization alias", () => {
it("sends legacy administration URLs to settings (§8.6)", () => {
expect(routeFromPath("/administration")).toEqual({
route: "administrationOrganization",
route: "settings",
...empty,
});
expect(routeFromPath("/integrations")).toEqual({
route: "settings",
...empty,
});
});
it("parses and creates administration subsection URLs", () => {
// Организация переехала в «Настройки» (§8.6); тарифы — устаревший адрес (ADR-HUB-0042).
expect(routeFromPath("/administration/organization")).toEqual({
route: "administrationOrganization",
route: "settings",
...empty,
});
// Устаревший адрес тарифов ведёт в организацию (ADR-HUB-0042).
expect(routeFromPath("/administration/subscription")).toEqual({
route: "administrationOrganization",
route: "settings",
...empty,
});
expect(routeFromPath("/administration/audit")).toEqual({
route: "administrationAudit",
...empty,
});
expect(pathFromRoute("administrationOrganization")).toBe("/administration/organization");
expect(pathFromRoute("administrationAudit")).toBe("/administration/audit");
});
});
+4 -5
View File
@@ -60,10 +60,11 @@ export function routeFromPath(pathname: string, search = ""): RouteState {
return Number.isInteger(id) && id > 0 ? { ...state, route: "aiKnowledgeDetail", knowledgeId: id } : { route: "aiKnowledge", ...state };
}
if (path === "/ai/usage") return { route: "aiUsage", ...state };
if (path === "/integrations") return { route: "integrations", ...state };
// /administration/subscription — устаревший адрес тарифов (ADR-HUB-0042), ведёт в организацию.
// Устаревшие адреса: интеграции и организация переехали в «Настройки» (§8.6).
if (path === "/integrations") return { route: "settings", ...state };
// /administration/subscription — устаревший адрес тарифов (ADR-HUB-0042).
if (path === "/administration" || path === "/administration/organization" || path === "/administration/subscription") {
return { route: "administrationOrganization", ...state };
return { route: "settings", ...state };
}
if (path === "/administration/audit") {
return { route: "administrationAudit", ...state };
@@ -90,8 +91,6 @@ export function pathFromRoute(route: RouteKey, entityId: number | null = null, p
if (route === "aiKnowledge") return `${prefix}/ai/knowledge`;
if (route === "aiKnowledgeCreate") return `${prefix}/ai/knowledge/new`;
if (route === "aiKnowledgeDetail") return entityId ? `${prefix}/ai/knowledge/${entityId}` : `${prefix}/ai/knowledge`;
if (route === "integrations") return `${prefix}/integrations`;
if (route === "administrationOrganization") return `${prefix}/administration/organization`;
if (route === "administrationAudit") return `${prefix}/administration/audit`;
if (route === "profile") return `${prefix}/profile`;
if (route === "settings") return `${prefix}/settings`;
-2
View File
@@ -1,7 +1,6 @@
import type { RouteKey } from "./types";
export const routes: Record<RouteKey, string> = {
administrationOrganization: "Организация",
administrationAudit: "Аудит",
command: "Обзор",
employeeDetail: "Сотрудники",
@@ -20,5 +19,4 @@ export const routes: Record<RouteKey, string> = {
aiKnowledgeCreate: "Создание знания",
aiKnowledgeDetail: "Знание",
aiUsage: "Использование AI",
integrations: "Интеграции",
};
+1
View File
@@ -24,5 +24,6 @@
@import "./features/ai/knowledge/styles-category.css";
@import "./features/integrations/styles.css";
@import "./features/administration/styles.css";
@import "./features/settings/styles.css";
@import "./features/notifications/styles.css";
@import "./shared/state.css";
+1 -1
View File
@@ -115,7 +115,7 @@ export type Product = {
updatedAt: string;
};
export type RouteKey = "administrationOrganization" | "administrationAudit" | "command" | "employeeDetail" | "employees" | "profile" | "settings" | "salesClientDetail" | "salesClients" | "chat" | "supportOverview" | "supportPortals" | "supportPortalDetail" | "agents" | "agentDetail" | "aiKnowledge" | "aiKnowledgeCreate" | "aiKnowledgeDetail" | "aiUsage" | "integrations";
export type RouteKey = "administrationAudit" | "command" | "employeeDetail" | "employees" | "profile" | "settings" | "salesClientDetail" | "salesClients" | "chat" | "supportOverview" | "supportPortals" | "supportPortalDetail" | "agents" | "agentDetail" | "aiKnowledge" | "aiKnowledgeCreate" | "aiKnowledgeDetail" | "aiUsage";
export type AppData = {
employees: Employee[];