From b6f1ff3337796d5175331ff0b0e146646a894da9 Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Wed, 12 Feb 2025 00:14:18 +0300 Subject: [PATCH] F: Rework Edit Operation dialog --- .../frontend/src/features/oss/backend/api.ts | 29 +-- .../oss/components/PickMultiOperation.tsx | 4 +- .../DlgCreateOperation/DlgCreateOperation.tsx | 4 +- .../DlgCreateOperation/TabInputOperation.tsx | 10 +- .../TabSynthesisOperation.tsx | 12 +- .../DlgEditOperation/DlgEditOperation.tsx | 183 +++++------------- .../dialogs/DlgEditOperation/TabArguments.tsx | 48 +++-- .../dialogs/DlgEditOperation/TabOperation.tsx | 30 +-- .../dialogs/DlgEditOperation/TabSynthesis.tsx | 62 +++--- .../frontend/src/features/oss/models/oss.ts | 17 +- .../src/features/oss/models/ossAPI.ts | 1 - .../pages/OssPage/EditorOssGraph/OssFlow.tsx | 5 +- .../oss/pages/OssPage/OssEditContext.tsx | 7 +- .../features/rsform/backend/useRSForms.tsx | 4 +- .../rsform/components/PickSubstitutions.tsx | 20 +- 15 files changed, 194 insertions(+), 242 deletions(-) diff --git a/rsconcept/frontend/src/features/oss/backend/api.ts b/rsconcept/frontend/src/features/oss/backend/api.ts index 421bb074..030c3dd6 100644 --- a/rsconcept/frontend/src/features/oss/backend/api.ts +++ b/rsconcept/frontend/src/features/oss/backend/api.ts @@ -6,11 +6,11 @@ import { DELAYS } from '@/backend/configuration'; import { ILibraryItem, ILibraryItemData, LibraryItemID } from '@/features/library/models/library'; import { IArgument, - ICstSubstitute, ICstSubstituteEx, IOperation, OperationID, - OperationType + OperationType, + schemaCstSubstitute } from '@/features/oss/models/oss'; import { IConstituentaReference, ITargetCst } from '@/features/rsform/models/rsform'; import { information } from '@/utils/labels'; @@ -122,15 +122,22 @@ export type IInputUpdateDTO = z.infer; /** * Represents {@link IOperation} data, used in update process. */ -export interface IOperationUpdateDTO extends ITargetOperation { - item_data: { - alias: string; - title: string; - comment: string; - }; - arguments: OperationID[] | undefined; - substitutions: ICstSubstitute[] | undefined; -} +export const schemaOperationUpdate = z.object({ + target: z.number(), + positions: z.array(schemaOperationPosition), + item_data: z.object({ + alias: z.string().nonempty(), + title: z.string(), + comment: z.string() + }), + arguments: z.array(z.number()), + substitutions: z.array(schemaCstSubstitute) +}); + +/** + * Represents {@link IOperation} data, used in update process. + */ +export type IOperationUpdateDTO = z.infer; /** * Represents data, used relocating {@link IConstituenta}s between {@link ILibraryItem}s. diff --git a/rsconcept/frontend/src/features/oss/components/PickMultiOperation.tsx b/rsconcept/frontend/src/features/oss/components/PickMultiOperation.tsx index 8c44b4c8..eecf616c 100644 --- a/rsconcept/frontend/src/features/oss/components/PickMultiOperation.tsx +++ b/rsconcept/frontend/src/features/oss/components/PickMultiOperation.tsx @@ -21,7 +21,7 @@ interface PickMultiOperationProps extends CProps.Styling { const columnHelper = createColumnHelper(); -function PickMultiOperation({ rows, items, value, onChange, className, ...restProps }: PickMultiOperationProps) { +export function PickMultiOperation({ rows, items, value, onChange, className, ...restProps }: PickMultiOperationProps) { const selectedItems = value.map(itemID => items.find(item => item.id === itemID)!); const nonSelectedItems = items.filter(item => !value.includes(item.id)); const [lastSelected, setLastSelected] = useState(undefined); @@ -134,5 +134,3 @@ function PickMultiOperation({ rows, items, value, onChange, className, ...restPr ); } - -export default PickMultiOperation; diff --git a/rsconcept/frontend/src/features/oss/dialogs/DlgCreateOperation/DlgCreateOperation.tsx b/rsconcept/frontend/src/features/oss/dialogs/DlgCreateOperation/DlgCreateOperation.tsx index 9fab219f..037c4bde 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/DlgCreateOperation/DlgCreateOperation.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/DlgCreateOperation/DlgCreateOperation.tsx @@ -115,11 +115,11 @@ function DlgCreateOperation() { - + - + diff --git a/rsconcept/frontend/src/features/oss/dialogs/DlgCreateOperation/TabInputOperation.tsx b/rsconcept/frontend/src/features/oss/dialogs/DlgCreateOperation/TabInputOperation.tsx index 7e6acde4..c001da5e 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/DlgCreateOperation/TabInputOperation.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/DlgCreateOperation/TabInputOperation.tsx @@ -7,17 +7,15 @@ import { IconReset } from '@/components/Icons'; import { Checkbox, Label, TextArea, TextInput } from '@/components/Input'; import { useLibrary } from '@/features/library/backend/useLibrary'; import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/features/library/models/library'; -import { IOperationSchema } from '@/features/oss/models/oss'; import { sortItemsForOSS } from '@/features/oss/models/ossAPI'; import PickSchema from '@/features/rsform/components/PickSchema'; +import { useDialogsStore } from '@/stores/dialogs'; import { IOperationCreateDTO } from '../../backend/api'; +import { DlgCreateOperationProps } from './DlgCreateOperation'; -interface TabInputOperationProps { - oss: IOperationSchema; -} - -function TabInputOperation({ oss }: TabInputOperationProps) { +function TabInputOperation() { + const { oss } = useDialogsStore(state => state.props as DlgCreateOperationProps); const { items: libraryItems } = useLibrary(); const sortedItems = sortItemsForOSS(oss, libraryItems); diff --git a/rsconcept/frontend/src/features/oss/dialogs/DlgCreateOperation/TabSynthesisOperation.tsx b/rsconcept/frontend/src/features/oss/dialogs/DlgCreateOperation/TabSynthesisOperation.tsx index cebda587..9cc2638e 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/DlgCreateOperation/TabSynthesisOperation.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/DlgCreateOperation/TabSynthesisOperation.tsx @@ -2,16 +2,14 @@ import { Controller, useFormContext, useWatch } from 'react-hook-form'; import { FlexColumn } from '@/components/Container'; import { Label, TextArea, TextInput } from '@/components/Input'; +import { useDialogsStore } from '@/stores/dialogs'; import { IOperationCreateDTO } from '../../backend/api'; -import PickMultiOperation from '../../components/PickMultiOperation'; -import { IOperationSchema } from '../../models/oss'; +import { PickMultiOperation } from '../../components/PickMultiOperation'; +import { DlgCreateOperationProps } from './DlgCreateOperation'; -interface TabSynthesisOperationProps { - oss: IOperationSchema; -} - -function TabSynthesisOperation({ oss }: TabSynthesisOperationProps) { +function TabSynthesisOperation() { + const { oss } = useDialogsStore(state => state.props as DlgCreateOperationProps); const { register, control, diff --git a/rsconcept/frontend/src/features/oss/dialogs/DlgEditOperation/DlgEditOperation.tsx b/rsconcept/frontend/src/features/oss/dialogs/DlgEditOperation/DlgEditOperation.tsx index 9a5e0dfd..832a27f4 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/DlgEditOperation/DlgEditOperation.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/DlgEditOperation/DlgEditOperation.tsx @@ -1,19 +1,19 @@ 'use client'; +import { zodResolver } from '@hookform/resolvers/zod'; import clsx from 'clsx'; -import { useCallback, useEffect, useState } from 'react'; +import { Suspense, useState } from 'react'; +import { FormProvider, useForm, useWatch } from 'react-hook-form'; +import { Loader } from '@/components/Loader'; import { ModalForm } from '@/components/Modal'; import { TabLabel, TabList, TabPanel, Tabs } from '@/components/Tabs'; import { HelpTopic } from '@/features/help/models/helpTopic'; -import { LibraryItemID } from '@/features/library/models/library'; -import { useRSForms } from '@/features/rsform/backend/useRSForms'; -import { ConstituentaID } from '@/features/rsform/models/rsform'; import { useDialogsStore } from '@/stores/dialogs'; -import { IOperationUpdateDTO } from '../../backend/api'; -import { ICstSubstitute, IOperation, IOperationSchema, OperationID, OperationType } from '../../models/oss'; -import { SubstitutionValidator } from '../../models/ossAPI'; +import { IOperationPosition, IOperationUpdateDTO, schemaOperationUpdate } from '../../backend/api'; +import { useOperationUpdate } from '../../backend/useOperationUpdate'; +import { IOperation, IOperationSchema, OperationType } from '../../models/oss'; import TabArguments from './TabArguments'; import TabOperation from './TabOperation'; import TabSynthesis from './TabSynthesis'; @@ -21,7 +21,7 @@ import TabSynthesis from './TabSynthesis'; export interface DlgEditOperationProps { oss: IOperationSchema; target: IOperation; - onSubmit: (data: IOperationUpdateDTO) => void; + positions: IOperationPosition[]; } export enum TabID { @@ -31,94 +31,32 @@ export enum TabID { } function DlgEditOperation() { - const { oss, target, onSubmit } = useDialogsStore(state => state.props as DlgEditOperationProps); + const { oss, target, positions } = useDialogsStore(state => state.props as DlgEditOperationProps); + const { operationUpdate } = useOperationUpdate(); + + const methods = useForm({ + resolver: zodResolver(schemaOperationUpdate), + defaultValues: { + item_data: { + alias: target.alias, + title: target.alias, + comment: target.comment + }, + arguments: target.arguments, + substitutions: target.substitutions.map(sub => ({ + original: sub.original, + substitution: sub.substitution + })), + positions: positions + } + }); + const alias = useWatch({ control: methods.control, name: 'item_data.alias' }); + const canSubmit = alias !== ''; + const [activeTab, setActiveTab] = useState(TabID.CARD); - const [alias, setAlias] = useState(target.alias); - const [title, setTitle] = useState(target.title); - const [comment, setComment] = useState(target.comment); - - const [isCorrect, setIsCorrect] = useState(true); - const [validationText, setValidationText] = useState(''); - - const initialInputs = oss.graph.expandInputs([target.id]); - const [inputs, setInputs] = useState(initialInputs); - const inputOperations = inputs.map(id => oss.operationByID.get(id)!); - - const [substitutions, setSubstitutions] = useState(target.substitutions); - const [suggestions, setSuggestions] = useState([]); - - const [schemasIDs, setSchemaIDs] = useState([]); - const schemas = useRSForms(schemasIDs); - - const isModified = - alias !== target.alias || - title !== target.title || - comment !== target.comment || - JSON.stringify(initialInputs) !== JSON.stringify(inputs) || - JSON.stringify(substitutions) !== JSON.stringify(target.substitutions); - - const canSubmit = isModified && alias !== ''; - - const getSchemaByCst = useCallback( - (id: ConstituentaID) => { - for (const schema of schemas) { - const cst = schema.items.find(cst => cst.id === id); - if (cst) { - return schema; - } - } - return undefined; - }, - [schemas] - ); - - useEffect(() => { - setSchemaIDs(inputOperations.map(operation => operation.result).filter(id => id !== null)); - }, [inputOperations]); - - useEffect(() => { - if (schemas.length !== schemasIDs.length || schemas.length === 0) { - return; - } - setSubstitutions(prev => - prev.filter(sub => { - const original = getSchemaByCst(sub.original); - if (!original || !schemasIDs.includes(original.id)) { - return false; - } - const substitution = getSchemaByCst(sub.substitution); - if (!substitution || !schemasIDs.includes(substitution.id)) { - return false; - } - return true; - }) - ); - }, [schemasIDs, schemas, getSchemaByCst]); - - useEffect(() => { - if (schemas.length !== schemasIDs.length || schemas.length === 0) { - return; - } - const validator = new SubstitutionValidator(schemas, substitutions); - setIsCorrect(validator.validate()); - setValidationText(validator.msg); - setSuggestions(validator.suggestions); - }, [substitutions, schemas, schemasIDs.length]); - - function handleSubmit() { - onSubmit({ - target: target.id, - item_data: { - alias: alias, - title: title, - comment: comment - }, - positions: [], - arguments: target.operation_type !== OperationType.SYNTHESIS ? undefined : inputs, - substitutions: target.operation_type !== OperationType.SYNTHESIS ? undefined : substitutions - }); - return true; + function onSubmit(data: IOperationUpdateDTO) { + return operationUpdate({ itemID: oss.id, data }); } return ( @@ -126,7 +64,7 @@ function DlgEditOperation() { header='Редактирование операции' submitText='Сохранить' canSubmit={canSubmit} - onSubmit={handleSubmit} + onSubmit={event => void methods.handleSubmit(onSubmit)(event)} className='w-[40rem] px-6 h-[32rem]' helpTopic={HelpTopic.UI_SUBSTITUTIONS} hideHelpWhen={() => activeTab !== TabID.SUBSTITUTION} @@ -143,47 +81,28 @@ function DlgEditOperation() { ) : null} {target.operation_type === OperationType.SYNTHESIS ? ( - (не прошла проверку)')} - label={isCorrect ? 'Отождествления' : 'Отождествления*'} - className='w-[8rem]' - /> + ) : null} - - - + + + + - {target.operation_type === OperationType.SYNTHESIS ? ( - - - - ) : null} - {target.operation_type === OperationType.SYNTHESIS ? ( - - - - ) : null} + {target.operation_type === OperationType.SYNTHESIS ? ( + + + + ) : null} + {target.operation_type === OperationType.SYNTHESIS ? ( + + }> + + + + ) : null} + ); diff --git a/rsconcept/frontend/src/features/oss/dialogs/DlgEditOperation/TabArguments.tsx b/rsconcept/frontend/src/features/oss/dialogs/DlgEditOperation/TabArguments.tsx index dd6a0ae0..41a35596 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/DlgEditOperation/TabArguments.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/DlgEditOperation/TabArguments.tsx @@ -1,25 +1,47 @@ 'use client'; +import { Controller, useFormContext } from 'react-hook-form'; + import { FlexColumn } from '@/components/Container'; import { Label } from '@/components/Input'; +import { LibraryItemID } from '@/features/library/models/library'; +import { useDialogsStore } from '@/stores/dialogs'; -import PickMultiOperation from '../../components/PickMultiOperation'; -import { IOperationSchema, OperationID } from '../../models/oss'; +import { IOperationUpdateDTO } from '../../backend/api'; +import { PickMultiOperation } from '../../components/PickMultiOperation'; +import { DlgEditOperationProps } from './DlgEditOperation'; -interface TabArgumentsProps { - oss: IOperationSchema; - target: OperationID; - inputs: OperationID[]; - setInputs: React.Dispatch>; -} - -function TabArguments({ oss, inputs, target, setInputs }: TabArgumentsProps) { - const potentialCycle = [target, ...oss.graph.expandAllOutputs([target])]; +function TabArguments() { + const { control, setValue } = useFormContext(); + const { oss, target } = useDialogsStore(state => state.props as DlgEditOperationProps); + const potentialCycle = [target.id, ...oss.graph.expandAllOutputs([target.id])]; const filtered = oss.items.filter(item => !potentialCycle.includes(item.id)); + + function handleChangeArguments(prev: LibraryItemID[], newValue: LibraryItemID[]) { + setValue('arguments', newValue); + if (prev.some(id => !newValue.includes(id))) { + setValue('substitutions', []); + } + } + return (
-
); diff --git a/rsconcept/frontend/src/features/oss/dialogs/DlgEditOperation/TabOperation.tsx b/rsconcept/frontend/src/features/oss/dialogs/DlgEditOperation/TabOperation.tsx index bd275d99..3f0a156f 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/DlgEditOperation/TabOperation.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/DlgEditOperation/TabOperation.tsx @@ -1,30 +1,30 @@ +import { useFormContext } from 'react-hook-form'; + import { TextArea, TextInput } from '@/components/Input'; -interface TabOperationProps { - alias: string; - onChangeAlias: (newValue: string) => void; - title: string; - onChangeTitle: (newValue: string) => void; - comment: string; - onChangeComment: (newValue: string) => void; -} +import { IOperationUpdateDTO } from '../../backend/api'; + +function TabOperation() { + const { + register, + formState: { errors } + } = useFormContext(); -function TabOperation({ alias, onChangeAlias, title, onChangeTitle, comment, onChangeComment }: TabOperationProps) { return (
onChangeTitle(event.target.value)} + {...register('item_data.title')} + error={errors.item_data?.title} />
onChangeAlias(event.target.value)} + {...register('item_data.alias')} + error={errors.item_data?.alias} />