mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 21:10:38 +03:00
Refactor and simplify UI
This commit is contained in:
parent
219bf4a111
commit
1009a2ec98
|
@ -9,7 +9,7 @@ Styling conventions
|
||||||
<pre>
|
<pre>
|
||||||
- layer: z-position
|
- layer: z-position
|
||||||
- outer layout: fixed bottom-1/2 left-0 -translate-x-1/2
|
- outer layout: fixed bottom-1/2 left-0 -translate-x-1/2
|
||||||
- rectangle: mt-3 w-full min-w-10 h-fit
|
- rectangle: mt-3 w-full min-w-10 h-fit flex-grow
|
||||||
- inner layout: px-3 py-2 flex flex-col gap-3 justify-start items-center
|
- inner layout: px-3 py-2 flex flex-col gap-3 justify-start items-center
|
||||||
- overflow behavior: overflow-auto
|
- overflow behavior: overflow-auto
|
||||||
- border: borer-2 outline-none shadow-md
|
- border: borer-2 outline-none shadow-md
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import clsx from 'clsx';
|
|
||||||
import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom';
|
import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom';
|
||||||
|
|
||||||
import ConceptToaster from './components/ConceptToaster';
|
import ConceptToaster from './components/ConceptToaster';
|
||||||
|
@ -19,14 +18,10 @@ import UserProfilePage from './pages/UserProfilePage';
|
||||||
import { globalIDs } from './utils/constants';
|
import { globalIDs } from './utils/constants';
|
||||||
|
|
||||||
function Root() {
|
function Root() {
|
||||||
const { noNavigation, noFooter, viewportHeight, mainHeight, showScroll } = useConceptTheme();
|
const { viewportHeight, mainHeight, showScroll } = useConceptTheme();
|
||||||
return (
|
return (
|
||||||
<NavigationState>
|
<NavigationState>
|
||||||
<div className={clsx(
|
<div className='min-w-[30rem] clr-app antialiased'>
|
||||||
'w-screen min-w-[30rem]',
|
|
||||||
'clr-app',
|
|
||||||
'antialiased'
|
|
||||||
)}>
|
|
||||||
|
|
||||||
<ConceptToaster
|
<ConceptToaster
|
||||||
className='mt-[4rem] text-sm'
|
className='mt-[4rem] text-sm'
|
||||||
|
@ -38,23 +33,20 @@ function Root() {
|
||||||
<Navigation />
|
<Navigation />
|
||||||
|
|
||||||
<div id={globalIDs.main_scroll}
|
<div id={globalIDs.main_scroll}
|
||||||
className='w-full overflow-x-auto overscroll-none'
|
className='overflow-x-auto overscroll-none'
|
||||||
style={{
|
style={{
|
||||||
maxHeight: viewportHeight,
|
maxHeight: viewportHeight,
|
||||||
overflowY: showScroll ? 'scroll': 'auto'
|
overflowY: showScroll ? 'scroll': 'auto'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<main
|
<main
|
||||||
className={clsx(
|
className='flex flex-col items-center'
|
||||||
'w-full h-full min-w-fit',
|
|
||||||
'flex flex-col items-center'
|
|
||||||
)}
|
|
||||||
style={{minHeight: mainHeight}}
|
style={{minHeight: mainHeight}}
|
||||||
>
|
>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{(!noNavigation && !noFooter) ? <Footer /> : null}
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NavigationState>);
|
</NavigationState>);
|
||||||
|
|
|
@ -18,7 +18,7 @@ function Dropdown({
|
||||||
layer='z-modal-tooltip'
|
layer='z-modal-tooltip'
|
||||||
position='mt-3'
|
position='mt-3'
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'flex flex-col items-stretch justify-start',
|
'flex flex-col items-stretch',
|
||||||
'border rounded-md shadow-lg',
|
'border rounded-md shadow-lg',
|
||||||
'text-sm',
|
'text-sm',
|
||||||
'clr-input',
|
'clr-input',
|
||||||
|
|
|
@ -59,7 +59,7 @@ function Modal({
|
||||||
'clr-app'
|
'clr-app'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Overlay position='right-[0.3rem] top-2' className='text-disabled'>
|
<Overlay position='right-[0.3rem] top-2'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Закрыть диалоговое окно [ESC]'
|
tooltip='Закрыть диалоговое окно [ESC]'
|
||||||
icon={<BiX size='1.25rem'/>}
|
icon={<BiX size='1.25rem'/>}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
interface SubmitButtonProps
|
interface SubmitButtonProps
|
||||||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children' | 'title'> {
|
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'title'> {
|
||||||
text?: string
|
text?: string
|
||||||
tooltip?: string
|
tooltip?: string
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
|
@ -10,21 +10,21 @@ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'child
|
||||||
}
|
}
|
||||||
|
|
||||||
function SubmitButton({
|
function SubmitButton({
|
||||||
text = 'ОК', icon, disabled, tooltip, loading,
|
text = 'ОК', icon, disabled, tooltip, loading, className,
|
||||||
dimensions = 'w-fit h-fit', ...restProps
|
dimensions = 'w-fit h-fit', ...restProps
|
||||||
}: SubmitButtonProps) {
|
}: SubmitButtonProps) {
|
||||||
return (
|
return (
|
||||||
<button type='submit'
|
<button type='submit'
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'px-3 py-2',
|
'px-3 py-2 inline-flex items-center gap-2 align-middle justify-center',
|
||||||
'inline-flex items-center gap-2 align-middle justify-center',
|
|
||||||
'border',
|
'border',
|
||||||
'font-semibold',
|
'font-semibold',
|
||||||
'clr-btn-primary',
|
'clr-btn-primary',
|
||||||
'select-none disabled:cursor-not-allowed',
|
'select-none disabled:cursor-not-allowed',
|
||||||
loading && 'cursor-progress',
|
loading && 'cursor-progress',
|
||||||
dimensions
|
dimensions,
|
||||||
|
className
|
||||||
)}
|
)}
|
||||||
disabled={disabled ?? loading}
|
disabled={disabled ?? loading}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Cell, ColumnSort,
|
ColumnSort,
|
||||||
createColumnHelper, flexRender, getCoreRowModel,
|
createColumnHelper, getCoreRowModel,
|
||||||
getPaginationRowModel, getSortedRowModel, Header, HeaderGroup,
|
getPaginationRowModel, getSortedRowModel,
|
||||||
PaginationState, Row, RowData, type RowSelectionState,
|
PaginationState, RowData, type RowSelectionState,
|
||||||
SortingState, TableOptions, useReactTable, type VisibilityState
|
SortingState, TableOptions, useReactTable, type VisibilityState
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
|
import clsx from 'clsx';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import DefaultNoData from './DefaultNoData';
|
import DefaultNoData from './DefaultNoData';
|
||||||
import PaginationTools from './PaginationTools';
|
import PaginationTools from './PaginationTools';
|
||||||
import SelectAll from './SelectAll';
|
import TableBody from './TableBody';
|
||||||
import SelectRow from './SelectRow';
|
import TableFooter from './TableFooter';
|
||||||
import SortingIcon from './SortingIcon';
|
import TableHeader from './TableHeader';
|
||||||
|
|
||||||
export { createColumnHelper, type ColumnSort, type RowSelectionState, type VisibilityState };
|
export { createColumnHelper, type ColumnSort, type RowSelectionState, type VisibilityState };
|
||||||
|
|
||||||
|
@ -27,14 +28,19 @@ extends Pick<TableOptions<TData>,
|
||||||
'data' | 'columns' |
|
'data' | 'columns' |
|
||||||
'onRowSelectionChange' | 'onColumnVisibilityChange'
|
'onRowSelectionChange' | 'onColumnVisibilityChange'
|
||||||
> {
|
> {
|
||||||
|
style?: React.CSSProperties
|
||||||
|
className?: string
|
||||||
|
|
||||||
dense?: boolean
|
dense?: boolean
|
||||||
headPosition?: string
|
headPosition?: string
|
||||||
noHeader?: boolean
|
noHeader?: boolean
|
||||||
noFooter?: boolean
|
noFooter?: boolean
|
||||||
|
|
||||||
conditionalRowStyles?: IConditionalStyle<TData>[]
|
conditionalRowStyles?: IConditionalStyle<TData>[]
|
||||||
|
noDataComponent?: React.ReactNode
|
||||||
|
|
||||||
onRowClicked?: (rowData: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
onRowClicked?: (rowData: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
||||||
onRowDoubleClicked?: (rowData: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
onRowDoubleClicked?: (rowData: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
||||||
noDataComponent?: React.ReactNode
|
|
||||||
|
|
||||||
enableRowSelection?: boolean
|
enableRowSelection?: boolean
|
||||||
rowSelection?: RowSelectionState
|
rowSelection?: RowSelectionState
|
||||||
|
@ -58,6 +64,7 @@ extends Pick<TableOptions<TData>,
|
||||||
* No sticky header if omitted
|
* No sticky header if omitted
|
||||||
*/
|
*/
|
||||||
function DataTable<TData extends RowData>({
|
function DataTable<TData extends RowData>({
|
||||||
|
style, className,
|
||||||
dense, headPosition, conditionalRowStyles, noFooter, noHeader,
|
dense, headPosition, conditionalRowStyles, noFooter, noHeader,
|
||||||
onRowClicked, onRowDoubleClicked, noDataComponent,
|
onRowClicked, onRowDoubleClicked, noDataComponent,
|
||||||
|
|
||||||
|
@ -104,104 +111,30 @@ function DataTable<TData extends RowData>({
|
||||||
|
|
||||||
const isEmpty = tableImpl.getRowModel().rows.length === 0;
|
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 (
|
return (
|
||||||
<div className='w-full'>
|
<div className={clsx(className)} style={style}>
|
||||||
<div className='flex flex-col items-stretch'>
|
<table className='w-full'>
|
||||||
<table>
|
|
||||||
{!noHeader ?
|
{!noHeader ?
|
||||||
<thead
|
<TableHeader
|
||||||
className={`clr-app shadow-border`}
|
table={tableImpl}
|
||||||
style={{
|
enableRowSelection={enableRowSelection}
|
||||||
top: headPosition,
|
enableSorting={enableSorting}
|
||||||
position: 'sticky'
|
headPosition={headPosition}
|
||||||
}}
|
/>: null}
|
||||||
>
|
|
||||||
{tableImpl.getHeaderGroups().map(
|
|
||||||
(headerGroup: HeaderGroup<TData>) => (
|
|
||||||
<tr key={headerGroup.id}>
|
|
||||||
{enableRowSelection ?
|
|
||||||
<th className='pl-3 pr-1'>
|
|
||||||
<SelectAll table={tableImpl} />
|
|
||||||
</th> : null}
|
|
||||||
{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 ? (
|
|
||||||
<div className='flex gap-1'>
|
|
||||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
||||||
{(enableSorting && header.column.getCanSort()) ? <SortingIcon column={header.column} /> : null}
|
|
||||||
</div>) : null}
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</thead> : null}
|
|
||||||
|
|
||||||
<tbody>
|
<TableBody
|
||||||
{tableImpl.getRowModel().rows.map(
|
table={tableImpl}
|
||||||
(row: Row<TData>, index) => (
|
dense={dense}
|
||||||
<tr
|
conditionalRowStyles={conditionalRowStyles}
|
||||||
key={row.id}
|
enableRowSelection={enableRowSelection}
|
||||||
className={
|
onRowClicked={onRowClicked}
|
||||||
row.getIsSelected() ? 'clr-selected clr-hover' :
|
onRowDoubleClicked={onRowDoubleClicked}
|
||||||
index % 2 === 0 ? 'clr-controls clr-hover' : 'clr-app clr-hover'
|
/>
|
||||||
}
|
|
||||||
style={conditionalRowStyles && getRowStyles(row)}
|
|
||||||
>
|
|
||||||
{enableRowSelection ?
|
|
||||||
<td key={`select-${row.id}`} className='pl-3 pr-1 border-y'>
|
|
||||||
<SelectRow row={row} />
|
|
||||||
</td> : null}
|
|
||||||
{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) : undefined}
|
|
||||||
onDoubleClick={event => onRowDoubleClicked ? onRowDoubleClicked(row.original, event) : undefined}
|
|
||||||
>
|
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
|
|
||||||
{!noFooter ?
|
{!noFooter ?
|
||||||
<tfoot>
|
<TableFooter
|
||||||
{tableImpl.getFooterGroups().map(
|
table={tableImpl}
|
||||||
(footerGroup: HeaderGroup<TData>) => (
|
/>: null}
|
||||||
<tr key={footerGroup.id}>
|
|
||||||
{footerGroup.headers.map(
|
|
||||||
(header: Header<TData, unknown>) => (
|
|
||||||
<th key={header.id}>
|
|
||||||
{!header.isPlaceholder ? flexRender(header.column.columnDef.footer, header.getContext()) : null}
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tfoot> : null}
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{(enablePagination && !isEmpty) ?
|
{(enablePagination && !isEmpty) ?
|
||||||
|
@ -210,8 +143,7 @@ function DataTable<TData extends RowData>({
|
||||||
paginationOptions={paginationOptions}
|
paginationOptions={paginationOptions}
|
||||||
onChangePaginationOption={onChangePaginationOption}
|
onChangePaginationOption={onChangePaginationOption}
|
||||||
/> : null}
|
/> : null}
|
||||||
</div>
|
{isEmpty ? (noDataComponent ?? <DefaultNoData />) : null}
|
||||||
{isEmpty ? (noDataComponent ?? <DefaultNoData />) : null}
|
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { Column } from '@tanstack/react-table';
|
import { Column } from '@tanstack/react-table';
|
||||||
|
import { BiCaretDown, BiCaretUp } from 'react-icons/bi';
|
||||||
import { AscendingIcon, DescendingIcon } from '@/components/Icons';
|
|
||||||
|
|
||||||
interface SortingIconProps<TData> {
|
interface SortingIconProps<TData> {
|
||||||
column: Column<TData>
|
column: Column<TData>
|
||||||
|
@ -9,10 +8,10 @@ interface SortingIconProps<TData> {
|
||||||
function SortingIcon<TData>({ column }: SortingIconProps<TData>) {
|
function SortingIcon<TData>({ column }: SortingIconProps<TData>) {
|
||||||
return (<>
|
return (<>
|
||||||
{{
|
{{
|
||||||
desc: <DescendingIcon size='1rem' />,
|
desc: <BiCaretDown size='1rem' />,
|
||||||
asc: <AscendingIcon size='1rem'/>,
|
asc: <BiCaretUp size='1rem'/>,
|
||||||
}[column.getIsSorted() as string] ??
|
}[column.getIsSorted() as string] ??
|
||||||
<DescendingIcon size='1rem' className='opacity-0 hover:opacity-50' />
|
<BiCaretDown size='1rem' className='opacity-0 hover:opacity-50' />
|
||||||
}
|
}
|
||||||
</>);
|
</>);
|
||||||
}
|
}
|
||||||
|
|
74
rsconcept/frontend/src/components/DataTable/TableBody.tsx
Normal file
74
rsconcept/frontend/src/components/DataTable/TableBody.tsx
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import { Cell, flexRender, Row, Table } from '@tanstack/react-table';
|
||||||
|
|
||||||
|
import { IConditionalStyle } from '.';
|
||||||
|
import SelectRow from './SelectRow';
|
||||||
|
|
||||||
|
interface TableBodyProps<TData> {
|
||||||
|
table: Table<TData>
|
||||||
|
dense?: boolean
|
||||||
|
enableRowSelection?: boolean
|
||||||
|
conditionalRowStyles?: IConditionalStyle<TData>[]
|
||||||
|
onRowClicked?: (rowData: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
||||||
|
onRowDoubleClicked?: (rowData: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableBody<TData>({
|
||||||
|
table, dense,
|
||||||
|
enableRowSelection,
|
||||||
|
conditionalRowStyles,
|
||||||
|
onRowClicked, onRowDoubleClicked
|
||||||
|
}: TableBodyProps<TData>) {
|
||||||
|
function handleRowClicked(row: Row<TData>, event: React.MouseEvent<Element, MouseEvent>) {
|
||||||
|
if (onRowClicked) {
|
||||||
|
onRowClicked(row.original, event);
|
||||||
|
}
|
||||||
|
if (enableRowSelection && row.getCanSelect()) {
|
||||||
|
row.getToggleSelectedHandler()(!row.getIsSelected());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRowStyles(row: Row<TData>) {
|
||||||
|
return ({...conditionalRowStyles!
|
||||||
|
.filter(item => item.when(row.original))
|
||||||
|
.reduce((prev, item) => ({...prev, ...item.style}), {})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tbody>
|
||||||
|
{table.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 key={`select-${row.id}`} className='pl-3 pr-1 border-y'>
|
||||||
|
<SelectRow row={row} />
|
||||||
|
</td> : null}
|
||||||
|
{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 => handleRowClicked(row, event)}
|
||||||
|
onDoubleClick={event => onRowDoubleClicked ? onRowDoubleClicked(row.original, event) : undefined}
|
||||||
|
>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TableBody;
|
24
rsconcept/frontend/src/components/DataTable/TableFooter.tsx
Normal file
24
rsconcept/frontend/src/components/DataTable/TableFooter.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { flexRender, Header, HeaderGroup, Table } from '@tanstack/react-table';
|
||||||
|
|
||||||
|
interface TableFooterProps<TData> {
|
||||||
|
table: Table<TData>
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableFooter<TData>({ table }: TableFooterProps<TData>) {
|
||||||
|
return (
|
||||||
|
<tfoot>
|
||||||
|
{table.getFooterGroups().map(
|
||||||
|
(footerGroup: HeaderGroup<TData>) => (
|
||||||
|
<tr key={footerGroup.id}>
|
||||||
|
{footerGroup.headers.map(
|
||||||
|
(header: Header<TData, unknown>) => (
|
||||||
|
<th key={header.id}>
|
||||||
|
{!header.isPlaceholder ? flexRender(header.column.columnDef.footer, header.getContext()) : null}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tfoot>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TableFooter;
|
56
rsconcept/frontend/src/components/DataTable/TableHeader.tsx
Normal file
56
rsconcept/frontend/src/components/DataTable/TableHeader.tsx
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import { flexRender, Header, HeaderGroup, Table } from '@tanstack/react-table';
|
||||||
|
|
||||||
|
import SelectAll from './SelectAll';
|
||||||
|
import SortingIcon from './SortingIcon';
|
||||||
|
|
||||||
|
interface TableHeaderProps<TData> {
|
||||||
|
table: Table<TData>
|
||||||
|
headPosition?: string
|
||||||
|
enableRowSelection?: boolean
|
||||||
|
enableSorting?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableHeader<TData>({
|
||||||
|
table, headPosition,
|
||||||
|
enableRowSelection, enableSorting
|
||||||
|
}: TableHeaderProps<TData>) {
|
||||||
|
return (
|
||||||
|
<thead
|
||||||
|
className={`clr-app shadow-border`}
|
||||||
|
style={{
|
||||||
|
top: headPosition,
|
||||||
|
position: 'sticky'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{table.getHeaderGroups().map(
|
||||||
|
(headerGroup: HeaderGroup<TData>) => (
|
||||||
|
<tr key={headerGroup.id}>
|
||||||
|
{enableRowSelection ?
|
||||||
|
<th className='pl-3 pr-1'>
|
||||||
|
<SelectAll table={table} />
|
||||||
|
</th> : null}
|
||||||
|
{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 ? (
|
||||||
|
<div className='flex gap-1'>
|
||||||
|
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
|
{(enableSorting && header.column.getCanSort()) ? <SortingIcon column={header.column} /> : null}
|
||||||
|
</div>) : null}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TableHeader;
|
|
@ -1,14 +1,19 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
import { useConceptTheme } from '@/context/ThemeContext';
|
||||||
import { urls } from '@/utils/constants';
|
import { urls } from '@/utils/constants';
|
||||||
|
|
||||||
import TextURL from './Common/TextURL';
|
import TextURL from './Common/TextURL';
|
||||||
|
|
||||||
function Footer() {
|
function Footer() {
|
||||||
|
const { noNavigation, noFooter } = useConceptTheme();
|
||||||
|
if (noNavigation || noFooter) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<footer tabIndex={-1}
|
<footer tabIndex={-1}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'w-full z-navigation',
|
'z-navigation',
|
||||||
'px-4 py-2 flex flex-col items-center gap-1',
|
'px-4 py-2 flex flex-col items-center gap-1',
|
||||||
'text-sm select-none whitespace-nowrap'
|
'text-sm select-none whitespace-nowrap'
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { EducationIcon, GroupIcon,SubscribedIcon } from '@/components/Icons';
|
import { BiCheckShield, BiShareAlt } from 'react-icons/bi';
|
||||||
|
import { FiBell } from 'react-icons/fi';
|
||||||
|
|
||||||
function HelpLibrary() {
|
function HelpLibrary() {
|
||||||
return (
|
return (
|
||||||
|
@ -9,15 +10,15 @@ function HelpLibrary() {
|
||||||
<p>На текущем этапе происходит наполнение Библиотеки концептуальными схемами.</p>
|
<p>На текущем этапе происходит наполнение Библиотеки концептуальными схемами.</p>
|
||||||
<p>Поиск осуществлеяется с помощью инструментов в верхней части страницы.</p>
|
<p>Поиск осуществлеяется с помощью инструментов в верхней части страницы.</p>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<SubscribedIcon size='1rem'/>
|
<FiBell size='1rem'/>
|
||||||
<p>Аттрибут <b>отслеживаемая</b> обозначает отслеживание схемы.</p>
|
<p>Аттрибут <b>отслеживаемая</b> обозначает отслеживание схемы.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<GroupIcon size='1rem'/>
|
<BiShareAlt size='1rem'/>
|
||||||
<p>Аттрибут <b>общедоступная</b> делает схему видимой в разделе библиотека.</p>
|
<p>Аттрибут <b>общедоступная</b> делает схему видимой в разделе библиотека.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<EducationIcon size='1rem'/>
|
<BiCheckShield size='1rem'/>
|
||||||
<p>Аттрибут <b>неизменная</b> выделяет стандартные схемы.</p>
|
<p>Аттрибут <b>неизменная</b> выделяет стандартные схемы.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
|
|
|
@ -27,62 +27,6 @@ function IconSVG({ viewbox, size = '1.5rem', className, props, children }: IconS
|
||||||
</svg>);
|
</svg>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SubscribedIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
|
||||||
<path d='M19 13.586V10c0-3.217-2.185-5.927-5.145-6.742C13.562 2.52 12.846 2 12 2s-1.562.52-1.855 1.258C7.185 4.074 5 6.783 5 10v3.586l-1.707 1.707A.996.996 0 003 16v2a1 1 0 001 1h16a1 1 0 001-1v-2a.996.996 0 00-.293-.707L19 13.586zM19 17H5v-.586l1.707-1.707A.996.996 0 007 14v-4c0-2.757 2.243-5 5-5s5 2.243 5 5v4c0 .266.105.52.293.707L19 16.414V17zm-7 5a2.98 2.98 0 002.818-2H9.182A2.98 2.98 0 0012 22z' />
|
|
||||||
</IconSVG>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NotSubscribedIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
|
||||||
<path d='M12 22a2.98 2.98 0 002.818-2H9.182A2.98 2.98 0 0012 22zm9-4v-2a.996.996 0 00-.293-.707L19 13.586V10c0-3.217-2.185-5.927-5.145-6.742C13.562 2.52 12.846 2 12 2s-1.562.52-1.855 1.258c-1.323.364-2.463 1.128-3.346 2.127L3.707 2.293 2.293 3.707l18 18 1.414-1.414-1.362-1.362A.993.993 0 0021 18zM12 5c2.757 0 5 2.243 5 5v4c0 .266.105.52.293.707L19 16.414V17h-.586L8.207 6.793C9.12 5.705 10.471 5 12 5zm-5.293 9.707A.996.996 0 007 14v-2.879L5.068 9.189C5.037 9.457 5 9.724 5 10v3.586l-1.707 1.707A.996.996 0 003 16v2a1 1 0 001 1h10.879l-2-2H5v-.586l1.707-1.707z'/>
|
|
||||||
</IconSVG>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ASTNetworkIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
|
||||||
<path d='M12 1a2.5 2.5 0 00-2.5 2.5A2.5 2.5 0 0011 5.79V7H7a2 2 0 00-2 2v.71A2.5 2.5 0 003.5 12 2.5 2.5 0 005 14.29V15H4a2 2 0 00-2 2v1.21A2.5 2.5 0 00.5 20.5 2.5 2.5 0 003 23a2.5 2.5 0 002.5-2.5A2.5 2.5 0 004 18.21V17h4v1.21a2.5 2.5 0 00-1.5 2.29A2.5 2.5 0 009 23a2.5 2.5 0 002.5-2.5 2.5 2.5 0 00-1.5-2.29V17a2 2 0 00-2-2H7v-.71A2.5 2.5 0 008.5 12 2.5 2.5 0 007 9.71V9h10v.71A2.5 2.5 0 0015.5 12a2.5 2.5 0 001.5 2.29V15h-1a2 2 0 00-2 2v1.21a2.5 2.5 0 00-1.5 2.29A2.5 2.5 0 0015 23a2.5 2.5 0 002.5-2.5 2.5 2.5 0 00-1.5-2.29V17h4v1.21a2.5 2.5 0 00-1.5 2.29A2.5 2.5 0 0021 23a2.5 2.5 0 002.5-2.5 2.5 2.5 0 00-1.5-2.29V17a2 2 0 00-2-2h-1v-.71A2.5 2.5 0 0020.5 12 2.5 2.5 0 0019 9.71V9a2 2 0 00-2-2h-4V5.79a2.5 2.5 0 001.5-2.29A2.5 2.5 0 0012 1m0 1.5a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1M6 11a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1m12 0a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1M3 19.5a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1m6 0a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1m6 0a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1m6 0a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1z'/>
|
|
||||||
</IconSVG>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GroupIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
|
||||||
<path d='M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z' />
|
|
||||||
</IconSVG>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ShareIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
|
||||||
<path d='M5.5 15a3.51 3.51 0 002.36-.93l6.26 3.58a3.06 3.06 0 00-.12.85 3.53 3.53 0 101.14-2.57l-6.26-3.58a2.74 2.74 0 00.12-.76l6.15-3.52A3.49 3.49 0 1014 5.5a3.35 3.35 0 00.12.85L8.43 9.6A3.5 3.5 0 105.5 15zm12 2a1.5 1.5 0 11-1.5 1.5 1.5 1.5 0 011.5-1.5zm0-13A1.5 1.5 0 1116 5.5 1.5 1.5 0 0117.5 4zm-12 6A1.5 1.5 0 114 11.5 1.5 1.5 0 015.5 10z' />
|
|
||||||
</IconSVG>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SortIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
|
||||||
<path d='M8 16H4l6 6V2H8zm6-11v17h2V8h4l-6-6z' />
|
|
||||||
</IconSVG>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function UserIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<IconSVG viewbox='0 0 512 512' {...props}>
|
|
||||||
<path d='M399 384.2c-22.1-38.4-63.6-64.2-111-64.2h-64c-47.4 0-88.9 25.8-111 64.2 35.2 39.2 86.2 63.8 143 63.8s107.8-24.7 143-63.8zM512 256c0 141.4-114.6 256-256 256S0 397.4 0 256 114.6 0 256 0s256 114.6 256 256zm-256 16c39.8 0 72-32.2 72-72s-32.2-72-72-72-72 32.2-72 72 32.2 72 72 72z' />
|
|
||||||
</IconSVG>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function EducationIcon(props: IconProps) {
|
export function EducationIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||||
|
@ -91,56 +35,6 @@ export function EducationIcon(props: IconProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LibraryIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<IconSVG viewbox='0 0 512 512' {...props}>
|
|
||||||
<path d='M64 480H48a32 32 0 01-32-32V112a32 32 0 0132-32h16a32 32 0 0132 32v336a32 32 0 01-32 32zM240 176a32 32 0 00-32-32h-64a32 32 0 00-32 32v28a4 4 0 004 4h120a4 4 0 004-4zM112 448a32 32 0 0032 32h64a32 32 0 0032-32v-30a2 2 0 00-2-2H114a2 2 0 00-2 2z' />
|
|
||||||
<path d='M114 240 H238 A2 2 0 0 1 240 242 V382 A2 2 0 0 1 238 384 H114 A2 2 0 0 1 112 382 V242 A2 2 0 0 1 114 240 z' />
|
|
||||||
<path d='M320 480h-32a32 32 0 01-32-32V64a32 32 0 0132-32h32a32 32 0 0132 32v384a32 32 0 01-32 32zM495.89 445.45l-32.23-340c-1.48-15.65-16.94-27-34.53-25.31l-31.85 3c-17.59 1.67-30.65 15.71-29.17 31.36l32.23 340c1.48 15.65 16.94 27 34.53 25.31l31.85-3c17.59-1.67 30.65-15.71 29.17-31.36z' />
|
|
||||||
</IconSVG>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PlusIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<IconSVG viewbox='0 0 1024 1024' {...props}>
|
|
||||||
<path d='M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM704 536c0 4.4-3.6 8-8 8H544v152c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V544H328c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h152V328c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v152h152c4.4 0 8 3.6 8 8v48z' />
|
|
||||||
</IconSVG>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ArrowLeftIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
|
||||||
<path d='M12.707 17.293L8.414 13H18v-2H8.414l4.293-4.293-1.414-1.414L4.586 12l6.707 6.707z' />
|
|
||||||
</IconSVG>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ArrowRightIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
|
||||||
<path d='M11.293 17.293l1.414 1.414L19.414 12l-6.707-6.707-1.414 1.414L15.586 11H6v2h9.586z' />
|
|
||||||
</IconSVG>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LetterAIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
|
||||||
<path d='M11.307 4l-6 16h2.137l1.875-5h6.363l1.875 5h2.137l-6-16h-2.387zm-1.239 9L12.5 6.515 14.932 13h-4.864z' />
|
|
||||||
</IconSVG>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LetterALinesIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
|
||||||
<path d='M15 4h7v2h-7zm1 4h6v2h-6zm2 4h4v2h-4zM9.307 4l-6 16h2.137l1.875-5h6.363l1.875 5h2.137l-6-16H9.307zm-1.239 9L10.5 6.515 12.932 13H8.068z' />
|
|
||||||
</IconSVG>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function InDoorIcon(props: IconProps) {
|
export function InDoorIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
|
@ -150,22 +44,6 @@ export function InDoorIcon(props: IconProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
export function CheckboxCheckedIcon() {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
|
|
|
@ -23,7 +23,7 @@ function DescribeError({error} : {error: ErrorData}) {
|
||||||
}
|
}
|
||||||
if (error.response.status === 404) {
|
if (error.response.status === 404) {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col justify-start'>
|
<div>
|
||||||
<p>{'Обращение к несуществующему API'}</p>
|
<p>{'Обращение к несуществующему API'}</p>
|
||||||
<PrettyJson data={error} />
|
<PrettyJson data={error} />
|
||||||
</div>);
|
</div>);
|
||||||
|
@ -32,7 +32,7 @@ function DescribeError({error} : {error: ErrorData}) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
||||||
const isHtml = isResponseHtml(error.response);
|
const isHtml = isResponseHtml(error.response);
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col justify-start'>
|
<div>
|
||||||
<p className='underline'>Ошибка</p>
|
<p className='underline'>Ошибка</p>
|
||||||
<p>{error.message}</p>
|
<p>{error.message}</p>
|
||||||
{error.response.data && (<>
|
{error.response.data && (<>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { FaSquarePlus } from 'react-icons/fa6';
|
||||||
|
import { IoLibrary } from 'react-icons/io5';
|
||||||
|
|
||||||
import { EducationIcon, LibraryIcon, PlusIcon } from '@/components/Icons';
|
import { EducationIcon } from '@/components/Icons';
|
||||||
import { useConceptNavigation } from '@/context/NagivationContext';
|
import { useConceptNavigation } from '@/context/NagivationContext';
|
||||||
import { useConceptTheme } from '@/context/ThemeContext';
|
import { useConceptTheme } from '@/context/ThemeContext';
|
||||||
|
|
||||||
|
@ -41,13 +43,13 @@ function Navigation () {
|
||||||
<NavigationButton
|
<NavigationButton
|
||||||
text='Новая схема'
|
text='Новая схема'
|
||||||
description='Создать новую схему'
|
description='Создать новую схему'
|
||||||
icon={<PlusIcon />}
|
icon={<FaSquarePlus size='1.5rem' />}
|
||||||
onClick={navigateCreateNew}
|
onClick={navigateCreateNew}
|
||||||
/>
|
/>
|
||||||
<NavigationButton
|
<NavigationButton
|
||||||
text='Библиотека'
|
text='Библиотека'
|
||||||
description='Библиотека концептуальных схем'
|
description='Библиотека концептуальных схем'
|
||||||
icon={<LibraryIcon />}
|
icon={<IoLibrary size='1.5rem' />}
|
||||||
onClick={navigateLibrary}
|
onClick={navigateLibrary}
|
||||||
/>
|
/>
|
||||||
<NavigationButton
|
<NavigationButton
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { InDoorIcon, UserIcon } from '@/components/Icons';
|
import { FaCircleUser } from 'react-icons/fa6';
|
||||||
|
|
||||||
|
import { InDoorIcon } from '@/components/Icons';
|
||||||
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';
|
||||||
|
@ -24,7 +26,7 @@ function UserMenu() {
|
||||||
{user ?
|
{user ?
|
||||||
<NavigationButton
|
<NavigationButton
|
||||||
description={`Пользователь ${user?.username}`}
|
description={`Пользователь ${user?.username}`}
|
||||||
icon={<UserIcon />}
|
icon={<FaCircleUser size='1.5rem' />}
|
||||||
onClick={menu.toggle}
|
onClick={menu.toggle}
|
||||||
/> : null}
|
/> : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -87,23 +87,20 @@ function ConstituentaPicker({
|
||||||
value={filterText}
|
value={filterText}
|
||||||
onChange={newValue => setFilterText(newValue)}
|
onChange={newValue => setFilterText(newValue)}
|
||||||
/>
|
/>
|
||||||
<div
|
<DataTable dense noHeader noFooter
|
||||||
className='overflow-y-auto text-sm border select-none'
|
className='overflow-y-auto text-sm border select-none'
|
||||||
style={{ maxHeight: size, minHeight: size }}
|
style={{ maxHeight: size, minHeight: size }}
|
||||||
>
|
data={filteredData}
|
||||||
<DataTable dense noHeader noFooter
|
columns={columns}
|
||||||
data={filteredData}
|
conditionalRowStyles={conditionalRowStyles}
|
||||||
columns={columns}
|
noDataComponent={
|
||||||
conditionalRowStyles={conditionalRowStyles}
|
<span className='p-2 min-h-[5rem] flex flex-col justify-center text-center'>
|
||||||
noDataComponent={
|
<p>Список конституент пуст</p>
|
||||||
<span className='p-2 min-h-[5rem] flex flex-col justify-center text-center'>
|
<p>Измените параметры фильтра</p>
|
||||||
<p>Список конституент пуст</p>
|
</span>
|
||||||
<p>Измените параметры фильтра</p>
|
}
|
||||||
</span>
|
onRowClicked={onSelectValue}
|
||||||
}
|
/>
|
||||||
onRowClicked={onSelectValue}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,29 +149,28 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col gap-3'>
|
<div className='flex flex-col gap-3'>
|
||||||
<div className={clsx(
|
<DataTable dense noFooter
|
||||||
'max-h-[5.8rem] min-h-[5.8rem]',
|
className={clsx(
|
||||||
'overflow-y-auto',
|
'max-h-[5.8rem] min-h-[5.8rem]',
|
||||||
'text-sm',
|
'overflow-y-auto',
|
||||||
'border',
|
'text-sm',
|
||||||
'select-none'
|
'border',
|
||||||
)}>
|
'select-none'
|
||||||
<DataTable dense noFooter
|
)}
|
||||||
data={state.arguments}
|
data={state.arguments}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
conditionalRowStyles={conditionalRowStyles}
|
conditionalRowStyles={conditionalRowStyles}
|
||||||
noDataComponent={
|
noDataComponent={
|
||||||
<p className={clsx(
|
<p className={clsx(
|
||||||
'min-h-[3.6rem] w-full',
|
'min-h-[3.6rem] w-full',
|
||||||
'p-2',
|
'p-2',
|
||||||
'text-center'
|
'text-center'
|
||||||
)}>
|
)}>
|
||||||
Аргументы отсутствуют
|
Аргументы отсутствуют
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
onRowClicked={handleSelectArgument}
|
onRowClicked={handleSelectArgument}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
'py-1 flex gap-2 justify-center items-center',
|
'py-1 flex gap-2 justify-center items-center',
|
||||||
|
@ -192,7 +191,7 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
||||||
<div className='flex'>
|
<div className='flex'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Подставить значение аргумента'
|
tooltip='Подставить значение аргумента'
|
||||||
icon={<BiCheck size='1.25rem' className={!argumentValue || !selectedArgument ? 'text-disabled' : 'clr-text-success'} />}
|
icon={<BiCheck size='1.25rem' className={!!argumentValue && !!selectedArgument ? 'clr-text-success' : ''} />}
|
||||||
disabled={!argumentValue || !selectedArgument}
|
disabled={!argumentValue || !selectedArgument}
|
||||||
onClick={() => handleAssignArgument(selectedArgument!, argumentValue)}
|
onClick={() => handleAssignArgument(selectedArgument!, argumentValue)}
|
||||||
/>
|
/>
|
||||||
|
@ -205,7 +204,7 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Очистить значение аргумента'
|
tooltip='Очистить значение аргумента'
|
||||||
disabled={!selectedClearable}
|
disabled={!selectedClearable}
|
||||||
icon={<BiX size='1.25rem' className={!selectedClearable ? 'text-disabled' : 'clr-text-warning'}/>}
|
icon={<BiX size='1.25rem' className={selectedClearable ? 'clr-text-warning': ''}/>}
|
||||||
onClick={() => selectedArgument ? handleClearArgument(selectedArgument) : undefined}
|
onClick={() => selectedArgument ? handleClearArgument(selectedArgument) : undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -76,24 +76,22 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
|
||||||
</div>
|
</div>
|
||||||
</TabList>
|
</TabList>
|
||||||
|
|
||||||
<div className='w-full'>
|
<TabPanel>
|
||||||
<TabPanel>
|
<EntityTab
|
||||||
<EntityTab
|
initial={initial}
|
||||||
initial={initial}
|
items={items}
|
||||||
items={items}
|
setReference={setReference}
|
||||||
setReference={setReference}
|
setIsValid={setIsValid}
|
||||||
setIsValid={setIsValid}
|
/>
|
||||||
/>
|
</TabPanel>
|
||||||
</TabPanel>
|
|
||||||
|
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<SyntacticTab
|
<SyntacticTab
|
||||||
initial={initial}
|
initial={initial}
|
||||||
setReference={setReference}
|
setReference={setReference}
|
||||||
setIsValid={setIsValid}
|
setIsValid={setIsValid}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</div>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Modal>);
|
</Modal>);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useLayoutEffect, useState } from 'react';
|
import { useLayoutEffect, useState } from 'react';
|
||||||
import { BiCheck, BiChevronsDown } from 'react-icons/bi';
|
import { BiCheck, BiChevronsDown, BiLeftArrow, BiRightArrow, BiX } from 'react-icons/bi';
|
||||||
|
|
||||||
import Label from '@/components/Common/Label';
|
import Label from '@/components/Common/Label';
|
||||||
import MiniButton from '@/components/Common/MiniButton';
|
import MiniButton from '@/components/Common/MiniButton';
|
||||||
|
@ -10,7 +10,6 @@ import Modal from '@/components/Common/Modal';
|
||||||
import Overlay from '@/components/Common/Overlay';
|
import Overlay from '@/components/Common/Overlay';
|
||||||
import TextArea from '@/components/Common/TextArea';
|
import TextArea from '@/components/Common/TextArea';
|
||||||
import HelpButton from '@/components/Help/HelpButton';
|
import HelpButton from '@/components/Help/HelpButton';
|
||||||
import { ArrowLeftIcon, ArrowRightIcon } from '@/components/Icons';
|
|
||||||
import SelectGrammeme from '@/components/Shared/SelectGrammeme';
|
import SelectGrammeme from '@/components/Shared/SelectGrammeme';
|
||||||
import useConceptText from '@/hooks/useConceptText';
|
import useConceptText from '@/hooks/useConceptText';
|
||||||
import { Grammeme, ITextRequest, IWordForm, IWordFormPlain } from '@/models/language';
|
import { Grammeme, ITextRequest, IWordForm, IWordFormPlain } from '@/models/language';
|
||||||
|
@ -118,6 +117,10 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleResetAll() {
|
||||||
|
setForms([]);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal canSubmit
|
<Modal canSubmit
|
||||||
title='Редактирование словоформ'
|
title='Редактирование словоформ'
|
||||||
|
@ -150,22 +153,22 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
||||||
value={inputText}
|
value={inputText}
|
||||||
onChange={event => setInputText(event.target.value)}
|
onChange={event => setInputText(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<div className='max-w-min'>
|
<div className='flex flex-col gap-1'>
|
||||||
<MiniButton
|
|
||||||
tooltip='Генерировать словоформу'
|
|
||||||
icon={<ArrowLeftIcon size='1.25rem' className={inputGrams.length == 0 ? 'text-disabled' : 'clr-text-primary'} />}
|
|
||||||
disabled={textProcessor.loading || inputGrams.length == 0}
|
|
||||||
onClick={handleInflect}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Определить граммемы'
|
tooltip='Определить граммемы'
|
||||||
icon={<ArrowRightIcon
|
icon={<BiRightArrow
|
||||||
size='1.25rem'
|
size='1.25rem'
|
||||||
className={!inputText ? 'text-disabled' : 'clr-text-primary'}
|
className={inputText ? 'clr-text-primary' : ''}
|
||||||
/>}
|
/>}
|
||||||
disabled={textProcessor.loading || !inputText}
|
disabled={textProcessor.loading || !inputText}
|
||||||
onClick={handleParse}
|
onClick={handleParse}
|
||||||
/>
|
/>
|
||||||
|
<MiniButton
|
||||||
|
tooltip='Генерировать словоформу'
|
||||||
|
icon={<BiLeftArrow size='1.25rem' className={inputGrams.length !== 0 ? 'clr-text-primary' : ''} />}
|
||||||
|
disabled={textProcessor.loading || inputGrams.length == 0}
|
||||||
|
onClick={handleInflect}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SelectGrammeme
|
<SelectGrammeme
|
||||||
|
@ -182,14 +185,14 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
||||||
tooltip='Внести словоформу'
|
tooltip='Внести словоформу'
|
||||||
icon={<BiCheck
|
icon={<BiCheck
|
||||||
size='1.25rem'
|
size='1.25rem'
|
||||||
className={!inputText || inputGrams.length == 0 ? 'text-disabled' : 'clr-text-success'}
|
className={inputText && inputGrams.length !== 0 ? 'clr-text-success' : ''}
|
||||||
/>}
|
/>}
|
||||||
disabled={textProcessor.loading || !inputText || inputGrams.length == 0}
|
disabled={textProcessor.loading || !inputText || inputGrams.length == 0}
|
||||||
onClick={handleAddForm}
|
onClick={handleAddForm}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Генерировать стандартные словоформы'
|
tooltip='Генерировать стандартные словоформы'
|
||||||
icon={<BiChevronsDown size='1.25rem' className={!inputText ? 'text-disabled' : 'clr-text-primary'}
|
icon={<BiChevronsDown size='1.25rem' className={inputText ? 'clr-text-primary' : ''}
|
||||||
/>}
|
/>}
|
||||||
disabled={textProcessor.loading || !inputText}
|
disabled={textProcessor.loading || !inputText}
|
||||||
onClick={handleGenerateLexeme}
|
onClick={handleGenerateLexeme}
|
||||||
|
@ -198,24 +201,23 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
||||||
|
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
'mt-3 mb-2',
|
'mt-3 mb-2',
|
||||||
|
'flex justify-center items-center',
|
||||||
'text-sm text-center font-semibold'
|
'text-sm text-center font-semibold'
|
||||||
)}>
|
)}>
|
||||||
Заданные вручную словоформы [{forms.length}]
|
<span>Заданные вручную словоформы [{forms.length}]</span>
|
||||||
|
<MiniButton noHover
|
||||||
|
tooltip='Сбросить все словоформы'
|
||||||
|
icon={<BiX size='1rem' className={forms.length !== 0 ? 'clr-text-warning' : ''} />}
|
||||||
|
disabled={textProcessor.loading || forms.length === 0}
|
||||||
|
onClick={handleResetAll}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={clsx(
|
|
||||||
'mb-2',
|
|
||||||
'max-h-[17.4rem] min-h-[17.4rem]',
|
|
||||||
'border',
|
|
||||||
'overflow-y-auto'
|
|
||||||
)}>
|
|
||||||
<WordFormsTable
|
<WordFormsTable
|
||||||
forms={forms}
|
forms={forms}
|
||||||
setForms={setForms}
|
setForms={setForms}
|
||||||
onFormSelect={handleSelectForm}
|
onFormSelect={handleSelectForm}
|
||||||
loading={textProcessor.loading}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</Modal>);
|
</Modal>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import clsx from 'clsx';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { BiX } from 'react-icons/bi';
|
import { BiX } from 'react-icons/bi';
|
||||||
|
|
||||||
import MiniButton from '@/components/Common/MiniButton';
|
import MiniButton from '@/components/Common/MiniButton';
|
||||||
import Overlay from '@/components/Common/Overlay';
|
|
||||||
import DataTable, { createColumnHelper } from '@/components/DataTable';
|
import DataTable, { createColumnHelper } from '@/components/DataTable';
|
||||||
import WordFormBadge from '@/components/Shared/WordFormBadge';
|
import WordFormBadge from '@/components/Shared/WordFormBadge';
|
||||||
import { IWordForm } from '@/models/language';
|
import { IWordForm } from '@/models/language';
|
||||||
|
@ -13,12 +13,11 @@ interface WordFormsTableProps {
|
||||||
forms: IWordForm[]
|
forms: IWordForm[]
|
||||||
setForms: React.Dispatch<React.SetStateAction<IWordForm[]>>
|
setForms: React.Dispatch<React.SetStateAction<IWordForm[]>>
|
||||||
onFormSelect?: (form: IWordForm) => void
|
onFormSelect?: (form: IWordForm) => void
|
||||||
loading?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<IWordForm>();
|
const columnHelper = createColumnHelper<IWordForm>();
|
||||||
|
|
||||||
function WordFormsTable({ forms, setForms, onFormSelect, loading }: WordFormsTableProps) {
|
function WordFormsTable({ forms, setForms, onFormSelect }: WordFormsTableProps) {
|
||||||
const handleDeleteRow = useCallback(
|
const handleDeleteRow = useCallback(
|
||||||
(row: number) => {
|
(row: number) => {
|
||||||
setForms(
|
setForms(
|
||||||
|
@ -34,10 +33,6 @@ function WordFormsTable({ forms, setForms, onFormSelect, loading }: WordFormsTab
|
||||||
});
|
});
|
||||||
}, [setForms]);
|
}, [setForms]);
|
||||||
|
|
||||||
function handleResetAll() {
|
|
||||||
setForms([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
columnHelper.accessor('text', {
|
columnHelper.accessor('text', {
|
||||||
|
@ -68,35 +63,31 @@ function WordFormsTable({ forms, setForms, onFormSelect, loading }: WordFormsTab
|
||||||
cell: props =>
|
cell: props =>
|
||||||
<MiniButton noHover
|
<MiniButton noHover
|
||||||
tooltip='Удалить словоформу'
|
tooltip='Удалить словоформу'
|
||||||
icon={<BiX size='1rem' className='text-warning'/>}
|
icon={<BiX size='1rem' className='clr-text-warning'/>}
|
||||||
onClick={() => handleDeleteRow(props.row.index)}
|
onClick={() => handleDeleteRow(props.row.index)}
|
||||||
/>
|
/>
|
||||||
})
|
})
|
||||||
], [handleDeleteRow]);
|
], [handleDeleteRow]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<DataTable dense noFooter
|
||||||
<Overlay position='top-1 right-4'>
|
className={clsx(
|
||||||
<MiniButton
|
'mb-2',
|
||||||
tooltip='Сбросить все словоформы'
|
'max-h-[17.4rem] min-h-[17.4rem]',
|
||||||
icon={<BiX size='1rem' className={forms.length === 0 ? 'text-disabled' : 'text-warning'} />}
|
'border',
|
||||||
disabled={loading || forms.length === 0}
|
'overflow-y-auto'
|
||||||
onClick={handleResetAll}
|
)}
|
||||||
/>
|
data={forms}
|
||||||
</Overlay>
|
columns={columns}
|
||||||
<DataTable dense noFooter
|
headPosition='0'
|
||||||
data={forms}
|
noDataComponent={
|
||||||
columns={columns}
|
<span className='p-2 text-center min-h-[2rem]'>
|
||||||
headPosition='0'
|
<p>Список пуст</p>
|
||||||
noDataComponent={
|
<p>Добавьте словоформу</p>
|
||||||
<span className='p-2 text-center min-h-[2rem]'>
|
</span>
|
||||||
<p>Список пуст</p>
|
}
|
||||||
<p>Добавьте словоформу</p>
|
onRowClicked={onFormSelect}
|
||||||
</span>
|
/>);
|
||||||
}
|
|
||||||
onRowClicked={onFormSelect}
|
|
||||||
/>
|
|
||||||
</>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WordFormsTable;
|
export default WordFormsTable;
|
|
@ -39,7 +39,7 @@ export function inferTemplatedType(templateType: CstType, args: IArgumentValue[]
|
||||||
* closing bracket ']' to determine the head and body parts.
|
* closing bracket ']' to determine the head and body parts.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const template = "[header] body content";
|
* const template = '[header] body content';
|
||||||
* const result = splitTemplateDefinition(template);
|
* const result = splitTemplateDefinition(template);
|
||||||
* // result: `{ head: 'header', body: 'body content' }`
|
* // result: `{ head: 'header', body: 'body content' }`
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -127,7 +127,7 @@ function CreateRSFormPage() {
|
||||||
value={common}
|
value={common}
|
||||||
setValue={value => setCommon(value ?? false)}
|
setValue={value => setCommon(value ?? false)}
|
||||||
/>
|
/>
|
||||||
<div className='flex items-center justify-around py-2'>
|
<div className='flex justify-around gap-6 py-3'>
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
text='Создать схему'
|
text='Создать схему'
|
||||||
loading={processing}
|
loading={processing}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import clsx from 'clsx';
|
|
||||||
import { useLayoutEffect } from 'react';
|
import { useLayoutEffect } from 'react';
|
||||||
|
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/context/AuthContext';
|
||||||
|
@ -22,10 +21,7 @@ function HomePage() {
|
||||||
}, [router, user])
|
}, [router, user])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx(
|
<div className='flex flex-col items-center justify-center px-4 py-2'>
|
||||||
'w-full',
|
|
||||||
'px-4 py-2 flex flex-col justify-center items-center'
|
|
||||||
)}>
|
|
||||||
{user?.is_staff ?
|
{user?.is_staff ?
|
||||||
<p>
|
<p>
|
||||||
Лендинг находится в разработке. Данная страница видна только пользователям с правами администратора.
|
Лендинг находится в разработке. Данная страница видна только пользователям с правами администратора.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { BiCheckShield, BiShareAlt } from 'react-icons/bi';
|
||||||
|
import { FiBell } from 'react-icons/fi';
|
||||||
|
|
||||||
import { EducationIcon, GroupIcon, SubscribedIcon } from '@/components/Icons';
|
|
||||||
import { ICurrentUser, ILibraryItem } from '@/models/library';
|
import { ICurrentUser, ILibraryItem } from '@/models/library';
|
||||||
import { prefixes } from '@/utils/constants';
|
import { prefixes } from '@/utils/constants';
|
||||||
|
|
||||||
|
@ -20,15 +21,15 @@ function ItemIcons({ user, item }: ItemIconsProps) {
|
||||||
>
|
>
|
||||||
{(user && user.subscriptions.includes(item.id)) ?
|
{(user && user.subscriptions.includes(item.id)) ?
|
||||||
<span title='Отслеживаемая'>
|
<span title='Отслеживаемая'>
|
||||||
<SubscribedIcon size='0.75rem' />
|
<FiBell size='0.75rem' />
|
||||||
</span> : null}
|
</span> : null}
|
||||||
{item.is_common ?
|
{item.is_common ?
|
||||||
<span title='Общедоступная'>
|
<span title='Общедоступная'>
|
||||||
<GroupIcon size='0.75rem'/>
|
<BiShareAlt size='0.75rem'/>
|
||||||
</span> : null}
|
</span> : null}
|
||||||
{item.is_canonical ?
|
{item.is_canonical ?
|
||||||
<span title='Неизменная'>
|
<span title='Неизменная'>
|
||||||
<EducationIcon size='0.75rem'/>
|
<BiCheckShield size='0.75rem'/>
|
||||||
</span> : null}
|
</span> : null}
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ function SearchPanel({ total, filtered, query, setQuery, strategy, setFilter }:
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
'sticky top-0',
|
'sticky top-0',
|
||||||
'w-full max-h-[2.3rem]',
|
'w-full max-h-[2.3rem]',
|
||||||
'pr-40 flex justify-start items-stretch',
|
'pr-40 flex items-stretch',
|
||||||
'border-b',
|
'border-b',
|
||||||
'clr-input'
|
'clr-input'
|
||||||
)}>
|
)}>
|
||||||
|
@ -61,7 +61,7 @@ function SearchPanel({ total, filtered, query, setQuery, strategy, setFilter }:
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
'w-full',
|
'flex-grow',
|
||||||
'flex gap-1 justify-center items-center'
|
'flex gap-1 justify-center items-center'
|
||||||
)}>
|
)}>
|
||||||
<ConceptSearch noBorder
|
<ConceptSearch noBorder
|
||||||
|
|
|
@ -87,14 +87,13 @@ function LoginPage() {
|
||||||
onChange={event => setPassword(event.target.value)}
|
onChange={event => setPassword(event.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='flex justify-center w-full py-2'>
|
<SubmitButton
|
||||||
<SubmitButton
|
text='Войти'
|
||||||
text='Войти'
|
dimensions='w-[12rem] mt-3'
|
||||||
dimensions='w-[12rem]'
|
className='self-center'
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={!username || !password}
|
disabled={!username || !password}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className='flex flex-col text-sm'>
|
<div className='flex flex-col text-sm'>
|
||||||
<TextURL text='Восстановить пароль...' href='/restore-password' />
|
<TextURL text='Восстановить пароль...' href='/restore-password' />
|
||||||
<TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' />
|
<TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' />
|
||||||
|
|
|
@ -7,7 +7,7 @@ interface ViewTopicProps {
|
||||||
|
|
||||||
function ViewTopic({ topic }: ViewTopicProps) {
|
function ViewTopic({ topic }: ViewTopicProps) {
|
||||||
return (
|
return (
|
||||||
<div className='w-full px-2 py-2 max-w-[80rem]'>
|
<div className='px-2 py-2 max-w-[80rem]'>
|
||||||
<InfoTopic topic={topic}/>
|
<InfoTopic topic={topic}/>
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { BiDiamond, BiDuplicate, BiPlusCircle, BiReset, BiTrash } from 'react-icons/bi';
|
import { BiDuplicate, BiPlusCircle, BiReset, BiTrash } from 'react-icons/bi';
|
||||||
import { FiSave } from "react-icons/fi";
|
import { FiSave } from 'react-icons/fi';
|
||||||
|
|
||||||
import MiniButton from '@/components/Common/MiniButton';
|
import MiniButton from '@/components/Common/MiniButton';
|
||||||
import Overlay from '@/components/Common/Overlay';
|
import Overlay from '@/components/Common/Overlay';
|
||||||
|
@ -19,17 +19,16 @@ interface ConstituentaToolbarProps {
|
||||||
onDelete: () => void
|
onDelete: () => void
|
||||||
onClone: () => void
|
onClone: () => void
|
||||||
onCreate: () => void
|
onCreate: () => void
|
||||||
onTemplates: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ConstituentaToolbar({
|
function ConstituentaToolbar({
|
||||||
isMutable, isModified,
|
isMutable, isModified,
|
||||||
onSubmit, onReset,
|
onSubmit, onReset,
|
||||||
onDelete, onClone, onCreate, onTemplates
|
onDelete, onClone, onCreate
|
||||||
}: ConstituentaToolbarProps) {
|
}: ConstituentaToolbarProps) {
|
||||||
const canSave = useMemo(() => (isModified && isMutable), [isModified, isMutable]);
|
const canSave = useMemo(() => (isModified && isMutable), [isModified, isMutable]);
|
||||||
return (
|
return (
|
||||||
<Overlay position='right-1/2 translate-x-1/2 top-1 flex items-start'>
|
<Overlay position='top-1 right-1/2 translate-x-1/2' className='flex'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Сохранить изменения [Ctrl + S]'
|
tooltip='Сохранить изменения [Ctrl + S]'
|
||||||
disabled={!canSave}
|
disabled={!canSave}
|
||||||
|
@ -54,12 +53,6 @@ function ConstituentaToolbar({
|
||||||
onClick={onClone}
|
onClick={onClone}
|
||||||
icon={<BiDuplicate size='1.25rem' className={isMutable ? 'clr-text-success' : ''} />}
|
icon={<BiDuplicate size='1.25rem' className={isMutable ? 'clr-text-success' : ''} />}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
|
||||||
tooltip='Создать конституенту из шаблона [Alt + E]'
|
|
||||||
icon={<BiDiamond className={isMutable ? 'clr-text-primary': ''} size={'1.25rem'}/>}
|
|
||||||
disabled={!isMutable}
|
|
||||||
onClick={onTemplates}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Удалить редактируемую конституенту'
|
tooltip='Удалить редактируемую конституенту'
|
||||||
disabled={!isMutable}
|
disabled={!isMutable}
|
||||||
|
|
|
@ -30,12 +30,11 @@ interface EditorConstituentaProps {
|
||||||
onRenameCst: (initial: ICstRenameData) => void
|
onRenameCst: (initial: ICstRenameData) => void
|
||||||
onEditTerm: () => void
|
onEditTerm: () => void
|
||||||
onDeleteCst: (selected: number[], callback?: (items: number[]) => void) => void
|
onDeleteCst: (selected: number[], callback?: (items: number[]) => void) => void
|
||||||
onTemplates: (insertAfter?: number) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorConstituenta({
|
function EditorConstituenta({
|
||||||
isMutable, isModified, setIsModified, activeID, activeCst, onEditTerm,
|
isMutable, isModified, setIsModified, activeID, activeCst, onEditTerm,
|
||||||
onCreateCst, onRenameCst, onOpenEdit, onDeleteCst, onTemplates
|
onCreateCst, onRenameCst, onOpenEdit, onDeleteCst
|
||||||
}: EditorConstituentaProps) {
|
}: EditorConstituentaProps) {
|
||||||
const windowSize = useWindowSize();
|
const windowSize = useWindowSize();
|
||||||
const { schema } = useRSForm();
|
const { schema } = useRSForm();
|
||||||
|
@ -114,13 +113,13 @@ function EditorConstituenta({
|
||||||
|
|
||||||
function processAltKey(code: string): boolean {
|
function processAltKey(code: string): boolean {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 'KeyE': onTemplates(); return true;
|
|
||||||
case 'KeyV': handleClone(); return true;
|
case 'KeyV': handleClone(); return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<>
|
return (
|
||||||
|
<>
|
||||||
<ConstituentaToolbar
|
<ConstituentaToolbar
|
||||||
isMutable={!disabled}
|
isMutable={!disabled}
|
||||||
isModified={isModified}
|
isModified={isModified}
|
||||||
|
@ -131,34 +130,29 @@ function EditorConstituenta({
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
onClone={handleClone}
|
onClone={handleClone}
|
||||||
onCreate={handleCreate}
|
onCreate={handleCreate}
|
||||||
onTemplates={() => onTemplates(activeID)}
|
|
||||||
/>
|
/>
|
||||||
<div tabIndex={-1}
|
<div tabIndex={-1}
|
||||||
className='max-w-[1500px] flex justify-start w-full'
|
className='flex max-w-[95rem]'
|
||||||
onKeyDown={handleInput}
|
onKeyDown={handleInput}
|
||||||
>
|
>
|
||||||
<div className='min-w-[47.8rem] max-w-[47.8rem] px-4 py-1'>
|
<FormConstituenta disabled={disabled}
|
||||||
<FormConstituenta disabled={disabled}
|
id={globalIDs.constituenta_editor}
|
||||||
id={globalIDs.constituenta_editor}
|
constituenta={activeCst}
|
||||||
constituenta={activeCst}
|
isModified={isModified}
|
||||||
isModified={isModified}
|
toggleReset={toggleReset}
|
||||||
toggleReset={toggleReset}
|
|
||||||
|
|
||||||
setIsModified={setIsModified}
|
setIsModified={setIsModified}
|
||||||
onEditTerm={onEditTerm}
|
onEditTerm={onEditTerm}
|
||||||
onRenameCst={onRenameCst}
|
onRenameCst={onRenameCst}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
{(windowSize.width && windowSize.width >= SIDELIST_HIDE_THRESHOLD) ?
|
{(windowSize.width && windowSize.width >= SIDELIST_HIDE_THRESHOLD) ?
|
||||||
<div className='w-full mt-[2.25rem] border h-fit'>
|
<ViewConstituents
|
||||||
<ViewConstituents
|
schema={schema}
|
||||||
schema={schema}
|
expression={activeCst?.definition_formal ?? ''}
|
||||||
expression={activeCst?.definition_formal ?? ''}
|
baseHeight={UNFOLDED_HEIGHT}
|
||||||
baseHeight={UNFOLDED_HEIGHT}
|
activeID={activeID}
|
||||||
activeID={activeID}
|
onOpenEdit={onOpenEdit}
|
||||||
onOpenEdit={onOpenEdit}
|
/>: null}
|
||||||
/>
|
|
||||||
</div> : null}
|
|
||||||
</div>
|
</div>
|
||||||
</>);
|
</>);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Dispatch, SetStateAction, useLayoutEffect, useState } from 'react';
|
import clsx from 'clsx';
|
||||||
|
import { Dispatch, SetStateAction, useEffect, useLayoutEffect, useState } from 'react';
|
||||||
import { FiSave } from 'react-icons/fi';
|
import { FiSave } from 'react-icons/fi';
|
||||||
import { LiaEdit } from 'react-icons/lia';
|
import { LiaEdit } from 'react-icons/lia';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
@ -45,7 +46,7 @@ function FormConstituenta({
|
||||||
const [convention, setConvention] = useState('');
|
const [convention, setConvention] = useState('');
|
||||||
const [typification, setTypification] = useState('N/A');
|
const [typification, setTypification] = useState('N/A');
|
||||||
|
|
||||||
useLayoutEffect(
|
useEffect(
|
||||||
() => {
|
() => {
|
||||||
if (!constituenta) {
|
if (!constituenta) {
|
||||||
setIsModified(false);
|
setIsModified(false);
|
||||||
|
@ -105,7 +106,10 @@ function FormConstituenta({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<Overlay position='top-0 left-[3rem]' className='flex justify-start select-none' >
|
<Overlay
|
||||||
|
position='top-1 left-[4rem]'
|
||||||
|
className='flex select-none'
|
||||||
|
>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip={`Редактировать словоформы термина: ${constituenta?.term_forms.length ?? 0}`}
|
tooltip={`Редактировать словоформы термина: ${constituenta?.term_forms.length ?? 0}`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -113,7 +117,7 @@ function FormConstituenta({
|
||||||
onClick={onEditTerm}
|
onClick={onEditTerm}
|
||||||
icon={<LiaEdit size='1rem' className={!disabled ? 'clr-text-primary' : ''} />}
|
icon={<LiaEdit size='1rem' className={!disabled ? 'clr-text-primary' : ''} />}
|
||||||
/>
|
/>
|
||||||
<div className='pt-1 pl-[1.375rem] text-sm font-semibold w-fit'>
|
<div className='pt-1 pl-[1.375rem] text-sm font-semibold whitespace-nowrap w-fit'>
|
||||||
<span>Имя </span>
|
<span>Имя </span>
|
||||||
<span className='ml-1'>{constituenta?.alias ?? ''}</span>
|
<span className='ml-1'>{constituenta?.alias ?? ''}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -125,7 +129,10 @@ function FormConstituenta({
|
||||||
/>
|
/>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
<form id={id}
|
<form id={id}
|
||||||
className='flex flex-col gap-3 mt-1'
|
className={clsx(
|
||||||
|
'mt-1 min-w-[47.8rem] max-w-[47.8rem]',
|
||||||
|
'px-4 py-1 flex flex-col gap-3'
|
||||||
|
)}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
<RefsInput
|
<RefsInput
|
||||||
|
@ -138,16 +145,14 @@ function FormConstituenta({
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={newValue => setTerm(newValue)}
|
onChange={newValue => setTerm(newValue)}
|
||||||
/>
|
/>
|
||||||
<TextArea dense noBorder
|
<TextArea dense noBorder disabled
|
||||||
label='Типизация'
|
label='Типизация'
|
||||||
rows={typification.length > 70 ? 2 : 1}
|
rows={typification.length > 70 ? 2 : 1}
|
||||||
value={typification}
|
value={typification}
|
||||||
colors='clr-app'
|
colors='clr-app'
|
||||||
dimensions='w-full'
|
|
||||||
style={{
|
style={{
|
||||||
resize: 'none'
|
resize: 'none'
|
||||||
}}
|
}}
|
||||||
disabled
|
|
||||||
/>
|
/>
|
||||||
<EditorRSExpression
|
<EditorRSExpression
|
||||||
label='Формальное определение'
|
label='Формальное определение'
|
||||||
|
@ -176,13 +181,12 @@ function FormConstituenta({
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={event => setConvention(event.target.value)}
|
onChange={event => setConvention(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<div className='flex justify-center w-full'>
|
<SubmitButton
|
||||||
<SubmitButton
|
text='Сохранить изменения'
|
||||||
text='Сохранить изменения'
|
className='self-center'
|
||||||
disabled={!isModified || disabled}
|
disabled={!isModified || disabled}
|
||||||
icon={<FiSave size='1.5rem' />}
|
icon={<FiSave size='1.5rem' />}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</>);
|
</>);
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,14 +123,15 @@ function EditorRSExpression({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (<>
|
||||||
<div className='flex flex-col items-start w-full'>
|
{showAST ?
|
||||||
{showAST ?
|
<DlgShowAST
|
||||||
<DlgShowAST
|
expression={expression}
|
||||||
expression={expression}
|
syntaxTree={syntaxTree}
|
||||||
syntaxTree={syntaxTree}
|
hideWindow={() => setShowAST(false)}
|
||||||
hideWindow={() => setShowAST(false)}
|
/> : null}
|
||||||
/> : null}
|
|
||||||
|
<div>
|
||||||
<Overlay position='top-[-0.375rem] left-[11rem]'>
|
<Overlay position='top-[-0.375rem] left-[11rem]'>
|
||||||
<MiniButton noHover
|
<MiniButton noHover
|
||||||
tooltip='Дерево разбора выражения'
|
tooltip='Дерево разбора выражения'
|
||||||
|
@ -160,7 +161,8 @@ function EditorRSExpression({
|
||||||
onCheckExpression={handleCheckExpression}
|
onCheckExpression={handleCheckExpression}
|
||||||
onShowError={onShowError}
|
onShowError={onShowError}
|
||||||
/>
|
/>
|
||||||
</div>);
|
</div>
|
||||||
|
</>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EditorRSExpression;
|
export default EditorRSExpression;
|
||||||
|
|
|
@ -33,7 +33,7 @@ function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) {
|
||||||
return (
|
return (
|
||||||
<div title={describeExpressionStatus(status)}
|
<div title={describeExpressionStatus(status)}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'w-full h-full',
|
'h-full',
|
||||||
'border rounded-none',
|
'border rounded-none',
|
||||||
'text-sm font-semibold small-caps text-center',
|
'text-sm font-semibold small-caps text-center',
|
||||||
'select-none'
|
'select-none'
|
||||||
|
|
|
@ -49,7 +49,7 @@ function EditorRSForm({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div tabIndex={-1} onKeyDown={handleInput}>
|
<>
|
||||||
<RSFormToolbar
|
<RSFormToolbar
|
||||||
isMutable={isMutable}
|
isMutable={isMutable}
|
||||||
processing={processing}
|
processing={processing}
|
||||||
|
@ -65,26 +65,25 @@ function EditorRSForm({
|
||||||
onDestroy={onDestroy}
|
onDestroy={onDestroy}
|
||||||
onToggleSubscribe={onToggleSubscribe}
|
onToggleSubscribe={onToggleSubscribe}
|
||||||
/>
|
/>
|
||||||
<div className='flex w-full'>
|
<div tabIndex={-1}
|
||||||
<div className='flex-grow max-w-[40rem] min-w-[30rem] px-4 pb-2'>
|
className='flex'
|
||||||
<div className='flex flex-col gap-3'>
|
onKeyDown={handleInput}
|
||||||
<FormRSForm disabled={!isMutable}
|
>
|
||||||
id={globalIDs.library_item_editor}
|
<div className='flex flex-col gap-3 px-4 pb-2'>
|
||||||
isModified={isModified}
|
<FormRSForm disabled={!isMutable}
|
||||||
setIsModified={setIsModified}
|
id={globalIDs.library_item_editor}
|
||||||
/>
|
isModified={isModified}
|
||||||
|
setIsModified={setIsModified}
|
||||||
|
/>
|
||||||
|
|
||||||
<Divider margins='my-2' />
|
<Divider margins='my-2' />
|
||||||
|
|
||||||
<InfoLibraryItem item={schema} />
|
<InfoLibraryItem item={schema} />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider vertical />
|
|
||||||
|
|
||||||
<RSFormStats stats={schema?.stats}/>
|
<RSFormStats stats={schema?.stats}/>
|
||||||
</div>
|
</div>
|
||||||
</div>);
|
</>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EditorRSForm;
|
export default EditorRSForm;
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Dispatch, SetStateAction, useLayoutEffect, useState } from 'react';
|
import { Dispatch, SetStateAction, useEffect, useLayoutEffect, useState } from 'react';
|
||||||
import { FiSave } from 'react-icons/fi';
|
import { FiSave } from 'react-icons/fi';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ function FormRSForm({
|
||||||
const [common, setCommon] = useState(false);
|
const [common, setCommon] = useState(false);
|
||||||
const [canonical, setCanonical] = useState(false);
|
const [canonical, setCanonical] = useState(false);
|
||||||
|
|
||||||
useLayoutEffect(
|
useEffect(
|
||||||
() => {
|
() => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
setIsModified(false);
|
setIsModified(false);
|
||||||
|
@ -79,7 +79,7 @@ function FormRSForm({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form id={id}
|
<form id={id}
|
||||||
className='flex flex-col gap-3 mt-2'
|
className='flex flex-col gap-3 mt-1 py-1 min-w-[22rem] w-[30rem]'
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
<TextInput required
|
<TextInput required
|
||||||
|
@ -121,15 +121,14 @@ function FormRSForm({
|
||||||
setValue={value => setCanonical(value)}
|
setValue={value => setCanonical(value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-center w-full'>
|
<SubmitButton
|
||||||
<SubmitButton
|
text='Сохранить изменения'
|
||||||
text='Сохранить изменения'
|
className='self-center'
|
||||||
loading={processing}
|
dimensions='my-2 w-fit'
|
||||||
disabled={!isModified || disabled}
|
loading={processing}
|
||||||
icon={<FiSave size='1.5rem' />}
|
disabled={!isModified || disabled}
|
||||||
dimensions='my-2 w-fit'
|
icon={<FiSave size='1.5rem' />}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</form>);
|
</form>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ function RSFormStats({ stats }: RSFormStatsProps) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col gap-1 px-4 mt-8 min-w-[16rem]'>
|
<div className='flex flex-col gap-1 px-4 mt-8 min-w-[16rem] max-w-[16rem]'>
|
||||||
<LabeledValue id='count_all'
|
<LabeledValue id='count_all'
|
||||||
label='Всего конституент '
|
label='Всего конституент '
|
||||||
text={stats.count_all}
|
text={stats.count_all}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { BiDownload, BiTrash } from 'react-icons/bi';
|
import { BiDownload, BiShareAlt, BiTrash } from 'react-icons/bi';
|
||||||
import { FiSave } from 'react-icons/fi';
|
import { FiBell, FiBellOff, FiSave } from 'react-icons/fi';
|
||||||
import { LuCrown } from 'react-icons/lu';
|
import { LuCrown } from 'react-icons/lu';
|
||||||
|
|
||||||
import MiniButton from '@/components/Common/MiniButton';
|
import MiniButton from '@/components/Common/MiniButton';
|
||||||
import Overlay from '@/components/Common/Overlay';
|
import Overlay from '@/components/Common/Overlay';
|
||||||
import HelpButton from '@/components/Help/HelpButton';
|
import HelpButton from '@/components/Help/HelpButton';
|
||||||
import { NotSubscribedIcon, ShareIcon, SubscribedIcon } from '@/components/Icons';
|
|
||||||
import { HelpTopic } from '@/models/miscelanious';
|
import { HelpTopic } from '@/models/miscelanious';
|
||||||
|
|
||||||
interface RSFormToolbarProps {
|
interface RSFormToolbarProps {
|
||||||
|
@ -35,7 +34,7 @@ function RSFormToolbar({
|
||||||
}: RSFormToolbarProps) {
|
}: RSFormToolbarProps) {
|
||||||
const canSave = useMemo(() => (modified && isMutable), [modified, isMutable]);
|
const canSave = useMemo(() => (modified && isMutable), [modified, isMutable]);
|
||||||
return (
|
return (
|
||||||
<Overlay position='w-full top-1 flex items-start justify-center'>
|
<Overlay position='top-1 right-1/2 translate-x-1/2' className='flex'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Сохранить изменения [Ctrl + S]'
|
tooltip='Сохранить изменения [Ctrl + S]'
|
||||||
disabled={!canSave}
|
disabled={!canSave}
|
||||||
|
@ -44,7 +43,7 @@ function RSFormToolbar({
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Поделиться схемой'
|
tooltip='Поделиться схемой'
|
||||||
icon={<ShareIcon size='1.25rem' className='clr-text-primary'/>}
|
icon={<BiShareAlt size='1.25rem' className='clr-text-primary'/>}
|
||||||
onClick={onShare}
|
onClick={onShare}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
|
@ -56,10 +55,10 @@ function RSFormToolbar({
|
||||||
tooltip={'отслеживание: ' + (isSubscribed ? '[включено]' : '[выключено]')}
|
tooltip={'отслеживание: ' + (isSubscribed ? '[включено]' : '[выключено]')}
|
||||||
disabled={anonymous || processing}
|
disabled={anonymous || processing}
|
||||||
icon={isSubscribed
|
icon={isSubscribed
|
||||||
? <SubscribedIcon size='1.25rem' className='clr-text-primary' />
|
? <FiBell size='1.25rem' className='clr-text-primary' />
|
||||||
: <NotSubscribedIcon size='1.25rem' className='clr-text-controls' />
|
: <FiBellOff size='1.25rem' className='clr-text-controls' />
|
||||||
}
|
}
|
||||||
dimensions='h-full w-fit pr-2'
|
dimensions='h-full w-fit'
|
||||||
style={{outlineColor: 'transparent'}}
|
style={{outlineColor: 'transparent'}}
|
||||||
onClick={onToggleSubscribe}
|
onClick={onToggleSubscribe}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -13,16 +13,14 @@ import RSTable from './RSTable';
|
||||||
interface EditorRSListProps {
|
interface EditorRSListProps {
|
||||||
isMutable: boolean
|
isMutable: boolean
|
||||||
onOpenEdit: (cstID: number) => void
|
onOpenEdit: (cstID: number) => void
|
||||||
onTemplates: (insertAfter?: number) => void
|
|
||||||
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
|
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
|
||||||
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
|
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
|
||||||
onReindex: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorRSList({
|
function EditorRSList({
|
||||||
isMutable,
|
isMutable,
|
||||||
onOpenEdit, onCreateCst,
|
onOpenEdit, onCreateCst,
|
||||||
onDeleteCst, onTemplates, onReindex
|
onDeleteCst
|
||||||
}: EditorRSListProps) {
|
}: EditorRSListProps) {
|
||||||
const { schema, cstMoveTo } = useRSForm();
|
const { schema, cstMoveTo } = useRSForm();
|
||||||
const [selected, setSelected] = useState<number[]>([]);
|
const [selected, setSelected] = useState<number[]>([]);
|
||||||
|
@ -185,8 +183,6 @@ function EditorRSList({
|
||||||
}
|
}
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 'Backquote': handleCreateCst(); return true;
|
case 'Backquote': handleCreateCst(); return true;
|
||||||
case 'KeyE': onTemplates(); return true;
|
|
||||||
case 'KeyR': onReindex(); return true;
|
|
||||||
|
|
||||||
case 'Digit1': handleCreateCst(CstType.BASE); return true;
|
case 'Digit1': handleCreateCst(CstType.BASE); return true;
|
||||||
case 'Digit2': handleCreateCst(CstType.STRUCTURED); return true;
|
case 'Digit2': handleCreateCst(CstType.STRUCTURED); return true;
|
||||||
|
@ -202,7 +198,7 @@ function EditorRSList({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div tabIndex={-1}
|
<div tabIndex={-1}
|
||||||
className='w-full outline-none'
|
className='outline-none'
|
||||||
onKeyDown={handleTableKey}
|
onKeyDown={handleTableKey}
|
||||||
>
|
>
|
||||||
<RSListToolbar
|
<RSListToolbar
|
||||||
|
@ -213,8 +209,6 @@ function EditorRSList({
|
||||||
onClone={handleClone}
|
onClone={handleClone}
|
||||||
onCreate={handleCreateCst}
|
onCreate={handleCreateCst}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
onTemplates={() => onTemplates(selected.length !== 0 ? selected[selected.length-1] : undefined)}
|
|
||||||
onReindex={onReindex}
|
|
||||||
/>
|
/>
|
||||||
<SelectedCounter
|
<SelectedCounter
|
||||||
total={schema?.stats?.count_all ?? 0}
|
total={schema?.stats?.count_all ?? 0}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { BiAnalyse, BiDiamond, BiDownArrowCircle, BiDownvote, BiDuplicate, BiPlusCircle, BiTrash, BiUpvote } from "react-icons/bi";
|
import {
|
||||||
|
BiDownArrowCircle, BiDownvote, BiDuplicate,
|
||||||
|
BiPlusCircle, BiTrash, BiUpvote
|
||||||
|
} from 'react-icons/bi';
|
||||||
|
|
||||||
import Dropdown from '@/components/Common/Dropdown';
|
import Dropdown from '@/components/Common/Dropdown';
|
||||||
import DropdownButton from '@/components/Common/DropdownButton';
|
import DropdownButton from '@/components/Common/DropdownButton';
|
||||||
|
@ -24,29 +27,27 @@ interface RSListToolbarProps {
|
||||||
onDelete: () => void
|
onDelete: () => void
|
||||||
onClone: () => void
|
onClone: () => void
|
||||||
onCreate: (type?: CstType) => void
|
onCreate: (type?: CstType) => void
|
||||||
onTemplates: () => void
|
|
||||||
onReindex: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function RSListToolbar({
|
function RSListToolbar({
|
||||||
selectedCount, isMutable,
|
selectedCount, isMutable,
|
||||||
onMoveUp, onMoveDown, onDelete, onClone,
|
onMoveUp, onMoveDown, onDelete, onClone,
|
||||||
onCreate, onTemplates, onReindex
|
onCreate
|
||||||
}: RSListToolbarProps) {
|
}: RSListToolbarProps) {
|
||||||
const insertMenu = useDropdown();
|
const insertMenu = useDropdown();
|
||||||
const nothingSelected = useMemo(() => selectedCount === 0, [selectedCount]);
|
const nothingSelected = useMemo(() => selectedCount === 0, [selectedCount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Overlay position='w-full top-1 flex items-start justify-center'>
|
<Overlay position='top-1 right-1/2 translate-x-1/2' className='flex'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Переместить вверх [Alt + вверх]'
|
tooltip='Переместить вверх [Alt + вверх]'
|
||||||
icon={<BiUpvote size='1.25rem'/>}
|
icon={<BiUpvote size='1.25rem' className={isMutable && !nothingSelected ? 'clr-text-primary': ''}/>}
|
||||||
disabled={!isMutable || nothingSelected}
|
disabled={!isMutable || nothingSelected}
|
||||||
onClick={onMoveUp}
|
onClick={onMoveUp}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Переместить вниз [Alt + вниз]'
|
tooltip='Переместить вниз [Alt + вниз]'
|
||||||
icon={<BiDownvote size='1.25rem'/>}
|
icon={<BiDownvote size='1.25rem' className={isMutable && !nothingSelected ? 'clr-text-primary': ''}/>}
|
||||||
disabled={!isMutable || nothingSelected}
|
disabled={!isMutable || nothingSelected}
|
||||||
onClick={onMoveDown}
|
onClick={onMoveDown}
|
||||||
/>
|
/>
|
||||||
|
@ -84,18 +85,6 @@ function RSListToolbar({
|
||||||
</Dropdown> : null}
|
</Dropdown> : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MiniButton
|
|
||||||
tooltip='Создать конституенту из шаблона [Alt + E]'
|
|
||||||
icon={<BiDiamond size='1.25rem' className={isMutable ? 'clr-text-primary': ''} />}
|
|
||||||
disabled={!isMutable}
|
|
||||||
onClick={onTemplates}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
|
||||||
tooltip='Сброс имён: присвоить порядковые имена [Alt + R]'
|
|
||||||
icon={<BiAnalyse size='1.25rem' className={isMutable ? 'clr-text-primary': ''} />}
|
|
||||||
disabled={!isMutable}
|
|
||||||
onClick={onReindex}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Удалить выбранные [Delete]'
|
tooltip='Удалить выбранные [Delete]'
|
||||||
icon={<BiTrash size='1.25rem' className={isMutable && !nothingSelected ? 'clr-text-warning' : ''} />}
|
icon={<BiTrash size='1.25rem' className={isMutable && !nothingSelected ? 'clr-text-warning' : ''} />}
|
||||||
|
|
|
@ -125,16 +125,15 @@ function RSTable({
|
||||||
}, [noNavigation]);
|
}, [noNavigation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<DataTable dense noFooter
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'w-full h-full min-h-[20rem]',
|
'min-h-[20rem]',
|
||||||
'overflow-auto',
|
'overflow-auto',
|
||||||
'text-sm',
|
'text-sm',
|
||||||
'select-none'
|
'select-none'
|
||||||
)}
|
)}
|
||||||
style={{maxHeight: tableHeight}}
|
style={{maxHeight: tableHeight}}
|
||||||
>
|
|
||||||
<DataTable dense noFooter
|
|
||||||
data={items ?? []}
|
data={items ?? []}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
headPosition='0rem'
|
headPosition='0rem'
|
||||||
|
@ -161,8 +160,7 @@ function RSTable({
|
||||||
</p>
|
</p>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
/>
|
/>);
|
||||||
</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RSTable;
|
export default RSTable;
|
|
@ -18,7 +18,7 @@ function GraphSidebar({
|
||||||
layout, setLayout
|
layout, setLayout
|
||||||
} : GraphSidebarProps) {
|
} : GraphSidebarProps) {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col px-2 text-sm select-none mt-9 h-fit'>
|
<div className='px-2 text-sm select-none mt-9'>
|
||||||
<SelectSingle
|
<SelectSingle
|
||||||
placeholder='Выберите цвет'
|
placeholder='Выберите цвет'
|
||||||
options={SelectorGraphColoring}
|
options={SelectorGraphColoring}
|
||||||
|
@ -28,7 +28,6 @@ function GraphSidebar({
|
||||||
/>
|
/>
|
||||||
<SelectSingle
|
<SelectSingle
|
||||||
placeholder='Способ расположения'
|
placeholder='Способ расположения'
|
||||||
className='w-full'
|
|
||||||
options={SelectorGraphLayout}
|
options={SelectorGraphLayout}
|
||||||
isSearchable={false}
|
isSearchable={false}
|
||||||
value={layout ? { value: layout, label: mapLableLayout.get(layout) } : null}
|
value={layout ? { value: layout, label: mapLableLayout.get(layout) } : null}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { BiCollapse, BiFilterAlt, BiPlanet, BiPlusCircle, BiTrash } from 'react-icons/bi';
|
import { BiCollapse, BiFilterAlt, BiFont, BiFontFamily, BiPlanet, BiPlusCircle, BiTrash } from 'react-icons/bi';
|
||||||
|
|
||||||
import MiniButton from '@/components/Common/MiniButton';
|
import MiniButton from '@/components/Common/MiniButton';
|
||||||
import Overlay from '@/components/Common/Overlay';
|
import Overlay from '@/components/Common/Overlay';
|
||||||
import HelpButton from '@/components/Help/HelpButton';
|
import HelpButton from '@/components/Help/HelpButton';
|
||||||
import { LetterAIcon, LetterALinesIcon } from '@/components/Icons';
|
|
||||||
import { HelpTopic } from '@/models/miscelanious';
|
import { HelpTopic } from '@/models/miscelanious';
|
||||||
|
|
||||||
interface GraphToolbarProps {
|
interface GraphToolbarProps {
|
||||||
|
@ -33,7 +32,7 @@ function GraphToolbar({
|
||||||
onCreate, onDelete, onResetViewpoint
|
onCreate, onDelete, onResetViewpoint
|
||||||
} : GraphToolbarProps) {
|
} : GraphToolbarProps) {
|
||||||
return (
|
return (
|
||||||
<Overlay position='w-full top-1 right-0 flex items-start justify-center'>
|
<Overlay position='top-1 right-1/2 translate-x-1/2' className='flex'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Настройки фильтрации узлов и связей'
|
tooltip='Настройки фильтрации узлов и связей'
|
||||||
icon={<BiFilterAlt size='1.25rem' className='clr-text-primary' />}
|
icon={<BiFilterAlt size='1.25rem' className='clr-text-primary' />}
|
||||||
|
@ -43,8 +42,8 @@ function GraphToolbar({
|
||||||
tooltip={!noText ? 'Скрыть текст' : 'Отобразить текст'}
|
tooltip={!noText ? 'Скрыть текст' : 'Отобразить текст'}
|
||||||
icon={
|
icon={
|
||||||
!noText
|
!noText
|
||||||
? <LetterALinesIcon size='1.25rem' className='clr-text-success' />
|
? <BiFontFamily size='1.25rem' className='clr-text-success' />
|
||||||
: <LetterAIcon size='1.25rem' className='clr-text-primary' />
|
: <BiFont size='1.25rem' className='clr-text-primary' />
|
||||||
}
|
}
|
||||||
onClick={toggleNoText}
|
onClick={toggleNoText}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -46,7 +46,7 @@ export enum RSTabID {
|
||||||
function ProcessError({error}: {error: ErrorData}): React.ReactElement {
|
function ProcessError({error}: {error: ErrorData}): React.ReactElement {
|
||||||
if (axios.isAxiosError(error) && error.response && error.response.status === 404) {
|
if (axios.isAxiosError(error) && error.response && error.response.status === 404) {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-center justify-center w-full p-2'>
|
<div className='p-2 text-center'>
|
||||||
<p>Схема с указанным идентификатором отсутствует на портале.</p>
|
<p>Схема с указанным идентификатором отсутствует на портале.</p>
|
||||||
<TextURL text='Перейти в Библиотеку' href='/library'/>
|
<TextURL text='Перейти в Библиотеку' href='/library'/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -403,11 +403,10 @@ function RSTabs() {
|
||||||
onSelect={onSelectTab}
|
onSelect={onSelectTab}
|
||||||
defaultFocus
|
defaultFocus
|
||||||
selectedTabClassName='clr-selected'
|
selectedTabClassName='clr-selected'
|
||||||
className='flex flex-col w-full'
|
className='flex flex-col items-center min-w-[45rem]'
|
||||||
>
|
>
|
||||||
<div className='flex justify-center w-[100vw]'>
|
|
||||||
<TabList className={clsx(
|
<TabList className={clsx(
|
||||||
'w-fit h-[1.9rem]',
|
'h-[1.9rem]',
|
||||||
'flex justify-stretch',
|
'flex justify-stretch',
|
||||||
'border-b-2 border-x-2 divide-x-2'
|
'border-b-2 border-x-2 divide-x-2'
|
||||||
)}>
|
)}>
|
||||||
|
@ -435,63 +434,52 @@ function RSTabs() {
|
||||||
<ConceptTab label='Редактор' />
|
<ConceptTab label='Редактор' />
|
||||||
<ConceptTab label='Граф термов' />
|
<ConceptTab label='Граф термов' />
|
||||||
</TabList>
|
</TabList>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div className='overflow-y-auto' style={{ maxHeight: panelHeight}}>
|
||||||
className={clsx(
|
|
||||||
'min-w-[48rem] w-[100vw]',
|
|
||||||
'flex justify-center',
|
|
||||||
'overflow-y-auto'
|
|
||||||
)}
|
|
||||||
style={{ maxHeight: panelHeight}}
|
|
||||||
>
|
|
||||||
<TabPanel forceRender style={{ display: activeTab === RSTabID.CARD ? '': 'none' }}>
|
<TabPanel forceRender style={{ display: activeTab === RSTabID.CARD ? '': 'none' }}>
|
||||||
<EditorRSForm
|
<EditorRSForm
|
||||||
isMutable={isMutable}
|
isMutable={isMutable}
|
||||||
isModified={isModified}
|
isModified={isModified}
|
||||||
setIsModified={setIsModified}
|
setIsModified={setIsModified}
|
||||||
onToggleSubscribe={handleToggleSubscribe}
|
onToggleSubscribe={handleToggleSubscribe}
|
||||||
onDownload={onDownloadSchema}
|
onDownload={onDownloadSchema}
|
||||||
onDestroy={onDestroySchema}
|
onDestroy={onDestroySchema}
|
||||||
onClaim={onClaimSchema}
|
onClaim={onClaimSchema}
|
||||||
onShare={onShareSchema}
|
onShare={onShareSchema}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_LIST ? '': 'none' }}>
|
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_LIST ? '': 'none' }}>
|
||||||
<EditorRSList
|
<EditorRSList
|
||||||
isMutable={isMutable}
|
isMutable={isMutable}
|
||||||
onOpenEdit={onOpenCst}
|
onOpenEdit={onOpenCst}
|
||||||
onCreateCst={promptCreateCst}
|
onCreateCst={promptCreateCst}
|
||||||
onDeleteCst={promptDeleteCst}
|
onDeleteCst={promptDeleteCst}
|
||||||
onTemplates={onShowTemplates}
|
/>
|
||||||
onReindex={onReindex}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_EDIT ? '': 'none' }}>
|
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_EDIT ? '': 'none' }}>
|
||||||
<EditorConstituenta
|
<EditorConstituenta
|
||||||
isMutable={isMutable}
|
isMutable={isMutable}
|
||||||
isModified={isModified}
|
isModified={isModified}
|
||||||
setIsModified={setIsModified}
|
setIsModified={setIsModified}
|
||||||
activeID={activeID}
|
activeID={activeID}
|
||||||
activeCst={activeCst}
|
activeCst={activeCst}
|
||||||
onOpenEdit={onOpenCst}
|
onOpenEdit={onOpenCst}
|
||||||
onCreateCst={promptCreateCst}
|
onCreateCst={promptCreateCst}
|
||||||
onDeleteCst={promptDeleteCst}
|
onDeleteCst={promptDeleteCst}
|
||||||
onRenameCst={promptRenameCst}
|
onRenameCst={promptRenameCst}
|
||||||
onEditTerm={promptShowEditTerm}
|
onEditTerm={promptShowEditTerm}
|
||||||
onTemplates={onShowTemplates}
|
/>
|
||||||
/>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel style={{ display: activeTab === RSTabID.TERM_GRAPH ? '': 'none' }}>
|
<TabPanel style={{ display: activeTab === RSTabID.TERM_GRAPH ? '': 'none' }}>
|
||||||
<EditorTermGraph
|
<EditorTermGraph
|
||||||
isMutable={isMutable}
|
isMutable={isMutable}
|
||||||
onOpenEdit={onOpenCst}
|
onOpenEdit={onOpenCst}
|
||||||
onCreateCst={promptCreateCst}
|
onCreateCst={promptCreateCst}
|
||||||
onDeleteCst={promptDeleteCst}
|
onDeleteCst={promptDeleteCst}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</div>
|
</div>
|
||||||
</Tabs> : null}
|
</Tabs> : null}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { BiAnalyse, BiDiamond, BiDownload, BiDuplicate, BiMenu, BiMeteor, BiPlusCircle, BiTrash, BiUpload } from 'react-icons/bi';
|
import { BiAnalyse, BiDiamond, BiDownload, BiDuplicate, BiMenu, BiMeteor, BiPlusCircle, BiShareAlt, BiTrash, BiUpload } from 'react-icons/bi';
|
||||||
import { FiEdit } from 'react-icons/fi';
|
import { FiEdit } from 'react-icons/fi';
|
||||||
import { LuCrown, LuGlasses } from 'react-icons/lu';
|
import { LuCrown, LuGlasses } from 'react-icons/lu';
|
||||||
|
|
||||||
import Button from '@/components/Common/Button';
|
import Button from '@/components/Common/Button';
|
||||||
import Dropdown from '@/components/Common/Dropdown';
|
import Dropdown from '@/components/Common/Dropdown';
|
||||||
import DropdownButton from '@/components/Common/DropdownButton';
|
import DropdownButton from '@/components/Common/DropdownButton';
|
||||||
import { ShareIcon } from '@/components/Icons';
|
|
||||||
import { useAccessMode } from '@/context/AccessModeContext';
|
import { useAccessMode } from '@/context/AccessModeContext';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/context/AuthContext';
|
||||||
import { useConceptNavigation } from '@/context/NagivationContext';
|
import { useConceptNavigation } from '@/context/NagivationContext';
|
||||||
|
@ -115,7 +114,7 @@ function RSTabsMenu({
|
||||||
/>
|
/>
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Поделиться'
|
text='Поделиться'
|
||||||
icon={<ShareIcon size='1rem' className='clr-text-primary' />}
|
icon={<BiShareAlt size='1rem' className='clr-text-primary' />}
|
||||||
onClick={handleShare}
|
onClick={handleShare}
|
||||||
/>
|
/>
|
||||||
<DropdownButton disabled={!user}
|
<DropdownButton disabled={!user}
|
||||||
|
|
|
@ -17,12 +17,14 @@ interface ConstituentsTableProps {
|
||||||
activeID?: number
|
activeID?: number
|
||||||
onOpenEdit: (cstID: number) => void
|
onOpenEdit: (cstID: number) => void
|
||||||
denseThreshold?: number
|
denseThreshold?: number
|
||||||
|
maxHeight: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<IConstituenta>();
|
const columnHelper = createColumnHelper<IConstituenta>();
|
||||||
|
|
||||||
function ConstituentsTable({
|
function ConstituentsTable({
|
||||||
items, activeID, onOpenEdit,
|
items, activeID, onOpenEdit,
|
||||||
|
maxHeight,
|
||||||
denseThreshold = 9999
|
denseThreshold = 9999
|
||||||
}: ConstituentsTableProps) {
|
}: ConstituentsTableProps) {
|
||||||
const { colors } = useConceptTheme();
|
const { colors } = useConceptTheme();
|
||||||
|
@ -107,6 +109,8 @@ function ConstituentsTable({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable dense noFooter
|
<DataTable dense noFooter
|
||||||
|
className='overflow-y-auto text-sm select-none overscroll-none'
|
||||||
|
style={{maxHeight : maxHeight}}
|
||||||
data={items}
|
data={items}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
conditionalRowStyles={conditionalRowStyles}
|
conditionalRowStyles={conditionalRowStyles}
|
||||||
|
@ -119,7 +123,7 @@ function ConstituentsTable({
|
||||||
noDataComponent={
|
noDataComponent={
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
'min-h-[5rem]',
|
'min-h-[5rem]',
|
||||||
'p-2 flex flex-col justify-center',
|
'p-2',
|
||||||
'text-center',
|
'text-center',
|
||||||
'select-none'
|
'select-none'
|
||||||
)}>
|
)}>
|
||||||
|
|
|
@ -35,22 +35,21 @@ function ViewConstituents({ expression, baseHeight, schema, activeID, onOpenEdit
|
||||||
: `calc(min(100vh - 11.7rem, ${siblingHeight}))`);
|
: `calc(min(100vh - 11.7rem, ${siblingHeight}))`);
|
||||||
}, [noNavigation, baseHeight]);
|
}, [noNavigation, baseHeight]);
|
||||||
|
|
||||||
return (<>
|
return (
|
||||||
|
<div className='mt-[2.25rem] border'>
|
||||||
<ConstituentsSearch
|
<ConstituentsSearch
|
||||||
schema={schema}
|
schema={schema}
|
||||||
activeID={activeID}
|
activeID={activeID}
|
||||||
activeExpression={expression}
|
activeExpression={expression}
|
||||||
setFiltered={setFilteredData}
|
setFiltered={setFilteredData}
|
||||||
/>
|
/>
|
||||||
<div className='overflow-y-auto text-sm select-none overscroll-none' style={{maxHeight : `${maxHeight}`}}>
|
<ConstituentsTable maxHeight={maxHeight}
|
||||||
<ConstituentsTable
|
|
||||||
items={filteredData}
|
items={filteredData}
|
||||||
activeID={activeID}
|
activeID={activeID}
|
||||||
onOpenEdit={onOpenEdit}
|
onOpenEdit={onOpenEdit}
|
||||||
denseThreshold={COLUMN_EXPRESSION_HIDE_THRESHOLD}
|
denseThreshold={COLUMN_EXPRESSION_HIDE_THRESHOLD}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>);
|
||||||
</>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ViewConstituents;
|
export default ViewConstituents;
|
|
@ -130,7 +130,7 @@ function RegisterPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex text-sm'>
|
<div className='flex gap-1 text-sm'>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label='Принимаю условия'
|
label='Принимаю условия'
|
||||||
value={acceptPrivacy}
|
value={acceptPrivacy}
|
||||||
|
@ -142,7 +142,7 @@ function RegisterPage() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center justify-around w-full my-3'>
|
<div className='flex justify-around my-3'>
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
text='Регистрировать'
|
text='Регистрировать'
|
||||||
dimensions='min-w-[10rem]'
|
dimensions='min-w-[10rem]'
|
||||||
|
|
|
@ -100,15 +100,14 @@ function EditorPassword() {
|
||||||
setNewPasswordRepeat(event.target.value);
|
setNewPasswordRepeat(event.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{error ? <ProcessError error={error} /> : null}
|
||||||
</div>
|
</div>
|
||||||
{error ? <ProcessError error={error} /> : null}
|
<SubmitButton
|
||||||
<div className='flex justify-center w-full'>
|
text='Сменить пароль'
|
||||||
<SubmitButton
|
className='self-center'
|
||||||
text='Сменить пароль'
|
disabled={!canSubmit}
|
||||||
disabled={!canSubmit}
|
loading={loading}
|
||||||
loading={loading}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>);
|
</form>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,37 +53,34 @@ function EditorProfile() {
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className='px-6 py-2 flex flex-col gap-8 min-w-[18rem]'
|
className='px-6 py-2 flex flex-col gap-3 min-w-[18rem]'
|
||||||
>
|
>
|
||||||
<div className='flex flex-col gap-3'>
|
<TextInput id='username' disabled
|
||||||
<TextInput id='username' disabled
|
label='Логин'
|
||||||
label='Логин'
|
tooltip='Логин изменить нельзя'
|
||||||
tooltip='Логин изменить нельзя'
|
value={username}
|
||||||
value={username}
|
/>
|
||||||
/>
|
<TextInput id='first_name' allowEnter
|
||||||
<TextInput id='first_name' allowEnter
|
label='Имя'
|
||||||
label='Имя'
|
value={first_name}
|
||||||
value={first_name}
|
onChange={event => setFirstName(event.target.value)}
|
||||||
onChange={event => setFirstName(event.target.value)}
|
/>
|
||||||
/>
|
<TextInput id='last_name' allowEnter
|
||||||
<TextInput id='last_name' allowEnter
|
label='Фамилия'
|
||||||
label='Фамилия'
|
value={last_name}
|
||||||
value={last_name}
|
onChange={event => setLastName(event.target.value)}
|
||||||
onChange={event => setLastName(event.target.value)}
|
/>
|
||||||
/>
|
<TextInput id='email' allowEnter
|
||||||
<TextInput id='email' allowEnter
|
label='Электронная почта'
|
||||||
label='Электронная почта'
|
value={email}
|
||||||
value={email}
|
onChange={event => setEmail(event.target.value)}
|
||||||
onChange={event => setEmail(event.target.value)}
|
/>
|
||||||
/>
|
<SubmitButton
|
||||||
</div>
|
className='self-center mt-6'
|
||||||
<div className='flex justify-center w-full'>
|
text='Сохранить данные'
|
||||||
<SubmitButton
|
loading={processing}
|
||||||
text='Сохранить данные'
|
disabled={!isModified}
|
||||||
loading={processing}
|
/>
|
||||||
disabled={!isModified}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>);
|
</form>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
import { FiBell, FiBellOff } from 'react-icons/fi';
|
||||||
|
|
||||||
import { ConceptLoader } from '@/components/Common/ConceptLoader';
|
import { ConceptLoader } from '@/components/Common/ConceptLoader';
|
||||||
import MiniButton from '@/components/Common/MiniButton';
|
import MiniButton from '@/components/Common/MiniButton';
|
||||||
import Overlay from '@/components/Common/Overlay';
|
import Overlay from '@/components/Common/Overlay';
|
||||||
import { NotSubscribedIcon,SubscribedIcon } from '@/components/Icons';
|
|
||||||
import InfoError from '@/components/InfoError';
|
import InfoError from '@/components/InfoError';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/context/AuthContext';
|
||||||
import { useLibrary } from '@/context/LibraryContext';
|
import { useLibrary } from '@/context/LibraryContext';
|
||||||
|
@ -32,27 +32,27 @@ function UserTabs() {
|
||||||
{loading ? <ConceptLoader /> : null}
|
{loading ? <ConceptLoader /> : null}
|
||||||
{error ? <InfoError error={error} /> : null}
|
{error ? <InfoError error={error} /> : null}
|
||||||
{user ?
|
{user ?
|
||||||
<div className='flex justify-center gap-2 py-2'>
|
<div className='flex gap-6 py-2'>
|
||||||
<div className='flex flex-col gap-2 min-w-max'>
|
<div>
|
||||||
<Overlay position='mt-2 top-0 right-0'>
|
<Overlay position='top-0 right-0'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Показать/Скрыть список отслеживаний'
|
tooltip='Показать/Скрыть список отслеживаний'
|
||||||
icon={showSubs
|
icon={showSubs
|
||||||
? <SubscribedIcon size='1.25rem' className='clr-text-primary' />
|
? <FiBell size='1.25rem' className='clr-text-primary' />
|
||||||
: <NotSubscribedIcon size='1.25rem' className='clr-text-primary' />
|
: <FiBellOff size='1.25rem' className='clr-text-primary' />
|
||||||
}
|
}
|
||||||
onClick={() => setShowSubs(prev => !prev)}
|
onClick={() => setShowSubs(prev => !prev)}
|
||||||
/>
|
/>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
<h1>Учетные данные пользователя</h1>
|
<h1 className='mb-4'>Учетные данные пользователя</h1>
|
||||||
<div className='flex justify-center py-2 max-w-fit'>
|
<div className='flex py-2 max-w-fit'>
|
||||||
<EditorProfile />
|
<EditorProfile />
|
||||||
<EditorPassword />
|
<EditorPassword />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{(subscriptions.length > 0 && showSubs) ?
|
{(subscriptions.length > 0 && showSubs) ?
|
||||||
<div className='flex flex-col w-full gap-6 pl-4'>
|
<div>
|
||||||
<h1>Отслеживаемые схемы</h1>
|
<h1 className='mb-6'>Отслеживаемые схемы</h1>
|
||||||
<ViewSubscriptions items={subscriptions} />
|
<ViewSubscriptions items={subscriptions} />
|
||||||
</div> : null}
|
</div> : null}
|
||||||
</div> : null}
|
</div> : null}
|
||||||
|
|
|
@ -52,8 +52,8 @@ function ViewSubscriptions({items}: ViewSubscriptionsProps) {
|
||||||
], [intl]);
|
], [intl]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='max-h-[23.8rem] w-fit overflow-auto text-sm border'>
|
|
||||||
<DataTable dense noFooter
|
<DataTable dense noFooter
|
||||||
|
className='max-h-[23.8rem] overflow-auto text-sm border'
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={items}
|
data={items}
|
||||||
headPosition='0'
|
headPosition='0'
|
||||||
|
@ -63,11 +63,14 @@ function ViewSubscriptions({items}: ViewSubscriptionsProps) {
|
||||||
id: 'time_update',
|
id: 'time_update',
|
||||||
desc: true
|
desc: true
|
||||||
}}
|
}}
|
||||||
noDataComponent={<div className='h-[10rem]'>Отслеживаемые схемы отсутствуют</div>}
|
noDataComponent={
|
||||||
|
<div className='h-[10rem]'>
|
||||||
|
Отслеживаемые схемы отсутствуют
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
onRowClicked={openRSForm}
|
onRowClicked={openRSForm}
|
||||||
/>
|
/>);
|
||||||
</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ViewSubscriptions;
|
export default ViewSubscriptions;
|
Loading…
Reference in New Issue
Block a user