2025-04-21 20:35:40 +03:00
|
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { useState } from 'react';
|
|
|
|
|
|
|
|
|
|
import { MiniButton } from '@/components/control';
|
|
|
|
|
import { createColumnHelper, DataTable } from '@/components/data-table';
|
|
|
|
|
import { IconMoveDown, IconMoveUp, IconRemove } from '@/components/icons';
|
|
|
|
|
import { ComboBox } from '@/components/input/combo-box';
|
|
|
|
|
import { type Styling } from '@/components/props';
|
|
|
|
|
import { cn } from '@/components/utils';
|
|
|
|
|
import { NoData } from '@/components/view';
|
2025-04-30 12:31:07 +03:00
|
|
|
|
import { type RO } from '@/utils/meta';
|
2025-04-21 20:35:40 +03:00
|
|
|
|
|
|
|
|
|
import { labelOssItem } from '../labels';
|
2025-04-30 12:31:07 +03:00
|
|
|
|
import { type IOperationSchema, type IOssItem } from '../models/oss';
|
2025-04-29 21:30:54 +03:00
|
|
|
|
import { getItemID, isOperation } from '../models/oss-api';
|
2025-04-21 20:35:40 +03:00
|
|
|
|
|
|
|
|
|
const SELECTION_CLEAR_TIMEOUT = 1000;
|
|
|
|
|
|
|
|
|
|
interface PickMultiOperationProps extends Styling {
|
|
|
|
|
value: number[];
|
|
|
|
|
onChange: (newValue: number[]) => void;
|
|
|
|
|
schema: IOperationSchema;
|
|
|
|
|
rows?: number;
|
2025-04-29 21:30:54 +03:00
|
|
|
|
exclude?: number[];
|
2025-04-21 20:35:40 +03:00
|
|
|
|
disallowBlocks?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-30 12:31:07 +03:00
|
|
|
|
const columnHelper = createColumnHelper<RO<IOssItem>>();
|
2025-04-21 20:35:40 +03:00
|
|
|
|
|
|
|
|
|
export function PickContents({
|
|
|
|
|
rows,
|
|
|
|
|
schema,
|
2025-04-29 21:30:54 +03:00
|
|
|
|
exclude,
|
2025-04-21 20:35:40 +03:00
|
|
|
|
value,
|
|
|
|
|
disallowBlocks,
|
|
|
|
|
onChange,
|
|
|
|
|
className,
|
|
|
|
|
...restProps
|
|
|
|
|
}: PickMultiOperationProps) {
|
|
|
|
|
const selectedItems = value
|
|
|
|
|
.map(itemID => (itemID > 0 ? schema.operationByID.get(itemID) : schema.blockByID.get(-itemID)))
|
|
|
|
|
.filter(item => item !== undefined);
|
2025-04-30 12:31:07 +03:00
|
|
|
|
const [lastSelected, setLastSelected] = useState<RO<IOssItem> | null>(null);
|
2025-04-21 20:35:40 +03:00
|
|
|
|
const items = [
|
2025-04-29 21:30:54 +03:00
|
|
|
|
...(disallowBlocks ? [] : schema.blocks.filter(item => !value.includes(-item.id) && !exclude?.includes(-item.id))),
|
|
|
|
|
...schema.operations.filter(item => !value.includes(item.id) && !exclude?.includes(item.id))
|
2025-04-21 20:35:40 +03:00
|
|
|
|
];
|
|
|
|
|
|
2025-04-29 21:30:54 +03:00
|
|
|
|
function handleDelete(target: number) {
|
|
|
|
|
onChange(value.filter(item => item !== target));
|
2025-04-21 20:35:40 +03:00
|
|
|
|
}
|
|
|
|
|
|
2025-04-30 12:31:07 +03:00
|
|
|
|
function handleSelect(target: RO<IOssItem> | null) {
|
2025-04-21 20:35:40 +03:00
|
|
|
|
if (target) {
|
|
|
|
|
setLastSelected(target);
|
2025-04-29 21:30:54 +03:00
|
|
|
|
onChange([...value, getItemID(target)]);
|
2025-04-21 20:35:40 +03:00
|
|
|
|
setTimeout(() => setLastSelected(null), SELECTION_CLEAR_TIMEOUT);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-29 21:30:54 +03:00
|
|
|
|
function handleMoveUp(target: number) {
|
|
|
|
|
const index = value.indexOf(target);
|
2025-04-21 20:35:40 +03:00
|
|
|
|
if (index > 0) {
|
|
|
|
|
const newSelected = [...value];
|
|
|
|
|
newSelected[index] = newSelected[index - 1];
|
2025-04-29 21:30:54 +03:00
|
|
|
|
newSelected[index - 1] = target;
|
2025-04-21 20:35:40 +03:00
|
|
|
|
onChange(newSelected);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-29 21:30:54 +03:00
|
|
|
|
function handleMoveDown(target: number) {
|
|
|
|
|
const index = value.indexOf(target);
|
2025-04-21 20:35:40 +03:00
|
|
|
|
if (index < value.length - 1) {
|
|
|
|
|
const newSelected = [...value];
|
|
|
|
|
newSelected[index] = newSelected[index + 1];
|
2025-04-29 21:30:54 +03:00
|
|
|
|
newSelected[index + 1] = target;
|
2025-04-21 20:35:40 +03:00
|
|
|
|
onChange(newSelected);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const columns = [
|
|
|
|
|
columnHelper.accessor(item => isOperation(item), {
|
|
|
|
|
id: 'type',
|
|
|
|
|
header: 'Тип',
|
|
|
|
|
size: 150,
|
|
|
|
|
minSize: 150,
|
|
|
|
|
maxSize: 150,
|
|
|
|
|
cell: props => <div>{isOperation(props.row.original) ? 'Операция' : 'Блок'}</div>
|
|
|
|
|
}),
|
|
|
|
|
columnHelper.accessor('title', {
|
|
|
|
|
id: 'title',
|
|
|
|
|
header: 'Название',
|
|
|
|
|
size: 1200,
|
|
|
|
|
minSize: 300,
|
|
|
|
|
maxSize: 1200,
|
|
|
|
|
cell: props => <div className='text-ellipsis'>{props.getValue()}</div>
|
|
|
|
|
}),
|
|
|
|
|
columnHelper.display({
|
|
|
|
|
id: 'actions',
|
|
|
|
|
size: 0,
|
|
|
|
|
cell: props => (
|
|
|
|
|
<div className='flex w-fit'>
|
|
|
|
|
<MiniButton
|
|
|
|
|
title='Удалить'
|
|
|
|
|
noHover
|
|
|
|
|
className='px-0'
|
|
|
|
|
icon={<IconRemove size='1rem' className='icon-red' />}
|
2025-04-29 21:30:54 +03:00
|
|
|
|
onClick={() => handleDelete(getItemID(props.row.original))}
|
2025-04-21 20:35:40 +03:00
|
|
|
|
/>
|
|
|
|
|
<MiniButton
|
|
|
|
|
title='Переместить выше'
|
|
|
|
|
noHover
|
|
|
|
|
className='px-0'
|
|
|
|
|
icon={<IconMoveUp size='1rem' className='icon-primary' />}
|
2025-04-29 21:30:54 +03:00
|
|
|
|
onClick={() => handleMoveUp(getItemID(props.row.original))}
|
2025-04-21 20:35:40 +03:00
|
|
|
|
/>
|
|
|
|
|
<MiniButton
|
|
|
|
|
title='Переместить ниже'
|
|
|
|
|
noHover
|
|
|
|
|
className='px-0'
|
|
|
|
|
icon={<IconMoveDown size='1rem' className='icon-primary' />}
|
2025-04-29 21:30:54 +03:00
|
|
|
|
onClick={() => handleMoveDown(getItemID(props.row.original))}
|
2025-04-21 20:35:40 +03:00
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className={cn('flex flex-col gap-1 border-t border-x rounded-md bg-input', className)} {...restProps}>
|
|
|
|
|
<ComboBox
|
|
|
|
|
noBorder
|
|
|
|
|
items={items}
|
|
|
|
|
value={lastSelected}
|
|
|
|
|
placeholder='Выберите операцию или блок'
|
2025-04-29 21:30:54 +03:00
|
|
|
|
idFunc={item => String(getItemID(item))}
|
2025-04-21 20:35:40 +03:00
|
|
|
|
labelValueFunc={item => labelOssItem(item)}
|
|
|
|
|
labelOptionFunc={item => labelOssItem(item)}
|
|
|
|
|
onChange={handleSelect}
|
|
|
|
|
/>
|
|
|
|
|
<DataTable
|
|
|
|
|
dense
|
|
|
|
|
noFooter
|
|
|
|
|
rows={rows}
|
|
|
|
|
contentHeight='1.3rem'
|
|
|
|
|
className='cc-scroll-y text-sm select-none border-y rounded-b-md'
|
|
|
|
|
data={selectedItems}
|
|
|
|
|
columns={columns}
|
|
|
|
|
headPosition='0rem'
|
|
|
|
|
noDataComponent={
|
|
|
|
|
<NoData>
|
|
|
|
|
<p>Список пуст</p>
|
|
|
|
|
</NoData>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|