diff --git a/rsconcept/frontend/src/app/ErrorFallback.tsx b/rsconcept/frontend/src/app/ErrorFallback.tsx
index c55f2af0..4447a277 100644
--- a/rsconcept/frontend/src/app/ErrorFallback.tsx
+++ b/rsconcept/frontend/src/app/ErrorFallback.tsx
@@ -5,7 +5,7 @@ import Button from '../components/ui/Button';
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
-
+
Что-то пошло не так!
diff --git a/rsconcept/frontend/src/components/info/InfoError.tsx b/rsconcept/frontend/src/components/info/InfoError.tsx
index e33544cd..b6cf1b99 100644
--- a/rsconcept/frontend/src/components/info/InfoError.tsx
+++ b/rsconcept/frontend/src/components/info/InfoError.tsx
@@ -1,11 +1,9 @@
import axios, { type AxiosError } from 'axios';
import clsx from 'clsx';
-import { external_urls } from '@/utils/constants';
import { isResponseHtml } from '@/utils/utils';
import PrettyJson from '../ui/PrettyJSON';
-import TextURL from '../ui/TextURL';
import AnimateFade from '../wrap/AnimateFade';
export type ErrorData = string | Error | AxiosError | undefined;
@@ -70,12 +68,12 @@ function InfoError({ error }: InfoErrorProps) {
'select-text'
)}
>
-
- Пожалуйста сделайте скриншот и отправьте вместе с описанием ситуации на почту{' '}
-
+
+
Пожалуйста сделайте скриншот и отправьте вместе с описанием ситуации на почту portal@acconcept.ru
- Для продолжения работы перезагрузите страницу
-
+
Для продолжения работы перезагрузите страницу
+
+
);
diff --git a/rsconcept/frontend/src/components/ui/SelectTree.tsx b/rsconcept/frontend/src/components/ui/SelectTree.tsx
new file mode 100644
index 00000000..0bc0a810
--- /dev/null
+++ b/rsconcept/frontend/src/components/ui/SelectTree.tsx
@@ -0,0 +1,112 @@
+import clsx from 'clsx';
+import { AnimatePresence, motion } from 'framer-motion';
+import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
+
+import { animateSideAppear } from '@/styling/animations';
+import { globals } from '@/utils/constants';
+
+import { IconDropArrow, IconPageRight } from '../Icons';
+import { CProps } from '../props';
+import MiniButton from './MiniButton';
+import Overlay from './Overlay';
+
+interface SelectTreeProps
extends CProps.Styling {
+ items: ItemType[];
+ value: ItemType;
+ setValue: (newItem: ItemType) => void;
+ getParent: (item: ItemType) => ItemType;
+ getLabel: (item: ItemType) => string;
+ getDescription: (item: ItemType) => string;
+ prefix: string;
+}
+
+function SelectTree({
+ items,
+ value,
+ getParent,
+ getLabel,
+ getDescription,
+ setValue,
+ prefix,
+ ...restProps
+}: SelectTreeProps) {
+ const foldable = useMemo(
+ () => new Set(items.filter(item => getParent(item) !== item).map(item => getParent(item))),
+ [items, getParent]
+ );
+ const [folded, setFolded] = useState(items);
+
+ useLayoutEffect(() => {
+ setFolded(items.filter(item => getParent(value) !== item && getParent(getParent(value)) !== item));
+ }, [value, getParent, items]);
+
+ const onFoldItem = useCallback(
+ (target: ItemType, showChildren: boolean) => {
+ setFolded(prev =>
+ items.filter(item => {
+ if (item === target) {
+ return !showChildren;
+ }
+ if (!showChildren && (getParent(item) === target || getParent(getParent(item)) === target)) {
+ return true;
+ } else {
+ return prev.includes(item);
+ }
+ })
+ );
+ },
+ [items, getParent]
+ );
+
+ const handleClickFold = useCallback(
+ (event: CProps.EventMouse, target: ItemType, showChildren: boolean) => {
+ event.preventDefault();
+ event.stopPropagation();
+ onFoldItem(target, showChildren);
+ },
+ [onFoldItem]
+ );
+
+ return (
+
+
+ {items.map((item, index) =>
+ getParent(item) === item || !folded.includes(getParent(item)) ? (
+ setValue(item)}
+ initial={{ ...animateSideAppear.initial }}
+ animate={{ ...animateSideAppear.animate }}
+ exit={{ ...animateSideAppear.exit }}
+ >
+ {foldable.has(item) ? (
+
+ : }
+ onClick={event => handleClickFold(event, item, folded.includes(item))}
+ />
+
+ ) : null}
+ {getParent(item) === item ? getLabel(item) : `- ${getLabel(item).toLowerCase()}`}
+
+ ) : null
+ )}
+
+
+ );
+}
+
+export default SelectTree;
diff --git a/rsconcept/frontend/src/components/ui/Tooltip.tsx b/rsconcept/frontend/src/components/ui/Tooltip.tsx
index cd0d4c42..627894d7 100644
--- a/rsconcept/frontend/src/components/ui/Tooltip.tsx
+++ b/rsconcept/frontend/src/components/ui/Tooltip.tsx
@@ -32,7 +32,13 @@ function Tooltip({
delayShow={1000}
delayHide={100}
opacity={0.97}
- className={clsx('overflow-auto sm:overflow-hidden overscroll-contain', 'border shadow-md', layer, className)}
+ className={clsx(
+ 'overflow-auto sm:overflow-hidden overscroll-contain',
+ 'border shadow-md',
+ 'text-balance',
+ layer,
+ className
+ )}
classNameArrow={layer}
style={{ ...{ paddingTop: '2px', paddingBottom: '2px' }, ...style }}
variant={darkMode ? 'dark' : 'light'}
diff --git a/rsconcept/frontend/src/pages/ManualsPage/TopicsDropdown.tsx b/rsconcept/frontend/src/pages/ManualsPage/TopicsDropdown.tsx
index bc05cfda..8fb9f4d1 100644
--- a/rsconcept/frontend/src/pages/ManualsPage/TopicsDropdown.tsx
+++ b/rsconcept/frontend/src/pages/ManualsPage/TopicsDropdown.tsx
@@ -6,12 +6,13 @@ import { useCallback } from 'react';
import { IconMenuFold, IconMenuUnfold } from '@/components/Icons';
import Button from '@/components/ui/Button';
+import SelectTree from '@/components/ui/SelectTree';
import { useConceptOptions } from '@/context/OptionsContext';
import useDropdown from '@/hooks/useDropdown';
-import { HelpTopic } from '@/models/miscellaneous';
+import { HelpTopic, topicParent } from '@/models/miscellaneous';
import { animateSlideLeft } from '@/styling/animations';
-
-import TopicsTree from './TopicsTree';
+import { prefixes } from '@/utils/constants';
+import { describeHelpTopic, labelHelpTopic } from '@/utils/labels';
interface TopicsDropdownProps {
activeTopic: HelpTopic;
@@ -22,7 +23,7 @@ function TopicsDropdown({ activeTopic, onChangeTopic }: TopicsDropdownProps) {
const menu = useDropdown();
const { noNavigation, calculateHeight } = useConceptOptions();
- const selectTheme = useCallback(
+ const handleSelectTopic = useCallback(
(topic: HelpTopic) => {
menu.hide();
onChangeTopic(topic);
@@ -65,7 +66,15 @@ function TopicsDropdown({ activeTopic, onChangeTopic }: TopicsDropdownProps) {
animate={menu.isOpen ? 'open' : 'closed'}
variants={animateSlideLeft}
>
-
+ item as HelpTopic)}
+ value={activeTopic}
+ setValue={handleSelectTopic}
+ prefix={prefixes.topic_list}
+ getParent={item => topicParent.get(item) ?? item}
+ getLabel={labelHelpTopic}
+ getDescription={describeHelpTopic}
+ />
);
diff --git a/rsconcept/frontend/src/pages/ManualsPage/TopicsStatic.tsx b/rsconcept/frontend/src/pages/ManualsPage/TopicsStatic.tsx
index c63b93e3..58f94d6c 100644
--- a/rsconcept/frontend/src/pages/ManualsPage/TopicsStatic.tsx
+++ b/rsconcept/frontend/src/pages/ManualsPage/TopicsStatic.tsx
@@ -1,9 +1,10 @@
import clsx from 'clsx';
+import SelectTree from '@/components/ui/SelectTree';
import { useConceptOptions } from '@/context/OptionsContext';
-import { HelpTopic } from '@/models/miscellaneous';
-
-import TopicsTree from './TopicsTree';
+import { HelpTopic, topicParent } from '@/models/miscellaneous';
+import { prefixes } from '@/utils/constants';
+import { describeHelpTopic, labelHelpTopic } from '@/utils/labels';
interface TopicsStaticProps {
activeTopic: HelpTopic;
@@ -13,7 +14,14 @@ interface TopicsStaticProps {
function TopicsStatic({ activeTopic, onChangeTopic }: TopicsStaticProps) {
const { calculateHeight } = useConceptOptions();
return (
-
item as HelpTopic)}
+ value={activeTopic}
+ setValue={onChangeTopic}
+ prefix={prefixes.topic_list}
+ getParent={item => topicParent.get(item) ?? item}
+ getLabel={labelHelpTopic}
+ getDescription={describeHelpTopic}
className={clsx(
'sticky top-0 left-0',
'w-[14.5rem] cc-scroll-y',
@@ -24,9 +32,7 @@ function TopicsStatic({ activeTopic, onChangeTopic }: TopicsStaticProps) {
'select-none'
)}
style={{ maxHeight: calculateHeight('2.25rem + 2px') }}
- >
-
-
+ />
);
}
diff --git a/rsconcept/frontend/src/pages/ManualsPage/TopicsTree.tsx b/rsconcept/frontend/src/pages/ManualsPage/TopicsTree.tsx
deleted file mode 100644
index 2898c4d3..00000000
--- a/rsconcept/frontend/src/pages/ManualsPage/TopicsTree.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-'use client';
-
-import clsx from 'clsx';
-import { AnimatePresence, motion } from 'framer-motion';
-import { useCallback, useLayoutEffect, useState } from 'react';
-
-import { IconDropArrow, IconPageRight } from '@/components/Icons';
-import { CProps } from '@/components/props';
-import MiniButton from '@/components/ui/MiniButton';
-import Overlay from '@/components/ui/Overlay';
-import { foldableTopics, HelpTopic, topicParent } from '@/models/miscellaneous';
-import { animateSideAppear } from '@/styling/animations';
-import { globals, prefixes } from '@/utils/constants';
-import { describeHelpTopic, labelHelpTopic } from '@/utils/labels';
-
-interface TopicsTreeProps {
- activeTopic: HelpTopic;
- onChangeTopic: (newTopic: HelpTopic) => void;
-}
-
-function TopicsTree({ activeTopic, onChangeTopic }: TopicsTreeProps) {
- const [topicFolded, setFolded] = useState