Implement DataTable features

This commit is contained in:
IRBorisov 2023-09-10 20:17:18 +03:00
parent 86c2965820
commit 38cdf31676
23 changed files with 646 additions and 444 deletions

View File

@ -27,9 +27,9 @@ class LibraryActiveView(generics.ListAPIView):
# pylint: disable=unsupported-binary-operation # pylint: disable=unsupported-binary-operation
return m.LibraryItem.objects.filter( return m.LibraryItem.objects.filter(
Q(is_common=True) | Q(owner=user) | Q(subscription__user=user) Q(is_common=True) | Q(owner=user) | Q(subscription__user=user)
).distinct() ).distinct().order_by('-time_update')
else: 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): class ConstituentAPIView(generics.RetrieveUpdateAPIView):

View File

@ -1,6 +1,6 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { CheckboxChecked } from '../Icons'; import { CheckboxCheckedIcon } from '../Icons';
import Label from './Label'; import Label from './Label';
export interface CheckboxProps export interface CheckboxProps
@ -53,7 +53,7 @@ function Checkbox({
{...props} {...props}
> >
<div className={`max-w-[1rem] min-w-[1rem] h-4 mt-0.5 border rounded-sm ${bgColor} ${cursor}`} > <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> </div>
{ label && { label &&
<Label <Label

View File

@ -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;

View File

@ -5,10 +5,7 @@ interface DividerProps {
function Divider({ vertical, margins = 'mx-2' }: DividerProps) { function Divider({ vertical, margins = 'mx-2' }: DividerProps) {
return ( return (
<> <div className={`${margins} ${vertical ? 'border-x-2 h-full': 'border-y-2 w-full'}`} />
{vertical && <div className={`${margins} border-x-2`} />}
{!vertical && <div className={`${margins} border-y-2`} />}
</>
); );
} }

View File

@ -1,7 +1,7 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { CheckboxChecked, CheckboxNull } from '../Icons'; import { CheckboxCheckedIcon, CheckboxNullIcon } from '../Icons';
import { CheckboxProps } from './Checkbox'; import { CheckboxProps } from './Checkbox';
import Label from './Label'; import Label from './Label';
@ -54,8 +54,8 @@ function Tristate({
{...props} {...props}
> >
<div className={`w-4 h-4 shrink-0 mt-0.5 border rounded-sm ${bgColor} ${cursor}`} > <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 && <div className='mt-[1px] ml-[1px]'><CheckboxCheckedIcon /></div>}
{ value == null && <div className='mt-[1px] ml-[1px]'><CheckboxNull /></div>} { value == null && <div className='mt-[1px] ml-[1px]'><CheckboxNullIcon /></div>}
</div> </div>
{ label && { label &&
<Label <Label

View File

@ -0,0 +1,8 @@
function defaultNoDataComponent() {
return (
<div className='p-2 text-center'>
Данные отсутствуют
</div>);
}
export default defaultNoDataComponent;

View File

@ -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;

View 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;

View 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;

View 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;

View 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>);
}

View File

@ -22,6 +22,7 @@ function HelpTermGraph() {
<p><b>Клик на конституенту</b> - выделение, включая скрытые конституенты</p> <p><b>Клик на конституенту</b> - выделение, включая скрытые конституенты</p>
<p><b>Двойной клик</b> - редактирование конституенты</p> <p><b>Двойной клик</b> - редактирование конституенты</p>
<p><b>Delete</b> - удалить выбранные</p> <p><b>Delete</b> - удалить выбранные</p>
<br />
<Divider margins='mt-2' /> <Divider margins='mt-2' />

View File

@ -19,7 +19,7 @@ function IconSVG({ viewbox, size = 6, color, props, children }: IconSVGProps) {
<svg <svg
width={width} width={width}
height={width} height={width}
className={`w-[${width}] h-[${width}] ${color ?? ''}`} className={`w-[${width}] h-[${width}] ${color}`}
fill='currentColor' fill='currentColor'
viewBox={viewbox} viewBox={viewbox}
{...props} {...props}
@ -309,7 +309,7 @@ export function MeshIcon(props: IconProps) {
); );
} }
export function InDoor(props: IconProps) { export function InDoorIcon(props: IconProps) {
return ( return (
<IconSVG viewbox='0 0 24 24' {...props}> <IconSVG viewbox='0 0 24 24' {...props}>
<path fill='none' d='M0 0h24v24H0z' /> <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 ( return (
<svg <svg
className='w-3 h-3' className='w-3 h-3'
@ -330,7 +378,7 @@ export function CheckboxChecked() {
); );
} }
export function CheckboxNull() { export function CheckboxNullIcon() {
return ( return (
<svg <svg
className='w-3 h-3' className='w-3 h-3'

View File

@ -1,7 +1,7 @@
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import { useConceptNavigation } from '../../context/NagivationContext'; import { useConceptNavigation } from '../../context/NagivationContext';
import useDropdown from '../../hooks/useDropdown'; import useDropdown from '../../hooks/useDropdown';
import { InDoor, UserIcon } from '../Icons'; import { InDoorIcon, UserIcon } from '../Icons';
import NavigationButton from './NavigationButton'; import NavigationButton from './NavigationButton';
import UserDropdown from './UserDropdown'; import UserDropdown from './UserDropdown';
@ -18,7 +18,7 @@ function UserMenu() {
<NavigationButton <NavigationButton
text='Войти...' text='Войти...'
description='Перейти на страницу логина' description='Перейти на страницу логина'
icon={<InDoor />} icon={<InDoorIcon />}
onClick={navigateLogin} onClick={navigateLogin}
/>} />}
{ user && { user &&

View File

@ -12,7 +12,7 @@
--cl-bg-60: hsl(000, 000%, 091%); --cl-bg-60: hsl(000, 000%, 091%);
--cl-bg-40: hsl(000, 000%, 080%); --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-80: hsl(000, 000%, 047%);
--cl-fg-100: hsl(000, 000%, 000%); --cl-fg-100: hsl(000, 000%, 000%);

View File

@ -80,7 +80,7 @@ function SearchPanel({ total, filtered, setFilter }: SearchPanelProps) {
value={strategy} value={strategy}
onChange={handleChangeStrategy} 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'> <div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-controls'>
<MagnifyingGlassIcon /> <MagnifyingGlassIcon />
</div> </div>

View File

@ -1,15 +1,15 @@
import { createColumnHelper } from '@tanstack/react-table';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import ConceptTooltip from '../../components/Common/ConceptTooltip'; import ConceptTooltip from '../../components/Common/ConceptTooltip';
import DataTable from '../../components/Common/DataTable';
import TextURL from '../../components/Common/TextURL'; import TextURL from '../../components/Common/TextURL';
import DataTable, { createColumnHelper } from '../../components/DataTable';
import HelpLibrary from '../../components/Help/HelpLibrary'; import HelpLibrary from '../../components/Help/HelpLibrary';
import { EducationIcon, EyeIcon, GroupIcon, HelpIcon } from '../../components/Icons'; import { EducationIcon, EyeIcon, GroupIcon, HelpIcon } from '../../components/Icons';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import { useConceptNavigation } from '../../context/NagivationContext'; import { useConceptNavigation } from '../../context/NagivationContext';
import { useUsers } from '../../context/UsersContext'; import { useUsers } from '../../context/UsersContext';
import useLocalStorage from '../../hooks/useLocalStorage';
import { prefixes } from '../../utils/constants'; import { prefixes } from '../../utils/constants';
import { ILibraryItem } from '../../utils/models'; import { ILibraryItem } from '../../utils/models';
@ -26,6 +26,8 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
const { user } = useAuth(); const { user } = useAuth();
const { getUserLabel } = useUsers(); const { getUserLabel } = useUsers();
const [ itemsPerPage, setItemsPerPage ] = useLocalStorage<number>('library_per_page', 50);
const openRSForm = (item: ILibraryItem) => navigateTo(`/rsforms/${item.id}`); const openRSForm = (item: ILibraryItem) => navigateTo(`/rsforms/${item.id}`);
const columns = useMemo( const columns = useMemo(
@ -34,6 +36,7 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
id: 'status', id: 'status',
header: '', header: '',
size: 60, size: 60,
minSize: 60,
maxSize: 60, maxSize: 60,
cell: props => { cell: props => {
const item = props.row.original; const item = props.row.original;
@ -55,34 +58,38 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
size: 200, size: 200,
minSize: 200, minSize: 200,
maxSize: 200, maxSize: 200,
enableSorting: true enableSorting: true,
sortingFn: 'text'
}), }),
columnHelper.accessor('title', { columnHelper.accessor('title', {
id: 'title', id: 'title',
header: 'Название', header: 'Название',
minSize: 200,
size: 1000, size: 1000,
minSize: 400,
maxSize: 1000, maxSize: 1000,
enableSorting: true enableSorting: true,
sortingFn: 'text'
}), }),
columnHelper.accessor(item => item.owner ?? 0, { columnHelper.accessor(item => item.owner ?? 0, {
id: 'owner', id: 'owner',
header: 'Владелец', header: 'Владелец',
size: 300,
minSize: 200,
maxSize: 300,
cell: props => getUserLabel(props.cell.getValue()), cell: props => getUserLabel(props.cell.getValue()),
enableSorting: true, enableSorting: true,
enableResizing: false, sortingFn: 'text'
minSize: 200,
size: 300,
maxSize: 300
}), }),
columnHelper.accessor('time_update', { columnHelper.accessor('time_update', {
id: 'time_update', id: 'time_update',
header: 'Обновлена', header: 'Обновлена',
minSize: 200, size: 220,
size: 200, minSize: 220,
maxSize: 200, maxSize: 220,
cell: props => new Date(props.cell.getValue()).toLocaleString(intl.locale), cell: props => new Date(props.cell.getValue()).toLocaleString(intl.locale),
enableSorting: true enableSorting: true,
sortingFn: 'datetime',
sortDescFirst: true
}) })
], [intl, getUserLabel, user]); ], [intl, getUserLabel, user]);
@ -103,8 +110,6 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
<DataTable <DataTable
columns={columns} columns={columns}
data={items} data={items}
// defaultSortFieldId='time_update'
// defaultSortAsc={false}
noDataComponent={ noDataComponent={
<div className='flex flex-col gap-4 justify-center p-2 text-center min-h-[10rem]'> <div className='flex flex-col gap-4 justify-center p-2 text-center min-h-[10rem]'>
@ -120,10 +125,18 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
</p> </p>
</div>} </div>}
// pagination
// paginationPerPage={50}
// paginationRowsPerPageOptions={[10, 20, 30, 50, 100]}
onRowClicked={openRSForm} onRowClicked={openRSForm}
enableSorting
initialSorting={{
id: 'time_update',
desc: true
}}
enablePagination
paginationPerPage={itemsPerPage}
onChangePaginationOption={setItemsPerPage}
paginationOptions={[10, 20, 30, 50, 100]}
/> />
</div> </div>
); );

View File

@ -9,6 +9,7 @@ import TextArea from '../../components/Common/TextArea';
import HelpConstituenta from '../../components/Help/HelpConstituenta'; import HelpConstituenta from '../../components/Help/HelpConstituenta';
import { DumpBinIcon, HelpIcon, PenIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons'; import { DumpBinIcon, HelpIcon, PenIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import useWindowSize from '../../hooks/useWindowSize';
import { CstType, EditMode, ICstCreateData, ICstRenameData, ICstUpdateData, SyntaxTree } from '../../utils/models'; import { CstType, EditMode, ICstCreateData, ICstRenameData, ICstUpdateData, SyntaxTree } from '../../utils/models';
import { getCstTypificationLabel } from '../../utils/staticUI'; import { getCstTypificationLabel } from '../../utils/staticUI';
import EditorRSExpression from './EditorRSExpression'; import EditorRSExpression from './EditorRSExpression';
@ -17,6 +18,8 @@ import ViewSideConstituents from './elements/ViewSideConstituents';
// Max height of content for left enditor pane // Max height of content for left enditor pane
const UNFOLDED_HEIGHT = '59.1rem'; const UNFOLDED_HEIGHT = '59.1rem';
const SIDELIST_HIDE_THRESHOLD = 1000;
interface EditorConstituentaProps { interface EditorConstituentaProps {
activeID?: number activeID?: number
onOpenEdit: (cstID: number) => void onOpenEdit: (cstID: number) => void
@ -32,6 +35,7 @@ function EditorConstituenta({
isModified, setIsModified, activeID, isModified, setIsModified, activeID,
onShowAST, onCreateCst, onRenameCst, onOpenEdit, onDeleteCst onShowAST, onCreateCst, onRenameCst, onOpenEdit, onDeleteCst
}: EditorConstituentaProps) { }: EditorConstituentaProps) {
const windowSize = useWindowSize();
const { schema, processing, isEditable, cstUpdate } = useRSForm(); const { schema, processing, isEditable, cstUpdate } = useRSForm();
const activeCst = useMemo( const activeCst = useMemo(
() => { () => {
@ -237,6 +241,7 @@ function EditorConstituenta({
/> />
</div> </div>
</form> </form>
{(windowSize.width ?? 0) >= SIDELIST_HIDE_THRESHOLD &&
<div className='self-stretch w-full pb-1 border'> <div className='self-stretch w-full pb-1 border'>
<ViewSideConstituents <ViewSideConstituents
expression={expression} expression={expression}
@ -244,7 +249,7 @@ function EditorConstituenta({
activeID={activeID} activeID={activeID}
onOpenEdit={onOpenEdit} onOpenEdit={onOpenEdit}
/> />
</div> </div>}
</div> </div>
); );
} }

View File

@ -1,19 +1,24 @@
import { createColumnHelper,RowSelectionState } from '@tanstack/react-table';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import Button from '../../components/Common/Button'; import Button from '../../components/Common/Button';
import ConceptTooltip from '../../components/Common/ConceptTooltip'; import ConceptTooltip from '../../components/Common/ConceptTooltip';
import DataTable from '../../components/Common/DataTable';
import Divider from '../../components/Common/Divider'; import Divider from '../../components/Common/Divider';
import DataTable, { createColumnHelper, type RowSelectionState,VisibilityState } from '../../components/DataTable';
import HelpRSFormItems from '../../components/Help/HelpRSFormItems'; import HelpRSFormItems from '../../components/Help/HelpRSFormItems';
import { ArrowDownIcon, ArrowUpIcon, DumpBinIcon, HelpIcon, MeshIcon, SmallPlusIcon } from '../../components/Icons'; import { ArrowDownIcon, ArrowUpIcon, DumpBinIcon, HelpIcon, MeshIcon, SmallPlusIcon } from '../../components/Icons';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import useWindowSize from '../../hooks/useWindowSize';
import { prefixes } from '../../utils/constants'; import { prefixes } from '../../utils/constants';
import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../utils/models' import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../utils/models'
import { getCstStatusFgColor, getCstTypePrefix, getCstTypeShortcut, getCstTypificationLabel, mapStatusInfo } from '../../utils/staticUI'; 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>(); const columnHelper = createColumnHelper<IConstituenta>();
interface EditorItemsProps { interface EditorItemsProps {
@ -24,11 +29,13 @@ interface EditorItemsProps {
function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) { function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) {
const { colors } = useConceptTheme(); const { colors } = useConceptTheme();
const windowSize = useWindowSize();
const { schema, isEditable, cstMoveTo, resetAliases } = useRSForm(); const { schema, isEditable, cstMoveTo, resetAliases } = useRSForm();
const [selected, setSelected] = useState<number[]>([]); const [selected, setSelected] = useState<number[]>([]);
const nothingSelected = useMemo(() => selected.length === 0, [selected]); const nothingSelected = useMemo(() => selected.length === 0, [selected]);
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}); const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
// Delete selected constituents // Delete selected constituents
function handleDelete() { function handleDelete() {
@ -155,7 +162,9 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
case '2': handleCreateCst(CstType.STRUCTURED); return true; case '2': handleCreateCst(CstType.STRUCTURED); return true;
case '3': handleCreateCst(CstType.TERM); return true; case '3': handleCreateCst(CstType.TERM); return true;
case '4': handleCreateCst(CstType.AXIOM); return true; case '4': handleCreateCst(CstType.AXIOM); return true;
case 'й':
case 'q': handleCreateCst(CstType.FUNCTION); return true; case 'q': handleCreateCst(CstType.FUNCTION); return true;
case 'ц':
case 'w': handleCreateCst(CstType.PREDICATE); return true; case 'w': handleCreateCst(CstType.PREDICATE); return true;
case '5': handleCreateCst(CstType.CONSTANT); return true; case '5': handleCreateCst(CstType.CONSTANT); return true;
case '6': handleCreateCst(CstType.THEOREM); return true; case '6': handleCreateCst(CstType.THEOREM); return true;
@ -177,6 +186,15 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
onOpenEdit(cst.id); onOpenEdit(cst.id);
}, [onOpenEdit]); }, [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( useLayoutEffect(
() => { () => {
if (!schema || Object.keys(rowSelection).length === 0) { if (!schema || Object.keys(rowSelection).length === 0) {
@ -199,13 +217,14 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
header: 'Имя', header: 'Имя',
size: 65, size: 65,
minSize: 65, minSize: 65,
maxSize: 65,
cell: props => { cell: props => {
const cst = props.row.original; const cst = props.row.original;
const info = mapStatusInfo.get(cst.status); const info = mapStatusInfo.get(cst.status);
return (<> return (<>
<div <div
id={`${prefixes.cst_list}${cst.alias}`} 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={{ style={{
borderWidth: "1px", borderWidth: "1px",
borderColor: getCstStatusFgColor(cst.status, colors), borderColor: getCstStatusFgColor(cst.status, colors),
@ -228,56 +247,45 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
columnHelper.accessor(cst => getCstTypificationLabel(cst), { columnHelper.accessor(cst => getCstTypificationLabel(cst), {
id: 'type', id: 'type',
header: 'Типизация', header: 'Типизация',
size: 175, size: 150,
maxSize: 175, minSize: 150,
cell: props => <div style={{ fontSize: 12 }}>{props.getValue()}</div> 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 || '', { columnHelper.accessor(cst => cst.term_resolved || cst.term_raw || '', {
id: 'term', id: 'term',
header: 'Термин', header: 'Термин',
size: 350, size: 500,
minSize: 150, minSize: 150,
maxSize: 350 maxSize: 500
}), }),
columnHelper.accessor('definition_formal', { columnHelper.accessor('definition_formal', {
id: 'expression', id: 'expression',
header: 'Формальное определение', header: 'Формальное определение',
size: 300, size: 1000,
minSize: 300, minSize: 300,
maxSize: 500 maxSize: 1000
}), }),
columnHelper.accessor(cst => cst.definition_resolved || cst.definition_raw || '', { columnHelper.accessor(cst => cst.definition_resolved || cst.definition_raw || '', {
id: 'definition', id: 'definition',
header: 'Текстовое определение', header: 'Текстовое определение',
size: 200, size: 1000,
minSize: 200, 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', { columnHelper.accessor('convention', {
id: 'convention', id: 'convention',
header: 'Конвенция / Комментарий', header: 'Конвенция / Комментарий',
size: 500,
minSize: 100, minSize: 100,
maxSize: undefined, maxSize: 500,
cell: props => <div style={{ fontSize: 12 }}>{props.getValue()}</div> enableHiding: true,
cell: props => <div className='text-xs'>{props.getValue()}</div>
}), }),
], [colors]); ], [colors]);
// name: 'Типизация',
// hide: 1600
// },
// {
// name: 'Формальное определение',
// grow: 2,
// },
// {
// name: 'Текстовое определение',
// grow: 2,
// },
// {
// name: 'Конвенция / Комментарий',
// id: 'convention',
// hide: 1800
return ( return (
<div className='w-full'> <div className='w-full'>
<div <div
@ -352,14 +360,17 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
<DataTable <DataTable
data={schema?.items ?? []} data={schema?.items ?? []}
columns={columns} columns={columns}
state={{ dense
rowSelection: rowSelection
}}
enableMultiRowSelection
onRowDoubleClicked={handleRowDoubleClicked} onRowDoubleClicked={handleRowDoubleClicked}
onRowClicked={handleRowClicked} onRowClicked={handleRowClicked}
enableHiding
columnVisibility={columnVisibility}
onColumnVisibilityChange={setColumnVisibility}
enableRowSelection
rowSelection={rowSelection}
onRowSelectionChange={setRowSelection} onRowSelectionChange={setRowSelection}
noDataComponent={ noDataComponent={
@ -374,8 +385,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
} }
/> />
</div> </div>
</div> </div>);
);
} }
export default EditorItems; export default EditorItems;

View File

@ -375,10 +375,10 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
</div>} </div>}
<div className='flex items-center justify-between py-1'> <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'> <span className='ml-1'>
<b>{allSelected.length}</b> из {schema?.stats?.count_all ?? 0} {allSelected.length} из {schema?.stats?.count_all ?? 0}
</span> </span>
</div> </div>
<div> <div>

View File

@ -1,10 +1,10 @@
import { createColumnHelper } from '@tanstack/react-table'; import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import DataTable from '../../../components/Common/DataTable'; import DataTable, { createColumnHelper, IConditionalStyle, VisibilityState } from '../../../components/DataTable';
import { useRSForm } from '../../../context/RSFormContext'; import { useRSForm } from '../../../context/RSFormContext';
import { useConceptTheme } from '../../../context/ThemeContext'; import { useConceptTheme } from '../../../context/ThemeContext';
import useLocalStorage from '../../../hooks/useLocalStorage'; import useLocalStorage from '../../../hooks/useLocalStorage';
import useWindowSize from '../../../hooks/useWindowSize';
import { prefixes } from '../../../utils/constants'; import { prefixes } from '../../../utils/constants';
import { applyGraphFilter, CstMatchMode, CstType, DependencyMode, extractGlobals, IConstituenta, matchConstituenta } from '../../../utils/models'; import { applyGraphFilter, CstMatchMode, CstType, DependencyMode, extractGlobals, IConstituenta, matchConstituenta } from '../../../utils/models';
import { getCstDescription, getCstStatusFgColor, getMockConstituenta } from '../../../utils/staticUI'; 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 // Height that should be left to accomodate navigation panel + bottom margin
const LOCAL_NAVIGATION_H = '2.1rem'; const LOCAL_NAVIGATION_H = '2.1rem';
// Window width cutoff for expression show
const COLUMN_EXPRESSION_HIDE_THRESHOLD = 1500;
interface ViewSideConstituentsProps { interface ViewSideConstituentsProps {
expression: string expression: string
baseHeight: string baseHeight: string
@ -29,16 +32,31 @@ function isMockCst(cst: IConstituenta) {
const columnHelper = createColumnHelper<IConstituenta>(); const columnHelper = createColumnHelper<IConstituenta>();
function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) { function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) {
const windowSize = useWindowSize();
const { noNavigation, colors } = useConceptTheme(); const { noNavigation, colors } = useConceptTheme();
const { schema } = useRSForm(); const { schema } = useRSForm();
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({'expression': true})
const [filterMatch, setFilterMatch] = useLocalStorage('side-filter-match', CstMatchMode.ALL); const [filterMatch, setFilterMatch] = useLocalStorage('side-filter-match', CstMatchMode.ALL);
const [filterText, setFilterText] = useLocalStorage('side-filter-text', ''); const [filterText, setFilterText] = useLocalStorage('side-filter-text', '');
const [filterSource, setFilterSource] = useLocalStorage('side-filter-dependency', DependencyMode.ALL); const [filterSource, setFilterSource] = useLocalStorage('side-filter-dependency', DependencyMode.ALL);
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []); 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) { if (!schema?.items) {
setFilteredData([]); setFilteredData([]);
@ -118,9 +136,9 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
columnHelper.accessor(cst => getCstDescription(cst), { columnHelper.accessor(cst => getCstDescription(cst), {
id: 'description', id: 'description',
header: 'Описание', header: 'Описание',
size: 350, size: 500,
minSize: 350, minSize: 350,
maxSize: 350, maxSize: 500,
cell: props => cell: props =>
<div style={{ <div style={{
fontSize: 12, fontSize: 12,
@ -132,9 +150,10 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
columnHelper.accessor('definition_formal', { columnHelper.accessor('definition_formal', {
id: 'expression', id: 'expression',
header: 'Выражение', header: 'Выражение',
size: 700, size: 1000,
minSize: 0, minSize: 0,
maxSize: 700, maxSize: 1000,
enableHiding: true,
cell: props => cell: props =>
<div style={{ <div style={{
fontSize: 12, fontSize: 12,
@ -145,6 +164,16 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
}) })
], [colors]); ], [colors]);
const conditionalRowStyles = useMemo(
(): IConditionalStyle<IConstituenta>[] => [
{
when: (cst: IConstituenta) => cst.id === activeID,
style: {
backgroundColor: colors.bgSelected
},
}
], [activeID, colors]);
const maxHeight = useMemo( const maxHeight = useMemo(
() => { () => {
const siblingHeight = `${baseHeight} - ${LOCAL_NAVIGATION_H}` const siblingHeight = `${baseHeight} - ${LOCAL_NAVIGATION_H}`
@ -174,9 +203,12 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
<DataTable <DataTable
data={filteredData} data={filteredData}
columns={columns} columns={columns}
conditionalRowStyles={conditionalRowStyles}
dense
enableHiding
// conditionalRowStyles={conditionalRowStyles} columnVisibility={columnVisibility}
onColumnVisibilityChange={setColumnVisibility}
noDataComponent={ noDataComponent={
<span className='flex flex-col justify-center p-2 text-center min-h-[5rem]'> <span className='flex flex-col justify-center p-2 text-center min-h-[5rem]'>

View File

@ -1,8 +1,7 @@
import { createColumnHelper } from '@tanstack/react-table';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import DataTable from '../../components/Common/DataTable'; import DataTable, { createColumnHelper } from '../../components/DataTable';
import { useConceptNavigation } from '../../context/NagivationContext'; import { useConceptNavigation } from '../../context/NagivationContext';
import { ILibraryItem } from '../../utils/models'; import { ILibraryItem } from '../../utils/models';
@ -52,9 +51,13 @@ function ViewSubscriptions({items}: ViewSubscriptionsProps) {
<DataTable <DataTable
columns={columns} columns={columns}
data={items} data={items}
// defaultSortFieldId='time_update'
// defaultSortAsc={false}
dense
enableSorting
initialSorting={{
id: 'time_update',
desc: true
}}
noDataComponent={ noDataComponent={
<div className='h-[10rem]'>Отслеживаемые схемы отсутствуют</div> <div className='h-[10rem]'>Отслеживаемые схемы отсутствуют</div>
} }

View File

@ -65,7 +65,7 @@ export const lightT: IColorTheme = {
fgGreen: 'hsl(100, 090%, 035%)', fgGreen: 'hsl(100, 090%, 035%)',
fgBlue: 'hsl(235, 100%, 050%)', fgBlue: 'hsl(235, 100%, 050%)',
fgPurple: 'hsl(270, 100%, 070%)', fgPurple: 'hsl(270, 100%, 070%)',
fgTeal: 'hsl(192, 090%, 040%)', fgTeal: 'hsl(200, 080%, 050%)',
fgOrange: 'hsl(030, 090%, 055%)' fgOrange: 'hsl(030, 090%, 055%)'
}; };