Refactoring: replacing data table component pt1

This commit is contained in:
IRBorisov 2023-09-09 20:36:55 +03:00
parent 82801e81c0
commit 86c2965820
28 changed files with 683 additions and 478 deletions

View File

@ -19,11 +19,11 @@ This readme file is used mostly to document project dependencies
- js-file-download
- react-tabs
- react-intl
- react-data-table-component
- react-select
- react-error-boundary
- reagraph
- react-tooltip
- @tanstack/react-table
- @uiw/react-codemirror
- @uiw/codemirror-themes
- @lezer/lr

View File

@ -9,12 +9,12 @@
"version": "1.0.0",
"dependencies": {
"@lezer/lr": "^1.3.10",
"@tanstack/react-table": "^8.9.7",
"@uiw/codemirror-themes": "^4.21.13",
"@uiw/react-codemirror": "^4.21.13",
"axios": "^1.5.0",
"js-file-download": "^0.4.12",
"react": "^18.2.0",
"react-data-table-component": "^7.5.4",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.11",
"react-intl": "^6.4.4",
@ -3931,6 +3931,37 @@
"@sinonjs/commons": "^3.0.0"
}
},
"node_modules/@tanstack/react-table": {
"version": "8.9.7",
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.9.7.tgz",
"integrity": "sha512-UKUekM8JNUyWbjT1q3s1GpH5OtBL9mJ4258Il23fsahvkh3ou9TuFVmqI0/UPiFROgHkRlCBDNPUhcsC9YPFgg==",
"dependencies": {
"@tanstack/table-core": "8.9.7"
},
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/@tanstack/table-core": {
"version": "8.9.7",
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.9.7.tgz",
"integrity": "sha512-lkhVcGDxa9GSoDFPkplPDvzsiUACPZrxT3U1edPs0DCMKFhBDgZ7d1DPd7cqHH0JoybfbQ/qiTQYOQBg8sinJg==",
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tweenjs/tween.js": {
"version": "18.6.4",
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz",
@ -5477,6 +5508,7 @@
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -9318,18 +9350,6 @@
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-data-table-component": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/react-data-table-component/-/react-data-table-component-7.5.4.tgz",
"integrity": "sha512-6DGVj3urJZfEEMuP652fSjxdRVKeyb+9d0YounVc+MX8jwoyXQW6KO10eyZqElE9QtVrKrCeJxR7vht9yxyJiw==",
"dependencies": {
"deepmerge": "^4.2.2"
},
"peerDependencies": {
"react": ">= 16.8.3",
"styled-components": ">= 4"
}
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",

View File

@ -13,12 +13,12 @@
},
"dependencies": {
"@lezer/lr": "^1.3.10",
"@tanstack/react-table": "^8.9.7",
"@uiw/codemirror-themes": "^4.21.13",
"@uiw/react-codemirror": "^4.21.13",
"axios": "^1.5.0",
"js-file-download": "^0.4.12",
"react": "^18.2.0",
"react-data-table-component": "^7.5.4",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.11",
"react-intl": "^6.4.4",

View File

@ -27,7 +27,7 @@ function Button({
disabled={disabled ?? loading}
onClick={onClick}
title={tooltip}
className={`inline-flex items-center gap-2 align-middle justify-center select-none ${padding} ${borderClass} ${colorClass} ${widthClass} ${cursor}`}
className={`inline-flex items-center gap-2 align-middle justify-center select-none ${padding} ${colorClass} ${widthClass} ${borderClass} ${cursor}`}
{...props}
>
{icon && <span>{icon}</span>}

View File

@ -3,7 +3,8 @@ import { useMemo } from 'react';
import { CheckboxChecked } from '../Icons';
import Label from './Label';
export interface CheckboxProps {
export interface CheckboxProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children' | 'title' | 'value' | 'onClick' > {
id?: string
label?: string
required?: boolean
@ -15,7 +16,10 @@ export interface CheckboxProps {
setValue?: (newValue: boolean) => void
}
function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit', value, setValue }: CheckboxProps) {
function Checkbox({
id, required, disabled, tooltip, label,
widthClass = 'w-fit', value, setValue, ...props
}: CheckboxProps) {
const cursor = useMemo(
() => {
if (disabled) {
@ -46,8 +50,11 @@ function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit'
title={tooltip}
disabled={disabled}
onClick={handleClick}
{...props}
>
<div className={`relative peer w-4 h-4 shrink-0 mt-0.5 border rounded-sm appearance-none ${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>}
</div>
{ label &&
<Label
className={`${cursor} px-2 text-start`}
@ -55,7 +62,6 @@ function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit'
required={required}
htmlFor={id}
/>}
{value && <CheckboxChecked />}
</button>
);
}

View File

@ -1,32 +0,0 @@
import DataTable, { createTheme, type TableProps } from 'react-data-table-component';
import { useConceptTheme } from '../../context/ThemeContext';
import { dataTableDarkT, dataTableLightT } from '../../utils/color';
export interface SelectionInfo<T> {
allSelected: boolean
selectedCount: number
selectedRows: T[]
}
createTheme('customDark', dataTableDarkT, 'dark');
createTheme('customLight', dataTableLightT, 'light');
interface ConceptDataTableProps<T>
extends Omit<TableProps<T>, 'paginationComponentOptions'> {}
function ConceptDataTable<T>({ theme, ...props }: ConceptDataTableProps<T>) {
const { darkMode } = useConceptTheme();
return (
<DataTable<T>
theme={ theme ?? (darkMode ? 'customDark' : 'customLight')}
paginationComponentOptions={{
rowsPerPageText: 'строк на страницу'
}}
{...props}
/>
);
}
export default ConceptDataTable;

View File

@ -17,34 +17,27 @@ function ConceptSelectSingle<
> ({ ...props }: ConceptSelectSingleProps<Option, Group>) {
const { darkMode, colors } = useConceptTheme();
const themeColors = useMemo(
() => {
return !darkMode ? selectLightT : selectDarkT;
}, [darkMode]);
() => !darkMode ? selectLightT : selectDarkT
, [darkMode]);
const adjustedStyles: StylesConfig<Option, false, Group> = useMemo(
() => {
return {
control: (styles, { isDisabled }) => {
return {
...styles,
borderRadius: '0.25rem',
cursor: isDisabled ? 'not-allowed' : 'pointer'
};
},
option: (styles, { isSelected }) => {
return {
...styles,
backgroundColor: isSelected ? colors.bgSelected : styles.backgroundColor,
color: isSelected ? colors.fgSelected : styles.color,
borderWidth: '1px',
borderColor: colors.border
};
},
input: (styles) => ({...styles}),
placeholder: (styles) => ({...styles}),
singleValue: (styles) => ({...styles}),
};
}, [colors]);
() => ({
control: (styles, { isDisabled }) => ({
...styles,
borderRadius: '0.25rem',
cursor: isDisabled ? 'not-allowed' : 'pointer'
}),
option: (styles, { isSelected }) => ({
...styles,
backgroundColor: isSelected ? colors.bgSelected : styles.backgroundColor,
color: isSelected ? colors.fgSelected : styles.color,
borderWidth: '1px',
borderColor: colors.border
}),
input: (styles) => ({...styles}),
placeholder: (styles) => ({...styles}),
singleValue: (styles) => ({...styles}),
}), [colors]);
return (
<Select

View File

@ -12,7 +12,7 @@ function ConceptTooltip({ className, layer, place='bottom', ...props }: ConceptT
return (
<Tooltip
opacity={0.95}
opacity={0.97}
className={`overflow-auto border shadow-md ${layer ?? 'z-tooltip'} ${className}`}
variant={(darkMode ? 'dark' : 'light')}
place={place}

View File

@ -0,0 +1,265 @@
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

@ -21,7 +21,7 @@ function TextArea({
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3'>
<Label
text={label}
required={required}
required={!props.disabled && required}
htmlFor={id}
/>
<textarea id={id}

View File

@ -20,7 +20,7 @@ function TextInput({
<div className={`flex [&:not(:first-child)]:mt-3 ${singleRow ? 'items-center gap-4 ' + widthClass : 'flex-col items-start'}`}>
<Label
text={label}
required={required}
required={!props.disabled && required}
htmlFor={id}
/>
<input id={id}

View File

@ -11,7 +11,10 @@ extends Omit<CheckboxProps, 'value' | 'setValue'> {
setValue?: (newValue: boolean | null) => void
}
function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit', value, setValue }: TristateProps) {
function Tristate({
id, required, disabled, tooltip, label,
widthClass = 'w-fit', value, setValue, ...props
}: TristateProps) {
const cursor = useMemo(
() => {
if (disabled) {
@ -33,9 +36,11 @@ function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit'
return;
}
if (value === false) {
setValue(null);
setValue(null);
} else if (value === null) {
setValue(true);
} else {
setValue(!value);
setValue(false);
}
}
@ -46,8 +51,12 @@ function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit'
title={tooltip}
disabled={disabled}
onClick={handleClick}
{...props}
>
<div className={`relative peer w-4 h-4 shrink-0 mt-0.5 border rounded-sm appearance-none ${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 == null && <div className='mt-[1px] ml-[1px]'><CheckboxNull /></div>}
</div>
{ label &&
<Label
className={`${cursor} px-2 text-start`}
@ -55,10 +64,8 @@ function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit'
required={required}
htmlFor={id}
/>}
{value && <CheckboxChecked />}
{value === null && <CheckboxNull />}
</button>
);
}
export default Checkbox;
export default Tristate;

View File

@ -1,6 +1,6 @@
import { useConceptTheme } from '../../context/ThemeContext';
import { prefixes } from '../../utils/constants';
import { getCstStatusColor, mapStatusInfo } from '../../utils/staticUI';
import { getCstStatusBgColor, mapStatusInfo } from '../../utils/staticUI';
interface InfoCstStatusProps {
title?: string
@ -18,7 +18,7 @@ function InfoCstStatus({ title }: InfoCstStatusProps) {
<p key={`${prefixes.cst_status_list}${index}`}>
<span
className='px-1 inline-block font-semibold min-w-[5rem] text-center border text-sm'
style={{backgroundColor: getCstStatusColor(status, colors)}}
style={{backgroundColor: getCstStatusBgColor(status, colors)}}
>
{info.text}
</span>

View File

@ -321,7 +321,7 @@ export function InDoor(props: IconProps) {
export function CheckboxChecked() {
return (
<svg
className='absolute w-3 h-3 mt-1 ml-0.5'
className='w-3 h-3'
viewBox='0 0 512 512'
fill='#ffffff'
>
@ -333,8 +333,8 @@ export function CheckboxChecked() {
export function CheckboxNull() {
return (
<svg
className='absolute w-3 h-3 mt-1 ml-0.5'
viewBox='0 0 512 512'
className='w-3 h-3'
viewBox='0 0 16 16'
fill='#ffffff'
>
<path d='M2 7.75A.75.75 0 012.75 7h10a.75.75 0 010 1.5h-10A.75.75 0 012 7.75z' />

View File

@ -7,18 +7,18 @@
:root {
/* Light Theme */
--cl-bg-120: hsl(000, 000%, 100%);
--cl-bg-100: hsl(220, 020%, 098%);
--cl-bg-80: hsl(220, 014%, 096%);
--cl-bg-60: hsl(220, 013%, 091%);
--cl-bg-40: hsl(216, 012%, 084%);
--cl-bg-100: hsl(000, 000%, 098%);
--cl-bg-80: hsl(000, 000%, 094%);
--cl-bg-60: hsl(000, 000%, 091%);
--cl-bg-40: hsl(000, 000%, 080%);
--cl-fg-60: hsl(000, 000%, 055%);
--cl-fg-80: hsl(000, 000%, 047%);
--cl-fg-100: hsl(000, 000%, 000%);
--cl-prim-bg-100: hsl(220, 100%, 060%);
--cl-prim-bg-80: hsl(220, 100%, 090%);
--cl-prim-bg-60: hsl(220, 100%, 094%);
--cl-prim-bg-80: hsl(220, 080%, 092%);
--cl-prim-bg-60: hsl(190, 080%, 094%);
--cl-prim-fg-80: hsl(220, 100%, 050%);
--cl-prim-fg-100: hsl(000, 000%, 100%);
@ -38,24 +38,16 @@
--cd-fg-80: hsl(000, 000%, 080%);
--cd-fg-100: hsl(000, 000%, 093%);
/* --cd-prim-bg-100: hsl(025, 079%, 052%);
--cd-prim-bg-80: hsl(035, 080%, 043%);
--cd-prim-bg-60: hsl(045, 080%, 031%);
--cd-prim-bg-100: hsl(267, 050%, 050%);
--cd-prim-bg-80: hsl(267, 050%, 032%);
--cd-prim-bg-60: hsl(269, 030%, 028%);
--cd-prim-fg-80: hsl(025, 080%, 050%);
--cd-prim-fg-100: hsl(000, 000%, 100%); */
--cd-prim-bg-100: hsl(267, 50%, 50%);
--cd-prim-bg-80: hsl(267, 50%, 35%);
--cd-prim-bg-60: hsl(269, 50%, 20%);
--cd-prim-fg-60: hsl(267, 50%, 35%);
--cd-prim-fg-80: hsl(267, 50%, 50%);
--cd-prim-fg-100: #ffffff;
--cd-prim-fg-80: hsl(267, 070%, 070%);
--cd-prim-fg-100: hsl(000, 000%, 100%);
--cd-red-bg-100: hsl(000, 100%, 015%);
--cd-red-fg-100: hsl(000, 100%, 060%);
--cd-green-fg-100: hsl(120, 80%, 40%);
--cd-red-fg-100: hsl(000, 080%, 055%);
--cd-green-fg-100: hsl(120, 080%, 040%);
/* Import overrides */
--toastify-color-dark: var(--cd-bg-60);
@ -246,14 +238,10 @@
}
}
.clr-btn-nav {
color: var(--cl-fg-80);
.dark & {
color: var(--cd-fg-80);
}
}
.clr-btn-clear {
:is(.text-controls,
.clr-btn-nav,
.clr-btn-clear
) {
color: var(--cl-fg-80);
&:disabled {
color: var(--cl-fg-60);

View File

@ -26,7 +26,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) {
return (
<div ref={pickerMenu.ref} className='h-full text-right'>
<Button
icon={<FilterCogIcon size={6} />}
icon={<FilterCogIcon color='text-controls' size={6} />}
dense
tooltip='Фильтры'
colorClass='clr-input clr-hover text-btn'

View File

@ -37,16 +37,14 @@ function SearchPanel({ total, filtered, setFilter }: SearchPanelProps) {
function handleChangeQuery(event: React.ChangeEvent<HTMLInputElement>) {
const newQuery = event.target.value;
setQuery(newQuery);
setFilter(prev => {
return {
query: newQuery,
is_owned: prev.is_owned,
is_common: prev.is_common,
is_canonical: prev.is_canonical,
is_subscribed: prev.is_subscribed,
is_personal: prev.is_personal
};
});
setFilter(prev => ({
query: newQuery,
is_owned: prev.is_owned,
is_common: prev.is_common,
is_canonical: prev.is_canonical,
is_subscribed: prev.is_subscribed,
is_personal: prev.is_personal
}));
}
useLayoutEffect(() => {
@ -83,7 +81,7 @@ function SearchPanel({ total, filtered, setFilter }: SearchPanelProps) {
onChange={handleChangeStrategy}
/>
<div className='relative w-96 min-w-[10rem]'>
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none'>
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-controls'>
<MagnifyingGlassIcon />
</div>
<input

View File

@ -1,8 +1,9 @@
import { createColumnHelper } from '@tanstack/react-table';
import { useMemo } from 'react';
import { useIntl } from 'react-intl';
import ConceptDataTable from '../../components/Common/ConceptDataTable';
import ConceptTooltip from '../../components/Common/ConceptTooltip';
import DataTable from '../../components/Common/DataTable';
import TextURL from '../../components/Common/TextURL';
import HelpLibrary from '../../components/Help/HelpLibrary';
import { EducationIcon, EyeIcon, GroupIcon, HelpIcon } from '../../components/Icons';
@ -17,6 +18,8 @@ interface ViewLibraryProps {
cleanQuery: () => void
}
const columnHelper = createColumnHelper<ILibraryItem>();
function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
const { navigateTo } = useConceptNavigation();
const intl = useIntl();
@ -27,12 +30,13 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
const columns = useMemo(
() => [
{
name: '',
columnHelper.display({
id: 'status',
minWidth: '60px',
maxWidth: '60px',
cell: (item: ILibraryItem) => {
header: '',
size: 60,
maxSize: 60,
cell: props => {
const item = props.row.original;
return (<>
<div
className='flex items-center justify-start gap-1'
@ -44,51 +48,50 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
</div>
</>);
},
sortable: true,
reorder: true
},
{
name: 'Шифр',
}),
columnHelper.accessor('alias', {
id: 'alias',
maxWidth: '140px',
selector: (item: ILibraryItem) => item.alias,
sortable: true,
reorder: true
},
{
name: 'Название',
header: 'Шифр',
size: 200,
minSize: 200,
maxSize: 200,
enableSorting: true
}),
columnHelper.accessor('title', {
id: 'title',
minWidth: '50%',
selector: (item: ILibraryItem) => item.title,
sortable: true,
reorder: true
},
{
name: 'Владелец',
header: 'Название',
minSize: 200,
size: 1000,
maxSize: 1000,
enableSorting: true
}),
columnHelper.accessor(item => item.owner ?? 0, {
id: 'owner',
selector: (item: ILibraryItem) => item.owner ?? 0,
format: (item: ILibraryItem) => {
return getUserLabel(item.owner);
},
sortable: true,
reorder: true
},
{
name: 'Обновлена',
header: 'Владелец',
cell: props => getUserLabel(props.cell.getValue()),
enableSorting: true,
enableResizing: false,
minSize: 200,
size: 300,
maxSize: 300
}),
columnHelper.accessor('time_update', {
id: 'time_update',
selector: (item: ILibraryItem) => item.time_update,
format: (item: ILibraryItem) => new Date(item.time_update).toLocaleString(intl.locale),
sortable: true,
reorder: true
}
header: 'Обновлена',
minSize: 200,
size: 200,
maxSize: 200,
cell: props => new Date(props.cell.getValue()).toLocaleString(intl.locale),
enableSorting: true
})
], [intl, getUserLabel, user]);
return (
<div>
<div className='relative w-full'>
<div className='absolute top-0 left-0 flex gap-1 mt-1 ml-5 z-pop'>
<div className='absolute top-[-0.125rem] left-0 flex gap-1 ml-3 z-pop'>
<div id='library-help' className='py-2'>
<HelpIcon color='text-primary' size={6} />
<HelpIcon color='text-primary' size={5} />
</div>
<ConceptTooltip anchorSelect='#library-help'>
<div className='max-w-[35rem]'>
@ -97,14 +100,11 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
</ConceptTooltip>
</div>
</div>
<ConceptDataTable
<DataTable
columns={columns}
data={items}
defaultSortFieldId='time_update'
defaultSortAsc={false}
striped
highlightOnHover
pointerOnHover
// defaultSortFieldId='time_update'
// defaultSortAsc={false}
noDataComponent={
<div className='flex flex-col gap-4 justify-center p-2 text-center min-h-[10rem]'>
@ -120,10 +120,9 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
</p>
</div>}
pagination
paginationPerPage={50}
paginationRowsPerPageOptions={[10, 20, 30, 50, 100]}
// pagination
// paginationPerPage={50}
// paginationRowsPerPageOptions={[10, 20, 30, 50, 100]}
onRowClicked={openRSForm}
/>
</div>

View File

@ -18,18 +18,15 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
const { darkMode, colors } = useConceptTheme();
const [hoverID, setHoverID] = useState<number | undefined>(undefined);
const hoverNode = useMemo(
() => {
return syntaxTree.find(node => node.uid === hoverID);
}, [hoverID, syntaxTree]);
() => syntaxTree.find(node => node.uid === hoverID)
, [hoverID, syntaxTree]);
const nodes: GraphNode[] = useMemo(
() => syntaxTree.map(node => {
return {
id: String(node.uid),
label: getASTNodeLabel(node),
fill: getASTNodeColor(node, colors),
};
}), [syntaxTree, colors]);
() => syntaxTree.map(node => ({
id: String(node.uid),
label: getASTNodeLabel(node),
fill: getASTNodeColor(node, colors),
})), [syntaxTree, colors]);
const edges: GraphEdge[] = useMemo(
() => {
@ -47,14 +44,12 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
}, [syntaxTree]);
const handleHoverIn = useCallback(
(node: GraphNode) => {
setHoverID(Number(node.id));
}, []);
(node: GraphNode) => setHoverID(Number(node.id))
, []);
const handleHoverOut = useCallback(
() => {
setHoverID(undefined);
}, []);
() => setHoverID(undefined)
, []);
return (
<Modal

View File

@ -1,9 +1,10 @@
import { useCallback, useMemo, useState } from 'react';
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 ConceptDataTable from '../../components/Common/ConceptDataTable';
import ConceptTooltip from '../../components/Common/ConceptTooltip';
import DataTable from '../../components/Common/DataTable';
import Divider from '../../components/Common/Divider';
import HelpRSFormItems from '../../components/Help/HelpRSFormItems';
import { ArrowDownIcon, ArrowUpIcon, DumpBinIcon, HelpIcon, MeshIcon, SmallPlusIcon } from '../../components/Icons';
@ -11,7 +12,9 @@ import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext';
import { prefixes } from '../../utils/constants';
import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../utils/models'
import { getCstStatusColor, getCstTypePrefix, getCstTypeShortcut, getCstTypificationLabel, mapStatusInfo } from '../../utils/staticUI';
import { getCstStatusFgColor, getCstTypePrefix, getCstTypeShortcut, getCstTypificationLabel, mapStatusInfo } from '../../utils/staticUI';
const columnHelper = createColumnHelper<IConstituenta>();
interface EditorItemsProps {
onOpenEdit: (cstID: number) => void
@ -25,7 +28,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
const [selected, setSelected] = useState<number[]>([]);
const nothingSelected = useMemo(() => selected.length === 0, [selected]);
const [toggledClearRows, setToggledClearRows] = useState(false);
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
// Delete selected constituents
function handleDelete() {
@ -33,8 +36,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
return;
}
onDeleteCst(selected, () => {
setToggledClearRows(prev => !prev);
setSelected([]);
setRowSelection({});
});
}
@ -53,12 +55,16 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
}, -1);
const target = Math.max(0, currentIndex - 1) + 1
const data = {
items: selected.map(id => {
return { id: id };
}),
items: selected.map(id => ({ id: id })),
move_to: target
}
cstMoveTo(data);
cstMoveTo(data, () => {
const newSelection: RowSelectionState = {};
selected.forEach((_, index) => {
newSelection[String(target + index - 1)] = true;
})
setRowSelection(newSelection);
});
}
// Move selected cst down
@ -80,12 +86,16 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
}, -1);
const target = Math.min(schema.items.length - 1, currentIndex - count + 2) + 1
const data: ICstMovetoData = {
items: selected.map(id => {
return { id: id };
}),
items: selected.map(id => ({ id: id })),
move_to: target
}
cstMoveTo(data);
cstMoveTo(data, () => {
const newSelection: RowSelectionState = {};
selected.forEach((_, index) => {
newSelection[String(target + index - 1)] = true;
})
setRowSelection(newSelection);
});
}
// Generate new names for all constituents
@ -156,41 +166,53 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
const handleRowClicked = useCallback(
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
if (event.altKey) {
event.preventDefault();
onOpenEdit(cst.id);
}
}, [onOpenEdit]);
const handleRowDoubleClicked = useCallback(
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
event.preventDefault();
onOpenEdit(cst.id);
}, [onOpenEdit]);
const handleSelectionChange = useCallback(
({ selectedRows }: {
allSelected: boolean
selectedCount: number
selectedRows: IConstituenta[]
}) => {
setSelected(selectedRows.map(cst => cst.id));
}, [setSelected]);
useLayoutEffect(
() => {
if (!schema || Object.keys(rowSelection).length === 0) {
setSelected([]);
} else {
const selected: number[] = [];
schema.items.forEach((cst, index) => {
if (rowSelection[String(index)] === true) {
selected.push(cst.id);
}
});
setSelected(selected);
}
}, [rowSelection, schema]);
const columns = useMemo(
() => [
{
name: 'ID',
id: 'id',
selector: (cst: IConstituenta) => cst.id,
omit: true
},
{
name: 'Имя',
columnHelper.accessor('alias', {
id: 'alias',
cell: (cst: IConstituenta) => {
const info = mapStatusInfo.get(cst.status)!;
header: 'Имя',
size: 65,
minSize: 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'
style={{borderWidth: "1px",
borderColor: getCstStatusColor(cst.status, colors),
color: getCstStatusColor(cst.status, colors),
style={{
borderWidth: "1px",
borderColor: getCstStatusFgColor(cst.status, colors),
color: getCstStatusFgColor(cst.status, colors),
fontWeight: 600,
backgroundColor: colors.bgDefault}}
backgroundColor: colors.bgInput
}}
>
{cst.alias}
</div>
@ -198,68 +220,64 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
anchorSelect={`#${prefixes.cst_list}${cst.alias}`}
place='right'
>
<p><b>Статус: </b> {info.tooltip}</p>
<p><span className='font-semibold'>Статус</span>: {info!.tooltip}</p>
</ConceptTooltip>
</>);
},
width: '65px',
maxWidth: '65px',
reorder: true,
},
{
name: 'Типизация',
}
}),
columnHelper.accessor(cst => getCstTypificationLabel(cst), {
id: 'type',
cell: (cst: IConstituenta) => <div style={{ fontSize: 12 }}>{getCstTypificationLabel(cst)}</div>,
width: '175px',
maxWidth: '175px',
wrap: true,
reorder: true,
hide: 1600
},
{
name: 'Термин',
header: 'Типизация',
size: 175,
maxSize: 175,
cell: props => <div style={{ fontSize: 12 }}>{props.getValue()}</div>
}),
columnHelper.accessor(cst => cst.term_resolved || cst.term_raw || '', {
id: 'term',
selector: (cst: IConstituenta) => cst.term_resolved || cst.term_raw || '',
width: '350px',
minWidth: '150px',
maxWidth: '350px',
wrap: true,
reorder: true
},
{
name: 'Формальное определение',
header: 'Термин',
size: 350,
minSize: 150,
maxSize: 350
}),
columnHelper.accessor('definition_formal', {
id: 'expression',
selector: (cst: IConstituenta) => cst.definition_formal || '',
minWidth: '300px',
maxWidth: '500px',
grow: 2,
wrap: true,
reorder: true
},
{
name: 'Текстовое определение',
header: 'Формальное определение',
size: 300,
minSize: 300,
maxSize: 500
}),
columnHelper.accessor(cst => cst.definition_resolved || cst.definition_raw || '', {
id: 'definition',
cell: (cst: IConstituenta) => (
<div style={{ fontSize: 12 }}>
{cst.definition_resolved || cst.definition_raw || ''}
</div>
),
minWidth: '200px',
grow: 2,
wrap: true,
reorder: true
},
{
name: 'Конвенция / Комментарий',
header: 'Текстовое определение',
size: 200,
minSize: 200,
cell: props => <div style={{ fontSize: 12 }}>{props.getValue()}</div>
}),
columnHelper.accessor('convention', {
id: 'convention',
cell: (cst: IConstituenta) => <div style={{ fontSize: 12 }}>{cst.convention ?? ''}</div>,
minWidth: '100px',
wrap: true,
reorder: true,
hide: 1800
}
header: 'Конвенция / Комментарий',
minSize: 100,
maxSize: undefined,
cell: props => <div style={{ fontSize: 12 }}>{props.getValue()}</div>
}),
], [colors]);
// name: 'Типизация',
// hide: 1600
// },
// {
// name: 'Формальное определение',
// grow: 2,
// },
// {
// name: 'Текстовое определение',
// grow: 2,
// },
// {
// name: 'Конвенция / Комментарий',
// id: 'convention',
// hide: 1800
return (
<div className='w-full'>
<div
@ -330,30 +348,30 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
</ConceptTooltip>
</div>
</div>
<div className='w-full h-full' onKeyDown={handleTableKey}>
<ConceptDataTable
<div className='w-full h-full text-sm' onKeyDown={handleTableKey}>
<DataTable
data={schema?.items ?? []}
columns={columns}
keyField='id'
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>Создайте новую конституенту</p>
<p
className='cursor-pointer text-primary hover:underline'
onClick={() => handleCreateCst()}>
Создать новую конституенту
</p>
</span>
}
striped
highlightOnHover
pointerOnHover
selectableRows
selectableRowsHighlight
selectableRowsComponentProps={{tabIndex: -1}}
onSelectedRowsChange={handleSelectionChange}
onRowDoubleClicked={cst => onOpenEdit(cst.id)}
onRowClicked={handleRowClicked}
clearSelectedRows={toggledClearRows}
dense
/>
</div>
</div>

View File

@ -19,7 +19,7 @@ import { graphDarkT, graphLightT, IColorTheme } from '../../utils/color';
import { prefixes, resources, TIMEOUT_GRAPH_REFRESH } from '../../utils/constants';
import { Graph } from '../../utils/Graph';
import { CstType, IConstituenta, ICstCreateData } from '../../utils/models';
import { getCstClassColor, getCstStatusColor,
import { getCstClassColor, getCstStatusBgColor,
GraphColoringSelector, GraphLayoutSelector,
mapColoringLabels, mapLayoutLabels
} from '../../utils/staticUI';
@ -34,7 +34,7 @@ function getCstNodeColor(cst: IConstituenta, coloringScheme: ColoringScheme, col
return getCstClassColor(cst.cst_class, colors);
}
if (coloringScheme === 'status') {
return getCstStatusColor(cst.status, colors);
return getCstStatusBgColor(cst.status, colors);
}
return '';
}

View File

@ -137,7 +137,7 @@ function RSTabs() {
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'end',
block: 'nearest',
inline: 'nearest'
});
}
@ -173,9 +173,7 @@ function RSTabs() {
return;
}
const data = {
items: deleted.map(id => {
return { id: id };
})
items: deleted.map(id => ({ id: id }))
};
let activeIndex = schema.items.findIndex(cst => cst.id === activeID);
cstDelete(data, () => {
@ -345,7 +343,7 @@ function RSTabs() {
showCloneDialog={handleShowClone}
showUploadDialog={() => setShowUpload(true)}
/>
<ConceptTab className='border-r-2 min-w-[7.8rem]'>Паспорт схемы</ConceptTab>
<ConceptTab className='border-x-2 min-w-[7.8rem]'>Паспорт схемы</ConceptTab>
<ConceptTab className='border-r-2 min-w-[10rem] flex justify-between gap-2'>
<span>Конституенты</span>
<span>{`${schema.stats?.count_errors ?? 0} | ${schema.stats?.count_all ?? 0}`}</span>

View File

@ -71,9 +71,10 @@ function RSTabsMenu({
<div ref={schemaMenu.ref}>
<Button
tooltip='Действия'
icon={<MenuIcon size={5}/>}
icon={<MenuIcon color='text-controls' size={5}/>}
borderClass=''
widthClass='h-full w-fit'
style={{outlineColor: 'transparent'}}
dense
onClick={schemaMenu.toggle}
tabIndex={-1}
@ -123,6 +124,7 @@ function RSTabsMenu({
tooltip={'измнение: ' + (isEditable ? '[доступно]' : '[запрещено]')}
borderClass=''
widthClass='h-full w-fit'
style={{outlineColor: 'transparent'}}
icon={<PenIcon size={5} color={isEditable ? 'text-success' : 'text-warning'}/>}
dense
onClick={editMenu.toggle}
@ -136,7 +138,7 @@ function RSTabsMenu({
tooltip={!user || !isClaimable ? 'Стать владельцем можно только для общей изменяемой схемы' : ''}
>
<div className='inline-flex items-center gap-1 justify-normal'>
<span className={isOwned ? 'text-success' : ''}><CrownIcon size={4} /></span>
<span><CrownIcon size={4} color={isOwned ? 'text-success' : 'text-controls'} /></span>
<p>
{ isOwned && <b>Владелец схемы</b> }
{ !isOwned && <b>Стать владельцем</b> }
@ -165,10 +167,11 @@ function RSTabsMenu({
disabled={processing}
icon={isTracking
? <EyeIcon color='text-primary' size={5}/>
: <EyeOffIcon size={5}/>
: <EyeOffIcon color='text-controls' size={5}/>
}
widthClass='h-full w-fit'
borderClass=''
style={{outlineColor: 'transparent'}}
dense
onClick={onToggleSubscribe}
tabIndex={-1}

View File

@ -2,7 +2,7 @@ import { useMemo } from 'react';
import { useConceptTheme } from '../../../context/ThemeContext';
import { ExpressionStatus, type IConstituenta, IExpressionParse,inferStatus, ParsingStatus } from '../../../utils/models';
import { getCstStatusColor, mapStatusInfo } from '../../../utils/staticUI';
import { getCstStatusBgColor, mapStatusInfo } from '../../../utils/staticUI';
interface StatusBarProps {
isModified?: boolean
@ -27,7 +27,7 @@ function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) {
return (
<div title={data.tooltip}
className='text-sm h-[1.6rem] w-[10rem] font-semibold inline-flex border items-center select-none justify-center align-middle'
style={{backgroundColor: getCstStatusColor(status, colors)}}
style={{backgroundColor: getCstStatusBgColor(status, colors)}}
>
Статус: [ {data.text} ]
</div>

View File

@ -1,12 +1,13 @@
import { createColumnHelper } from '@tanstack/react-table';
import { useCallback, useEffect, useMemo, useState } from 'react';
import ConceptDataTable from '../../../components/Common/ConceptDataTable';
import DataTable from '../../../components/Common/DataTable';
import { useRSForm } from '../../../context/RSFormContext';
import { useConceptTheme } from '../../../context/ThemeContext';
import useLocalStorage from '../../../hooks/useLocalStorage';
import { prefixes } from '../../../utils/constants';
import { applyGraphFilter, CstMatchMode, CstType, DependencyMode, extractGlobals, IConstituenta, matchConstituenta } from '../../../utils/models';
import { getCstDescription, getCstStatusColor, getMockConstituenta } from '../../../utils/staticUI';
import { getCstDescription, getCstStatusFgColor, getMockConstituenta } from '../../../utils/staticUI';
import ConstituentaTooltip from './ConstituentaTooltip';
import DependencyModePicker from './DependencyModePicker';
import MatchModePicker from './MatchModePicker';
@ -25,6 +26,8 @@ function isMockCst(cst: IConstituenta) {
return cst.id <= 0;
}
const columnHelper = createColumnHelper<IConstituenta>();
function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) {
const { noNavigation, colors } = useConceptTheme();
const { schema } = useRSForm();
@ -85,75 +88,61 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
}
}, [onOpenEdit]);
const conditionalRowStyles = useMemo(
() => [
{
when: (cst: IConstituenta) => cst.id === activeID,
style: {
backgroundColor: colors.bgSelected,
},
}
], [activeID, colors]);
const columns = useMemo(
() => [
{
id: 'id',
selector: (cst: IConstituenta) => cst.id,
omit: true
},
{
name: 'ID',
columnHelper.accessor('alias', {
id: 'alias',
cell: (cst: IConstituenta) => {
header: 'Имя',
size: 65,
minSize: 65,
cell: props => {
const cst = props.row.original;
return (<>
<div
id={`${prefixes.cst_list}${cst.alias}`}
className='w-full px-1 text-center rounded-md min-w-fit whitespace-nowrap'
style={{backgroundColor: getCstStatusColor(cst.status, colors)}}
className='w-full px-1 text-center rounded-md whitespace-nowrap'
style={{
borderWidth: '1px',
borderColor: getCstStatusFgColor(cst.status, colors),
color: getCstStatusFgColor(cst.status, colors),
fontWeight: 600,
backgroundColor: isMockCst(cst) ? colors.bgWarning : colors.bgInput
}}
>
{cst.alias}
</div>
<ConstituentaTooltip data={cst} anchor={`#${prefixes.cst_list}${cst.alias}`} />
</>);
},
width: '65px',
maxWidth: '65px',
conditionalCellStyles: [
{
when: (cst: IConstituenta) => isMockCst(cst),
style: {backgroundColor: colors.bgWarning}
}
]
},
{
name: 'Описание',
}
}),
columnHelper.accessor(cst => getCstDescription(cst), {
id: 'description',
selector: (cst: IConstituenta) => getCstDescription(cst),
minWidth: '350px',
wrap: true,
conditionalCellStyles: [
{
when: (cst: IConstituenta) => isMockCst(cst),
style: {backgroundColor: colors.bgWarning}
}
]
},
{
name: 'Выражение',
header: 'Описание',
size: 350,
minSize: 350,
maxSize: 350,
cell: props =>
<div style={{
fontSize: 12,
color: isMockCst(props.row.original) ? colors.fgWarning : undefined
}}>
{props.getValue()}
</div>
}),
columnHelper.accessor('definition_formal', {
id: 'expression',
selector: (cst: IConstituenta) => cst.definition_formal || '',
minWidth: '200px',
hide: 1600,
grow: 2,
wrap: true,
conditionalCellStyles: [
{
when: (cst: IConstituenta) => isMockCst(cst),
style: {backgroundColor: colors.fgWarning}
}
]
}
header: 'Выражение',
size: 700,
minSize: 0,
maxSize: 700,
cell: props =>
<div style={{
fontSize: 12,
color: isMockCst(props.row.original) ? colors.fgWarning : undefined
}}>
{props.getValue()}
</div>
})
], [colors]);
const maxHeight = useMemo(
@ -181,12 +170,14 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
onChange={setFilterSource}
/>
</div>
<div className='overflow-y-auto' style={{maxHeight : `${maxHeight}`}}>
<ConceptDataTable
<div className='overflow-y-auto text-sm' style={{maxHeight : `${maxHeight}`}}>
<DataTable
data={filteredData}
columns={columns}
keyField='id'
conditionalRowStyles={conditionalRowStyles}
// conditionalRowStyles={conditionalRowStyles}
noDataComponent={
<span className='flex flex-col justify-center p-2 text-center min-h-[5rem]'>
<p>Список конституент пуст</p>
@ -194,13 +185,8 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
</span>
}
striped
highlightOnHover
pointerOnHover
onRowDoubleClicked={handleDoubleClick}
onRowClicked={handleRowClicked}
dense
/>
</div>
</>);

View File

@ -1,7 +1,8 @@
import { createColumnHelper } from '@tanstack/react-table';
import { useMemo } from 'react';
import { useIntl } from 'react-intl';
import ConceptDataTable from '../../components/Common/ConceptDataTable';
import DataTable from '../../components/Common/DataTable';
import { useConceptNavigation } from '../../context/NagivationContext';
import { ILibraryItem } from '../../utils/models';
@ -9,6 +10,8 @@ interface ViewSubscriptionsProps {
items: ILibraryItem[]
}
const columnHelper = createColumnHelper<ILibraryItem>();
function ViewSubscriptions({items}: ViewSubscriptionsProps) {
const { navigateTo } = useConceptNavigation();
const intl = useIntl();
@ -17,50 +20,48 @@ function ViewSubscriptions({items}: ViewSubscriptionsProps) {
const columns = useMemo(() =>
[
{
name: 'Шифр',
columnHelper.accessor('alias', {
id: 'alias',
maxWidth: '140px',
selector: (item: ILibraryItem) => item.alias,
sortable: true,
reorder: true
},
{
name: 'Название',
header: 'Шифр',
size: 200,
minSize: 200,
maxSize: 200,
enableSorting: true
}),
columnHelper.accessor('title', {
id: 'title',
minWidth: '50%',
selector: (item: ILibraryItem) => item.title,
sortable: true,
reorder: true
},
{
name: 'Обновлена',
header: 'Название',
minSize: 200,
size: 800,
maxSize: 800,
enableSorting: true
}),
columnHelper.accessor('time_update', {
id: 'time_update',
selector: (item: ILibraryItem) => item.time_update,
format: (item: ILibraryItem) => new Date(item.time_update).toLocaleString(intl.locale),
sortable: true,
reorder: true
}
header: 'Обновлена',
minSize: 200,
size: 200,
maxSize: 200,
cell: props => new Date(props.cell.getValue()).toLocaleString(intl.locale),
enableSorting: true
})
], [intl]);
return (
<ConceptDataTable
className='h-full overflow-auto border'
<div className='h-full overflow-auto text-sm border w-fit'>
<DataTable
columns={columns}
data={items}
defaultSortFieldId='time_update'
defaultSortAsc={false}
// defaultSortFieldId='time_update'
// defaultSortAsc={false}
noDataComponent={
<div className='h-[10rem]'>Отслеживаемые схемы отсутствуют</div>
}
striped
dense
highlightOnHover
pointerOnHover
onRowClicked={openRSForm}
/>
</div>
)
}

View File

@ -95,67 +95,14 @@ export const darkT: IColorTheme = {
bgTeal: 'hsl(192, 080%, 030%)',
bgOrange: 'hsl(035, 100%, 035%)',
fgRed: 'hsl(000, 080%, 050%)',
fgGreen: 'hsl(100, 080%, 040%)',
fgRed: 'hsl(000, 080%, 045%)',
fgGreen: 'hsl(100, 080%, 035%)',
fgBlue: 'hsl(235, 100%, 080%)',
fgPurple: 'hsl(270, 100%, 080%)',
fgTeal: 'hsl(192, 100%, 030%)',
fgOrange: 'hsl(035, 100%, 050%)'
};
// ========= DATA TABLE THEMES ========
export const dataTableLightT = {
text: {
primary: lightT.fgDefault,
secondary: lightT.fgDefault,
disabled: lightT.fgDisabled
},
background: {
default: lightT.bgDefault
},
highlightOnHover: {
default: lightT.bgHover,
text: lightT.fgDefault
},
divider: {
default: lightT.border
},
striped: {
default: lightT.bgControls,
text: lightT.fgDefault
},
selected: {
default: lightT.bgSelected,
text: lightT.fgDefault
}
}
export const dataTableDarkT = {
text: {
primary: darkT.fgDefault,
secondary: darkT.fgDefault,
disabled: darkT.fgDisabled
},
background: {
default: darkT.bgDefault
},
highlightOnHover: {
default: darkT.bgHover,
text: darkT.fgDefault
},
divider: {
default: darkT.border
},
striped: {
default: darkT.bgControls,
text: darkT.fgDefault
},
selected: {
default: darkT.bgSelected,
text: darkT.fgDefault
}
};
// ============ SELECT THEMES ==========
export const selectLightT = {
primary: lightT.bgPrimary,

View File

@ -242,11 +242,13 @@ export function getCstTypeShortcut(type: CstType) {
}
}
export const CstTypeSelector = (Object.values(CstType)).map(
(typeStr) => {
const type = typeStr as CstType;
return { value: type, label: getCstTypeLabel(type) };
});
export const CstTypeSelector = (
Object.values(CstType)).map(
typeStr => ({
value: typeStr as CstType,
label: getCstTypeLabel(typeStr as CstType)
})
);
export function getCstCompareLabel(mode: CstMatchMode): string {
switch(mode) {
@ -313,7 +315,18 @@ export const GraphColoringSelector: {value: ColoringScheme, label: string}[] = [
{ value: 'type', label: 'Цвет: класс'},
];
export function getCstStatusColor(status: ExpressionStatus, colors: IColorTheme): string {
export function getCstStatusBgColor(status: ExpressionStatus, colors: IColorTheme): string {
switch (status) {
case ExpressionStatus.VERIFIED: return colors.bgGreen;
case ExpressionStatus.INCORRECT: return colors.bgRed;
case ExpressionStatus.INCALCULABLE: return colors.bgOrange;
case ExpressionStatus.PROPERTY: return colors.bgTeal;
case ExpressionStatus.UNKNOWN: return colors.bgBlue;
case ExpressionStatus.UNDEFINED: return colors.bgBlue;
}
}
export function getCstStatusFgColor(status: ExpressionStatus, colors: IColorTheme): string {
switch (status) {
case ExpressionStatus.VERIFIED: return colors.fgGreen;
case ExpressionStatus.INCORRECT: return colors.fgRed;