mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-09-13 16:23:08 +00:00
fix(settings): вернуть в админку категории, которых не было в дереве
Дерево разделов настроек — захардкоженный список категорий, а на бэкенде категория выводится из имени ключа. Любая настройка, создавшая новую категорию, просто исчезала из админки: настройки были в API, но ни один раздел их не показывал. Так пропали 33 категории из 102, в том числе: - CABINET целиком (13 настроек, включая CABINET_ENABLED, CABINET_URL, CABINET_REQUIRE_LEGAL_CONSENT и CABINET_LEGAL_CONSENT_PRECHECKED); - INFO_PAGES — режимы показа оферты, политики, правил и FAQ; - MENU — кнопка меню Telegram с открытием кабинета. Эти три разложены по дереву явно. Остальное собирает новый пункт «Прочие настройки» — по образцу админки бота, где такой раздел уже есть. Это и есть главная часть фикса: теперь новая категория на бэкенде не приводит к молчаливому исчезновению настроек, максимум — к неудачному разделу. Локали ru/en/zh/fa, +5 тестов на само дерево.
This commit is contained in:
44
src/components/admin/constants.test.ts
Normal file
44
src/components/admin/constants.test.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { OTHER_CATEGORIES, SETTINGS_TREE, getMappedCategoryKeys } from './constants';
|
||||||
|
|
||||||
|
// Категории настроек на бэкенде выводятся из имени ключа, поэтому каждая новая
|
||||||
|
// настройка может создать категорию, которой в дереве нет. Раньше такие настройки
|
||||||
|
// просто не отображались: так исчезла вся категория CABINET (13 настроек, включая
|
||||||
|
// CABINET_ENABLED и CABINET_URL) и правовые документы из INFO_PAGES.
|
||||||
|
|
||||||
|
describe('SETTINGS_TREE', () => {
|
||||||
|
it('содержит пункт-сборник для категорий без своего места', () => {
|
||||||
|
const children = SETTINGS_TREE.groups.flatMap((group) => group.children);
|
||||||
|
const catchAll = children.filter((child) => child.categories.includes(OTHER_CATEGORIES));
|
||||||
|
|
||||||
|
expect(catchAll).toHaveLength(1);
|
||||||
|
expect(catchAll[0].id).toBe('sys_other');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('не считает маркер сборника обычной категорией', () => {
|
||||||
|
expect(getMappedCategoryKeys().has(OTHER_CATEGORIES)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('раскладывает по дереву категории, которые раньше терялись', () => {
|
||||||
|
const mapped = getMappedCategoryKeys();
|
||||||
|
|
||||||
|
for (const category of ['CABINET', 'INFO_PAGES', 'MENU']) {
|
||||||
|
expect(mapped.has(category)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('незнакомая категория попадает в сборник, а не пропадает', () => {
|
||||||
|
const mapped = getMappedCategoryKeys();
|
||||||
|
// Так выглядит любая категория, появившаяся на бэкенде после этого релиза.
|
||||||
|
expect(mapped.has('SOME_FUTURE_CATEGORY')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('не содержит категорию дважды', () => {
|
||||||
|
const all = SETTINGS_TREE.groups
|
||||||
|
.flatMap((group) => group.children)
|
||||||
|
.flatMap((child) => child.categories)
|
||||||
|
.filter((category) => category !== OTHER_CATEGORIES);
|
||||||
|
|
||||||
|
expect(new Set(all).size).toBe(all.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ThemeColors, DEFAULT_THEME_COLORS } from '../../types/theme';
|
import { type ThemeColors, DEFAULT_THEME_COLORS } from '../../types/theme';
|
||||||
|
|
||||||
// Tree sidebar types
|
// Tree sidebar types
|
||||||
export interface TreeSubItem {
|
export interface TreeSubItem {
|
||||||
@@ -23,6 +23,9 @@ export interface SettingsTreeConfig {
|
|||||||
groups: TreeGroup[];
|
groups: TreeGroup[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Маркер пункта-сборника: подставляется вместо списка категорий.
|
||||||
|
export const OTHER_CATEGORIES = '*';
|
||||||
|
|
||||||
// Hierarchical settings tree — all 61 backend category keys mapped into 7 groups
|
// Hierarchical settings tree — all 61 backend category keys mapped into 7 groups
|
||||||
export const SETTINGS_TREE: SettingsTreeConfig = {
|
export const SETTINGS_TREE: SettingsTreeConfig = {
|
||||||
specialItems: [
|
specialItems: [
|
||||||
@@ -91,6 +94,8 @@ export const SETTINGS_TREE: SettingsTreeConfig = {
|
|||||||
{ id: 'iface_widget', categories: ['TELEGRAM_WIDGET'] },
|
{ id: 'iface_widget', categories: ['TELEGRAM_WIDGET'] },
|
||||||
{ id: 'iface_oidc', categories: ['TELEGRAM_OIDC'] },
|
{ id: 'iface_oidc', categories: ['TELEGRAM_OIDC'] },
|
||||||
{ id: 'iface_skip', categories: ['SKIP'] },
|
{ id: 'iface_skip', categories: ['SKIP'] },
|
||||||
|
{ id: 'iface_info', categories: ['INFO_PAGES'] },
|
||||||
|
{ id: 'iface_menu', categories: ['MENU'] },
|
||||||
{ id: 'iface_additional', categories: ['ADDITIONAL'] },
|
{ id: 'iface_additional', categories: ['ADDITIONAL'] },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -131,6 +136,7 @@ export const SETTINGS_TREE: SettingsTreeConfig = {
|
|||||||
{ id: 'sys_core', categories: ['CORE', 'DEBUG'] },
|
{ id: 'sys_core', categories: ['CORE', 'DEBUG'] },
|
||||||
{ id: 'sys_remnawave', categories: ['REMNAWAVE'] },
|
{ id: 'sys_remnawave', categories: ['REMNAWAVE'] },
|
||||||
{ id: 'sys_webapi', categories: ['WEB_API', 'EXTERNAL_ADMIN'] },
|
{ id: 'sys_webapi', categories: ['WEB_API', 'EXTERNAL_ADMIN'] },
|
||||||
|
{ id: 'sys_cabinet', categories: ['CABINET'] },
|
||||||
{ id: 'sys_webhook', categories: ['WEBHOOK'] },
|
{ id: 'sys_webhook', categories: ['WEBHOOK'] },
|
||||||
{ id: 'sys_server', categories: ['SERVER_STATUS'] },
|
{ id: 'sys_server', categories: ['SERVER_STATUS'] },
|
||||||
{ id: 'sys_monitoring', categories: ['MONITORING'] },
|
{ id: 'sys_monitoring', categories: ['MONITORING'] },
|
||||||
@@ -138,11 +144,29 @@ export const SETTINGS_TREE: SettingsTreeConfig = {
|
|||||||
{ id: 'sys_backup', categories: ['BACKUP'] },
|
{ id: 'sys_backup', categories: ['BACKUP'] },
|
||||||
{ id: 'sys_version', categories: ['VERSION'] },
|
{ id: 'sys_version', categories: ['VERSION'] },
|
||||||
{ id: 'sys_logging', categories: ['LOG'] },
|
{ id: 'sys_logging', categories: ['LOG'] },
|
||||||
|
// Категории на бэкенде выводятся из имени ключа, поэтому каждая новая
|
||||||
|
// настройка может создать категорию, которой в дереве нет — и тогда она
|
||||||
|
// просто исчезала из админки (так пропала вся категория CABINET).
|
||||||
|
// Этот пункт собирает всё неразложенное, как «Прочее» в админке бота.
|
||||||
|
{ id: 'sys_other', categories: [OTHER_CATEGORIES] },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Категории, у которых есть своё место в дереве (маркер сборника не считается).
|
||||||
|
export function getMappedCategoryKeys(): Set<string> {
|
||||||
|
const mapped = new Set<string>();
|
||||||
|
for (const group of SETTINGS_TREE.groups) {
|
||||||
|
for (const child of group.children) {
|
||||||
|
for (const category of child.categories) {
|
||||||
|
if (category !== OTHER_CATEGORIES) mapped.add(category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
|
||||||
// Helper: find which group and sub-item a backend category key belongs to
|
// Helper: find which group and sub-item a backend category key belongs to
|
||||||
export function findTreeLocation(
|
export function findTreeLocation(
|
||||||
categoryKey: string,
|
categoryKey: string,
|
||||||
|
|||||||
@@ -2306,9 +2306,16 @@
|
|||||||
"sys_maintenance": "Maintenance",
|
"sys_maintenance": "Maintenance",
|
||||||
"sys_backup": "Backup",
|
"sys_backup": "Backup",
|
||||||
"sys_version": "Version",
|
"sys_version": "Version",
|
||||||
"sys_logging": "Logging"
|
"sys_logging": "Logging",
|
||||||
|
"iface_info": "Legal documents",
|
||||||
|
"iface_menu": "Menu button",
|
||||||
|
"sys_cabinet": "Cabinet",
|
||||||
|
"sys_other": "Other settings"
|
||||||
},
|
},
|
||||||
"categories": {
|
"categories": {
|
||||||
|
"CABINET": "Cabinet",
|
||||||
|
"INFO_PAGES": "Legal documents and FAQ",
|
||||||
|
"MENU": "Menu button",
|
||||||
"TELEGRAM_WIDGET": "Telegram Login Widget",
|
"TELEGRAM_WIDGET": "Telegram Login Widget",
|
||||||
"TELEGRAM_OIDC": "Telegram Login (OIDC)"
|
"TELEGRAM_OIDC": "Telegram Login (OIDC)"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1969,9 +1969,16 @@
|
|||||||
"sys_maintenance": "نگهداری",
|
"sys_maintenance": "نگهداری",
|
||||||
"sys_backup": "پشتیبانگیری",
|
"sys_backup": "پشتیبانگیری",
|
||||||
"sys_version": "نسخه",
|
"sys_version": "نسخه",
|
||||||
"sys_logging": "لاگها"
|
"sys_logging": "لاگها",
|
||||||
|
"iface_info": "اسناد حقوقی",
|
||||||
|
"iface_menu": "دکمه منو",
|
||||||
|
"sys_cabinet": "کابین",
|
||||||
|
"sys_other": "سایر تنظیمات"
|
||||||
},
|
},
|
||||||
"categories": {
|
"categories": {
|
||||||
|
"CABINET": "کابین",
|
||||||
|
"INFO_PAGES": "اسناد حقوقی و پرسشهای متداول",
|
||||||
|
"MENU": "دکمه منو",
|
||||||
"TELEGRAM_WIDGET": "Telegram Login Widget",
|
"TELEGRAM_WIDGET": "Telegram Login Widget",
|
||||||
"TELEGRAM_OIDC": "Telegram Login (OIDC)"
|
"TELEGRAM_OIDC": "Telegram Login (OIDC)"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2313,7 +2313,11 @@
|
|||||||
"sys_maintenance": "Обслуживание",
|
"sys_maintenance": "Обслуживание",
|
||||||
"sys_backup": "Бэкап",
|
"sys_backup": "Бэкап",
|
||||||
"sys_version": "Версия",
|
"sys_version": "Версия",
|
||||||
"sys_logging": "Логи"
|
"sys_logging": "Логи",
|
||||||
|
"iface_info": "Правовые документы",
|
||||||
|
"iface_menu": "Кнопка меню",
|
||||||
|
"sys_cabinet": "Кабинет",
|
||||||
|
"sys_other": "Прочие настройки"
|
||||||
},
|
},
|
||||||
"presets": {
|
"presets": {
|
||||||
"standard": "Стандарт",
|
"standard": "Стандарт",
|
||||||
@@ -2769,6 +2773,9 @@
|
|||||||
"telegramOidcClientSecret": "Client Secret"
|
"telegramOidcClientSecret": "Client Secret"
|
||||||
},
|
},
|
||||||
"categories": {
|
"categories": {
|
||||||
|
"CABINET": "Кабинет",
|
||||||
|
"INFO_PAGES": "Правовые документы и FAQ",
|
||||||
|
"MENU": "Кнопка меню",
|
||||||
"PAYMENT": "Платежи",
|
"PAYMENT": "Платежи",
|
||||||
"PAYMENT_VERIFICATION": "Верификация платежей",
|
"PAYMENT_VERIFICATION": "Верификация платежей",
|
||||||
"YOOKASSA": "YooKassa",
|
"YOOKASSA": "YooKassa",
|
||||||
|
|||||||
@@ -2036,9 +2036,16 @@
|
|||||||
"sys_maintenance": "维护",
|
"sys_maintenance": "维护",
|
||||||
"sys_backup": "备份",
|
"sys_backup": "备份",
|
||||||
"sys_version": "版本",
|
"sys_version": "版本",
|
||||||
"sys_logging": "日志"
|
"sys_logging": "日志",
|
||||||
|
"iface_info": "法律文件",
|
||||||
|
"iface_menu": "菜单按钮",
|
||||||
|
"sys_cabinet": "用户中心",
|
||||||
|
"sys_other": "其他设置"
|
||||||
},
|
},
|
||||||
"categories": {
|
"categories": {
|
||||||
|
"CABINET": "用户中心",
|
||||||
|
"INFO_PAGES": "法律文件与 FAQ",
|
||||||
|
"MENU": "菜单按钮",
|
||||||
"TELEGRAM_WIDGET": "Telegram Login Widget",
|
"TELEGRAM_WIDGET": "Telegram Login Widget",
|
||||||
"TELEGRAM_OIDC": "Telegram Login (OIDC)"
|
"TELEGRAM_OIDC": "Telegram Login (OIDC)"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,10 +2,16 @@ import { useState, useMemo, useEffect, useCallback } from 'react';
|
|||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { adminSettingsApi, SettingDefinition } from '../api/adminSettings';
|
import { adminSettingsApi, type SettingDefinition } from '../api/adminSettings';
|
||||||
import { themeColorsApi } from '../api/themeColors';
|
import { themeColorsApi } from '../api/themeColors';
|
||||||
import { useFavoriteSettings } from '../hooks/useFavoriteSettings';
|
import { useFavoriteSettings } from '../hooks/useFavoriteSettings';
|
||||||
import { SETTINGS_TREE, findTreeLocation, formatSettingKey } from '../components/admin';
|
import {
|
||||||
|
OTHER_CATEGORIES,
|
||||||
|
SETTINGS_TREE,
|
||||||
|
findTreeLocation,
|
||||||
|
formatSettingKey,
|
||||||
|
getMappedCategoryKeys,
|
||||||
|
} from '../components/admin';
|
||||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||||
import { AnalyticsTab } from '../components/admin/AnalyticsTab';
|
import { AnalyticsTab } from '../components/admin/AnalyticsTab';
|
||||||
import { BrandingTab } from '../components/admin/BrandingTab';
|
import { BrandingTab } from '../components/admin/BrandingTab';
|
||||||
@@ -75,10 +81,18 @@ export default function AdminSettings() {
|
|||||||
if (!activeTreeInfo || !allSettings || !Array.isArray(allSettings)) return [];
|
if (!activeTreeInfo || !allSettings || !Array.isArray(allSettings)) return [];
|
||||||
|
|
||||||
const categoryKeys = activeTreeInfo.child.categories;
|
const categoryKeys = activeTreeInfo.child.categories;
|
||||||
|
// Пункт-сборник показывает всё, чему не нашлось места в дереве: категории на
|
||||||
|
// бэкенде выводятся из имени ключа, поэтому новая настройка легко создаёт
|
||||||
|
// категорию, которой здесь нет — раньше такие настройки просто исчезали.
|
||||||
|
const isOtherSection = categoryKeys.includes(OTHER_CATEGORIES);
|
||||||
|
const mappedCategories = isOtherSection ? getMappedCategoryKeys() : null;
|
||||||
const categoryMap = new Map<string, SettingDefinition[]>();
|
const categoryMap = new Map<string, SettingDefinition[]>();
|
||||||
|
|
||||||
for (const setting of allSettings) {
|
for (const setting of allSettings) {
|
||||||
if (categoryKeys.includes(setting.category.key)) {
|
const belongsHere = mappedCategories
|
||||||
|
? !mappedCategories.has(setting.category.key)
|
||||||
|
: categoryKeys.includes(setting.category.key);
|
||||||
|
if (belongsHere) {
|
||||||
// Hide tariff-dependent settings when not in tariffs mode
|
// Hide tariff-dependent settings when not in tariffs mode
|
||||||
if (!isTariffsMode && TARIFF_MODE_SETTINGS.includes(setting.key)) continue;
|
if (!isTariffsMode && TARIFF_MODE_SETTINGS.includes(setting.key)) continue;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user