2023-12-13 14:32:57 +03:00
|
|
|
|
'use client';
|
|
|
|
|
|
2023-09-04 22:17:04 +03:00
|
|
|
|
import axios from 'axios';
|
2023-12-15 17:34:50 +03:00
|
|
|
|
import clsx from 'clsx';
|
2023-12-25 16:53:27 +03:00
|
|
|
|
import { AnimatePresence } from 'framer-motion';
|
2023-08-25 22:51:20 +03:00
|
|
|
|
import fileDownload from 'js-file-download';
|
2023-09-11 20:31:54 +03:00
|
|
|
|
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
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
|
|
|
|
|
2023-12-13 14:32:57 +03:00
|
|
|
|
import InfoError, { ErrorData } from '@/components/InfoError';
|
2024-01-04 19:38:12 +03:00
|
|
|
|
import { Loader } from '@/components/ui/Loader';
|
|
|
|
|
import TabLabel from '@/components/ui/TabLabel';
|
|
|
|
|
import TextURL from '@/components/ui/TextURL';
|
2023-12-16 19:20:26 +03:00
|
|
|
|
import { useAccessMode } from '@/context/AccessModeContext';
|
|
|
|
|
import { useAuth } from '@/context/AuthContext';
|
2023-12-13 14:32:57 +03:00
|
|
|
|
import { useLibrary } from '@/context/LibraryContext';
|
2023-12-26 14:23:51 +03:00
|
|
|
|
import { useBlockNavigation, useConceptNavigation } from '@/context/NavigationContext';
|
2023-12-13 14:32:57 +03:00
|
|
|
|
import { useRSForm } from '@/context/RSFormContext';
|
|
|
|
|
import { useConceptTheme } from '@/context/ThemeContext';
|
|
|
|
|
import DlgCloneLibraryItem from '@/dialogs/DlgCloneLibraryItem';
|
|
|
|
|
import DlgConstituentaTemplate from '@/dialogs/DlgConstituentaTemplate';
|
|
|
|
|
import DlgCreateCst from '@/dialogs/DlgCreateCst';
|
|
|
|
|
import DlgDeleteCst from '@/dialogs/DlgDeleteCst';
|
|
|
|
|
import DlgEditWordForms from '@/dialogs/DlgEditWordForms';
|
|
|
|
|
import DlgRenameCst from '@/dialogs/DlgRenameCst';
|
|
|
|
|
import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm';
|
|
|
|
|
import useQueryStrings from '@/hooks/useQueryStrings';
|
2023-12-26 14:23:51 +03:00
|
|
|
|
import { UserAccessMode } from '@/models/miscellaneous';
|
2023-12-13 14:32:57 +03:00
|
|
|
|
import { IConstituenta, ICstCreateData, ICstRenameData, ICstUpdateData, TermForm } from '@/models/rsform';
|
2024-01-04 14:35:46 +03:00
|
|
|
|
import { generateAlias } from '@/models/rsformAPI';
|
2023-12-13 14:32:57 +03:00
|
|
|
|
import { EXTEOR_TRS_FILE, prefixes, TIMEOUT_UI_REFRESH } from '@/utils/constants';
|
|
|
|
|
|
2023-07-28 00:03:37 +03:00
|
|
|
|
import EditorConstituenta from './EditorConstituenta';
|
|
|
|
|
import EditorRSForm from './EditorRSForm';
|
2023-11-27 18:27:23 +03:00
|
|
|
|
import EditorRSList from './EditorRSList';
|
2023-07-29 21:23:18 +03:00
|
|
|
|
import EditorTermGraph from './EditorTermGraph';
|
2023-11-27 18:27:23 +03:00
|
|
|
|
import RSTabsMenu from './RSTabsMenu';
|
2023-07-15 17:46:19 +03:00
|
|
|
|
|
2023-08-27 15:39:49 +03:00
|
|
|
|
export enum RSTabID {
|
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-12-28 14:04:44 +03:00
|
|
|
|
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
|
2023-09-04 22:17:04 +03:00
|
|
|
|
if (axios.isAxiosError(error) && error.response && error.response.status === 404) {
|
|
|
|
|
return (
|
2023-12-17 20:19:28 +03:00
|
|
|
|
<div className='p-2 text-center'>
|
2023-09-04 22:17:04 +03:00
|
|
|
|
<p>Схема с указанным идентификатором отсутствует на портале.</p>
|
2023-12-28 14:04:44 +03:00
|
|
|
|
<TextURL text='Перейти в Библиотеку' href='/library' />
|
2023-09-04 22:17:04 +03:00
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2023-12-28 14:04:44 +03:00
|
|
|
|
return <InfoError error={error} />;
|
2023-09-04 22:17:04 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-27 22:04:25 +03:00
|
|
|
|
function RSTabs() {
|
2023-12-13 14:32:57 +03:00
|
|
|
|
const router = useConceptNavigation();
|
|
|
|
|
const query = useQueryStrings();
|
2023-12-16 19:20:26 +03:00
|
|
|
|
const activeTab = (Number(query.get('tab')) ?? RSTabID.CARD) as RSTabID;
|
2023-12-13 14:32:57 +03:00
|
|
|
|
const cstQuery = query.get('active');
|
|
|
|
|
|
2023-12-16 19:20:26 +03:00
|
|
|
|
const {
|
2023-12-28 14:04:44 +03:00
|
|
|
|
error,
|
|
|
|
|
schema,
|
|
|
|
|
loading,
|
|
|
|
|
processing,
|
|
|
|
|
isOwned,
|
|
|
|
|
claim,
|
|
|
|
|
download,
|
|
|
|
|
isSubscribed,
|
|
|
|
|
cstCreate,
|
|
|
|
|
cstDelete,
|
|
|
|
|
cstRename,
|
|
|
|
|
subscribe,
|
|
|
|
|
unsubscribe,
|
|
|
|
|
cstUpdate,
|
|
|
|
|
resetAliases
|
2023-08-02 18:24:17 +03:00
|
|
|
|
} = useRSForm();
|
2023-11-01 13:41:32 +03:00
|
|
|
|
const { destroyItem } = useLibrary();
|
2023-12-18 12:25:39 +03:00
|
|
|
|
const { setNoFooter } = useConceptTheme();
|
2023-12-16 19:20:26 +03:00
|
|
|
|
const { user } = useAuth();
|
|
|
|
|
const { mode, setMode } = useAccessMode();
|
2023-07-31 23:47:18 +03:00
|
|
|
|
|
2023-12-13 14:32:57 +03:00
|
|
|
|
const [isModified, setIsModified] = useState(false);
|
2023-12-13 15:03:58 +03:00
|
|
|
|
useBlockNavigation(isModified);
|
2023-08-31 17:25:42 +03:00
|
|
|
|
|
2023-12-28 14:04:44 +03:00
|
|
|
|
const isMutable = useMemo(() => {
|
2023-12-16 19:20:26 +03:00
|
|
|
|
return (
|
2023-12-28 14:04:44 +03:00
|
|
|
|
!loading &&
|
|
|
|
|
!processing &&
|
|
|
|
|
mode !== UserAccessMode.READER &&
|
2023-12-16 19:20:26 +03:00
|
|
|
|
((isOwned || (mode === UserAccessMode.ADMIN && user?.is_staff)) ?? false)
|
|
|
|
|
);
|
|
|
|
|
}, [user?.is_staff, mode, isOwned, loading, processing]);
|
|
|
|
|
|
2023-08-22 22:38:27 +03:00
|
|
|
|
const [activeID, setActiveID] = useState<number | undefined>(undefined);
|
2023-12-28 14:04:44 +03:00
|
|
|
|
const activeCst = useMemo(() => schema?.items?.find(cst => cst.id === activeID), [schema?.items, activeID]);
|
|
|
|
|
|
2023-07-29 03:31:21 +03:00
|
|
|
|
const [showUpload, setShowUpload] = useState(false);
|
|
|
|
|
const [showClone, setShowClone] = useState(false);
|
2023-12-28 14:04:44 +03:00
|
|
|
|
|
2023-08-22 22:38:27 +03:00
|
|
|
|
const [afterDelete, setAfterDelete] = useState<((items: number[]) => void) | undefined>(undefined);
|
|
|
|
|
const [toBeDeleted, setToBeDeleted] = useState<number[]>([]);
|
2023-08-02 18:24:17 +03:00
|
|
|
|
const [showDeleteCst, setShowDeleteCst] = useState(false);
|
2023-12-28 14:04:44 +03:00
|
|
|
|
|
2023-08-16 18:32:37 +03:00
|
|
|
|
const [createInitialData, setCreateInitialData] = useState<ICstCreateData>();
|
2023-07-29 15:37:49 +03:00
|
|
|
|
const [showCreateCst, setShowCreateCst] = useState(false);
|
2023-12-28 14:04:44 +03:00
|
|
|
|
|
2023-08-23 01:36:17 +03:00
|
|
|
|
const [renameInitialData, setRenameInitialData] = useState<ICstRenameData>();
|
|
|
|
|
const [showRenameCst, setShowRenameCst] = useState(false);
|
2023-07-29 15:37:49 +03:00
|
|
|
|
|
2023-09-11 20:31:54 +03:00
|
|
|
|
const [showEditTerm, setShowEditTerm] = useState(false);
|
2023-12-28 14:04:44 +03:00
|
|
|
|
|
2023-11-30 02:14:24 +03:00
|
|
|
|
const [insertCstID, setInsertCstID] = useState<number | undefined>(undefined);
|
2023-10-25 21:21:43 +03:00
|
|
|
|
const [showTemplates, setShowTemplates] = useState(false);
|
|
|
|
|
|
2023-07-31 23:47:18 +03:00
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
|
if (schema) {
|
2023-12-13 15:24:10 +03:00
|
|
|
|
const oldTitle = document.title;
|
|
|
|
|
document.title = schema.title;
|
2023-07-31 23:47:18 +03:00
|
|
|
|
return () => {
|
2023-12-13 15:24:10 +03:00
|
|
|
|
document.title = oldTitle;
|
2023-12-28 14:04:44 +03:00
|
|
|
|
};
|
2023-07-31 23:47:18 +03:00
|
|
|
|
}
|
2023-12-13 15:24:10 +03:00
|
|
|
|
}, [schema, schema?.title]);
|
2023-08-02 18:24:17 +03:00
|
|
|
|
|
|
|
|
|
useLayoutEffect(() => {
|
2023-12-16 19:20:26 +03:00
|
|
|
|
setNoFooter(activeTab === RSTabID.CST_EDIT || activeTab === RSTabID.CST_LIST);
|
2023-12-28 14:04:44 +03:00
|
|
|
|
setActiveID(Number(cstQuery) ?? (schema && schema?.items.length > 0 ? schema.items[0].id : undefined));
|
2023-12-01 02:57:19 +03:00
|
|
|
|
setIsModified(false);
|
2023-08-27 15:39:49 +03:00
|
|
|
|
return () => setNoFooter(false);
|
2023-12-16 19:20:26 +03:00
|
|
|
|
}, [activeTab, cstQuery, setActiveID, schema, setNoFooter, setIsModified]);
|
|
|
|
|
|
|
|
|
|
useLayoutEffect(
|
2023-12-28 14:04:44 +03:00
|
|
|
|
() =>
|
|
|
|
|
setMode(prev => {
|
|
|
|
|
if (prev === UserAccessMode.ADMIN) {
|
|
|
|
|
return prev;
|
|
|
|
|
} else if (isOwned) {
|
|
|
|
|
return UserAccessMode.OWNER;
|
|
|
|
|
} else {
|
|
|
|
|
return UserAccessMode.READER;
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
[schema, setMode, isOwned]
|
|
|
|
|
);
|
2023-07-31 23:47:18 +03:00
|
|
|
|
|
|
|
|
|
function onSelectTab(index: number) {
|
2023-09-05 00:23:53 +03:00
|
|
|
|
navigateTab(index, activeID);
|
2023-07-31 23:47:18 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-05 00:23:53 +03:00
|
|
|
|
const navigateTab = useCallback(
|
2023-12-28 14:04:44 +03:00
|
|
|
|
(tab: RSTabID, activeID?: number) => {
|
|
|
|
|
if (!schema) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (activeID) {
|
|
|
|
|
if (tab === activeTab && tab !== RSTabID.CST_EDIT) {
|
|
|
|
|
router.replace(`/rsforms/${schema.id}?tab=${tab}&active=${activeID}`);
|
|
|
|
|
} else {
|
|
|
|
|
router.push(`/rsforms/${schema.id}?tab=${tab}&active=${activeID}`);
|
|
|
|
|
}
|
|
|
|
|
} else if (tab !== activeTab && tab === RSTabID.CST_EDIT && schema.items.length > 0) {
|
|
|
|
|
activeID = schema.items[0].id;
|
2023-12-13 14:32:57 +03:00
|
|
|
|
router.replace(`/rsforms/${schema.id}?tab=${tab}&active=${activeID}`);
|
|
|
|
|
} else {
|
2023-12-28 14:04:44 +03:00
|
|
|
|
router.push(`/rsforms/${schema.id}?tab=${tab}`);
|
2023-12-13 14:32:57 +03:00
|
|
|
|
}
|
2023-12-28 14:04:44 +03:00
|
|
|
|
},
|
|
|
|
|
[router, schema, activeTab]
|
|
|
|
|
);
|
2023-08-02 18:24:17 +03:00
|
|
|
|
|
|
|
|
|
const handleCreateCst = useCallback(
|
2023-12-28 14:04:44 +03:00
|
|
|
|
(data: ICstCreateData) => {
|
|
|
|
|
if (!schema?.items) {
|
|
|
|
|
return;
|
2023-07-29 15:37:49 +03:00
|
|
|
|
}
|
2024-01-04 14:35:46 +03:00
|
|
|
|
data.alias = data.alias || generateAlias(data.cst_type, schema);
|
2023-12-28 14:04:44 +03:00
|
|
|
|
cstCreate(data, newCst => {
|
|
|
|
|
toast.success(`Конституента добавлена: ${newCst.alias}`);
|
|
|
|
|
navigateTab(activeTab, newCst.id);
|
|
|
|
|
if (activeTab === RSTabID.CST_EDIT || activeTab === RSTabID.CST_LIST) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
const element = document.getElementById(`${prefixes.cst_list}${newCst.alias}`);
|
|
|
|
|
if (element) {
|
|
|
|
|
element.scrollIntoView({
|
|
|
|
|
behavior: 'smooth',
|
|
|
|
|
block: 'nearest',
|
|
|
|
|
inline: 'nearest'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}, TIMEOUT_UI_REFRESH);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
[schema, cstCreate, navigateTab, activeTab]
|
|
|
|
|
);
|
2023-08-02 18:24:17 +03:00
|
|
|
|
|
|
|
|
|
const promptCreateCst = useCallback(
|
2023-12-28 14:04:44 +03:00
|
|
|
|
(initialData: ICstCreateData, skipDialog?: boolean) => {
|
|
|
|
|
if (skipDialog) {
|
|
|
|
|
handleCreateCst(initialData);
|
|
|
|
|
} else {
|
|
|
|
|
setCreateInitialData(initialData);
|
|
|
|
|
setShowCreateCst(true);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[handleCreateCst]
|
|
|
|
|
);
|
2023-08-02 18:24:17 +03:00
|
|
|
|
|
2023-08-23 01:36:17 +03:00
|
|
|
|
const handleRenameCst = useCallback(
|
2023-12-28 14:04:44 +03:00
|
|
|
|
(data: ICstRenameData) => {
|
|
|
|
|
cstRename(data, () => toast.success(`Переименование: ${renameInitialData!.alias} -> ${data.alias}`));
|
|
|
|
|
},
|
|
|
|
|
[cstRename, renameInitialData]
|
|
|
|
|
);
|
2023-08-23 01:36:17 +03:00
|
|
|
|
|
2023-12-28 14:04:44 +03:00
|
|
|
|
const promptRenameCst = useCallback((initialData: ICstRenameData) => {
|
2023-08-23 01:36:17 +03:00
|
|
|
|
setRenameInitialData(initialData);
|
|
|
|
|
setShowRenameCst(true);
|
|
|
|
|
}, []);
|
|
|
|
|
|
2023-12-28 14:04:44 +03:00
|
|
|
|
const onReindex = useCallback(() => resetAliases(() => toast.success('Имена конституент обновлены')), [resetAliases]);
|
2023-12-16 19:20:26 +03:00
|
|
|
|
|
2023-08-02 18:24:17 +03:00
|
|
|
|
const handleDeleteCst = useCallback(
|
2023-12-28 14:04:44 +03:00
|
|
|
|
(deleted: number[]) => {
|
|
|
|
|
if (!schema) {
|
|
|
|
|
return;
|
2023-07-29 15:37:49 +03:00
|
|
|
|
}
|
2023-12-28 14:04:44 +03:00
|
|
|
|
const data = {
|
|
|
|
|
items: deleted
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const deletedNames = deleted.map(id => schema.items.find(cst => cst.id === id)?.alias).join(', ');
|
|
|
|
|
const isEmpty = deleted.length === schema.items.length;
|
|
|
|
|
const nextActive = isEmpty ? undefined : getNextActiveOnDelete(activeID, schema.items, deleted);
|
|
|
|
|
|
|
|
|
|
cstDelete(data, () => {
|
|
|
|
|
toast.success(`Конституенты удалены: ${deletedNames}`);
|
|
|
|
|
if (isEmpty) {
|
|
|
|
|
navigateTab(RSTabID.CST_LIST);
|
|
|
|
|
} else if (!nextActive) {
|
|
|
|
|
navigateTab(activeTab);
|
|
|
|
|
} else {
|
|
|
|
|
navigateTab(activeTab, nextActive);
|
|
|
|
|
}
|
|
|
|
|
if (afterDelete) afterDelete(deleted);
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
[afterDelete, cstDelete, schema, activeID, activeTab, navigateTab]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const promptDeleteCst = useCallback((selected: number[], callback?: (items: number[]) => void) => {
|
|
|
|
|
setAfterDelete(() => (items: number[]) => {
|
2023-08-02 18:24:17 +03:00
|
|
|
|
if (callback) callback(items);
|
2023-12-28 14:04:44 +03:00
|
|
|
|
});
|
2023-08-02 18:24:17 +03:00
|
|
|
|
setToBeDeleted(selected);
|
2023-12-28 14:04:44 +03:00
|
|
|
|
setShowDeleteCst(true);
|
2023-08-02 18:24:17 +03:00
|
|
|
|
}, []);
|
2023-07-29 03:31:21 +03:00
|
|
|
|
|
2023-08-02 18:24:17 +03:00
|
|
|
|
const onOpenCst = useCallback(
|
2023-12-28 14:04:44 +03:00
|
|
|
|
(cstID: number) => {
|
|
|
|
|
navigateTab(RSTabID.CST_EDIT, cstID);
|
|
|
|
|
},
|
|
|
|
|
[navigateTab]
|
|
|
|
|
);
|
2023-07-16 20:25:55 +03:00
|
|
|
|
|
2023-12-28 14:04:44 +03:00
|
|
|
|
const onDestroySchema = useCallback(() => {
|
2023-08-11 19:28:12 +03:00
|
|
|
|
if (!schema || !window.confirm('Вы уверены, что хотите удалить данную схему?')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-11-01 13:41:32 +03:00
|
|
|
|
destroyItem(schema.id, () => {
|
2023-08-11 19:28:12 +03:00
|
|
|
|
toast.success('Схема удалена');
|
2023-12-13 14:32:57 +03:00
|
|
|
|
router.push('/library');
|
2023-08-11 19:28:12 +03:00
|
|
|
|
});
|
2023-12-13 14:32:57 +03:00
|
|
|
|
}, [schema, destroyItem, router]);
|
2023-08-11 19:28:12 +03:00
|
|
|
|
|
2023-12-28 14:04:44 +03:00
|
|
|
|
const onClaimSchema = useCallback(() => {
|
2023-08-25 22:51:20 +03:00
|
|
|
|
if (!window.confirm('Вы уверены, что хотите стать владельцем данной схемы?')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
claim(() => toast.success('Вы стали владельцем схемы'));
|
|
|
|
|
}, [claim]);
|
|
|
|
|
|
2023-12-28 14:04:44 +03:00
|
|
|
|
const onShareSchema = useCallback(() => {
|
2023-08-25 22:51:20 +03:00
|
|
|
|
const url = window.location.href + '&share';
|
2023-12-28 14:04:44 +03:00
|
|
|
|
navigator.clipboard
|
|
|
|
|
.writeText(url)
|
|
|
|
|
.then(() => toast.success(`Ссылка скопирована: ${url}`))
|
|
|
|
|
.catch(console.error);
|
2023-08-25 22:51:20 +03:00
|
|
|
|
}, []);
|
|
|
|
|
|
2023-12-28 14:04:44 +03:00
|
|
|
|
const onShowTemplates = useCallback((selectedID?: number) => {
|
2023-11-30 02:14:24 +03:00
|
|
|
|
setInsertCstID(selectedID);
|
2023-10-25 21:21:43 +03:00
|
|
|
|
setShowTemplates(true);
|
|
|
|
|
}, []);
|
|
|
|
|
|
2023-12-28 14:04:44 +03:00
|
|
|
|
const onDownloadSchema = useCallback(() => {
|
2023-08-31 17:25:42 +03:00
|
|
|
|
if (isModified) {
|
|
|
|
|
if (!window.confirm('Присутствуют несохраненные изменения. Продолжить без их учета?')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-28 14:04:44 +03:00
|
|
|
|
const fileName = (schema?.alias ?? 'Schema') + EXTEOR_TRS_FILE;
|
|
|
|
|
download((data: Blob) => {
|
2023-08-25 22:51:20 +03:00
|
|
|
|
try {
|
|
|
|
|
fileDownload(data, fileName);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-08-31 17:25:42 +03:00
|
|
|
|
}, [schema?.alias, download, isModified]);
|
|
|
|
|
|
2023-12-28 14:04:44 +03:00
|
|
|
|
const promptClone = useCallback(() => {
|
2023-08-31 17:25:42 +03:00
|
|
|
|
if (isModified) {
|
|
|
|
|
if (!window.confirm('Присутствуют несохраненные изменения. Продолжить без их учета?')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
setShowClone(true);
|
|
|
|
|
}, [isModified]);
|
|
|
|
|
|
2023-12-28 14:04:44 +03:00
|
|
|
|
const handleToggleSubscribe = useCallback(() => {
|
2023-12-16 19:20:26 +03:00
|
|
|
|
if (isSubscribed) {
|
2023-09-11 20:31:54 +03:00
|
|
|
|
unsubscribe(() => toast.success('Отслеживание отключено'));
|
2023-08-26 17:26:49 +03:00
|
|
|
|
} else {
|
2023-09-11 20:31:54 +03:00
|
|
|
|
subscribe(() => toast.success('Отслеживание включено'));
|
2023-08-26 17:26:49 +03:00
|
|
|
|
}
|
2023-12-16 19:20:26 +03:00
|
|
|
|
}, [isSubscribed, subscribe, unsubscribe]);
|
2023-08-25 22:51:20 +03:00
|
|
|
|
|
2023-12-28 14:04:44 +03:00
|
|
|
|
const promptShowEditTerm = useCallback(() => {
|
2023-09-11 20:31:54 +03:00
|
|
|
|
if (!activeCst) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (isModified) {
|
|
|
|
|
if (!window.confirm('Присутствуют несохраненные изменения. Продолжить без их учета?')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
setShowEditTerm(true);
|
|
|
|
|
}, [isModified, activeCst]);
|
|
|
|
|
|
2023-09-25 14:17:52 +03:00
|
|
|
|
const handleSaveWordforms = useCallback(
|
2023-12-28 14:04:44 +03:00
|
|
|
|
(forms: TermForm[]) => {
|
|
|
|
|
if (!activeID) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const data: ICstUpdateData = {
|
|
|
|
|
id: activeID,
|
|
|
|
|
term_forms: forms
|
|
|
|
|
};
|
|
|
|
|
cstUpdate(data, () => toast.success('Изменения сохранены'));
|
|
|
|
|
},
|
|
|
|
|
[cstUpdate, activeID]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
2024-01-04 19:30:10 +03:00
|
|
|
|
{loading ? <Loader /> : null}
|
2023-12-28 14:04:44 +03:00
|
|
|
|
{error ? <ProcessError error={error} /> : null}
|
|
|
|
|
<AnimatePresence>
|
|
|
|
|
{showUpload ? <DlgUploadRSForm hideWindow={() => setShowUpload(false)} /> : null}
|
|
|
|
|
{showClone ? <DlgCloneLibraryItem base={schema!} hideWindow={() => setShowClone(false)} /> : null}
|
|
|
|
|
{showCreateCst ? (
|
|
|
|
|
<DlgCreateCst
|
|
|
|
|
hideWindow={() => setShowCreateCst(false)}
|
|
|
|
|
onCreate={handleCreateCst}
|
|
|
|
|
schema={schema!}
|
|
|
|
|
initial={createInitialData}
|
|
|
|
|
/>
|
|
|
|
|
) : null}
|
|
|
|
|
{showRenameCst ? (
|
|
|
|
|
<DlgRenameCst
|
|
|
|
|
hideWindow={() => setShowRenameCst(false)}
|
|
|
|
|
onRename={handleRenameCst}
|
|
|
|
|
initial={renameInitialData!}
|
|
|
|
|
/>
|
|
|
|
|
) : null}
|
|
|
|
|
{showDeleteCst ? (
|
|
|
|
|
<DlgDeleteCst
|
|
|
|
|
schema={schema!}
|
|
|
|
|
hideWindow={() => setShowDeleteCst(false)}
|
|
|
|
|
onDelete={handleDeleteCst}
|
|
|
|
|
selected={toBeDeleted}
|
|
|
|
|
/>
|
|
|
|
|
) : null}
|
|
|
|
|
{showEditTerm ? (
|
|
|
|
|
<DlgEditWordForms
|
|
|
|
|
hideWindow={() => setShowEditTerm(false)}
|
|
|
|
|
onSave={handleSaveWordforms}
|
|
|
|
|
target={activeCst!}
|
|
|
|
|
/>
|
|
|
|
|
) : null}
|
|
|
|
|
{showTemplates ? (
|
|
|
|
|
<DlgConstituentaTemplate
|
|
|
|
|
schema={schema!}
|
|
|
|
|
hideWindow={() => setShowTemplates(false)}
|
|
|
|
|
insertAfter={insertCstID}
|
|
|
|
|
onCreate={handleCreateCst}
|
|
|
|
|
/>
|
|
|
|
|
) : null}
|
|
|
|
|
</AnimatePresence>
|
|
|
|
|
|
|
|
|
|
{schema && !loading ? (
|
|
|
|
|
<Tabs
|
|
|
|
|
selectedIndex={activeTab}
|
|
|
|
|
onSelect={onSelectTab}
|
|
|
|
|
defaultFocus
|
|
|
|
|
selectedTabClassName='clr-selected'
|
|
|
|
|
className='flex flex-col min-w-[45rem]'
|
|
|
|
|
>
|
|
|
|
|
<TabList className={clsx('mx-auto', 'flex', 'border-b-2 border-x-2 divide-x-2')}>
|
|
|
|
|
<RSTabsMenu
|
|
|
|
|
isMutable={isMutable}
|
|
|
|
|
onTemplates={onShowTemplates}
|
|
|
|
|
onDownload={onDownloadSchema}
|
|
|
|
|
onDestroy={onDestroySchema}
|
|
|
|
|
onClaim={onClaimSchema}
|
|
|
|
|
onShare={onShareSchema}
|
|
|
|
|
onReindex={onReindex}
|
|
|
|
|
showCloneDialog={promptClone}
|
|
|
|
|
showUploadDialog={() => setShowUpload(true)}
|
|
|
|
|
/>
|
2024-01-04 19:30:10 +03:00
|
|
|
|
<TabLabel label='Карточка' title={`Название схемы: ${schema.title ?? ''}`} />
|
|
|
|
|
<TabLabel
|
2023-12-28 14:04:44 +03:00
|
|
|
|
label='Содержание'
|
|
|
|
|
title={`Конституент: ${schema.stats?.count_all ?? 0} | Ошибок: ${schema.stats?.count_errors ?? 0}`}
|
|
|
|
|
/>
|
2024-01-04 19:30:10 +03:00
|
|
|
|
<TabLabel label='Редактор' />
|
|
|
|
|
<TabLabel label='Граф термов' />
|
2023-12-28 14:04:44 +03:00
|
|
|
|
</TabList>
|
|
|
|
|
|
|
|
|
|
<TabPanel forceRender style={{ display: activeTab === RSTabID.CARD ? '' : 'none' }}>
|
|
|
|
|
<EditorRSForm
|
|
|
|
|
isMutable={isMutable}
|
|
|
|
|
isModified={isModified}
|
|
|
|
|
setIsModified={setIsModified}
|
|
|
|
|
onToggleSubscribe={handleToggleSubscribe}
|
|
|
|
|
onDownload={onDownloadSchema}
|
|
|
|
|
onDestroy={onDestroySchema}
|
|
|
|
|
onClaim={onClaimSchema}
|
|
|
|
|
onShare={onShareSchema}
|
|
|
|
|
/>
|
|
|
|
|
</TabPanel>
|
|
|
|
|
|
|
|
|
|
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_LIST ? '' : 'none' }}>
|
|
|
|
|
<EditorRSList
|
|
|
|
|
isMutable={isMutable}
|
|
|
|
|
onOpenEdit={onOpenCst}
|
|
|
|
|
onCreateCst={promptCreateCst}
|
|
|
|
|
onDeleteCst={promptDeleteCst}
|
|
|
|
|
/>
|
|
|
|
|
</TabPanel>
|
|
|
|
|
|
|
|
|
|
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_EDIT ? '' : 'none' }}>
|
|
|
|
|
<EditorConstituenta
|
|
|
|
|
isMutable={isMutable}
|
|
|
|
|
isModified={isModified}
|
|
|
|
|
setIsModified={setIsModified}
|
|
|
|
|
activeID={activeID}
|
|
|
|
|
activeCst={activeCst}
|
|
|
|
|
onOpenEdit={onOpenCst}
|
|
|
|
|
onCreateCst={promptCreateCst}
|
|
|
|
|
onDeleteCst={promptDeleteCst}
|
|
|
|
|
onRenameCst={promptRenameCst}
|
|
|
|
|
onEditTerm={promptShowEditTerm}
|
|
|
|
|
/>
|
|
|
|
|
</TabPanel>
|
|
|
|
|
|
|
|
|
|
<TabPanel style={{ display: activeTab === RSTabID.TERM_GRAPH ? '' : 'none' }}>
|
|
|
|
|
<EditorTermGraph
|
|
|
|
|
isMutable={isMutable}
|
|
|
|
|
onOpenEdit={onOpenCst}
|
|
|
|
|
onCreateCst={promptCreateCst}
|
|
|
|
|
onDeleteCst={promptDeleteCst}
|
|
|
|
|
/>
|
|
|
|
|
</TabPanel>
|
|
|
|
|
</Tabs>
|
|
|
|
|
) : null}
|
|
|
|
|
</>
|
|
|
|
|
);
|
2023-07-15 17:46:19 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-27 22:04:25 +03:00
|
|
|
|
export default RSTabs;
|
2023-11-27 14:20:21 +03:00
|
|
|
|
|
|
|
|
|
// ====== Internals =========
|
2023-12-28 14:04:44 +03:00
|
|
|
|
function getNextActiveOnDelete(
|
|
|
|
|
activeID: number | undefined,
|
|
|
|
|
items: IConstituenta[],
|
|
|
|
|
deleted: number[]
|
|
|
|
|
): number | undefined {
|
2023-11-27 14:20:21 +03:00
|
|
|
|
if (items.length === deleted.length) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let activeIndex = items.findIndex(cst => cst.id === activeID);
|
|
|
|
|
if (activeIndex === -1) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-28 14:04:44 +03:00
|
|
|
|
while (activeIndex < items.length && deleted.find(id => id === items[activeIndex].id)) {
|
2023-11-27 14:20:21 +03:00
|
|
|
|
++activeIndex;
|
|
|
|
|
}
|
|
|
|
|
if (activeIndex >= items.length) {
|
|
|
|
|
activeIndex = items.length - 1;
|
|
|
|
|
while (activeIndex >= 0 && deleted.find(id => id === items[activeIndex].id)) {
|
|
|
|
|
--activeIndex;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return items[activeIndex].id;
|
2023-12-28 14:04:44 +03:00
|
|
|
|
}
|