2023-07-25 20:27:29 +03:00
|
|
|
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
|
|
|
|
2023-07-30 15:49:30 +03:00
|
|
|
|
import ConceptDataTable from '../../../components/Common/ConceptDataTable';
|
2023-07-28 00:03:37 +03:00
|
|
|
|
import { useRSForm } from '../../../context/RSFormContext';
|
2023-07-31 22:38:58 +03:00
|
|
|
|
import { useConceptTheme } from '../../../context/ThemeContext';
|
2023-07-28 00:03:37 +03:00
|
|
|
|
import useLocalStorage from '../../../hooks/useLocalStorage';
|
2023-07-31 22:38:58 +03:00
|
|
|
|
import { prefixes } from '../../../utils/constants';
|
2023-08-02 21:35:24 +03:00
|
|
|
|
import { applyGraphFilter, CstMatchMode, CstType, DependencyMode, extractGlobals, IConstituenta, matchConstituenta } from '../../../utils/models';
|
2023-08-02 18:24:17 +03:00
|
|
|
|
import { getCstDescription, getMockConstituenta, mapStatusInfo } from '../../../utils/staticUI';
|
2023-07-30 16:48:25 +03:00
|
|
|
|
import ConstituentaTooltip from './ConstituentaTooltip';
|
2023-08-02 21:35:24 +03:00
|
|
|
|
import DependencyModePicker from './DependencyModePicker';
|
|
|
|
|
import MatchModePicker from './MatchModePicker';
|
2023-07-20 17:11:03 +03:00
|
|
|
|
|
2023-08-13 13:18:50 +03:00
|
|
|
|
// Height that should be left to accomodate navigation panel + bottom margin
|
|
|
|
|
const LOCAL_NAVIGATION_H = '2.6rem';
|
|
|
|
|
|
2023-07-28 00:03:37 +03:00
|
|
|
|
interface ViewSideConstituentsProps {
|
2023-07-20 17:11:03 +03:00
|
|
|
|
expression: string
|
2023-08-13 13:18:50 +03:00
|
|
|
|
baseHeight: string
|
2023-08-22 22:38:27 +03:00
|
|
|
|
activeID?: number
|
|
|
|
|
onOpenEdit: (cstID: number) => void
|
2023-08-22 20:29:07 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isMockCst(cst: IConstituenta) {
|
2023-08-22 22:38:27 +03:00
|
|
|
|
return cst.id <= 0;
|
2023-07-20 17:11:03 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 13:18:50 +03:00
|
|
|
|
function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) {
|
|
|
|
|
const { darkMode, noNavigation } = useConceptTheme();
|
2023-08-02 18:24:17 +03:00
|
|
|
|
const { schema } = useRSForm();
|
2023-08-02 21:35:24 +03:00
|
|
|
|
|
|
|
|
|
const [filterMatch, setFilterMatch] = useLocalStorage('side-filter-match', CstMatchMode.ALL);
|
|
|
|
|
const [filterText, setFilterText] = useLocalStorage('side-filter-text', '');
|
|
|
|
|
const [filterSource, setFilterSource] = useLocalStorage('side-filter-dependency', DependencyMode.ALL);
|
|
|
|
|
|
2023-07-25 20:27:29 +03:00
|
|
|
|
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []);
|
2023-07-20 17:11:03 +03:00
|
|
|
|
|
2023-08-13 13:18:50 +03:00
|
|
|
|
useEffect(
|
|
|
|
|
() => {
|
2023-07-20 17:11:03 +03:00
|
|
|
|
if (!schema?.items) {
|
|
|
|
|
setFilteredData([]);
|
2023-07-26 23:11:00 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
2023-08-02 21:35:24 +03:00
|
|
|
|
let filtered: IConstituenta[] = [];
|
|
|
|
|
if (filterSource === DependencyMode.EXPRESSION) {
|
2023-07-22 03:18:48 +03:00
|
|
|
|
const aliases = extractGlobals(expression);
|
2023-08-02 21:35:24 +03:00
|
|
|
|
filtered = schema.items.filter((cst) => aliases.has(cst.alias));
|
2023-07-22 03:18:48 +03:00
|
|
|
|
const names = filtered.map(cst => cst.alias)
|
2023-07-25 20:27:29 +03:00
|
|
|
|
const diff = Array.from(aliases).filter(name => !names.includes(name));
|
2023-07-22 03:18:48 +03:00
|
|
|
|
if (diff.length > 0) {
|
|
|
|
|
diff.forEach(
|
2023-08-22 22:38:27 +03:00
|
|
|
|
(alias, index) => filtered.push(getMockConstituenta(-index, alias, CstType.BASE, 'Конституента отсутствует')));
|
2023-07-22 03:18:48 +03:00
|
|
|
|
}
|
2023-08-02 21:35:24 +03:00
|
|
|
|
} else if (!activeID) {
|
|
|
|
|
filtered = schema.items
|
2023-07-20 17:11:03 +03:00
|
|
|
|
} else {
|
2023-08-02 21:35:24 +03:00
|
|
|
|
filtered = applyGraphFilter(schema, activeID, filterSource);
|
2023-07-20 17:11:03 +03:00
|
|
|
|
}
|
2023-08-02 21:35:24 +03:00
|
|
|
|
if (filterText) {
|
|
|
|
|
filtered = filtered.filter((cst) => matchConstituenta(filterText, cst, filterMatch));
|
|
|
|
|
}
|
|
|
|
|
setFilteredData(filtered);
|
|
|
|
|
}, [filterText, setFilteredData, filterSource, expression, schema, filterMatch, activeID]);
|
2023-07-20 17:11:03 +03:00
|
|
|
|
|
|
|
|
|
const handleRowClicked = useCallback(
|
2023-07-25 20:27:29 +03:00
|
|
|
|
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
2023-08-22 20:29:07 +03:00
|
|
|
|
if (event.altKey && !isMockCst(cst)) {
|
2023-08-02 18:24:17 +03:00
|
|
|
|
onOpenEdit(cst.id);
|
2023-07-20 17:11:03 +03:00
|
|
|
|
}
|
2023-08-02 18:24:17 +03:00
|
|
|
|
}, [onOpenEdit]);
|
2023-07-20 17:11:03 +03:00
|
|
|
|
|
|
|
|
|
const handleDoubleClick = useCallback(
|
2023-07-26 23:11:00 +03:00
|
|
|
|
(cst: IConstituenta) => {
|
2023-08-22 20:29:07 +03:00
|
|
|
|
if (!isMockCst(cst)) {
|
2023-08-02 18:24:17 +03:00
|
|
|
|
onOpenEdit(cst.id);
|
|
|
|
|
}
|
|
|
|
|
}, [onOpenEdit]);
|
2023-07-20 17:11:03 +03:00
|
|
|
|
|
2023-08-13 13:18:50 +03:00
|
|
|
|
const conditionalRowStyles = useMemo(
|
|
|
|
|
() => [
|
2023-07-31 22:38:58 +03:00
|
|
|
|
{
|
|
|
|
|
when: (cst: IConstituenta) => cst.id === activeID,
|
|
|
|
|
style: {
|
|
|
|
|
backgroundColor: darkMode ? '#0068b3' : '#def1ff',
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
], [activeID, darkMode]);
|
|
|
|
|
|
2023-08-13 13:18:50 +03:00
|
|
|
|
const columns = useMemo(
|
|
|
|
|
() => [
|
|
|
|
|
{
|
|
|
|
|
id: 'id',
|
|
|
|
|
selector: (cst: IConstituenta) => cst.id,
|
|
|
|
|
omit: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'ID',
|
|
|
|
|
id: 'alias',
|
|
|
|
|
cell: (cst: IConstituenta) => {
|
|
|
|
|
const info = mapStatusInfo.get(cst.status)!;
|
|
|
|
|
return (<>
|
|
|
|
|
<div
|
|
|
|
|
id={`${prefixes.cst_list}${cst.alias}`}
|
|
|
|
|
className={`w-full rounded-md text-center ${info.color}`}
|
|
|
|
|
>
|
|
|
|
|
{cst.alias}
|
|
|
|
|
</div>
|
|
|
|
|
<ConstituentaTooltip data={cst} anchor={`#${prefixes.cst_list}${cst.alias}`} />
|
|
|
|
|
</>);
|
2023-07-20 17:11:03 +03:00
|
|
|
|
},
|
2023-08-13 13:18:50 +03:00
|
|
|
|
width: '65px',
|
|
|
|
|
maxWidth: '65px',
|
|
|
|
|
conditionalCellStyles: [
|
|
|
|
|
{
|
2023-08-22 20:29:07 +03:00
|
|
|
|
when: (cst: IConstituenta) => isMockCst(cst),
|
2023-08-13 13:18:50 +03:00
|
|
|
|
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Описание',
|
|
|
|
|
id: 'description',
|
|
|
|
|
selector: (cst: IConstituenta) => getCstDescription(cst),
|
|
|
|
|
minWidth: '350px',
|
|
|
|
|
wrap: true,
|
|
|
|
|
conditionalCellStyles: [
|
|
|
|
|
{
|
2023-08-22 20:29:07 +03:00
|
|
|
|
when: (cst: IConstituenta) => isMockCst(cst),
|
2023-08-13 13:18:50 +03:00
|
|
|
|
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Выражение',
|
|
|
|
|
id: 'expression',
|
|
|
|
|
selector: (cst: IConstituenta) => cst.definition?.formal ?? '',
|
|
|
|
|
minWidth: '200px',
|
|
|
|
|
hide: 1600,
|
|
|
|
|
grow: 2,
|
|
|
|
|
wrap: true,
|
|
|
|
|
conditionalCellStyles: [
|
|
|
|
|
{
|
2023-08-22 20:29:07 +03:00
|
|
|
|
when: (cst: IConstituenta) => isMockCst(cst),
|
2023-08-13 13:18:50 +03:00
|
|
|
|
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
], []);
|
|
|
|
|
|
|
|
|
|
const maxHeight = useMemo(
|
|
|
|
|
() => {
|
|
|
|
|
const siblingHeight = `${baseHeight} - ${LOCAL_NAVIGATION_H}`
|
|
|
|
|
return (noNavigation ?
|
|
|
|
|
`calc(min(100vh - 5.2rem, ${siblingHeight}))`
|
|
|
|
|
: `calc(min(100vh - 8.7rem, ${siblingHeight}))`);
|
|
|
|
|
}, [noNavigation, baseHeight]);
|
|
|
|
|
|
|
|
|
|
return (<>
|
2023-08-22 17:52:59 +03:00
|
|
|
|
<div className='sticky top-0 left-0 right-0 z-10 flex items-start justify-between w-full gap-1 px-2 py-1 bg-white border-b rounded clr-bg-pop clr-border'>
|
2023-08-13 13:18:50 +03:00
|
|
|
|
<MatchModePicker
|
|
|
|
|
value={filterMatch}
|
|
|
|
|
onChange={setFilterMatch}
|
|
|
|
|
/>
|
|
|
|
|
<input type='text'
|
2023-08-22 17:52:59 +03:00
|
|
|
|
className='w-full px-2 bg-white outline-none select-none hover:text-clip clr-bg-pop'
|
2023-08-13 13:18:50 +03:00
|
|
|
|
placeholder='наберите текст фильтра'
|
|
|
|
|
value={filterText}
|
|
|
|
|
onChange={event => setFilterText(event.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
<DependencyModePicker value={filterSource} onChange={setFilterSource}/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className='overflow-y-auto' style={{maxHeight : `${maxHeight}`}}>
|
2023-07-30 15:49:30 +03:00
|
|
|
|
<ConceptDataTable
|
2023-07-20 17:11:03 +03:00
|
|
|
|
data={filteredData}
|
|
|
|
|
columns={columns}
|
|
|
|
|
keyField='id'
|
2023-08-13 13:18:50 +03:00
|
|
|
|
conditionalRowStyles={conditionalRowStyles}
|
|
|
|
|
noDataComponent={
|
|
|
|
|
<span className='flex flex-col justify-center p-2 text-center min-h-[5rem]'>
|
|
|
|
|
<p>Список конституент пуст</p>
|
|
|
|
|
<p>Измените параметры фильтра</p>
|
|
|
|
|
</span>
|
|
|
|
|
}
|
2023-07-20 17:11:03 +03:00
|
|
|
|
|
|
|
|
|
striped
|
|
|
|
|
highlightOnHover
|
|
|
|
|
pointerOnHover
|
|
|
|
|
|
|
|
|
|
onRowDoubleClicked={handleDoubleClick}
|
|
|
|
|
onRowClicked={handleRowClicked}
|
|
|
|
|
dense
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2023-08-13 13:18:50 +03:00
|
|
|
|
</>);
|
2023-07-20 17:11:03 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-28 00:03:37 +03:00
|
|
|
|
export default ViewSideConstituents;
|