UI refactoring and small fixes

This commit is contained in:
IRBorisov 2024-03-24 19:25:42 +03:00
parent 3d6d802cc0
commit 41eac3fc4f
9 changed files with 290 additions and 268 deletions

View File

@ -6,15 +6,13 @@ import { colorFgGrammeme } from '@/styling/color';
import { labelGrammeme } from '@/utils/labels'; import { labelGrammeme } from '@/utils/labels';
interface GrammemeBadgeProps { interface GrammemeBadgeProps {
key?: string;
grammeme: GramData; grammeme: GramData;
} }
function GrammemeBadge({ key, grammeme }: GrammemeBadgeProps) { function GrammemeBadge({ grammeme }: GrammemeBadgeProps) {
const { colors } = useConceptTheme(); const { colors } = useConceptTheme();
return ( return (
<div <div
key={key}
className={clsx( className={clsx(
'min-w-[3rem]', // prettier: split lines 'min-w-[3rem]', // prettier: split lines
'px-1', 'px-1',

View File

@ -119,13 +119,7 @@ function ConstituentaMultiPicker({ id, schema, prefixID, rows, selected, setSele
noFooter noFooter
rows={rows} rows={rows}
contentHeight='1.3rem' contentHeight='1.3rem'
className={clsx( className={clsx('overflow-y-auto', 'border', 'text-sm', 'select-none')}
'min-h-[16rem]', // prettier: split lines
'overflow-y-auto',
'border',
'text-sm',
'select-none'
)}
data={schema?.items ?? []} data={schema?.items ?? []}
columns={columns} columns={columns}
headPosition='0rem' headPosition='0rem'

View File

@ -0,0 +1,263 @@
'use client';
import { useCallback, useMemo, useState } from 'react';
import { BiChevronLeft, BiChevronRight, BiFirstPage, BiLastPage, BiX } from 'react-icons/bi';
import { LuLocate, LuLocateOff, LuPower, LuPowerOff, LuReplace } from 'react-icons/lu';
import ConstituentaBadge from '@/components/info/ConstituentaBadge';
import ConstituentaSelector from '@/components/select/ConstituentaSelector';
import DataTable, { createColumnHelper } from '@/components/ui/DataTable';
import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton';
import { useConceptTheme } from '@/context/ThemeContext';
import { IConstituenta, IRSForm, ISubstitution } from '@/models/rsform';
import { describeConstituenta } from '@/utils/labels';
interface SubstitutionsPickerProps {
prefixID: string;
rows?: number;
schema1?: IRSForm;
schema2?: IRSForm;
filter1?: (cst: IConstituenta) => boolean;
filter2?: (cst: IConstituenta) => boolean;
items: ISubstitution[];
setItems: React.Dispatch<React.SetStateAction<ISubstitution[]>>;
}
function SubstitutionIcon({ item }: { item: ISubstitution }) {
if (item.deleteRight) {
if (item.takeLeftTerm) {
return <BiChevronRight size='1.2rem' />;
} else {
return <BiLastPage size='1.2rem' />;
}
} else {
if (item.takeLeftTerm) {
return <BiFirstPage size='1.2rem' />;
} else {
return <BiChevronLeft size='1.2rem' />;
}
}
}
const columnHelper = createColumnHelper<ISubstitution>();
function SubstitutionsPicker({
items,
schema1,
schema2,
filter1,
filter2,
rows,
setItems,
prefixID
}: SubstitutionsPickerProps) {
const { colors } = useConceptTheme();
const [leftCst, setLeftCst] = useState<IConstituenta | undefined>(undefined);
const [rightCst, setRightCst] = useState<IConstituenta | undefined>(undefined);
const [deleteRight, setDeleteRight] = useState(true);
const [takeLeftTerm, setTakeLeftTerm] = useState(true);
const toggleDelete = () => setDeleteRight(prev => !prev);
const toggleTerm = () => setTakeLeftTerm(prev => !prev);
function addSubstitution() {
if (!leftCst || !rightCst) {
return;
}
const newSubstitution: ISubstitution = {
leftCst: leftCst,
rightCst: rightCst,
deleteRight: deleteRight,
takeLeftTerm: takeLeftTerm
};
setItems([
newSubstitution,
...items.filter(
item =>
(!item.deleteRight && item.leftCst.id !== leftCst.id) ||
(item.deleteRight && item.rightCst.id !== rightCst.id)
)
]);
}
const handleDeleteRow = useCallback(
(row: number) => {
setItems(prev => {
const newItems: ISubstitution[] = [];
prev.forEach((item, index) => {
if (index !== row) {
newItems.push(item);
}
});
return newItems;
});
},
[setItems]
);
const columns = useMemo(
() => [
columnHelper.accessor(item => describeConstituenta(item.leftCst), {
id: 'left_text',
header: 'Описание',
size: 1000,
cell: props => <div className='text-xs text-ellipsis'>{props.getValue()}</div>
}),
columnHelper.accessor(item => item.leftCst.alias, {
id: 'left_alias',
header: 'Имя',
size: 65,
cell: props => (
<ConstituentaBadge theme={colors} value={props.row.original.leftCst} prefixID={`${prefixID}_1_`} />
)
}),
columnHelper.display({
id: 'status',
header: '',
size: 40,
cell: props => <SubstitutionIcon item={props.row.original} />
}),
columnHelper.accessor(item => item.rightCst.alias, {
id: 'right_alias',
header: 'Имя',
size: 65,
cell: props => (
<ConstituentaBadge theme={colors} value={props.row.original.rightCst} prefixID={`${prefixID}_2_`} />
)
}),
columnHelper.accessor(item => describeConstituenta(item.rightCst), {
id: 'right_text',
header: 'Описание',
size: 1000,
cell: props => <div className='text-xs text-ellipsis'>{props.getValue()}</div>
}),
columnHelper.display({
id: 'actions',
size: 50,
minSize: 50,
maxSize: 50,
cell: props => (
<MiniButton
noHover
title='Удалить'
icon={<BiX size='1rem' className='icon-red' />}
onClick={() => handleDeleteRow(props.row.index)}
/>
)
})
],
[handleDeleteRow, colors, prefixID]
);
return (
<div className='flex flex-col'>
<div className='flex items-end gap-3 justify-stretch'>
<div className='flex-grow basis-1/2'>
<div className='flex items-center justify-between'>
<Label text={schema1?.alias ?? 'Схема 1'} />
<div>
<MiniButton
title='Сохранить конституенту'
noHover
onClick={toggleDelete}
icon={
deleteRight ? (
<LuPower size='1rem' className='clr-text-green' />
) : (
<LuPowerOff size='1rem' className='clr-text-red' />
)
}
/>
<MiniButton
title='Сохранить термин'
noHover
onClick={toggleTerm}
icon={
takeLeftTerm ? (
<LuLocate size='1rem' className='clr-text-green' />
) : (
<LuLocateOff size='1rem' className='clr-text-red' />
)
}
/>
</div>
</div>
<ConstituentaSelector
items={schema1?.items.filter(cst => !filter1 || filter1(cst))}
value={leftCst}
onSelectValue={setLeftCst}
/>
</div>
<MiniButton
noHover
title='Добавить в таблицу отождествлений'
className='mb-[0.375rem] grow-0'
icon={<LuReplace size='1.5rem' className='icon-primary' />}
disabled={!leftCst || !rightCst}
onClick={addSubstitution}
/>
<div className='flex-grow basis-1/2'>
<div className='flex items-center justify-between'>
<Label text={schema2?.alias ?? 'Схема 2'} />
<div>
<MiniButton
title='Сохранить конституенту'
noHover
onClick={toggleDelete}
icon={
!deleteRight ? (
<LuPower size='1rem' className='clr-text-green' />
) : (
<LuPowerOff size='1rem' className='clr-text-red' />
)
}
/>
<MiniButton
title='Сохранить термин'
noHover
onClick={toggleTerm}
icon={
!takeLeftTerm ? (
<LuLocate size='1rem' className='clr-text-green' />
) : (
<LuLocateOff size='1rem' className='clr-text-red' />
)
}
/>
</div>
</div>
<ConstituentaSelector
items={schema2?.items.filter(cst => !filter2 || filter2(cst))}
value={rightCst}
onSelectValue={setRightCst}
/>
</div>
</div>
<DataTable
dense
noFooter
className='overflow-y-auto border select-none'
rows={rows}
contentHeight='1.3rem'
data={items}
columns={columns}
headPosition='0'
noDataComponent={
<span className='p-2 text-center min-h-[2rem]'>
<p>Список пуст</p>
<p>Добавьте отождествление</p>
</span>
}
/>
</div>
);
}
export default SubstitutionsPicker;

View File

@ -1,19 +1,22 @@
'use client'; 'use client';
import clsx from 'clsx';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { IVersionInfo } from '@/models/library'; import { IVersionInfo } from '@/models/library';
import { labelVersion } from '@/utils/labels'; import { labelVersion } from '@/utils/labels';
import { CProps } from '../props';
import SelectSingle from '../ui/SelectSingle'; import SelectSingle from '../ui/SelectSingle';
interface VersionSelectorProps { interface VersionSelectorProps extends CProps.Styling {
id?: string;
items?: IVersionInfo[]; items?: IVersionInfo[];
value?: number; value?: number;
onSelectValue: (newValue?: number) => void; onSelectValue: (newValue?: number) => void;
} }
function VersionSelector({ items, value, onSelectValue }: VersionSelectorProps) { function VersionSelector({ id, className, items, value, onSelectValue, ...restProps }: VersionSelectorProps) {
const options = useMemo(() => { const options = useMemo(() => {
return [ return [
{ {
@ -33,10 +36,12 @@ function VersionSelector({ items, value, onSelectValue }: VersionSelectorProps)
return ( return (
<SelectSingle <SelectSingle
className='w-full min-w-[12rem] text-ellipsis' id={id}
className={clsx('w-full min-w-[12rem] text-ellipsis', className)}
options={options} options={options}
value={{ value: value, label: valueLabel }} value={{ value: value, label: valueLabel }}
onChange={data => onSelectValue(data?.value)} onChange={data => onSelectValue(data?.value)}
{...restProps}
/> />
); );
} }

View File

@ -53,7 +53,10 @@ function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInli
onInlineSynthesis(data); onInlineSynthesis(data);
} }
useEffect(() => setSelected(source.schema ? source.schema?.items.map(cst => cst.id) : []), [source.schema]); useEffect(() => {
setSelected(source.schema ? source.schema?.items.map(cst => cst.id) : []);
setSubstitutions([]);
}, [source.schema]);
return ( return (
<Modal <Modal

View File

@ -30,7 +30,12 @@ function SchemaTab({ selected, setSelected }: SchemaTabProps) {
dense dense
/> />
</div> </div>
<SchemaPicker rows={15} value={selected} onSelectValue={setSelected} /> <SchemaPicker
id='dlg_schema_picker' // prettier: split lines
rows={15}
value={selected}
onSelectValue={setSelected}
/>
</div> </div>
); );
} }

View File

@ -1,19 +1,11 @@
'use client'; 'use client';
import { useState } from 'react';
import { LuLocate, LuLocateOff, LuPower, LuPowerOff, LuReplace } from 'react-icons/lu';
import { ErrorData } from '@/components/info/InfoError'; import { ErrorData } from '@/components/info/InfoError';
import ConstituentaSelector from '@/components/select/ConstituentaSelector';
import Button from '@/components/ui/Button';
import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay';
import DataLoader from '@/components/wrap/DataLoader'; import DataLoader from '@/components/wrap/DataLoader';
import { ConstituentaID, IConstituenta, IRSForm, ISubstitution } from '@/models/rsform'; import { ConstituentaID, IRSForm, ISubstitution } from '@/models/rsform';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
import SubstitutionsTable from './SubstitutionsTable'; import SubstitutionsPicker from '../../components/select/SubstitutionsPicker';
interface SubstitutionsTabProps { interface SubstitutionsTabProps {
receiver?: IRSForm; receiver?: IRSForm;
@ -38,125 +30,16 @@ function SubstitutionsTab({
substitutions, substitutions,
setSubstitutions setSubstitutions
}: SubstitutionsTabProps) { }: SubstitutionsTabProps) {
const [leftCst, setLeftCst] = useState<IConstituenta | undefined>(undefined);
const [rightCst, setRightCst] = useState<IConstituenta | undefined>(undefined);
const [deleteRight, setDeleteRight] = useState(false);
const [takeLeftTerm, setTakeLeftTerm] = useState(false);
const toggleDelete = () => setDeleteRight(prev => !prev);
const toggleTerm = () => setTakeLeftTerm(prev => !prev);
function addSubstitution() {
if (!leftCst || !rightCst) {
return;
}
const newSubstitution: ISubstitution = {
leftCst: leftCst,
rightCst: rightCst,
deleteRight: deleteRight,
takeLeftTerm: takeLeftTerm
};
setSubstitutions([
newSubstitution,
...substitutions.filter(
item =>
(!item.deleteRight && item.leftCst.id !== leftCst.id) ||
(item.deleteRight && item.rightCst.id !== rightCst.id)
)
]);
}
return ( return (
<DataLoader id='dlg-substitutions-tab' className='cc-column' isLoading={loading} error={error} hasNoData={!source}> <DataLoader id='dlg-substitutions-tab' className='cc-column' isLoading={loading} error={error} hasNoData={!source}>
<div className='flex items-end justify-between'> <SubstitutionsPicker
<div>
<Overlay className='flex select-none'>
<MiniButton
title='Сохранить конституенту'
noHover
onClick={toggleDelete}
icon={
deleteRight ? (
<LuPower size='1rem' className='clr-text-green' />
) : (
<LuPowerOff size='1rem' className='clr-text-red' />
)
}
/>
<MiniButton
title='Сохранить термин'
noHover
onClick={toggleTerm}
icon={
takeLeftTerm ? (
<LuLocate size='1rem' className='clr-text-green' />
) : (
<LuLocateOff size='1rem' className='clr-text-red' />
)
}
/>
</Overlay>
<Label text='Импортируемая схема' />
<ConstituentaSelector
className='w-[15rem] mt-1'
items={source?.items.filter(cst => selected.includes(cst.id))}
value={leftCst}
onSelectValue={setLeftCst}
/>
</div>
<Button
title='Добавить в таблицу отождествлений'
className='h-[2.4rem] w-[5rem]'
icon={<LuReplace size='1.25rem' className='icon-primary' />}
disabled={!leftCst || !rightCst}
onClick={addSubstitution}
/>
<div>
<Overlay className='flex select-none'>
<MiniButton
title='Сохранить конституенту'
noHover
onClick={toggleDelete}
icon={
!deleteRight ? (
<LuPower size='1rem' className='clr-text-green' />
) : (
<LuPowerOff size='1rem' className='clr-text-red' />
)
}
/>
<MiniButton
title='Сохранить термин'
noHover
onClick={toggleTerm}
icon={
!takeLeftTerm ? (
<LuLocate size='1rem' className='clr-text-green' />
) : (
<LuLocateOff size='1rem' className='clr-text-red' />
)
}
/>
</Overlay>
<Label text='Текущая схема' />
<ConstituentaSelector
className='w-[15rem] mt-1'
items={receiver?.items}
value={rightCst}
onSelectValue={setRightCst}
/>
</div>
</div>
<h2>Таблица отождествлений</h2>
<SubstitutionsTable
items={substitutions} items={substitutions}
setItems={setSubstitutions} setItems={setSubstitutions}
rows={10} rows={10}
prefixID={prefixes.cst_inline_synth_substitutes} prefixID={prefixes.cst_inline_synth_substitutes}
schema1={receiver}
schema2={source}
filter2={cst => selected.includes(cst.id)}
/> />
</DataLoader> </DataLoader>
); );

View File

@ -1,130 +0,0 @@
'use client';
import { useCallback, useMemo } from 'react';
import { BiChevronLeft, BiChevronRight, BiFirstPage, BiLastPage, BiX } from 'react-icons/bi';
import ConstituentaBadge from '@/components/info/ConstituentaBadge';
import DataTable, { createColumnHelper } from '@/components/ui/DataTable';
import MiniButton from '@/components/ui/MiniButton';
import { useConceptTheme } from '@/context/ThemeContext';
import { ISubstitution } from '@/models/rsform';
import { describeConstituenta } from '@/utils/labels';
interface SubstitutionsTableProps {
prefixID: string;
rows?: number;
items: ISubstitution[];
setItems: React.Dispatch<React.SetStateAction<ISubstitution[]>>;
}
function SubstitutionIcon({ item }: { item: ISubstitution }) {
if (item.deleteRight) {
if (item.takeLeftTerm) {
return <BiChevronRight size='1.2rem' />;
} else {
return <BiLastPage size='1.2rem' />;
}
} else {
if (item.takeLeftTerm) {
return <BiFirstPage size='1.2rem' />;
} else {
return <BiChevronLeft size='1.2rem' />;
}
}
}
const columnHelper = createColumnHelper<ISubstitution>();
function SubstitutionsTable({ items, rows, setItems, prefixID }: SubstitutionsTableProps) {
const { colors } = useConceptTheme();
const handleDeleteRow = useCallback(
(row: number) => {
setItems(prev => {
const newItems: ISubstitution[] = [];
prev.forEach((item, index) => {
if (index !== row) {
newItems.push(item);
}
});
return newItems;
});
},
[setItems]
);
const columns = useMemo(
() => [
columnHelper.accessor(item => describeConstituenta(item.leftCst), {
id: 'left_text',
header: 'Описание',
size: 1000,
cell: props => <div className='text-xs text-ellipsis'>{props.getValue()}</div>
}),
columnHelper.accessor(item => item.leftCst.alias, {
id: 'left_alias',
header: 'Имя',
size: 65,
cell: props => (
<ConstituentaBadge theme={colors} value={props.row.original.leftCst} prefixID={`${prefixID}_1_`} />
)
}),
columnHelper.display({
id: 'status',
header: '',
size: 40,
cell: props => <SubstitutionIcon item={props.row.original} />
}),
columnHelper.accessor(item => item.rightCst.alias, {
id: 'right_alias',
header: 'Имя',
size: 65,
cell: props => (
<ConstituentaBadge theme={colors} value={props.row.original.rightCst} prefixID={`${prefixID}_2_`} />
)
}),
columnHelper.accessor(item => describeConstituenta(item.rightCst), {
id: 'right_text',
header: 'Описание',
size: 1000,
cell: props => <div className='text-xs text-ellipsis'>{props.getValue()}</div>
}),
columnHelper.display({
id: 'actions',
size: 50,
minSize: 50,
maxSize: 50,
cell: props => (
<MiniButton
noHover
title='Удалить'
icon={<BiX size='1rem' className='icon-red' />}
onClick={() => handleDeleteRow(props.row.index)}
/>
)
})
],
[handleDeleteRow, colors, prefixID]
);
return (
<DataTable
dense
noFooter
className='mb-2 overflow-y-auto border select-none'
rows={rows}
contentHeight='1.3rem'
data={items}
columns={columns}
headPosition='0'
noDataComponent={
<span className='p-2 text-center min-h-[2rem]'>
<p>Список пуст</p>
<p>Добавьте отождествление</p>
</span>
}
/>
);
}
export default SubstitutionsTable;

View File

@ -140,6 +140,7 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
</Overlay> </Overlay>
<Label text='Версия' className='mb-2' /> <Label text='Версия' className='mb-2' />
<VersionSelector <VersionSelector
id='schema_version'
value={schema?.version} // prettier: split lines value={schema?.version} // prettier: split lines
items={schema?.versions} items={schema?.versions}
onSelectValue={controller.viewVersion} onSelectValue={controller.viewVersion}