mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Minor UI improvements
This commit is contained in:
parent
6999e086d5
commit
b026a57fad
|
@ -35,17 +35,16 @@ function Modal({
|
||||||
if (onSubmit) onSubmit();
|
if (onSubmit) onSubmit();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (<>
|
||||||
<>
|
|
||||||
<div className='fixed top-0 left-0 w-full h-full z-navigation clr-modal-backdrop' />
|
<div className='fixed top-0 left-0 w-full h-full z-navigation clr-modal-backdrop' />
|
||||||
<div ref={ref}
|
<div ref={ref}
|
||||||
className='fixed bottom-1/2 left-1/2 translate-y-1/2 -translate-x-1/2 px-4 py-3 flex flex-col justify-start w-fit max-w-[calc(100vw-2rem)] overflow-x-auto h-fit z-modal clr-app border shadow-md'
|
className='fixed bottom-1/2 left-1/2 translate-y-1/2 -translate-x-1/2 px-4 flex flex-col justify-start w-fit max-w-[calc(100vw-2rem)] overflow-x-auto h-fit z-modal clr-app border shadow-md'
|
||||||
>
|
>
|
||||||
{title ? <h1 className='pb-3 text-xl select-none'>{title}</h1> : null}
|
{title ? <h1 className='py-2 text-lg select-none'>{title}</h1> : null}
|
||||||
<div className='max-h-[calc(100vh-8rem)] overflow-auto px-2'>
|
<div className='max-h-[calc(100vh-8rem)] overflow-auto px-2'>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-center w-full gap-4 pt-3 mt-2 border-t-2 z-modal-controls'>
|
<div className='flex justify-center w-full gap-6 py-3 z-modal-controls'>
|
||||||
{!readonly ?
|
{!readonly ?
|
||||||
<Button autoFocus
|
<Button autoFocus
|
||||||
text={submitText}
|
text={submitText}
|
||||||
|
@ -62,8 +61,7 @@ function Modal({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Modal;
|
export default Modal;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
function HelpTerminologyControl() {
|
function HelpTerminologyControl() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className='flex flex-col gap-1'>
|
||||||
<h1>Терминологизация: Контроль терминологии</h1>
|
<h1>Терминологизация: Контроль терминологии</h1>
|
||||||
<p>Портал позволяет контролировать употребление терминов, привязанных к сущностям в концептуальных схемах.</p>
|
<p>Портал позволяет контролировать употребление терминов, привязанных к сущностям в концептуальных схемах.</p>
|
||||||
<p>Для этого используется механизм текстовых отсылок: <i>использование термина</i> и <i>связывание слов.</i></p>
|
<p>Для этого используется механизм текстовых отсылок: <i>использование термина</i> и <i>связывание слов.</i></p>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Dispatch, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import MiniButton from '../../components/Common/MiniButton';
|
import MiniButton from '../../components/Common/MiniButton';
|
||||||
import DataTable, { IConditionalStyle } from '../../components/DataTable';
|
import DataTable, { IConditionalStyle } from '../../components/DataTable';
|
||||||
import { CheckIcon, CrossIcon } from '../../components/Icons';
|
import { ArrowsRotateIcon, CheckIcon, CrossIcon } from '../../components/Icons';
|
||||||
import RSInput from '../../components/RSInput';
|
import RSInput from '../../components/RSInput';
|
||||||
import ConstituentaPicker from '../../components/Shared/ConstituentaPicker';
|
import ConstituentaPicker from '../../components/Shared/ConstituentaPicker';
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
|
@ -32,6 +32,15 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
||||||
|
|
||||||
const [argumentValue, setArgumentValue] = useState('');
|
const [argumentValue, setArgumentValue] = useState('');
|
||||||
|
|
||||||
|
const selectedClearable = useMemo(
|
||||||
|
() => {
|
||||||
|
return argumentValue && !!selectedArgument && !!selectedArgument.value;
|
||||||
|
}, [argumentValue, selectedArgument]);
|
||||||
|
|
||||||
|
const isModified = useMemo(
|
||||||
|
() => (selectedArgument && argumentValue !== selectedArgument.value),
|
||||||
|
[selectedArgument, argumentValue]);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => {
|
() => {
|
||||||
if (!selectedArgument && state.arguments.length > 0) {
|
if (!selectedArgument && state.arguments.length > 0) {
|
||||||
|
@ -55,24 +64,25 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
||||||
|
|
||||||
const handleClearArgument = useCallback(
|
const handleClearArgument = useCallback(
|
||||||
(target: IArgumentValue) => {
|
(target: IArgumentValue) => {
|
||||||
target.value = '';
|
const newArg = { ...target, value: '' }
|
||||||
partialUpdate({
|
partialUpdate({
|
||||||
arguments: [
|
arguments: state.arguments.map((arg) => (arg.alias !== target.alias ? arg : newArg))
|
||||||
target,
|
|
||||||
...state.arguments.filter(arg => arg.alias !== target.alias)
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
setSelectedArgument(newArg);
|
||||||
}, [partialUpdate, state.arguments]);
|
}, [partialUpdate, state.arguments]);
|
||||||
|
|
||||||
|
const handleReset = useCallback(
|
||||||
|
() => {
|
||||||
|
setArgumentValue(selectedArgument?.value ?? '');
|
||||||
|
}, [selectedArgument]);
|
||||||
|
|
||||||
const handleAssignArgument = useCallback(
|
const handleAssignArgument = useCallback(
|
||||||
(target: IArgumentValue, value: string) => {
|
(target: IArgumentValue, value: string) => {
|
||||||
target.value = value;
|
const newArg = { ...target, value: value }
|
||||||
partialUpdate({
|
partialUpdate({
|
||||||
arguments: [
|
arguments: state.arguments.map((arg) => (arg.alias !== target.alias ? arg : newArg))
|
||||||
target,
|
|
||||||
...state.arguments.filter(arg => arg.alias !== target.alias)
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
setSelectedArgument(newArg);
|
||||||
}, [partialUpdate, state.arguments]);
|
}, [partialUpdate, state.arguments]);
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
|
@ -148,21 +158,31 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
||||||
{selectedArgument?.alias || 'ARG'}
|
{selectedArgument?.alias || 'ARG'}
|
||||||
</span>
|
</span>
|
||||||
<span>=</span>
|
<span>=</span>
|
||||||
<RSInput
|
<RSInput noTooltip
|
||||||
dimensions='max-w-[12rem] w-full'
|
dimensions='max-w-[12rem] w-full'
|
||||||
value={argumentValue}
|
value={argumentValue}
|
||||||
noTooltip
|
|
||||||
onChange={newValue => setArgumentValue(newValue)}
|
onChange={newValue => setArgumentValue(newValue)}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<div className='flex'>
|
||||||
tooltip='Подставить значение аргумента'
|
<MiniButton
|
||||||
icon={<CheckIcon
|
tooltip='Подставить значение аргумента'
|
||||||
size={5}
|
icon={<CheckIcon size={5} color={!argumentValue || !selectedArgument ? 'text-disabled' : 'text-success'} />}
|
||||||
color={!argumentValue || !selectedArgument ? 'text-disabled' : 'text-success'}
|
disabled={!argumentValue || !selectedArgument}
|
||||||
/>}
|
onClick={() => handleAssignArgument(selectedArgument!, argumentValue)}
|
||||||
disabled={!argumentValue || !selectedArgument}
|
/>
|
||||||
onClick={() => handleAssignArgument(selectedArgument!, argumentValue)}
|
<MiniButton
|
||||||
/>
|
tooltip='Откатить значение'
|
||||||
|
disabled={!isModified}
|
||||||
|
onClick={handleReset}
|
||||||
|
icon={<ArrowsRotateIcon size={5} color={isModified ? 'text-primary' : ''} />}
|
||||||
|
/>
|
||||||
|
<MiniButton
|
||||||
|
tooltip='Очистить значение аргумента'
|
||||||
|
disabled={!selectedClearable}
|
||||||
|
icon={<CrossIcon size={5} color={!selectedClearable ? 'text-disabled' : 'text-warning'}/>}
|
||||||
|
onClick={() => selectedArgument ? handleClearArgument(selectedArgument) : undefined}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ConstituentaPicker
|
<ConstituentaPicker
|
||||||
|
|
|
@ -218,8 +218,9 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
||||||
</div>
|
</div>
|
||||||
<ConceptTooltip
|
<ConceptTooltip
|
||||||
anchorSelect='#terminology-help'
|
anchorSelect='#terminology-help'
|
||||||
className='max-w-[30rem] z-modal-tooltip'
|
className='max-w-[40rem]'
|
||||||
offset={4}
|
layer='z-modal-tooltip'
|
||||||
|
offset={1}
|
||||||
>
|
>
|
||||||
<HelpTerminologyControl />
|
<HelpTerminologyControl />
|
||||||
</ConceptTooltip>
|
</ConceptTooltip>
|
||||||
|
|
|
@ -27,7 +27,7 @@ function ConstituentaToolbar({
|
||||||
return (
|
return (
|
||||||
<div className='relative w-full'>
|
<div className='relative w-full'>
|
||||||
<div className='absolute right-0 flex items-start justify-center w-full top-1'>
|
<div className='absolute right-0 flex items-start justify-center w-full top-1'>
|
||||||
<div className=' flex justify-start w-fit select-auto z-pop'>
|
<div className='flex justify-start select-auto w-fit z-tooltip'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Сохранить изменения'
|
tooltip='Сохранить изменения'
|
||||||
disabled={!canSave}
|
disabled={!canSave}
|
||||||
|
@ -35,7 +35,7 @@ function ConstituentaToolbar({
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Сборсить несохраненные изменения'
|
tooltip='Сбросить несохраненные изменения'
|
||||||
disabled={!canSave}
|
disabled={!canSave}
|
||||||
onClick={onReset}
|
onClick={onReset}
|
||||||
icon={<ArrowsRotateIcon size={5} color={canSave ? 'text-primary' : ''} />}
|
icon={<ArrowsRotateIcon size={5} color={canSave ? 'text-primary' : ''} />}
|
||||||
|
@ -67,7 +67,10 @@ function ConstituentaToolbar({
|
||||||
<div id='cst-help' className='px-1 py-1'>
|
<div id='cst-help' className='px-1 py-1'>
|
||||||
<HelpIcon color='text-primary' size={5} />
|
<HelpIcon color='text-primary' size={5} />
|
||||||
</div>
|
</div>
|
||||||
<ConceptTooltip anchorSelect='#cst-help' offset={4}>
|
<ConceptTooltip
|
||||||
|
anchorSelect='#cst-help'
|
||||||
|
offset={4}
|
||||||
|
>
|
||||||
<HelpConstituenta />
|
<HelpConstituenta />
|
||||||
</ConceptTooltip>
|
</ConceptTooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,22 +1,11 @@
|
||||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
import { useLayoutEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import DataTable, { createColumnHelper, type RowSelectionState,VisibilityState } from '../../../components/DataTable';
|
import { type RowSelectionState } from '../../../components/DataTable';
|
||||||
import ConstituentaBadge from '../../../components/Shared/ConstituentaBadge';
|
|
||||||
import { useRSForm } from '../../../context/RSFormContext';
|
import { useRSForm } from '../../../context/RSFormContext';
|
||||||
import { useConceptTheme } from '../../../context/ThemeContext';
|
import { CstType, ICstCreateData, ICstMovetoData } from '../../../models/rsform'
|
||||||
import useWindowSize from '../../../hooks/useWindowSize';
|
|
||||||
import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../../models/rsform'
|
|
||||||
import { prefixes } from '../../../utils/constants';
|
|
||||||
import { labelCstTypification } from '../../../utils/labels';
|
|
||||||
import RSListToolbar from './RSListToolbar';
|
import RSListToolbar from './RSListToolbar';
|
||||||
|
import RSTable from './RSTable';
|
||||||
// Window width cutoff for columns
|
|
||||||
const COLUMN_DEFINITION_HIDE_THRESHOLD = 1000;
|
|
||||||
const COLUMN_TYPE_HIDE_THRESHOLD = 1200;
|
|
||||||
const COLUMN_CONVENTION_HIDE_THRESHOLD = 1800;
|
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<IConstituenta>();
|
|
||||||
|
|
||||||
interface EditorRSListProps {
|
interface EditorRSListProps {
|
||||||
onOpenEdit: (cstID: number) => void
|
onOpenEdit: (cstID: number) => void
|
||||||
|
@ -26,13 +15,25 @@ interface EditorRSListProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: EditorRSListProps) {
|
function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: EditorRSListProps) {
|
||||||
const { colors, noNavigation } = useConceptTheme();
|
|
||||||
const windowSize = useWindowSize();
|
|
||||||
const { schema, editorMode: isEditable, cstMoveTo, resetAliases } = useRSForm();
|
const { schema, editorMode: isEditable, cstMoveTo, resetAliases } = useRSForm();
|
||||||
const [selected, setSelected] = useState<number[]>([]);
|
const [selected, setSelected] = useState<number[]>([]);
|
||||||
|
|
||||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
|
|
||||||
|
useLayoutEffect(
|
||||||
|
() => {
|
||||||
|
if (!schema || Object.keys(rowSelection).length === 0) {
|
||||||
|
setSelected([]);
|
||||||
|
} else {
|
||||||
|
const selected: number[] = [];
|
||||||
|
schema.items.forEach((cst, index) => {
|
||||||
|
if (rowSelection[String(index)] === true) {
|
||||||
|
selected.push(cst.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setSelected(selected);
|
||||||
|
}
|
||||||
|
}, [rowSelection, schema]);
|
||||||
|
|
||||||
// Delete selected constituents
|
// Delete selected constituents
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
|
@ -179,123 +180,19 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 'Backquote': handleCreateCst(); return true;
|
case 'Backquote': handleCreateCst(); return true;
|
||||||
case 'Digit1': handleCreateCst(CstType.BASE); return true;
|
case 'Digit1': handleCreateCst(CstType.BASE); return true;
|
||||||
case 'Digit2': handleCreateCst(CstType.STRUCTURED); return true;
|
case 'Digit2': handleCreateCst(CstType.STRUCTURED); return true;
|
||||||
case 'Digit3': handleCreateCst(CstType.TERM); return true;
|
case 'Digit3': handleCreateCst(CstType.TERM); return true;
|
||||||
case 'Digit4': handleCreateCst(CstType.AXIOM); return true;
|
case 'Digit4': handleCreateCst(CstType.AXIOM); return true;
|
||||||
case 'KeyQ': handleCreateCst(CstType.FUNCTION); return true;
|
case 'KeyQ': handleCreateCst(CstType.FUNCTION); return true;
|
||||||
case 'KeyW': handleCreateCst(CstType.PREDICATE); return true;
|
case 'KeyW': handleCreateCst(CstType.PREDICATE); return true;
|
||||||
case 'Digit5': handleCreateCst(CstType.CONSTANT); return true;
|
case 'Digit5': handleCreateCst(CstType.CONSTANT); return true;
|
||||||
case 'Digit6': handleCreateCst(CstType.THEOREM); return true;
|
case 'Digit6': handleCreateCst(CstType.THEOREM); return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRowClicked = useCallback(
|
|
||||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
|
||||||
if (event.altKey) {
|
|
||||||
event.preventDefault();
|
|
||||||
onOpenEdit(cst.id);
|
|
||||||
}
|
|
||||||
}, [onOpenEdit]);
|
|
||||||
|
|
||||||
const handleRowDoubleClicked = useCallback(
|
|
||||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
onOpenEdit(cst.id);
|
|
||||||
}, [onOpenEdit]);
|
|
||||||
|
|
||||||
useLayoutEffect(
|
|
||||||
() => {
|
|
||||||
setColumnVisibility({
|
|
||||||
'type': (windowSize.width ?? 0) >= COLUMN_TYPE_HIDE_THRESHOLD,
|
|
||||||
'convention': (windowSize.width ?? 0) >= COLUMN_CONVENTION_HIDE_THRESHOLD,
|
|
||||||
'definition': (windowSize.width ?? 0) >= COLUMN_DEFINITION_HIDE_THRESHOLD
|
|
||||||
});
|
|
||||||
}, [windowSize]);
|
|
||||||
|
|
||||||
useLayoutEffect(
|
|
||||||
() => {
|
|
||||||
if (!schema || Object.keys(rowSelection).length === 0) {
|
|
||||||
setSelected([]);
|
|
||||||
} else {
|
|
||||||
const selected: number[] = [];
|
|
||||||
schema.items.forEach((cst, index) => {
|
|
||||||
if (rowSelection[String(index)] === true) {
|
|
||||||
selected.push(cst.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setSelected(selected);
|
|
||||||
}
|
|
||||||
}, [rowSelection, schema]);
|
|
||||||
|
|
||||||
const columns = useMemo(
|
|
||||||
() => [
|
|
||||||
columnHelper.accessor('alias', {
|
|
||||||
id: 'alias',
|
|
||||||
header: 'Имя',
|
|
||||||
size: 65,
|
|
||||||
minSize: 65,
|
|
||||||
maxSize: 65,
|
|
||||||
cell: props =>
|
|
||||||
<ConstituentaBadge
|
|
||||||
theme={colors}
|
|
||||||
value={props.row.original}
|
|
||||||
prefixID={prefixes.cst_list}
|
|
||||||
shortTooltip
|
|
||||||
/>
|
|
||||||
}),
|
|
||||||
columnHelper.accessor(cst => labelCstTypification(cst), {
|
|
||||||
id: 'type',
|
|
||||||
header: 'Типизация',
|
|
||||||
size: 150,
|
|
||||||
minSize: 150,
|
|
||||||
maxSize: 150,
|
|
||||||
enableHiding: true,
|
|
||||||
cell: props => <div className='text-sm min-w-[9.3rem] max-w-[9.3rem] break-words'>{props.getValue()}</div>
|
|
||||||
}),
|
|
||||||
columnHelper.accessor(cst => cst.term_resolved || cst.term_raw || '', {
|
|
||||||
id: 'term',
|
|
||||||
header: 'Термин',
|
|
||||||
size: 500,
|
|
||||||
minSize: 150,
|
|
||||||
maxSize: 500
|
|
||||||
}),
|
|
||||||
columnHelper.accessor('definition_formal', {
|
|
||||||
id: 'expression',
|
|
||||||
header: 'Формальное определение',
|
|
||||||
size: 1000,
|
|
||||||
minSize: 300,
|
|
||||||
maxSize: 1000,
|
|
||||||
cell: props => <div className='break-words'>{props.getValue()}</div>
|
|
||||||
}),
|
|
||||||
columnHelper.accessor(cst => cst.definition_resolved || cst.definition_raw || '', {
|
|
||||||
id: 'definition',
|
|
||||||
header: 'Текстовое определение',
|
|
||||||
size: 1000,
|
|
||||||
minSize: 200,
|
|
||||||
maxSize: 1000,
|
|
||||||
cell: props => <div className='text-xs'>{props.getValue()}</div>
|
|
||||||
}),
|
|
||||||
columnHelper.accessor('convention', {
|
|
||||||
id: 'convention',
|
|
||||||
header: 'Конвенция / Комментарий',
|
|
||||||
size: 500,
|
|
||||||
minSize: 100,
|
|
||||||
maxSize: 500,
|
|
||||||
enableHiding: true,
|
|
||||||
cell: props => <div className='text-xs'>{props.getValue()}</div>
|
|
||||||
})
|
|
||||||
], [colors]);
|
|
||||||
|
|
||||||
const tableHeight = useMemo(
|
|
||||||
() => {
|
|
||||||
return !noNavigation ?
|
|
||||||
'calc(100vh - 7.2rem - 4px)'
|
|
||||||
: 'calc(100vh - 4.4rem - 4px)';
|
|
||||||
}, [noNavigation]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div tabIndex={-1}
|
<div tabIndex={-1}
|
||||||
className='w-full outline-none'
|
className='w-full outline-none'
|
||||||
|
@ -317,37 +214,14 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi
|
||||||
onReindex={handleReindex}
|
onReindex={handleReindex}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='w-full h-full overflow-auto text-sm' style={{maxHeight: tableHeight}}>
|
|
||||||
<DataTable dense noFooter
|
|
||||||
data={schema?.items ?? []}
|
|
||||||
columns={columns}
|
|
||||||
headPosition='0rem'
|
|
||||||
|
|
||||||
onRowDoubleClicked={handleRowDoubleClicked}
|
<RSTable
|
||||||
onRowClicked={handleRowClicked}
|
items={schema?.items}
|
||||||
|
selected={rowSelection}
|
||||||
enableHiding
|
setSelected={setRowSelection}
|
||||||
columnVisibility={columnVisibility}
|
onEdit={onOpenEdit}
|
||||||
onColumnVisibilityChange={setColumnVisibility}
|
onCreateNew={() => handleCreateCst()}
|
||||||
|
/>
|
||||||
enableRowSelection
|
|
||||||
rowSelection={rowSelection}
|
|
||||||
onRowSelectionChange={setRowSelection}
|
|
||||||
|
|
||||||
noDataComponent={
|
|
||||||
<span className='flex flex-col justify-center p-2 text-center'>
|
|
||||||
<p>Список пуст</p>
|
|
||||||
<p
|
|
||||||
className='cursor-pointer text-primary hover:underline'
|
|
||||||
onClick={() => handleCreateCst()}
|
|
||||||
>
|
|
||||||
Создать новую конституенту
|
|
||||||
</p>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
159
rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSTable.tsx
Normal file
159
rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSTable.tsx
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import DataTable, { createColumnHelper,RowSelectionState,VisibilityState } from '../../../components/DataTable';
|
||||||
|
import ConstituentaBadge from '../../../components/Shared/ConstituentaBadge';
|
||||||
|
import { useConceptTheme } from '../../../context/ThemeContext';
|
||||||
|
import useWindowSize from '../../../hooks/useWindowSize';
|
||||||
|
import { IConstituenta } from '../../../models/rsform';
|
||||||
|
import { prefixes } from '../../../utils/constants';
|
||||||
|
import { labelCstTypification } from '../../../utils/labels';
|
||||||
|
|
||||||
|
interface RSTableProps {
|
||||||
|
items?: IConstituenta[]
|
||||||
|
selected: RowSelectionState
|
||||||
|
setSelected: React.Dispatch<React.SetStateAction<RowSelectionState>>
|
||||||
|
|
||||||
|
onEdit: (cstID: number) => void
|
||||||
|
onCreateNew: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window width cutoff for columns
|
||||||
|
const COLUMN_DEFINITION_HIDE_THRESHOLD = 1000;
|
||||||
|
const COLUMN_TYPE_HIDE_THRESHOLD = 1200;
|
||||||
|
const COLUMN_CONVENTION_HIDE_THRESHOLD = 1800;
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<IConstituenta>();
|
||||||
|
|
||||||
|
function RSTable({
|
||||||
|
items, selected, setSelected,
|
||||||
|
onEdit, onCreateNew
|
||||||
|
}: RSTableProps) {
|
||||||
|
const { colors, noNavigation } = useConceptTheme();
|
||||||
|
const windowSize = useWindowSize();
|
||||||
|
|
||||||
|
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||||
|
|
||||||
|
|
||||||
|
useLayoutEffect(
|
||||||
|
() => {
|
||||||
|
setColumnVisibility({
|
||||||
|
'type': (windowSize.width ?? 0) >= COLUMN_TYPE_HIDE_THRESHOLD,
|
||||||
|
'convention': (windowSize.width ?? 0) >= COLUMN_CONVENTION_HIDE_THRESHOLD,
|
||||||
|
'definition': (windowSize.width ?? 0) >= COLUMN_DEFINITION_HIDE_THRESHOLD
|
||||||
|
});
|
||||||
|
}, [windowSize]);
|
||||||
|
|
||||||
|
const handleRowClicked = useCallback(
|
||||||
|
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||||
|
if (event.altKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
onEdit(cst.id);
|
||||||
|
}
|
||||||
|
}, [onEdit]);
|
||||||
|
|
||||||
|
const handleRowDoubleClicked = useCallback(
|
||||||
|
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
onEdit(cst.id);
|
||||||
|
}, [onEdit]);
|
||||||
|
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
columnHelper.accessor('alias', {
|
||||||
|
id: 'alias',
|
||||||
|
header: 'Имя',
|
||||||
|
size: 65,
|
||||||
|
minSize: 65,
|
||||||
|
maxSize: 65,
|
||||||
|
cell: props =>
|
||||||
|
<ConstituentaBadge
|
||||||
|
theme={colors}
|
||||||
|
value={props.row.original}
|
||||||
|
prefixID={prefixes.cst_list}
|
||||||
|
shortTooltip
|
||||||
|
/>
|
||||||
|
}),
|
||||||
|
columnHelper.accessor(cst => labelCstTypification(cst), {
|
||||||
|
id: 'type',
|
||||||
|
header: 'Типизация',
|
||||||
|
size: 150,
|
||||||
|
minSize: 150,
|
||||||
|
maxSize: 150,
|
||||||
|
enableHiding: true,
|
||||||
|
cell: props => <div className='text-sm min-w-[9.3rem] max-w-[9.3rem] break-words'>{props.getValue()}</div>
|
||||||
|
}),
|
||||||
|
columnHelper.accessor(cst => cst.term_resolved || cst.term_raw || '', {
|
||||||
|
id: 'term',
|
||||||
|
header: 'Термин',
|
||||||
|
size: 500,
|
||||||
|
minSize: 150,
|
||||||
|
maxSize: 500
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('definition_formal', {
|
||||||
|
id: 'expression',
|
||||||
|
header: 'Формальное определение',
|
||||||
|
size: 1000,
|
||||||
|
minSize: 300,
|
||||||
|
maxSize: 1000,
|
||||||
|
cell: props => <div className='break-words'>{props.getValue()}</div>
|
||||||
|
}),
|
||||||
|
columnHelper.accessor(cst => cst.definition_resolved || cst.definition_raw || '', {
|
||||||
|
id: 'definition',
|
||||||
|
header: 'Текстовое определение',
|
||||||
|
size: 1000,
|
||||||
|
minSize: 200,
|
||||||
|
maxSize: 1000,
|
||||||
|
cell: props => <div className='text-xs'>{props.getValue()}</div>
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('convention', {
|
||||||
|
id: 'convention',
|
||||||
|
header: 'Конвенция / Комментарий',
|
||||||
|
size: 500,
|
||||||
|
minSize: 100,
|
||||||
|
maxSize: 500,
|
||||||
|
enableHiding: true,
|
||||||
|
cell: props => <div className='text-xs'>{props.getValue()}</div>
|
||||||
|
})
|
||||||
|
], [colors]);
|
||||||
|
|
||||||
|
const tableHeight = useMemo(
|
||||||
|
() => {
|
||||||
|
return !noNavigation ?
|
||||||
|
'calc(100vh - 7.2rem - 4px)'
|
||||||
|
: 'calc(100vh - 4.4rem - 4px)';
|
||||||
|
}, [noNavigation]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full h-full overflow-auto text-sm min-h-[20rem]' style={{maxHeight: tableHeight}}>
|
||||||
|
<DataTable dense noFooter
|
||||||
|
data={items ?? []}
|
||||||
|
columns={columns}
|
||||||
|
headPosition='0rem'
|
||||||
|
|
||||||
|
onRowDoubleClicked={handleRowDoubleClicked}
|
||||||
|
onRowClicked={handleRowClicked}
|
||||||
|
|
||||||
|
enableHiding
|
||||||
|
columnVisibility={columnVisibility}
|
||||||
|
onColumnVisibilityChange={setColumnVisibility}
|
||||||
|
|
||||||
|
enableRowSelection
|
||||||
|
rowSelection={selected}
|
||||||
|
onRowSelectionChange={setSelected}
|
||||||
|
|
||||||
|
noDataComponent={
|
||||||
|
<span className='flex flex-col justify-center p-2 text-center'>
|
||||||
|
<p>Список пуст</p>
|
||||||
|
<p
|
||||||
|
className='cursor-pointer text-primary hover:underline'
|
||||||
|
onClick={() => onCreateNew()}
|
||||||
|
>
|
||||||
|
Создать новую конституенту
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RSTable;
|
Loading…
Reference in New Issue
Block a user