ConceptPortal-public/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx

275 lines
8.8 KiB
TypeScript
Raw Normal View History

import { useCallback, useLayoutEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
2023-07-25 20:27:29 +03:00
import { TabList, TabPanel, Tabs } from 'react-tabs';
2023-07-29 15:37:49 +03:00
import { toast } from 'react-toastify';
2023-07-25 20:27:29 +03:00
import BackendError from '../../components/BackendError';
2023-07-15 17:46:19 +03:00
import ConceptTab from '../../components/Common/ConceptTab';
import { Loader } from '../../components/Common/Loader';
import { useLibrary } from '../../context/LibraryContext';
2023-07-25 20:27:29 +03:00
import { useRSForm } from '../../context/RSFormContext';
import { prefixes, TIMEOUT_UI_REFRESH } from '../../utils/constants';
import { CstType, ICstCreateData, SyntaxTree } from '../../utils/models';
2023-07-29 15:37:49 +03:00
import { createAliasFor } from '../../utils/staticUI';
2023-07-28 18:23:37 +03:00
import DlgCloneRSForm from './DlgCloneRSForm';
2023-07-29 15:37:49 +03:00
import DlgCreateCst from './DlgCreateCst';
import DlgDeleteCst from './DlgDeleteCst';
2023-07-29 03:31:21 +03:00
import DlgShowAST from './DlgShowAST';
2023-07-28 18:23:37 +03:00
import DlgUploadRSForm from './DlgUploadRSForm';
2023-07-28 00:03:37 +03:00
import EditorConstituenta from './EditorConstituenta';
import EditorItems from './EditorItems';
import EditorRSForm from './EditorRSForm';
2023-07-29 21:23:18 +03:00
import EditorTermGraph from './EditorTermGraph';
2023-07-28 00:03:37 +03:00
import RSFormStats from './elements/RSFormStats';
2023-07-27 22:04:25 +03:00
import RSTabsMenu from './RSTabsMenu';
2023-07-15 17:46:19 +03:00
2023-07-27 22:04:25 +03:00
export enum RSTabsList {
2023-07-15 17:46:19 +03:00
CARD = 0,
CST_LIST = 1,
2023-07-29 21:23:18 +03:00
CST_EDIT = 2,
TERM_GRAPH = 3
2023-07-15 17:46:19 +03:00
}
2023-07-27 22:04:25 +03:00
function RSTabs() {
2023-07-29 15:37:49 +03:00
const navigate = useNavigate();
const search = useLocation().search;
const {
error, schema, loading,
cstCreate, cstDelete
} = useRSForm();
const { destroySchema } = useLibrary();
2023-07-31 23:47:18 +03:00
const [activeTab, setActiveTab] = useState(RSTabsList.CARD);
const [activeID, setActiveID] = useState<number | undefined>(undefined)
2023-07-31 23:47:18 +03:00
2023-07-29 03:31:21 +03:00
const [showUpload, setShowUpload] = useState(false);
const [showClone, setShowClone] = useState(false);
2023-07-29 03:31:21 +03:00
const [syntaxTree, setSyntaxTree] = useState<SyntaxTree>([]);
2023-08-01 21:55:18 +03:00
const [expression, setExpression] = useState('');
2023-07-29 03:31:21 +03:00
const [showAST, setShowAST] = useState(false);
2023-07-29 15:37:49 +03:00
const [afterDelete, setAfterDelete] = useState<((items: number[]) => void) | undefined>(undefined);
const [toBeDeleted, setToBeDeleted] = useState<number[]>([]);
const [showDeleteCst, setShowDeleteCst] = useState(false);
2023-07-29 15:37:49 +03:00
const [defaultType, setDefaultType] = useState<CstType | undefined>(undefined);
const [insertWhere, setInsertWhere] = useState<number | undefined>(undefined);
2023-07-29 15:37:49 +03:00
const [showCreateCst, setShowCreateCst] = useState(false);
2023-07-31 23:47:18 +03:00
useLayoutEffect(() => {
if (schema) {
const oldTitle = document.title
document.title = schema.title
return () => {
document.title = oldTitle
}
}
}, [schema]);
useLayoutEffect(() => {
const activeTab = Number(new URLSearchParams(search).get('tab')) ?? RSTabsList.CARD;
const cstQuery = new URLSearchParams(search).get('active');
setActiveTab(activeTab);
setActiveID(Number(cstQuery) ?? (schema && schema?.items.length > 0 && schema?.items[0]));
}, [search, setActiveTab, setActiveID, schema]);
2023-07-31 23:47:18 +03:00
function onSelectTab(index: number) {
navigateTo(index, activeID);
2023-07-31 23:47:18 +03:00
}
const navigateTo = useCallback(
(tab: RSTabsList, activeID?: number) => {
if (activeID) {
navigate(`/rsforms/${schema!.id}?tab=${tab}&active=${activeID}`, {
replace: tab === activeTab && tab !== RSTabsList.CST_EDIT
});
} else {
navigate(`/rsforms/${schema!.id}?tab=${tab}`);
}
}, [navigate, schema, activeTab]);
const handleCreateCst = useCallback(
(type: CstType, selectedCst?: number) => {
2023-07-29 15:37:49 +03:00
if (!schema?.items) {
return;
}
const data: ICstCreateData = {
cst_type: type,
alias: createAliasFor(type, schema),
insert_after: selectedCst ?? insertWhere ?? null
2023-07-29 15:37:49 +03:00
}
cstCreate(data, newCst => {
toast.success(`Конституента добавлена: ${newCst.alias}`);
navigateTo(activeTab, newCst.id);
if (activeTab === RSTabsList.CST_EDIT || activeTab == RSTabsList.CST_LIST) {
setTimeout(() => {
const element = document.getElementById(`${prefixes.cst_list}${newCst.alias}`);
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'end',
inline: 'nearest'
});
}
}, TIMEOUT_UI_REFRESH);
2023-07-29 15:37:49 +03:00
}
});
}, [schema, cstCreate, insertWhere, navigateTo, activeTab]);
const promptCreateCst = useCallback(
(selectedID: number | undefined, type: CstType | undefined, skipDialog?: boolean) => {
if (skipDialog && type) {
handleCreateCst(type, selectedID);
} else {
setDefaultType(type);
setInsertWhere(selectedID);
setShowCreateCst(true);
}
}, [handleCreateCst]);
const handleDeleteCst = useCallback(
(deleted: number[]) => {
if (!schema) {
return;
}
const data = {
items: deleted.map(id => { return { id: id }; })
};
let activeIndex = schema.items.findIndex(cst => cst.id === activeID);
cstDelete(data, () => {
const deletedNames = deleted.map(id => schema.items.find(cst => cst.id === id)?.alias).join(', ');
toast.success(`Конституенты удалены: ${deletedNames}`);
if (deleted.length === schema.items.length) {
navigateTo(RSTabsList.CST_LIST);
}
if (activeIndex) {
while (activeIndex < schema.items.length && deleted.find(id => id === schema.items[activeIndex].id)) {
++activeIndex;
}
navigateTo(activeTab, schema.items[activeIndex].id);
2023-07-29 15:37:49 +03:00
}
if (afterDelete) afterDelete(deleted);
});
}, [afterDelete, cstDelete, schema, activeID, activeTab, navigateTo]);
const promptDeleteCst = useCallback(
(selected: number[], callback?: (items: number[]) => void) => {
setAfterDelete(() => (
(items: number[]) => {
if (callback) callback(items);
}));
setToBeDeleted(selected);
setShowDeleteCst(true)
}, []);
2023-07-29 03:31:21 +03:00
const onShowAST = useCallback(
2023-08-01 21:55:18 +03:00
(expression: string, ast: SyntaxTree) => {
2023-07-29 03:31:21 +03:00
setSyntaxTree(ast);
2023-08-01 21:55:18 +03:00
setExpression(expression);
2023-07-29 03:31:21 +03:00
setShowAST(true);
2023-07-29 15:37:49 +03:00
}, []);
2023-07-28 18:23:37 +03:00
const onOpenCst = useCallback(
(cstID: number) => {
navigateTo(RSTabsList.CST_EDIT, cstID)
}, [navigateTo]);
const onDestroySchema = useCallback(
() => {
if (!schema || !window.confirm('Вы уверены, что хотите удалить данную схему?')) {
return;
}
destroySchema(schema.id, () => {
toast.success('Схема удалена');
navigate('/library?filter=personal');
});
}, [schema, destroySchema, navigate]);
2023-07-15 17:46:19 +03:00
return (
2023-07-20 17:11:03 +03:00
<div className='w-full'>
2023-07-15 17:46:19 +03:00
{ loading && <Loader /> }
{ error && <BackendError error={error} />}
{ schema && !loading && <>
{showUpload &&
<DlgUploadRSForm
hideWindow={() => setShowUpload(false)}
/>}
{showClone &&
<DlgCloneRSForm
hideWindow={() => setShowClone(false)}
/>}
2023-07-29 15:37:49 +03:00
{showAST &&
<DlgShowAST
2023-08-01 21:55:18 +03:00
expression={expression}
2023-07-29 15:37:49 +03:00
syntaxTree={syntaxTree}
hideWindow={() => setShowAST(false)}
2023-07-29 15:37:49 +03:00
/>}
{showCreateCst &&
<DlgCreateCst
hideWindow={() => setShowCreateCst(false)}
onCreate={handleCreateCst}
defaultType={defaultType}
/>}
{showDeleteCst &&
<DlgDeleteCst
hideWindow={() => setShowDeleteCst(false)}
onDelete={handleDeleteCst}
selected={toBeDeleted}
2023-07-29 15:37:49 +03:00
/>}
2023-07-25 20:27:29 +03:00
<Tabs
2023-07-29 15:37:49 +03:00
selectedIndex={activeTab}
2023-07-20 17:11:03 +03:00
onSelect={onSelectTab}
defaultFocus={true}
selectedTabClassName='font-bold'
>
<TabList className='flex items-start w-fit clr-bg-pop'>
2023-07-28 18:23:37 +03:00
<RSTabsMenu
onDestroy={onDestroySchema}
2023-07-29 03:31:21 +03:00
showCloneDialog={() => setShowClone(true)}
showUploadDialog={() => setShowUpload(true)}
2023-07-28 18:23:37 +03:00
/>
2023-07-30 00:47:07 +03:00
<ConceptTab className='border-r-2'>Паспорт схемы</ConceptTab>
<ConceptTab className='border-r-2 min-w-[10rem] flex justify-between gap-2'>
2023-07-20 17:11:03 +03:00
<span>Конституенты</span>
2023-07-25 20:27:29 +03:00
<span>{`${schema.stats?.count_errors ?? 0} | ${schema.stats?.count_all ?? 0}`}</span>
2023-07-20 17:11:03 +03:00
</ConceptTab>
2023-07-30 00:47:07 +03:00
<ConceptTab className='border-r-2'>Редактор</ConceptTab>
2023-07-29 21:23:18 +03:00
<ConceptTab>Граф термов</ConceptTab>
2023-07-20 17:11:03 +03:00
</TabList>
2023-07-15 17:46:19 +03:00
2023-07-20 17:11:03 +03:00
<TabPanel className='flex items-start w-full gap-2'>
<EditorRSForm
onDestroy={onDestroySchema}
/>
2023-07-20 17:11:03 +03:00
{schema.stats && <RSFormStats stats={schema.stats}/>}
</TabPanel>
2023-07-15 17:46:19 +03:00
2023-07-20 17:11:03 +03:00
<TabPanel className='w-full'>
<EditorItems
onOpenEdit={onOpenCst}
onCreateCst={promptCreateCst}
onDeleteCst={promptDeleteCst}
/>
2023-07-20 17:11:03 +03:00
</TabPanel>
2023-07-15 17:46:19 +03:00
2023-07-20 17:11:03 +03:00
<TabPanel>
<EditorConstituenta
activeID={activeID}
onOpenEdit={onOpenCst}
onShowAST={onShowAST}
onCreateCst={promptCreateCst}
onDeleteCst={promptDeleteCst}
/>
2023-07-20 17:11:03 +03:00
</TabPanel>
2023-07-29 21:23:18 +03:00
<TabPanel>
<EditorTermGraph />
</TabPanel>
2023-07-29 15:37:49 +03:00
</Tabs>
</>}
2023-07-15 17:46:19 +03:00
</div>);
}
2023-07-27 22:04:25 +03:00
export default RSTabs;