mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
Implement DataTable features
This commit is contained in:
parent
86c2965820
commit
38cdf31676
|
@ -27,9 +27,9 @@ class LibraryActiveView(generics.ListAPIView):
|
|||
# pylint: disable=unsupported-binary-operation
|
||||
return m.LibraryItem.objects.filter(
|
||||
Q(is_common=True) | Q(owner=user) | Q(subscription__user=user)
|
||||
).distinct()
|
||||
).distinct().order_by('-time_update')
|
||||
else:
|
||||
return m.LibraryItem.objects.filter(is_common=True)
|
||||
return m.LibraryItem.objects.filter(is_common=True).order_by('-time_update')
|
||||
|
||||
|
||||
class ConstituentAPIView(generics.RetrieveUpdateAPIView):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { CheckboxChecked } from '../Icons';
|
||||
import { CheckboxCheckedIcon } from '../Icons';
|
||||
import Label from './Label';
|
||||
|
||||
export interface CheckboxProps
|
||||
|
@ -53,7 +53,7 @@ function Checkbox({
|
|||
{...props}
|
||||
>
|
||||
<div className={`max-w-[1rem] min-w-[1rem] h-4 mt-0.5 border rounded-sm ${bgColor} ${cursor}`} >
|
||||
{ value && <div className='mt-[1px] ml-[1px]'><CheckboxChecked /></div>}
|
||||
{ value && <div className='mt-[1px] ml-[1px]'><CheckboxCheckedIcon /></div>}
|
||||
</div>
|
||||
{ label &&
|
||||
<Label
|
||||
|
|
|
@ -1,265 +0,0 @@
|
|||
import { Cell, flexRender, getCoreRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
Header, HeaderGroup, Row, RowData, TableOptions, useReactTable
|
||||
} from '@tanstack/react-table';
|
||||
|
||||
import Checkbox from './Checkbox';
|
||||
import Tristate from './Tristate';
|
||||
|
||||
export interface DataTableProps<TData extends RowData>
|
||||
extends Omit<TableOptions<TData>, 'getCoreRowModel' | 'getSortedRowModel'| 'getPaginationRowModel'> {
|
||||
onRowClicked?: (row: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
||||
onRowDoubleClicked?: (row: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
||||
noDataComponent?: React.ReactNode
|
||||
pagination?: boolean
|
||||
}
|
||||
|
||||
function defaultNoDataComponent() {
|
||||
return (
|
||||
<div className='p-2 text-center'>
|
||||
Данные отсутствуют
|
||||
</div>);
|
||||
}
|
||||
|
||||
export default function DataTable<TData extends RowData>({
|
||||
onRowClicked, onRowDoubleClicked, noDataComponent=defaultNoDataComponent(),
|
||||
enableRowSelection, enableMultiRowSelection,
|
||||
pagination,
|
||||
...options
|
||||
}: DataTableProps<TData>) {
|
||||
// const [sorting, setSorting] = React.useState<SortingState>([])
|
||||
|
||||
const tableImpl = useReactTable({
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: options.enableSorting ? getSortedRowModel() : undefined,
|
||||
getPaginationRowModel: pagination ? getPaginationRowModel() : undefined,
|
||||
|
||||
state: {
|
||||
...options.state
|
||||
},
|
||||
// onSortingChange: setSorting,
|
||||
enableRowSelection: enableRowSelection,
|
||||
enableMultiRowSelection: enableMultiRowSelection,
|
||||
...options
|
||||
});
|
||||
|
||||
const isEmpty = tableImpl.getRowModel().rows.length === 0;
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
{isEmpty && noDataComponent}
|
||||
{!isEmpty &&
|
||||
<table>
|
||||
<thead>
|
||||
{tableImpl.getHeaderGroups().map(
|
||||
(headerGroup: HeaderGroup<TData>) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{(enableRowSelection ?? enableMultiRowSelection) &&
|
||||
<th className='pl-3 pr-1'>
|
||||
<Tristate
|
||||
tabIndex={-1}
|
||||
value={
|
||||
!tableImpl.getIsAllPageRowsSelected() && tableImpl.getIsSomePageRowsSelected() ? null :
|
||||
tableImpl.getIsAllPageRowsSelected()
|
||||
}
|
||||
tooltip='Выделить все'
|
||||
setValue={value => tableImpl.toggleAllPageRowsSelected(value !== false)}
|
||||
/>
|
||||
</th>
|
||||
}
|
||||
{headerGroup.headers.map(
|
||||
(header: Header<TData, unknown>) => (
|
||||
<th key={header.id}
|
||||
colSpan={header.colSpan}
|
||||
className='p-2 text-xs font-semibold select-none whitespace-nowrap'
|
||||
style={{
|
||||
textAlign: header.getSize() > 100 ? 'left': 'center',
|
||||
width: header.getSize()
|
||||
}}
|
||||
>
|
||||
{/* {header.isPlaceholder ? null : (
|
||||
<div
|
||||
{...{
|
||||
className: header.column.getCanSort()
|
||||
? 'cursor-pointer select-none'
|
||||
: '',
|
||||
onClick: header.column.getToggleSortingHandler(),
|
||||
}}
|
||||
>
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
{{
|
||||
asc: ' 🔼',
|
||||
desc: ' 🔽',
|
||||
}[header.column.getIsSorted() as string] ?? null}
|
||||
</div>
|
||||
)} */}
|
||||
{header.isPlaceholder ? null
|
||||
: flexRender(header.column.columnDef.header, header.getContext())
|
||||
}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{tableImpl.getRowModel().rows.map(
|
||||
(row: Row<TData>) => (
|
||||
<tr
|
||||
key={row.id}
|
||||
className={
|
||||
row.getIsSelected() ? 'clr-selected clr-hover' :
|
||||
row.index % 2 === 0 ? 'clr-controls clr-hover' :
|
||||
'clr-app clr-hover'
|
||||
}
|
||||
>
|
||||
{(enableRowSelection ?? enableMultiRowSelection) &&
|
||||
<td className='pl-3 pr-1 border-y'>
|
||||
<Checkbox
|
||||
tabIndex={-1}
|
||||
value={row.getIsSelected()}
|
||||
setValue={row.getToggleSelectedHandler()}
|
||||
/>
|
||||
</td>
|
||||
}
|
||||
{row.getVisibleCells().map(
|
||||
(cell: Cell<TData, unknown>) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className='px-2 py-1 border-y'
|
||||
style={{
|
||||
cursor: onRowClicked || onRowDoubleClicked ? 'pointer': 'auto'
|
||||
}}
|
||||
onClick={event => onRowClicked && onRowClicked(row.original, event)}
|
||||
onDoubleClick={event => onRowDoubleClicked && onRowDoubleClicked(row.original, event)}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
<tfoot>
|
||||
{tableImpl.getFooterGroups().map(
|
||||
(footerGroup: HeaderGroup<TData>) => (
|
||||
<tr key={footerGroup.id}>
|
||||
{footerGroup.headers.map(
|
||||
(header: Header<TData, unknown>) => (
|
||||
<th key={header.id}>
|
||||
{header.isPlaceholder ? null
|
||||
: flexRender(header.column.columnDef.footer, header.getContext())
|
||||
}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tfoot>
|
||||
</table>}
|
||||
{/*
|
||||
<div className="h-2" />
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
className="p-1 border rounded"
|
||||
onClick={() => table.setPageIndex(0)}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
{'<<'}
|
||||
</button>
|
||||
<button
|
||||
className="p-1 border rounded"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
{'<'}
|
||||
</button>
|
||||
<button
|
||||
className="p-1 border rounded"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
{'>'}
|
||||
</button>
|
||||
<button
|
||||
className="p-1 border rounded"
|
||||
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
{'>>'}
|
||||
</button>
|
||||
<span className="flex items-center gap-1">
|
||||
<div>Page</div>
|
||||
<strong>
|
||||
{table.getState().pagination.pageIndex + 1} of{' '}
|
||||
{table.getPageCount()}
|
||||
</strong>
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
| Go to page:
|
||||
<input
|
||||
type="number"
|
||||
defaultValue={table.getState().pagination.pageIndex + 1}
|
||||
onChange={e => {
|
||||
const page = e.target.value ? Number(e.target.value) - 1 : 0
|
||||
table.setPageIndex(page)
|
||||
}}
|
||||
className="w-16 p-1 border rounded"
|
||||
/>
|
||||
</span>
|
||||
<select
|
||||
value={table.getState().pagination.pageSize}
|
||||
onChange={e => {
|
||||
table.setPageSize(Number(e.target.value))
|
||||
}}
|
||||
>
|
||||
{[10, 20, 30, 40, 50].map(pageSize => (
|
||||
<option key={pageSize} value={pageSize}>
|
||||
Show {pageSize}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="h-4" />
|
||||
<button onClick={() => rerender()} className="p-2 border">
|
||||
Rerender
|
||||
</button>
|
||||
</div>
|
||||
) */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// import { TableOptions, useReactTable } from '@tanstack/react-table'
|
||||
|
||||
// import { useConceptTheme } from '../../context/ThemeContext';
|
||||
// import { dataTableDarkT, dataTableLightT } from '../../utils/color';
|
||||
|
||||
// export interface SelectionInfo<T> {
|
||||
// allSelected: boolean
|
||||
// selectedCount: number
|
||||
// selectedRows: T[]
|
||||
// }
|
||||
|
||||
// interface DataTableProps<T>
|
||||
// extends TableOptions<T>{}
|
||||
|
||||
// function DataTable<T>({ ...props }: DataTableProps<T>) {
|
||||
// const { darkMode } = useConceptTheme();
|
||||
// const table = useReactTable(props);
|
||||
|
||||
// return (
|
||||
// <DataTable<T>
|
||||
// theme={ theme ?? (darkMode ? 'customDark' : 'customLight')}
|
||||
// paginationComponentOptions={{
|
||||
// rowsPerPageText: 'строк на страницу'
|
||||
// }}
|
||||
// {...props}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
||||
// export default DataTable;
|
|
@ -5,10 +5,7 @@ interface DividerProps {
|
|||
|
||||
function Divider({ vertical, margins = 'mx-2' }: DividerProps) {
|
||||
return (
|
||||
<>
|
||||
{vertical && <div className={`${margins} border-x-2`} />}
|
||||
{!vertical && <div className={`${margins} border-y-2`} />}
|
||||
</>
|
||||
<div className={`${margins} ${vertical ? 'border-x-2 h-full': 'border-y-2 w-full'}`} />
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { CheckboxChecked, CheckboxNull } from '../Icons';
|
||||
import { CheckboxCheckedIcon, CheckboxNullIcon } from '../Icons';
|
||||
import { CheckboxProps } from './Checkbox';
|
||||
import Label from './Label';
|
||||
|
||||
|
@ -54,8 +54,8 @@ function Tristate({
|
|||
{...props}
|
||||
>
|
||||
<div className={`w-4 h-4 shrink-0 mt-0.5 border rounded-sm ${bgColor} ${cursor}`} >
|
||||
{ value && <div className='mt-[1px] ml-[1px]'><CheckboxChecked /></div>}
|
||||
{ value == null && <div className='mt-[1px] ml-[1px]'><CheckboxNull /></div>}
|
||||
{ value && <div className='mt-[1px] ml-[1px]'><CheckboxCheckedIcon /></div>}
|
||||
{ value == null && <div className='mt-[1px] ml-[1px]'><CheckboxNullIcon /></div>}
|
||||
</div>
|
||||
{ label &&
|
||||
<Label
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
function defaultNoDataComponent() {
|
||||
return (
|
||||
<div className='p-2 text-center'>
|
||||
Данные отсутствуют
|
||||
</div>);
|
||||
}
|
||||
|
||||
export default defaultNoDataComponent;
|
|
@ -0,0 +1,88 @@
|
|||
import { Table } from '@tanstack/react-table';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { GotoFirstIcon, GotoLastIcon, GotoNextIcon, GotoPrevIcon } from '../Icons';
|
||||
|
||||
interface PaginationToolsProps<TData> {
|
||||
table: Table<TData>
|
||||
paginationOptions: number[]
|
||||
onChangePaginationOption?: (newValue: number) => void
|
||||
}
|
||||
|
||||
function PaginationTools<TData>({ table, paginationOptions, onChangePaginationOption }: PaginationToolsProps<TData>) {
|
||||
const handlePaginationOptionsChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const perPage = Number(event.target.value);
|
||||
table.setPageSize(perPage);
|
||||
if (onChangePaginationOption) {
|
||||
onChangePaginationOption(perPage);
|
||||
}
|
||||
}, [onChangePaginationOption, table]);
|
||||
|
||||
return (
|
||||
<div className='text-sm flex justify-end w-full items-center text-controls select-none my-2'>
|
||||
<div className='flex items-center gap-1 mr-3'>
|
||||
<div className=''>
|
||||
{table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1}
|
||||
-
|
||||
{Math.min(table.getFilteredRowModel().rows.length, (table.getState().pagination.pageIndex + 1) * table.getState().pagination.pageSize)}
|
||||
</div>
|
||||
<span>из</span>
|
||||
<div className=''>{table.getFilteredRowModel().rows.length}</div>
|
||||
</div>
|
||||
<div className='flex'>
|
||||
<button
|
||||
className='clr-hover text-controls'
|
||||
onClick={() => table.setPageIndex(0)}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<GotoFirstIcon />
|
||||
</button>
|
||||
<button
|
||||
className='clr-hover text-controls'
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<GotoPrevIcon />
|
||||
</button>
|
||||
<input type='text'
|
||||
title='Номер страницы. Выделите для ручного ввода'
|
||||
className='w-6 clr-app text-center'
|
||||
value={table.getState().pagination.pageIndex + 1}
|
||||
onChange={event => {
|
||||
const page = event.target.value ? Number(event.target.value) - 1 : 0;
|
||||
if (page + 1 <= table.getPageCount()) {
|
||||
table.setPageIndex(page);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
className='clr-hover text-controls'
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<GotoNextIcon />
|
||||
</button>
|
||||
<button className='clr-hover text-controls'
|
||||
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<GotoLastIcon />
|
||||
</button>
|
||||
</div>
|
||||
<select
|
||||
value={table.getState().pagination.pageSize}
|
||||
onChange={handlePaginationOptionsChange}
|
||||
className='clr-app mx-2 cursor-pointer'
|
||||
>
|
||||
{paginationOptions.map(
|
||||
pageSize => (
|
||||
<option key={pageSize} value={pageSize}>
|
||||
{pageSize} на стр
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>);
|
||||
}
|
||||
|
||||
export default PaginationTools;
|
23
rsconcept/frontend/src/components/DataTable/SelectAll.tsx
Normal file
23
rsconcept/frontend/src/components/DataTable/SelectAll.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { Table } from '@tanstack/react-table';
|
||||
|
||||
import Tristate from '../Common/Tristate';
|
||||
|
||||
interface SelectAllProps<TData> {
|
||||
table: Table<TData>
|
||||
}
|
||||
|
||||
function SelectAll<TData>({ table }: SelectAllProps<TData>) {
|
||||
return (
|
||||
<Tristate
|
||||
tabIndex={-1}
|
||||
value={
|
||||
!table.getIsAllPageRowsSelected() && table.getIsSomePageRowsSelected() ? null :
|
||||
table.getIsAllPageRowsSelected()
|
||||
}
|
||||
tooltip='Выделить все'
|
||||
setValue={value => table.toggleAllPageRowsSelected(value !== false)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default SelectAll;
|
19
rsconcept/frontend/src/components/DataTable/SelectRow.tsx
Normal file
19
rsconcept/frontend/src/components/DataTable/SelectRow.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { Row } from '@tanstack/react-table';
|
||||
|
||||
import Checkbox from '../Common/Checkbox';
|
||||
|
||||
interface SelectRowProps<TData> {
|
||||
row: Row<TData>
|
||||
}
|
||||
|
||||
function SelectRow<TData>({ row }: SelectRowProps<TData>) {
|
||||
return (
|
||||
<Checkbox
|
||||
tabIndex={-1}
|
||||
value={row.getIsSelected()}
|
||||
setValue={row.getToggleSelectedHandler()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default SelectRow;
|
19
rsconcept/frontend/src/components/DataTable/SortingIcon.tsx
Normal file
19
rsconcept/frontend/src/components/DataTable/SortingIcon.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { Column } from '@tanstack/react-table';
|
||||
|
||||
import { AscendingIcon, DescendingIcon } from '../Icons';
|
||||
|
||||
interface SortingIconProps<TData> {
|
||||
column: Column<TData>
|
||||
}
|
||||
|
||||
function SortingIcon<TData>({ column }: SortingIconProps<TData>) {
|
||||
return (<>
|
||||
{{
|
||||
desc: <DescendingIcon size={4} />,
|
||||
asc: <AscendingIcon size={4}/>,
|
||||
}[column.getIsSorted() as string] ??
|
||||
<DescendingIcon size={4} color='opacity-0 hover:opacity-50' />}
|
||||
</>);
|
||||
}
|
||||
|
||||
export default SortingIcon;
|
201
rsconcept/frontend/src/components/DataTable/index.tsx
Normal file
201
rsconcept/frontend/src/components/DataTable/index.tsx
Normal file
|
@ -0,0 +1,201 @@
|
|||
import {
|
||||
Cell, ColumnSort,
|
||||
createColumnHelper, flexRender, getCoreRowModel,
|
||||
getPaginationRowModel, getSortedRowModel, Header, HeaderGroup,
|
||||
PaginationState, Row, RowData, type RowSelectionState,
|
||||
SortingState, TableOptions, useReactTable, type VisibilityState
|
||||
} from '@tanstack/react-table';
|
||||
import { useState } from 'react';
|
||||
|
||||
import DefaultNoData from './DefaultNoData';
|
||||
import PaginationTools from './PaginationTools';
|
||||
import SelectAll from './SelectAll';
|
||||
import SelectRow from './SelectRow';
|
||||
import SortingIcon from './SortingIcon';
|
||||
|
||||
export { createColumnHelper, type ColumnSort, type RowSelectionState, type VisibilityState };
|
||||
|
||||
export interface IConditionalStyle<TData> {
|
||||
when: (rowData: TData) => boolean
|
||||
style: React.CSSProperties
|
||||
}
|
||||
|
||||
export interface DataTableProps<TData extends RowData>
|
||||
extends Pick<TableOptions<TData>,
|
||||
'data' | 'columns' |
|
||||
'onRowSelectionChange' | 'onColumnVisibilityChange'
|
||||
> {
|
||||
dense?: boolean
|
||||
conditionalRowStyles?: IConditionalStyle<TData>[]
|
||||
onRowClicked?: (rowData: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
||||
onRowDoubleClicked?: (rowData: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
||||
noDataComponent?: React.ReactNode
|
||||
|
||||
enableRowSelection?: boolean
|
||||
rowSelection?: RowSelectionState
|
||||
|
||||
enableHiding?: boolean
|
||||
columnVisibility?: VisibilityState
|
||||
|
||||
enablePagination?: boolean
|
||||
paginationPerPage?: number
|
||||
paginationOptions?: number[]
|
||||
onChangePaginationOption?: (newValue: number) => void
|
||||
|
||||
enableSorting?: boolean
|
||||
initialSorting?: ColumnSort
|
||||
}
|
||||
|
||||
export default function DataTable<TData extends RowData>({
|
||||
dense, conditionalRowStyles,
|
||||
onRowClicked, onRowDoubleClicked, noDataComponent,
|
||||
|
||||
enableRowSelection,
|
||||
rowSelection,
|
||||
|
||||
enableHiding,
|
||||
columnVisibility,
|
||||
|
||||
enableSorting,
|
||||
initialSorting,
|
||||
|
||||
enablePagination,
|
||||
paginationPerPage=10,
|
||||
paginationOptions=[10, 20, 30, 40, 50],
|
||||
onChangePaginationOption,
|
||||
|
||||
...options
|
||||
}: DataTableProps<TData>) {
|
||||
const [sorting, setSorting] = useState<SortingState>(initialSorting ? [initialSorting] : []);
|
||||
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: paginationPerPage,
|
||||
});
|
||||
|
||||
const tableImpl = useReactTable({
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: enableSorting ? getSortedRowModel() : undefined,
|
||||
getPaginationRowModel: enablePagination ? getPaginationRowModel() : undefined,
|
||||
|
||||
state: {
|
||||
pagination: pagination,
|
||||
sorting: sorting,
|
||||
rowSelection: rowSelection ?? {},
|
||||
columnVisibility: columnVisibility ?? {}
|
||||
},
|
||||
enableHiding: enableHiding,
|
||||
onPaginationChange: enablePagination ? setPagination : undefined,
|
||||
onSortingChange: enableSorting ? setSorting : undefined,
|
||||
enableMultiRowSelection: enableRowSelection,
|
||||
...options
|
||||
});
|
||||
|
||||
const isEmpty = tableImpl.getRowModel().rows.length === 0;
|
||||
|
||||
function getRowStyles(row: Row<TData>) {
|
||||
return {...conditionalRowStyles!
|
||||
.filter(item => item.when(row.original))
|
||||
.reduce((prev, item) => ({...prev, ...item.style}), {})
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
{isEmpty && (noDataComponent ?? <DefaultNoData />)}
|
||||
|
||||
{!isEmpty &&
|
||||
<table>
|
||||
<thead>
|
||||
{tableImpl.getHeaderGroups().map(
|
||||
(headerGroup: HeaderGroup<TData>) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{enableRowSelection &&
|
||||
<th className='pl-3 pr-1'>
|
||||
<SelectAll table={tableImpl} />
|
||||
</th>}
|
||||
{headerGroup.headers.map(
|
||||
(header: Header<TData, unknown>) => (
|
||||
<th key={header.id}
|
||||
colSpan={header.colSpan}
|
||||
className='px-2 py-2 text-xs font-semibold select-none whitespace-nowrap'
|
||||
style={{
|
||||
textAlign: header.getSize() > 100 ? 'left': 'center',
|
||||
width: header.getSize(),
|
||||
cursor: enableSorting && header.column.getCanSort() ? 'pointer': 'auto'
|
||||
}}
|
||||
onClick={enableSorting ? header.column.getToggleSortingHandler() : undefined}
|
||||
>
|
||||
{header.isPlaceholder ? null : (
|
||||
<div className='flex gap-1'>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
{enableSorting && header.column.getCanSort() && <SortingIcon column={header.column} />}
|
||||
</div>)}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{tableImpl.getRowModel().rows.map(
|
||||
(row: Row<TData>, index) => (
|
||||
<tr
|
||||
key={row.id}
|
||||
className={
|
||||
row.getIsSelected() ? 'clr-selected clr-hover' :
|
||||
index % 2 === 0 ? 'clr-controls clr-hover' :
|
||||
'clr-app clr-hover'
|
||||
}
|
||||
style={conditionalRowStyles && getRowStyles(row)}
|
||||
>
|
||||
{enableRowSelection &&
|
||||
<td className='pl-3 pr-1 border-y'>
|
||||
<SelectRow row={row} />
|
||||
</td>}
|
||||
{row.getVisibleCells().map(
|
||||
(cell: Cell<TData, unknown>) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className='px-2 border-y'
|
||||
style={{
|
||||
cursor: onRowClicked || onRowDoubleClicked ? 'pointer': 'auto',
|
||||
paddingBottom: dense ? '0.25rem': '0.5rem',
|
||||
paddingTop: dense ? '0.25rem': '0.5rem'
|
||||
}}
|
||||
onClick={event => onRowClicked && onRowClicked(row.original, event)}
|
||||
onDoubleClick={event => onRowDoubleClicked && onRowDoubleClicked(row.original, event)}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
<tfoot>
|
||||
{tableImpl.getFooterGroups().map(
|
||||
(footerGroup: HeaderGroup<TData>) => (
|
||||
<tr key={footerGroup.id}>
|
||||
{footerGroup.headers.map(
|
||||
(header: Header<TData, unknown>) => (
|
||||
<th key={header.id}>
|
||||
{header.isPlaceholder ? null
|
||||
: flexRender(header.column.columnDef.footer, header.getContext())
|
||||
}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tfoot>
|
||||
</table>}
|
||||
|
||||
{enablePagination &&
|
||||
<PaginationTools
|
||||
table={tableImpl}
|
||||
paginationOptions={paginationOptions}
|
||||
onChangePaginationOption={onChangePaginationOption}
|
||||
/>
|
||||
}
|
||||
</div>);
|
||||
}
|
|
@ -22,6 +22,7 @@ function HelpTermGraph() {
|
|||
<p><b>Клик на конституенту</b> - выделение, включая скрытые конституенты</p>
|
||||
<p><b>Двойной клик</b> - редактирование конституенты</p>
|
||||
<p><b>Delete</b> - удалить выбранные</p>
|
||||
<br />
|
||||
|
||||
<Divider margins='mt-2' />
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ function IconSVG({ viewbox, size = 6, color, props, children }: IconSVGProps) {
|
|||
<svg
|
||||
width={width}
|
||||
height={width}
|
||||
className={`w-[${width}] h-[${width}] ${color ?? ''}`}
|
||||
className={`w-[${width}] h-[${width}] ${color}`}
|
||||
fill='currentColor'
|
||||
viewBox={viewbox}
|
||||
{...props}
|
||||
|
@ -309,7 +309,7 @@ export function MeshIcon(props: IconProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function InDoor(props: IconProps) {
|
||||
export function InDoorIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path fill='none' d='M0 0h24v24H0z' />
|
||||
|
@ -318,7 +318,55 @@ export function InDoor(props: IconProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function CheckboxChecked() {
|
||||
export function GotoLastIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M7.707 17.707L13.414 12 7.707 6.293 6.293 7.707 10.586 12l-4.293 4.293zM15 6h2v12h-2z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function GotoFirstIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M16.293 17.707l1.414-1.414L13.414 12l4.293-4.293-1.414-1.414L10.586 12zM7 6h2v12H7z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function GotoNextIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M10.707 17.707L16.414 12l-5.707-5.707-1.414 1.414L13.586 12l-4.293 4.293z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function GotoPrevIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M13.293 6.293L7.586 12l5.707 5.707 1.414-1.414L10.414 12l4.293-4.293z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function DescendingIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M11.998 17l7-8h-14z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function AscendingIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M5 15h14l-7-8z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function CheckboxCheckedIcon() {
|
||||
return (
|
||||
<svg
|
||||
className='w-3 h-3'
|
||||
|
@ -330,7 +378,7 @@ export function CheckboxChecked() {
|
|||
);
|
||||
}
|
||||
|
||||
export function CheckboxNull() {
|
||||
export function CheckboxNullIcon() {
|
||||
return (
|
||||
<svg
|
||||
className='w-3 h-3'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import useDropdown from '../../hooks/useDropdown';
|
||||
import { InDoor, UserIcon } from '../Icons';
|
||||
import { InDoorIcon, UserIcon } from '../Icons';
|
||||
import NavigationButton from './NavigationButton';
|
||||
import UserDropdown from './UserDropdown';
|
||||
|
||||
|
@ -18,7 +18,7 @@ function UserMenu() {
|
|||
<NavigationButton
|
||||
text='Войти...'
|
||||
description='Перейти на страницу логина'
|
||||
icon={<InDoor />}
|
||||
icon={<InDoorIcon />}
|
||||
onClick={navigateLogin}
|
||||
/>}
|
||||
{ user &&
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
--cl-bg-60: hsl(000, 000%, 091%);
|
||||
--cl-bg-40: hsl(000, 000%, 080%);
|
||||
|
||||
--cl-fg-60: hsl(000, 000%, 055%);
|
||||
--cl-fg-60: hsl(000, 000%, 065%);
|
||||
--cl-fg-80: hsl(000, 000%, 047%);
|
||||
--cl-fg-100: hsl(000, 000%, 000%);
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ function SearchPanel({ total, filtered, setFilter }: SearchPanelProps) {
|
|||
value={strategy}
|
||||
onChange={handleChangeStrategy}
|
||||
/>
|
||||
<div className='relative w-96 min-w-[10rem]'>
|
||||
<div className='relative w-96 min-w-[10rem] select-none'>
|
||||
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-controls'>
|
||||
<MagnifyingGlassIcon />
|
||||
</div>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
import { useMemo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||
import DataTable from '../../components/Common/DataTable';
|
||||
import TextURL from '../../components/Common/TextURL';
|
||||
import DataTable, { createColumnHelper } from '../../components/DataTable';
|
||||
import HelpLibrary from '../../components/Help/HelpLibrary';
|
||||
import { EducationIcon, EyeIcon, GroupIcon, HelpIcon } from '../../components/Icons';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import { useUsers } from '../../context/UsersContext';
|
||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||
import { prefixes } from '../../utils/constants';
|
||||
import { ILibraryItem } from '../../utils/models';
|
||||
|
||||
|
@ -26,6 +26,8 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
|
|||
const { user } = useAuth();
|
||||
const { getUserLabel } = useUsers();
|
||||
|
||||
const [ itemsPerPage, setItemsPerPage ] = useLocalStorage<number>('library_per_page', 50);
|
||||
|
||||
const openRSForm = (item: ILibraryItem) => navigateTo(`/rsforms/${item.id}`);
|
||||
|
||||
const columns = useMemo(
|
||||
|
@ -34,6 +36,7 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
|
|||
id: 'status',
|
||||
header: '',
|
||||
size: 60,
|
||||
minSize: 60,
|
||||
maxSize: 60,
|
||||
cell: props => {
|
||||
const item = props.row.original;
|
||||
|
@ -55,34 +58,38 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
|
|||
size: 200,
|
||||
minSize: 200,
|
||||
maxSize: 200,
|
||||
enableSorting: true
|
||||
enableSorting: true,
|
||||
sortingFn: 'text'
|
||||
}),
|
||||
columnHelper.accessor('title', {
|
||||
id: 'title',
|
||||
header: 'Название',
|
||||
minSize: 200,
|
||||
size: 1000,
|
||||
minSize: 400,
|
||||
maxSize: 1000,
|
||||
enableSorting: true
|
||||
enableSorting: true,
|
||||
sortingFn: 'text'
|
||||
}),
|
||||
columnHelper.accessor(item => item.owner ?? 0, {
|
||||
id: 'owner',
|
||||
header: 'Владелец',
|
||||
size: 300,
|
||||
minSize: 200,
|
||||
maxSize: 300,
|
||||
cell: props => getUserLabel(props.cell.getValue()),
|
||||
enableSorting: true,
|
||||
enableResizing: false,
|
||||
minSize: 200,
|
||||
size: 300,
|
||||
maxSize: 300
|
||||
sortingFn: 'text'
|
||||
}),
|
||||
columnHelper.accessor('time_update', {
|
||||
id: 'time_update',
|
||||
header: 'Обновлена',
|
||||
minSize: 200,
|
||||
size: 200,
|
||||
maxSize: 200,
|
||||
size: 220,
|
||||
minSize: 220,
|
||||
maxSize: 220,
|
||||
cell: props => new Date(props.cell.getValue()).toLocaleString(intl.locale),
|
||||
enableSorting: true
|
||||
enableSorting: true,
|
||||
sortingFn: 'datetime',
|
||||
sortDescFirst: true
|
||||
})
|
||||
], [intl, getUserLabel, user]);
|
||||
|
||||
|
@ -103,8 +110,6 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
|
|||
<DataTable
|
||||
columns={columns}
|
||||
data={items}
|
||||
// defaultSortFieldId='time_update'
|
||||
// defaultSortAsc={false}
|
||||
|
||||
noDataComponent={
|
||||
<div className='flex flex-col gap-4 justify-center p-2 text-center min-h-[10rem]'>
|
||||
|
@ -119,11 +124,19 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
|
|||
</span>
|
||||
</p>
|
||||
</div>}
|
||||
|
||||
// pagination
|
||||
// paginationPerPage={50}
|
||||
// paginationRowsPerPageOptions={[10, 20, 30, 50, 100]}
|
||||
|
||||
onRowClicked={openRSForm}
|
||||
|
||||
enableSorting
|
||||
initialSorting={{
|
||||
id: 'time_update',
|
||||
desc: true
|
||||
}}
|
||||
|
||||
enablePagination
|
||||
paginationPerPage={itemsPerPage}
|
||||
onChangePaginationOption={setItemsPerPage}
|
||||
paginationOptions={[10, 20, 30, 50, 100]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -9,6 +9,7 @@ import TextArea from '../../components/Common/TextArea';
|
|||
import HelpConstituenta from '../../components/Help/HelpConstituenta';
|
||||
import { DumpBinIcon, HelpIcon, PenIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import useWindowSize from '../../hooks/useWindowSize';
|
||||
import { CstType, EditMode, ICstCreateData, ICstRenameData, ICstUpdateData, SyntaxTree } from '../../utils/models';
|
||||
import { getCstTypificationLabel } from '../../utils/staticUI';
|
||||
import EditorRSExpression from './EditorRSExpression';
|
||||
|
@ -17,6 +18,8 @@ import ViewSideConstituents from './elements/ViewSideConstituents';
|
|||
// Max height of content for left enditor pane
|
||||
const UNFOLDED_HEIGHT = '59.1rem';
|
||||
|
||||
const SIDELIST_HIDE_THRESHOLD = 1000;
|
||||
|
||||
interface EditorConstituentaProps {
|
||||
activeID?: number
|
||||
onOpenEdit: (cstID: number) => void
|
||||
|
@ -32,6 +35,7 @@ function EditorConstituenta({
|
|||
isModified, setIsModified, activeID,
|
||||
onShowAST, onCreateCst, onRenameCst, onOpenEdit, onDeleteCst
|
||||
}: EditorConstituentaProps) {
|
||||
const windowSize = useWindowSize();
|
||||
const { schema, processing, isEditable, cstUpdate } = useRSForm();
|
||||
const activeCst = useMemo(
|
||||
() => {
|
||||
|
@ -237,6 +241,7 @@ function EditorConstituenta({
|
|||
/>
|
||||
</div>
|
||||
</form>
|
||||
{(windowSize.width ?? 0) >= SIDELIST_HIDE_THRESHOLD &&
|
||||
<div className='self-stretch w-full pb-1 border'>
|
||||
<ViewSideConstituents
|
||||
expression={expression}
|
||||
|
@ -244,7 +249,7 @@ function EditorConstituenta({
|
|||
activeID={activeID}
|
||||
onOpenEdit={onOpenEdit}
|
||||
/>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
import { createColumnHelper,RowSelectionState } from '@tanstack/react-table';
|
||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import Button from '../../components/Common/Button';
|
||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||
import DataTable from '../../components/Common/DataTable';
|
||||
import Divider from '../../components/Common/Divider';
|
||||
import DataTable, { createColumnHelper, type RowSelectionState,VisibilityState } from '../../components/DataTable';
|
||||
import HelpRSFormItems from '../../components/Help/HelpRSFormItems';
|
||||
import { ArrowDownIcon, ArrowUpIcon, DumpBinIcon, HelpIcon, MeshIcon, SmallPlusIcon } from '../../components/Icons';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import useWindowSize from '../../hooks/useWindowSize';
|
||||
import { prefixes } from '../../utils/constants';
|
||||
import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../utils/models'
|
||||
import { getCstStatusFgColor, getCstTypePrefix, getCstTypeShortcut, getCstTypificationLabel, mapStatusInfo } from '../../utils/staticUI';
|
||||
|
||||
// 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 EditorItemsProps {
|
||||
|
@ -24,11 +29,13 @@ interface EditorItemsProps {
|
|||
|
||||
function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) {
|
||||
const { colors } = useConceptTheme();
|
||||
const windowSize = useWindowSize();
|
||||
const { schema, isEditable, cstMoveTo, resetAliases } = useRSForm();
|
||||
const [selected, setSelected] = useState<number[]>([]);
|
||||
const nothingSelected = useMemo(() => selected.length === 0, [selected]);
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
|
||||
|
||||
// Delete selected constituents
|
||||
function handleDelete() {
|
||||
|
@ -155,7 +162,9 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
|||
case '2': handleCreateCst(CstType.STRUCTURED); return true;
|
||||
case '3': handleCreateCst(CstType.TERM); return true;
|
||||
case '4': handleCreateCst(CstType.AXIOM); return true;
|
||||
case 'й':
|
||||
case 'q': handleCreateCst(CstType.FUNCTION); return true;
|
||||
case 'ц':
|
||||
case 'w': handleCreateCst(CstType.PREDICATE); return true;
|
||||
case '5': handleCreateCst(CstType.CONSTANT); return true;
|
||||
case '6': handleCreateCst(CstType.THEOREM); return true;
|
||||
|
@ -176,6 +185,15 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
|||
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(
|
||||
() => {
|
||||
|
@ -199,13 +217,14 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
|||
header: 'Имя',
|
||||
size: 65,
|
||||
minSize: 65,
|
||||
maxSize: 65,
|
||||
cell: props => {
|
||||
const cst = props.row.original;
|
||||
const info = mapStatusInfo.get(cst.status);
|
||||
return (<>
|
||||
<div
|
||||
id={`${prefixes.cst_list}${cst.alias}`}
|
||||
className='w-full px-1 text-center rounded-md whitespace-nowrap'
|
||||
className='w-full min-w-[3.1rem] px-1 text-center rounded-md whitespace-nowrap'
|
||||
style={{
|
||||
borderWidth: "1px",
|
||||
borderColor: getCstStatusFgColor(cst.status, colors),
|
||||
|
@ -228,154 +247,145 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
|||
columnHelper.accessor(cst => getCstTypificationLabel(cst), {
|
||||
id: 'type',
|
||||
header: 'Типизация',
|
||||
size: 175,
|
||||
maxSize: 175,
|
||||
cell: props => <div style={{ fontSize: 12 }}>{props.getValue()}</div>
|
||||
size: 150,
|
||||
minSize: 150,
|
||||
maxSize: 150,
|
||||
enableHiding: true,
|
||||
cell: props => <div className='text-sm min-w-[8.4rem]'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor(cst => cst.term_resolved || cst.term_raw || '', {
|
||||
id: 'term',
|
||||
header: 'Термин',
|
||||
size: 350,
|
||||
size: 500,
|
||||
minSize: 150,
|
||||
maxSize: 350
|
||||
maxSize: 500
|
||||
}),
|
||||
columnHelper.accessor('definition_formal', {
|
||||
id: 'expression',
|
||||
header: 'Формальное определение',
|
||||
size: 300,
|
||||
size: 1000,
|
||||
minSize: 300,
|
||||
maxSize: 500
|
||||
maxSize: 1000
|
||||
}),
|
||||
columnHelper.accessor(cst => cst.definition_resolved || cst.definition_raw || '', {
|
||||
id: 'definition',
|
||||
header: 'Текстовое определение',
|
||||
size: 200,
|
||||
size: 1000,
|
||||
minSize: 200,
|
||||
cell: props => <div style={{ fontSize: 12 }}>{props.getValue()}</div>
|
||||
maxSize: 1000,
|
||||
cell: props => <div className='text-xs'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor('convention', {
|
||||
id: 'convention',
|
||||
header: 'Конвенция / Комментарий',
|
||||
size: 500,
|
||||
minSize: 100,
|
||||
maxSize: undefined,
|
||||
cell: props => <div style={{ fontSize: 12 }}>{props.getValue()}</div>
|
||||
maxSize: 500,
|
||||
enableHiding: true,
|
||||
cell: props => <div className='text-xs'>{props.getValue()}</div>
|
||||
}),
|
||||
], [colors]);
|
||||
|
||||
// name: 'Типизация',
|
||||
// hide: 1600
|
||||
// },
|
||||
// {
|
||||
// name: 'Формальное определение',
|
||||
// grow: 2,
|
||||
// },
|
||||
// {
|
||||
// name: 'Текстовое определение',
|
||||
// grow: 2,
|
||||
// },
|
||||
// {
|
||||
// name: 'Конвенция / Комментарий',
|
||||
// id: 'convention',
|
||||
// hide: 1800
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
<div
|
||||
className='sticky top-0 flex justify-start w-full gap-1 px-2 py-1 border-y items-center h-[2.2rem] select-none clr-app'
|
||||
>
|
||||
<div className='mr-3 whitespace-nowrap'>
|
||||
Выбраны
|
||||
<span className='ml-2'>
|
||||
{selected.length} из {schema?.stats?.count_all ?? 0}
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center justify-start w-full gap-1'>
|
||||
<Button
|
||||
tooltip='Переместить вверх'
|
||||
icon={<ArrowUpIcon size={6}/>}
|
||||
disabled={!isEditable || nothingSelected}
|
||||
dense
|
||||
onClick={handleMoveUp}
|
||||
/>
|
||||
<Button
|
||||
tooltip='Переместить вниз'
|
||||
icon={<ArrowDownIcon size={6}/>}
|
||||
disabled={!isEditable || nothingSelected}
|
||||
dense
|
||||
onClick={handleMoveDown}
|
||||
/>
|
||||
<Button
|
||||
tooltip='Удалить выбранные'
|
||||
icon={<DumpBinIcon color={isEditable && !nothingSelected ? 'text-warning' : ''} size={6}/>}
|
||||
disabled={!isEditable || nothingSelected}
|
||||
dense
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
<Divider vertical margins='my-1' />
|
||||
<Button
|
||||
tooltip='Сбросить имена'
|
||||
icon={<MeshIcon color={isEditable ? 'text-primary': ''} size={6}/>}
|
||||
dense
|
||||
disabled={!isEditable}
|
||||
onClick={handleReindex}
|
||||
/>
|
||||
<Button
|
||||
tooltip='Новая конституента'
|
||||
icon={<SmallPlusIcon color={isEditable ? 'text-success': ''} size={6}/>}
|
||||
dense
|
||||
disabled={!isEditable}
|
||||
onClick={() => handleCreateCst()}
|
||||
/>
|
||||
{(Object.values(CstType)).map(
|
||||
(typeStr) => {
|
||||
const type = typeStr as CstType;
|
||||
return (
|
||||
<Button key={type}
|
||||
text={getCstTypePrefix(type)}
|
||||
tooltip={getCstTypeShortcut(type)}
|
||||
dense
|
||||
widthClass='w-[1.4rem]'
|
||||
disabled={!isEditable}
|
||||
tabIndex={-1}
|
||||
onClick={() => handleCreateCst(type)}
|
||||
/>);
|
||||
})}
|
||||
<div id='items-table-help'>
|
||||
<HelpIcon color='text-primary' size={6} />
|
||||
</div>
|
||||
<ConceptTooltip anchorSelect='#items-table-help' offset={30}>
|
||||
<HelpRSFormItems />
|
||||
</ConceptTooltip>
|
||||
</div>
|
||||
<div className='w-full'>
|
||||
<div
|
||||
className='sticky top-0 flex justify-start w-full gap-1 px-2 py-1 border-y items-center h-[2.2rem] select-none clr-app'
|
||||
>
|
||||
<div className='mr-3 whitespace-nowrap'>
|
||||
Выбраны
|
||||
<span className='ml-2'>
|
||||
{selected.length} из {schema?.stats?.count_all ?? 0}
|
||||
</span>
|
||||
</div>
|
||||
<div className='w-full h-full text-sm' onKeyDown={handleTableKey}>
|
||||
<DataTable
|
||||
data={schema?.items ?? []}
|
||||
columns={columns}
|
||||
state={{
|
||||
rowSelection: rowSelection
|
||||
}}
|
||||
|
||||
enableMultiRowSelection
|
||||
|
||||
onRowDoubleClicked={handleRowDoubleClicked}
|
||||
onRowClicked={handleRowClicked}
|
||||
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 className='flex items-center justify-start w-full gap-1'>
|
||||
<Button
|
||||
tooltip='Переместить вверх'
|
||||
icon={<ArrowUpIcon size={6}/>}
|
||||
disabled={!isEditable || nothingSelected}
|
||||
dense
|
||||
onClick={handleMoveUp}
|
||||
/>
|
||||
<Button
|
||||
tooltip='Переместить вниз'
|
||||
icon={<ArrowDownIcon size={6}/>}
|
||||
disabled={!isEditable || nothingSelected}
|
||||
dense
|
||||
onClick={handleMoveDown}
|
||||
/>
|
||||
<Button
|
||||
tooltip='Удалить выбранные'
|
||||
icon={<DumpBinIcon color={isEditable && !nothingSelected ? 'text-warning' : ''} size={6}/>}
|
||||
disabled={!isEditable || nothingSelected}
|
||||
dense
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
<Divider vertical margins='my-1' />
|
||||
<Button
|
||||
tooltip='Сбросить имена'
|
||||
icon={<MeshIcon color={isEditable ? 'text-primary': ''} size={6}/>}
|
||||
dense
|
||||
disabled={!isEditable}
|
||||
onClick={handleReindex}
|
||||
/>
|
||||
<Button
|
||||
tooltip='Новая конституента'
|
||||
icon={<SmallPlusIcon color={isEditable ? 'text-success': ''} size={6}/>}
|
||||
dense
|
||||
disabled={!isEditable}
|
||||
onClick={() => handleCreateCst()}
|
||||
/>
|
||||
{(Object.values(CstType)).map(
|
||||
(typeStr) => {
|
||||
const type = typeStr as CstType;
|
||||
return (
|
||||
<Button key={type}
|
||||
text={getCstTypePrefix(type)}
|
||||
tooltip={getCstTypeShortcut(type)}
|
||||
dense
|
||||
widthClass='w-[1.4rem]'
|
||||
disabled={!isEditable}
|
||||
tabIndex={-1}
|
||||
onClick={() => handleCreateCst(type)}
|
||||
/>);
|
||||
})}
|
||||
<div id='items-table-help'>
|
||||
<HelpIcon color='text-primary' size={6} />
|
||||
</div>
|
||||
<ConceptTooltip anchorSelect='#items-table-help' offset={30}>
|
||||
<HelpRSFormItems />
|
||||
</ConceptTooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className='w-full h-full text-sm' onKeyDown={handleTableKey}>
|
||||
<DataTable
|
||||
data={schema?.items ?? []}
|
||||
columns={columns}
|
||||
dense
|
||||
|
||||
onRowDoubleClicked={handleRowDoubleClicked}
|
||||
onRowClicked={handleRowClicked}
|
||||
|
||||
enableHiding
|
||||
columnVisibility={columnVisibility}
|
||||
onColumnVisibilityChange={setColumnVisibility}
|
||||
|
||||
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>);
|
||||
}
|
||||
|
||||
export default EditorItems;
|
||||
|
|
|
@ -375,10 +375,10 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
|||
</div>}
|
||||
|
||||
<div className='flex items-center justify-between py-1'>
|
||||
<div className='mr-3 whitespace-nowrap'>
|
||||
<div className='mr-3 whitespace-nowrap text-base'>
|
||||
Выбраны
|
||||
<span className='ml-1'>
|
||||
<b>{allSelected.length}</b> из {schema?.stats?.count_all ?? 0}
|
||||
{allSelected.length} из {schema?.stats?.count_all ?? 0}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import DataTable from '../../../components/Common/DataTable';
|
||||
import DataTable, { createColumnHelper, IConditionalStyle, VisibilityState } from '../../../components/DataTable';
|
||||
import { useRSForm } from '../../../context/RSFormContext';
|
||||
import { useConceptTheme } from '../../../context/ThemeContext';
|
||||
import useLocalStorage from '../../../hooks/useLocalStorage';
|
||||
import useWindowSize from '../../../hooks/useWindowSize';
|
||||
import { prefixes } from '../../../utils/constants';
|
||||
import { applyGraphFilter, CstMatchMode, CstType, DependencyMode, extractGlobals, IConstituenta, matchConstituenta } from '../../../utils/models';
|
||||
import { getCstDescription, getCstStatusFgColor, getMockConstituenta } from '../../../utils/staticUI';
|
||||
|
@ -15,6 +15,9 @@ import MatchModePicker from './MatchModePicker';
|
|||
// Height that should be left to accomodate navigation panel + bottom margin
|
||||
const LOCAL_NAVIGATION_H = '2.1rem';
|
||||
|
||||
// Window width cutoff for expression show
|
||||
const COLUMN_EXPRESSION_HIDE_THRESHOLD = 1500;
|
||||
|
||||
interface ViewSideConstituentsProps {
|
||||
expression: string
|
||||
baseHeight: string
|
||||
|
@ -29,8 +32,11 @@ function isMockCst(cst: IConstituenta) {
|
|||
const columnHelper = createColumnHelper<IConstituenta>();
|
||||
|
||||
function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) {
|
||||
const windowSize = useWindowSize();
|
||||
const { noNavigation, colors } = useConceptTheme();
|
||||
const { schema } = useRSForm();
|
||||
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({'expression': true})
|
||||
|
||||
const [filterMatch, setFilterMatch] = useLocalStorage('side-filter-match', CstMatchMode.ALL);
|
||||
const [filterText, setFilterText] = useLocalStorage('side-filter-text', '');
|
||||
|
@ -38,7 +44,19 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
|
|||
|
||||
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []);
|
||||
|
||||
useEffect(
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
setColumnVisibility(prev => {
|
||||
const newValue = (windowSize.width ?? 0) >= COLUMN_EXPRESSION_HIDE_THRESHOLD;
|
||||
if (newValue === prev['expression']) {
|
||||
return prev;
|
||||
} else {
|
||||
return {'expression': newValue}
|
||||
}
|
||||
});
|
||||
}, [windowSize]);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
if (!schema?.items) {
|
||||
setFilteredData([]);
|
||||
|
@ -118,9 +136,9 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
|
|||
columnHelper.accessor(cst => getCstDescription(cst), {
|
||||
id: 'description',
|
||||
header: 'Описание',
|
||||
size: 350,
|
||||
size: 500,
|
||||
minSize: 350,
|
||||
maxSize: 350,
|
||||
maxSize: 500,
|
||||
cell: props =>
|
||||
<div style={{
|
||||
fontSize: 12,
|
||||
|
@ -132,9 +150,10 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
|
|||
columnHelper.accessor('definition_formal', {
|
||||
id: 'expression',
|
||||
header: 'Выражение',
|
||||
size: 700,
|
||||
size: 1000,
|
||||
minSize: 0,
|
||||
maxSize: 700,
|
||||
maxSize: 1000,
|
||||
enableHiding: true,
|
||||
cell: props =>
|
||||
<div style={{
|
||||
fontSize: 12,
|
||||
|
@ -145,6 +164,16 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
|
|||
})
|
||||
], [colors]);
|
||||
|
||||
const conditionalRowStyles = useMemo(
|
||||
(): IConditionalStyle<IConstituenta>[] => [
|
||||
{
|
||||
when: (cst: IConstituenta) => cst.id === activeID,
|
||||
style: {
|
||||
backgroundColor: colors.bgSelected
|
||||
},
|
||||
}
|
||||
], [activeID, colors]);
|
||||
|
||||
const maxHeight = useMemo(
|
||||
() => {
|
||||
const siblingHeight = `${baseHeight} - ${LOCAL_NAVIGATION_H}`
|
||||
|
@ -174,9 +203,12 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
|
|||
<DataTable
|
||||
data={filteredData}
|
||||
columns={columns}
|
||||
conditionalRowStyles={conditionalRowStyles}
|
||||
dense
|
||||
|
||||
|
||||
// conditionalRowStyles={conditionalRowStyles}
|
||||
enableHiding
|
||||
columnVisibility={columnVisibility}
|
||||
onColumnVisibilityChange={setColumnVisibility}
|
||||
|
||||
noDataComponent={
|
||||
<span className='flex flex-col justify-center p-2 text-center min-h-[5rem]'>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
import { useMemo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import DataTable from '../../components/Common/DataTable';
|
||||
import DataTable, { createColumnHelper } from '../../components/DataTable';
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import { ILibraryItem } from '../../utils/models';
|
||||
|
||||
|
@ -52,9 +51,13 @@ function ViewSubscriptions({items}: ViewSubscriptionsProps) {
|
|||
<DataTable
|
||||
columns={columns}
|
||||
data={items}
|
||||
// defaultSortFieldId='time_update'
|
||||
// defaultSortAsc={false}
|
||||
|
||||
dense
|
||||
enableSorting
|
||||
initialSorting={{
|
||||
id: 'time_update',
|
||||
desc: true
|
||||
}}
|
||||
noDataComponent={
|
||||
<div className='h-[10rem]'>Отслеживаемые схемы отсутствуют</div>
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ export const lightT: IColorTheme = {
|
|||
fgGreen: 'hsl(100, 090%, 035%)',
|
||||
fgBlue: 'hsl(235, 100%, 050%)',
|
||||
fgPurple: 'hsl(270, 100%, 070%)',
|
||||
fgTeal: 'hsl(192, 090%, 040%)',
|
||||
fgTeal: 'hsl(200, 080%, 050%)',
|
||||
fgOrange: 'hsl(030, 090%, 055%)'
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user