F: Implement edit block

This commit is contained in:
Ivan 2025-04-22 13:58:25 +03:00
parent 070ab18231
commit 58040f593f
16 changed files with 196 additions and 58 deletions

View File

@ -118,6 +118,11 @@ const DlgCreateBlock = React.lazy(() =>
default: module.DlgCreateBlock
}))
);
const DlgEditBlock = React.lazy(() =>
import('@/features/oss/dialogs/dlg-edit-block').then(module => ({
default: module.DlgEditBlock
}))
);
export const GlobalDialogs = () => {
const active = useDialogsStore(state => state.active);
@ -134,6 +139,8 @@ export const GlobalDialogs = () => {
return <DlgCreateOperation />;
case DialogType.CREATE_BLOCK:
return <DlgCreateBlock />;
case DialogType.EDIT_BLOCK:
return <DlgEditBlock />;
case DialogType.DELETE_CONSTITUENTA:
return <DlgDeleteCst />;
case DialogType.EDIT_EDITORS:

View File

@ -66,7 +66,7 @@ export const ossApi = {
updateBlock: ({ itemID, data }: { itemID: number; data: IUpdateBlockDTO }) =>
axiosPatch<IUpdateBlockDTO, IOperationSchemaDTO>({
schema: schemaOperationSchema,
endpoint: `/api/oss/${itemID}/update-operation`,
endpoint: `/api/oss/${itemID}/update-block`,
request: {
data: data,
successMessage: infoMsg.changesSaved

View File

@ -0,0 +1,35 @@
import clsx from 'clsx';
import { IconConceptBlock } from '@/components/icons';
import { globalIDs } from '@/utils/constants';
import { type IBlock } from '../models/oss';
import { SelectBlock } from './select-block';
interface SelectParentProps {
id?: string;
value: IBlock | null;
onChange: (newValue: IBlock | null) => void;
fullWidth?: boolean;
items?: IBlock[];
placeholder?: string;
noBorder?: boolean;
popoverClassname?: string;
}
export function SelectParent({ fullWidth, ...restProps }: SelectParentProps) {
return (
<div className={clsx('flex gap-2 items-center', !fullWidth ? 'w-80' : 'w-full')}>
<IconConceptBlock
tabIndex={-1}
size='2rem'
className='text-primary min-w-8'
data-tooltip-id={globalIDs.tooltip}
data-tooltip-content='Родительский блок содержания'
/>
<SelectBlock className={fullWidth ? 'grow' : 'w-70'} {...restProps} />
</div>
);
}

View File

@ -78,7 +78,7 @@ export function DlgCreateBlock() {
submitText='Создать'
canSubmit={isValid}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
className='w-160 px-6 h-128'
className='w-160 px-6 h-110'
helpTopic={HelpTopic.CC_OSS}
>
<Tabs

View File

@ -6,7 +6,7 @@ import { TextArea, TextInput } from '@/components/input';
import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateBlockDTO } from '../../backend/types';
import { SelectBlock } from '../../components/select-block';
import { SelectParent } from '../../components/select-parent';
import { type DlgCreateBlockProps } from './dlg-create-block';
@ -30,9 +30,8 @@ export function TabBlockCard() {
name='item_data.parent'
control={control}
render={({ field }) => (
<SelectBlock
<SelectParent
items={oss.blocks}
className='w-80'
value={field.value ? oss.blockByID.get(field.value) ?? null : null}
placeholder='Блок содержания не выбран'
onChange={value => field.onChange(value ? value.id : null)}
@ -44,7 +43,7 @@ export function TabBlockCard() {
id='operation_comment' //
label='Описание'
noResize
rows={3}
rows={5}
{...register('item_data.description')}
/>
</div>

View File

@ -33,7 +33,7 @@ export function TabBlockChildren() {
return (
<div className='cc-fade-in cc-column'>
<Label text={`Выбор содержания: [ ${value.length} ]`} />
<PickContents schema={oss} value={value} onChange={newValue => handleChangeSelected(newValue)} rows={8} />
<PickContents schema={oss} value={value} onChange={newValue => handleChangeSelected(newValue)} rows={10} />
</div>
);
}

View File

@ -93,7 +93,7 @@ export function DlgCreateOperation() {
submitText='Создать'
canSubmit={isValid}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
className='w-160 px-6 h-128'
className='w-180 px-6 h-128'
helpTopic={HelpTopic.CC_OSS}
>
<Tabs

View File

@ -12,7 +12,7 @@ import { Checkbox, Label, TextArea, TextInput } from '@/components/input';
import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateOperationDTO } from '../../backend/types';
import { SelectBlock } from '../../components/select-block';
import { SelectParent } from '../../components/select-parent';
import { sortItemsForOSS } from '../../models/oss-api';
import { type DlgCreateOperationProps } from './dlg-create-operation';
@ -66,7 +66,7 @@ export function TabInputOperation() {
<TextInput
id='operation_alias' //
label='Сокращение'
className='w-64'
className='w-80'
{...register('item_data.alias')}
error={errors.item_data?.alias}
/>
@ -74,7 +74,7 @@ export function TabInputOperation() {
name='item_data.parent'
control={control}
render={({ field }) => (
<SelectBlock
<SelectParent
items={oss.blocks}
value={field.value ? oss.blockByID.get(field.value) ?? null : null}
placeholder='Блок содержания'

View File

@ -5,7 +5,7 @@ import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateOperationDTO } from '../../backend/types';
import { PickMultiOperation } from '../../components/pick-multi-operation';
import { SelectBlock } from '../../components/select-block';
import { SelectParent } from '../../components/select-parent';
import { type DlgCreateOperationProps } from './dlg-create-operation';
@ -31,7 +31,7 @@ export function TabSynthesisOperation() {
<TextInput
id='operation_alias' //
label='Сокращение'
className='w-64'
className='w-80'
{...register('item_data.alias')}
error={errors.item_data?.alias}
/>
@ -39,7 +39,7 @@ export function TabSynthesisOperation() {
name='item_data.parent'
control={control}
render={({ field }) => (
<SelectBlock
<SelectParent
items={oss.blocks}
value={field.value ? oss.blockByID.get(field.value) ?? null : null}
placeholder='Блок содержания'

View File

@ -0,0 +1,84 @@
'use client';
import { Controller, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { TextArea, TextInput } from '@/components/input';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { type IOssLayout, type IUpdateBlockDTO, schemaUpdateBlock } from '../backend/types';
import { useUpdateBlock } from '../backend/use-update-block';
import { SelectParent } from '../components/select-parent';
import { type IBlock, type IOperationSchema } from '../models/oss';
export interface DlgEditBlockProps {
oss: IOperationSchema;
target: IBlock;
layout: IOssLayout;
}
export function DlgEditBlock() {
const { oss, target, layout } = useDialogsStore(state => state.props as DlgEditBlockProps);
const { updateBlock } = useUpdateBlock();
const {
handleSubmit,
control,
register,
formState: { errors, isValid }
} = useForm<IUpdateBlockDTO>({
resolver: zodResolver(schemaUpdateBlock),
defaultValues: {
target: target.id,
item_data: {
title: target.title,
description: target.description,
parent: target.parent
},
layout: layout
},
mode: 'onChange'
});
function onSubmit(data: IUpdateBlockDTO) {
return updateBlock({ itemID: oss.id, data });
}
return (
<ModalForm
header='Редактирование блока'
submitText='Сохранить'
canSubmit={isValid}
onSubmit={event => void handleSubmit(onSubmit)(event)}
className='w-160 px-6 h-fit cc-column'
>
<TextInput
id='operation_title' //
label='Название'
{...register('item_data.title')}
error={errors.item_data?.title}
/>
<Controller
name='item_data.parent'
control={control}
render={({ field }) => (
<SelectParent
items={oss.blocks.filter(block => block.id !== target.id)}
value={field.value ? oss.blockByID.get(field.value) ?? null : null}
placeholder='Блок содержания не выбран'
onChange={value => field.onChange(value ? value.id : null)}
/>
)}
/>
<TextArea
id='operation_comment' //
label='Описание'
noResize
rows={5}
{...register('item_data.description')}
/>
</ModalForm>
);
}

View File

@ -4,7 +4,7 @@ import { TextArea, TextInput } from '@/components/input';
import { useDialogsStore } from '@/stores/dialogs';
import { type IUpdateOperationDTO } from '../../backend/types';
import { SelectBlock } from '../../components/select-block';
import { SelectParent } from '../../components/select-parent';
import { type DlgEditOperationProps } from './dlg-edit-operation';
@ -24,12 +24,11 @@ export function TabOperation() {
{...register('item_data.title')}
error={errors.item_data?.title}
/>
<div className='flex gap-6'>
<div className='grid gap-1'>
<TextInput
id='operation_alias' //
label='Сокращение'
className='w-64'
className='w-80'
{...register('item_data.alias')}
error={errors.item_data?.alias}
/>
@ -37,7 +36,7 @@ export function TabOperation() {
name='item_data.parent'
control={control}
render={({ field }) => (
<SelectBlock
<SelectParent
items={oss.blocks}
value={field.value ? oss.blockByID.get(field.value) ?? null : null}
placeholder='Блок содержания'
@ -45,16 +44,15 @@ export function TabOperation() {
/>
)}
/>
</div>
<TextArea
id='operation_comment'
label='Описание'
noResize
rows={3}
rows={5}
{...register('item_data.description')}
error={errors.item_data?.description}
/>
</div>
</div>
);
}

View File

@ -44,10 +44,10 @@ export function BlockNode(node: BlockInternalNode) {
>
<div
className={clsx(
'w-fit mx-auto -translate-y-[14px]',
'w-fit mx-auto -translate-y-1/2 -mt-[8px]',
'px-2',
'bg-background rounded-lg',
'text-xs line-clamp-1 text-ellipsis',
'text-[18px]/[20px] line-clamp-2 text-center text-ellipsis',
'pointer-events-auto'
)}
>

View File

@ -18,6 +18,7 @@ import { type IOperationSchema } from '@/features/oss/models/oss';
import { useMainHeight } from '@/stores/app-layout';
import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants';
import { promptText } from '@/utils/labels';
import { useMutatingOss } from '../../../backend/use-mutating-oss';
import { useUpdateLayout } from '../../../backend/use-update-layout';
@ -190,6 +191,9 @@ export function OssFlow() {
if (!block) {
return;
}
if (!window.confirm(promptText.deleteBlock)) {
return;
}
void deleteBlock({ itemID: schema.id, data: { target: block.id, layout: getLayout() } });
}
}

View File

@ -69,10 +69,11 @@ export function ToolbarOssGraph({
const toggleEdgeAnimate = useOSSGraphStore(state => state.toggleEdgeAnimate);
const toggleEdgeStraight = useOSSGraphStore(state => state.toggleEdgeStraight);
const { updateLayout: updatePositions } = useUpdateLayout();
const { executeOperation: operationExecute } = useExecuteOperation();
const { updateLayout } = useUpdateLayout();
const { executeOperation } = useExecuteOperation();
const showEditOperation = useDialogsStore(state => state.showEditOperation);
const showEditBlock = useDialogsStore(state => state.showEditBlock);
const readyForSynthesis = (() => {
if (!selectedOperation || selectedOperation.operation_type !== OperationType.SYNTHESIS) {
@ -100,28 +101,33 @@ export function ToolbarOssGraph({
}
function handleSavePositions() {
void updatePositions({ itemID: schema.id, data: getLayout() });
void updateLayout({ itemID: schema.id, data: getLayout() });
}
function handleOperationExecute() {
if (!readyForSynthesis || !selectedOperation) {
return;
}
void operationExecute({
void executeOperation({
itemID: schema.id, //
data: { target: selectedOperation.id, layout: getLayout() }
});
}
function handleEditItem() {
if (!selectedOperation) {
return;
}
if (selectedOperation) {
showEditOperation({
oss: schema,
target: selectedOperation,
layout: getLayout()
});
} else if (selectedBlock) {
showEditBlock({
oss: schema,
target: selectedBlock,
layout: getLayout()
});
}
}
return (

View File

@ -9,6 +9,7 @@ import { type DlgChangeInputSchemaProps } from '@/features/oss/dialogs/dlg-chang
import { type DlgCreateBlockProps } from '@/features/oss/dialogs/dlg-create-block/dlg-create-block';
import { type DlgCreateOperationProps } from '@/features/oss/dialogs/dlg-create-operation/dlg-create-operation';
import { type DlgDeleteOperationProps } from '@/features/oss/dialogs/dlg-delete-operation';
import { type DlgEditBlockProps } from '@/features/oss/dialogs/dlg-edit-block';
import { type DlgEditOperationProps } from '@/features/oss/dialogs/dlg-edit-operation/dlg-edit-operation';
import { type DlgRelocateConstituentsProps } from '@/features/oss/dialogs/dlg-relocate-constituents';
import { type DlgCreateCstProps } from '@/features/rsform/dialogs/dlg-create-cst/dlg-create-cst';
@ -36,6 +37,7 @@ export const DialogType = {
RENAME_CONSTITUENTA: 6,
CREATE_BLOCK: 7,
EDIT_BLOCK: 25,
CREATE_OPERATION: 8,
EDIT_OPERATION: 9,
@ -76,6 +78,7 @@ interface DialogsStore {
showDeleteCst: (props: DlgDeleteCstProps) => void;
showEditEditors: (props: DlgEditEditorsProps) => void;
showEditOperation: (props: DlgEditOperationProps) => void;
showEditBlock: (props: DlgEditBlockProps) => void;
showEditReference: (props: DlgEditReferenceProps) => void;
showEditVersions: (props: DlgEditVersionsProps) => void;
showEditWordForms: (props: DlgEditWordFormsProps) => void;
@ -112,6 +115,7 @@ export const useDialogsStore = create<DialogsStore>()(set => ({
showDeleteCst: props => set({ active: DialogType.DELETE_CONSTITUENTA, props: props }),
showEditEditors: props => set({ active: DialogType.EDIT_EDITORS, props: props }),
showEditOperation: props => set({ active: DialogType.EDIT_OPERATION, props: props }),
showEditBlock: props => set({ active: DialogType.EDIT_BLOCK, props: props }),
showEditReference: props => set({ active: DialogType.EDIT_REFERENCE, props: props }),
showEditVersions: props => set({ active: DialogType.EDIT_VERSIONS, props: props }),
showEditWordForms: props => set({ active: DialogType.EDIT_WORD_FORMS, props: props }),

View File

@ -79,6 +79,7 @@ export const tooltipText = {
export const promptText = {
promptUnsaved: 'Присутствуют несохраненные изменения. Продолжить без их учета?',
deleteLibraryItem: 'Вы уверены, что хотите удалить данную схему?',
deleteBlock: 'Вы уверены, что хотите удалить данный блок?',
deleteOSS:
'Внимание!!\nУдаление операционной схемы приведет к удалению всех операций и собственных концептуальных схем.\nДанное действие нельзя отменить.\nВы уверены, что хотите удалить данную ОСС?',
generateWordforms: 'Данное действие приведет к перезаписи словоформ при совпадении граммем. Продолжить?',