From acf0f4e9f66a47f606c4d854ebb727138972325d Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Wed, 12 Feb 2025 02:11:01 +0300 Subject: [PATCH] R: Upgrade SubstituteCst dialog --- .../frontend/src/features/oss/backend/api.ts | 10 +--- .../frontend/src/features/oss/models/oss.ts | 24 +-------- .../src/features/oss/models/ossAPI.ts | 3 +- .../src/features/rsform/backend/api.ts | 34 ++++++++++-- .../rsform/backend/useCstSubstitute.tsx | 5 +- .../rsform/components/PickSubstitutions.tsx | 3 +- .../DlgInlineSynthesis/DlgInlineSynthesis.tsx | 3 +- .../DlgInlineSynthesis/TabSubstitutions.tsx | 2 +- .../rsform/dialogs/DlgSubstituteCst.tsx | 54 +++++++++++++------ .../rsform/pages/RSFormPage/MenuRSTabs.tsx | 6 +-- rsconcept/frontend/src/utils/labels.ts | 3 +- 11 files changed, 80 insertions(+), 67 deletions(-) diff --git a/rsconcept/frontend/src/features/oss/backend/api.ts b/rsconcept/frontend/src/features/oss/backend/api.ts index 8ed381ce..eecefaa9 100644 --- a/rsconcept/frontend/src/features/oss/backend/api.ts +++ b/rsconcept/frontend/src/features/oss/backend/api.ts @@ -4,14 +4,8 @@ import { z } from 'zod'; import { axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport'; import { DELAYS } from '@/backend/configuration'; import { ILibraryItem, ILibraryItemData, LibraryItemID } from '@/features/library/models/library'; -import { - IArgument, - ICstSubstituteEx, - IOperation, - OperationID, - OperationType, - schemaCstSubstitute -} from '@/features/oss/models/oss'; +import { IArgument, ICstSubstituteEx, IOperation, OperationID, OperationType } from '@/features/oss/models/oss'; +import { schemaCstSubstitute } from '@/features/rsform/backend/api'; import { IConstituentaReference, ITargetCst } from '@/features/rsform/models/rsform'; import { infoMsg } from '@/utils/labels'; diff --git a/rsconcept/frontend/src/features/oss/models/oss.ts b/rsconcept/frontend/src/features/oss/models/oss.ts index 0315d408..e7238565 100644 --- a/rsconcept/frontend/src/features/oss/models/oss.ts +++ b/rsconcept/frontend/src/features/oss/models/oss.ts @@ -1,10 +1,8 @@ /** * Module: Schema of Synthesis Operations. */ - -import { z } from 'zod'; - import { ILibraryItem, ILibraryItemData, LibraryItemID } from '@/features/library/models/library'; +import { ICstSubstitute } from '@/features/rsform/backend/api'; import { IConstituenta } from '@/features/rsform/models/rsform'; import { Graph } from '../../../models/Graph'; @@ -53,26 +51,6 @@ export interface IArgument { argument: OperationID; } -/** - * Represents data, used in merging single {@link IConstituenta}. - */ -export const schemaCstSubstitute = z.object({ - original: z.number(), - substitution: z.number() -}); - -/** - * Represents data, used in merging single {@link IConstituenta}. - */ -export type ICstSubstitute = z.infer; - -/** - * Represents data, used in merging multiple {@link IConstituenta}. - */ -export interface ICstSubstitutions { - substitutions: ICstSubstitute[]; -} - /** * Represents substitution for multi synthesis table. */ diff --git a/rsconcept/frontend/src/features/oss/models/ossAPI.ts b/rsconcept/frontend/src/features/oss/models/ossAPI.ts index 4ed8a75f..4230cdbb 100644 --- a/rsconcept/frontend/src/features/oss/models/ossAPI.ts +++ b/rsconcept/frontend/src/features/oss/models/ossAPI.ts @@ -3,6 +3,7 @@ */ import { ILibraryItem, LibraryItemID } from '@/features/library/models/library'; +import { ICstSubstitute } from '@/features/rsform/backend/api'; import { ConstituentaID, CstClass, CstType, IConstituenta, IRSForm } from '@/features/rsform/models/rsform'; import { AliasMapping, ParsingStatus } from '@/features/rsform/models/rslang'; import { @@ -18,7 +19,7 @@ import { TextMatcher } from '@/utils/utils'; import { Graph } from '../../../models/Graph'; import { IOperationPosition } from '../backend/api'; import { describeSubstitutionError } from '../labels'; -import { ICstSubstitute, IOperation, IOperationSchema, OperationID, OperationType, SubstitutionErrorType } from './oss'; +import { IOperation, IOperationSchema, OperationID, OperationType, SubstitutionErrorType } from './oss'; import { Position2D } from './ossLayout'; /** diff --git a/rsconcept/frontend/src/features/rsform/backend/api.ts b/rsconcept/frontend/src/features/rsform/backend/api.ts index e045a945..f9e74adf 100644 --- a/rsconcept/frontend/src/features/rsform/backend/api.ts +++ b/rsconcept/frontend/src/features/rsform/backend/api.ts @@ -9,8 +9,7 @@ import { LibraryItemID, VersionID } from '@/features/library/models/library'; -import { ICstSubstitute, ICstSubstitutions } from '@/features/oss/models/oss'; -import { infoMsg } from '@/utils/labels'; +import { errorMsg, infoMsg } from '@/utils/labels'; import { ConstituentaID, @@ -127,6 +126,19 @@ export interface IProduceStructureResponse { schema: IRSFormDTO; } +/** + * Represents data, used in merging single {@link IConstituenta}. + */ +export const schemaCstSubstitute = z.object({ + original: z.number(), + substitution: z.number() +}); + +/** + * Represents data, used in merging single {@link IConstituenta}. + */ +export type ICstSubstitute = z.infer; + /** * Represents input data for inline synthesis. */ @@ -146,6 +158,18 @@ export interface ICheckConstituentaDTO { definition_formal: string; } +/** + * Represents data, used in renaming {@link IConstituenta}. + */ +export const schemaCstSubstitutions = z.object({ + substitutions: z.array(schemaCstSubstitute).min(1, { message: errorMsg.emptySubstitutions }) +}); + +/** + * Represents data, used in merging multiple {@link IConstituenta}. + */ +export type ICstSubstitutionsDTO = z.infer; + export const rsformsApi = { baseKey: 'rsform', @@ -214,8 +238,8 @@ export const rsformsApi = { successMessage: infoMsg.changesSaved } }), - cstSubstitute: ({ itemID, data }: { itemID: LibraryItemID; data: ICstSubstitutions }) => - axiosPatch({ + cstSubstitute: ({ itemID, data }: { itemID: LibraryItemID; data: ICstSubstitutionsDTO }) => + axiosPatch({ endpoint: `/api/rsforms/${itemID}/substitute`, request: { data: data, @@ -229,7 +253,7 @@ export const rsformsApi = { }), produceStructure: ({ itemID, data }: { itemID: LibraryItemID; data: ITargetCst }) => - axiosPost({ + axiosPatch({ endpoint: `/api/rsforms/${itemID}/produce-structure`, request: { data: data, diff --git a/rsconcept/frontend/src/features/rsform/backend/useCstSubstitute.tsx b/rsconcept/frontend/src/features/rsform/backend/useCstSubstitute.tsx index 69cd8624..932d821c 100644 --- a/rsconcept/frontend/src/features/rsform/backend/useCstSubstitute.tsx +++ b/rsconcept/frontend/src/features/rsform/backend/useCstSubstitute.tsx @@ -3,9 +3,8 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useUpdateTimestamp } from '@/features/library/backend/useUpdateTimestamp'; import { LibraryItemID } from '@/features/library/models/library'; import { ossApi } from '@/features/oss/backend/api'; -import { ICstSubstitutions } from '@/features/oss/models/oss'; -import { rsformsApi } from './api'; +import { ICstSubstitutionsDTO, rsformsApi } from './api'; export const useCstSubstitute = () => { const client = useQueryClient(); @@ -27,6 +26,6 @@ export const useCstSubstitute = () => { } }); return { - cstSubstitute: (data: { itemID: LibraryItemID; data: ICstSubstitutions }) => mutation.mutateAsync(data) + cstSubstitute: (data: { itemID: LibraryItemID; data: ICstSubstitutionsDTO }) => mutation.mutateAsync(data) }; }; diff --git a/rsconcept/frontend/src/features/rsform/components/PickSubstitutions.tsx b/rsconcept/frontend/src/features/rsform/components/PickSubstitutions.tsx index 473784ca..8d60d0cb 100644 --- a/rsconcept/frontend/src/features/rsform/components/PickSubstitutions.tsx +++ b/rsconcept/frontend/src/features/rsform/components/PickSubstitutions.tsx @@ -11,10 +11,11 @@ import { CProps } from '@/components/props'; import { NoData } from '@/components/View'; import SelectLibraryItem from '@/features/library/components/SelectLibraryItem'; import { ILibraryItem } from '@/features/library/models/library'; -import { ICstSubstitute, IMultiSubstitution } from '@/features/oss/models/oss'; +import { IMultiSubstitution } from '@/features/oss/models/oss'; import { APP_COLORS } from '@/styling/colors'; import { errorMsg } from '@/utils/labels'; +import { ICstSubstitute } from '../backend/api'; import { ConstituentaID, IConstituenta, IRSForm } from '../models/rsform'; import BadgeConstituenta from './BadgeConstituenta'; import SelectConstituenta from './SelectConstituenta'; diff --git a/rsconcept/frontend/src/features/rsform/dialogs/DlgInlineSynthesis/DlgInlineSynthesis.tsx b/rsconcept/frontend/src/features/rsform/dialogs/DlgInlineSynthesis/DlgInlineSynthesis.tsx index 91736459..028d0fdf 100644 --- a/rsconcept/frontend/src/features/rsform/dialogs/DlgInlineSynthesis/DlgInlineSynthesis.tsx +++ b/rsconcept/frontend/src/features/rsform/dialogs/DlgInlineSynthesis/DlgInlineSynthesis.tsx @@ -7,11 +7,10 @@ import { Loader } from '@/components/Loader'; import { ModalForm } from '@/components/Modal'; import { TabLabel, TabList, TabPanel, Tabs } from '@/components/Tabs'; import { LibraryItemID } from '@/features/library/models/library'; -import { ICstSubstitute } from '@/features/oss/models/oss'; import { useRSForm } from '@/features/rsform/backend/useRSForm'; import { useDialogsStore } from '@/stores/dialogs'; -import { IInlineSynthesisDTO } from '../../backend/api'; +import { ICstSubstitute, IInlineSynthesisDTO } from '../../backend/api'; import { ConstituentaID, IRSForm } from '../../models/rsform'; import TabConstituents from './TabConstituents'; import TabSource from './TabSource'; diff --git a/rsconcept/frontend/src/features/rsform/dialogs/DlgInlineSynthesis/TabSubstitutions.tsx b/rsconcept/frontend/src/features/rsform/dialogs/DlgInlineSynthesis/TabSubstitutions.tsx index 3be9c1fc..df56964a 100644 --- a/rsconcept/frontend/src/features/rsform/dialogs/DlgInlineSynthesis/TabSubstitutions.tsx +++ b/rsconcept/frontend/src/features/rsform/dialogs/DlgInlineSynthesis/TabSubstitutions.tsx @@ -1,8 +1,8 @@ 'use client'; import { LibraryItemID } from '@/features/library/models/library'; -import { ICstSubstitute } from '@/features/oss/models/oss'; +import { ICstSubstitute } from '../../backend/api'; import { useRSFormSuspense } from '../../backend/useRSForm'; import PickSubstitutions from '../../components/PickSubstitutions'; import { ConstituentaID, IRSForm } from '../../models/rsform'; diff --git a/rsconcept/frontend/src/features/rsform/dialogs/DlgSubstituteCst.tsx b/rsconcept/frontend/src/features/rsform/dialogs/DlgSubstituteCst.tsx index 828c67fa..2ed4094d 100644 --- a/rsconcept/frontend/src/features/rsform/dialogs/DlgSubstituteCst.tsx +++ b/rsconcept/frontend/src/features/rsform/dialogs/DlgSubstituteCst.tsx @@ -1,29 +1,42 @@ 'use client'; +import { zodResolver } from '@hookform/resolvers/zod'; import clsx from 'clsx'; -import { useState } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { ErrorField } from '@/components/Input'; import { ModalForm } from '@/components/Modal'; import { HelpTopic } from '@/features/help/models/helpTopic'; -import { ICstSubstitute, ICstSubstitutions } from '@/features/oss/models/oss'; -import PickSubstitutions from '@/features/rsform/components/PickSubstitutions'; import { useDialogsStore } from '@/stores/dialogs'; +import { ICstSubstitutionsDTO, schemaCstSubstitutions } from '../backend/api'; +import { useCstSubstitute } from '../backend/useCstSubstitute'; +import PickSubstitutions from '../components/PickSubstitutions'; import { IRSForm } from '../models/rsform'; export interface DlgSubstituteCstProps { schema: IRSForm; - onSubstitute: (data: ICstSubstitutions) => void; + onSubstitute: (data: ICstSubstitutionsDTO) => void; } function DlgSubstituteCst() { const { onSubstitute, schema } = useDialogsStore(state => state.props as DlgSubstituteCstProps); - const [substitutions, setSubstitutions] = useState([]); - const canSubmit = substitutions.length > 0; + const { cstSubstitute } = useCstSubstitute(); - function handleSubmit() { - onSubstitute({ substitutions: substitutions }); - return true; + const { + handleSubmit, + control, + formState: { errors, isValid } + } = useForm({ + resolver: zodResolver(schemaCstSubstitutions), + defaultValues: { + substitutions: [] + }, + mode: 'onChange' + }); + + function onSubmit(data: ICstSubstitutionsDTO) { + return cstSubstitute({ itemID: schema.id, data: data }).then(() => onSubstitute(data)); } return ( @@ -31,18 +44,25 @@ function DlgSubstituteCst() { header='Отождествление' submitText='Отождествить' submitInvalidTooltip='Выберите две различные конституенты' - canSubmit={canSubmit} - onSubmit={handleSubmit} + canSubmit={isValid} + onSubmit={event => void handleSubmit(onSubmit)(event)} className={clsx('w-[40rem]', 'px-6 pb-3')} helpTopic={HelpTopic.UI_SUBSTITUTIONS} > - ( + + )} /> + ); } diff --git a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuRSTabs.tsx b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuRSTabs.tsx index cf5ced6e..e3b6d8ca 100644 --- a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuRSTabs.tsx +++ b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuRSTabs.tsx @@ -42,7 +42,6 @@ import { EXTEOR_TRS_FILE } from '@/utils/constants'; import { describeAccessMode, labelAccessMode, tooltipText } from '@/utils/labels'; import { generatePageQR, promptUnsaved, sharePage } from '@/utils/utils'; -import { useCstSubstitute } from '../../backend/useCstSubstitute'; import { useDownloadRSForm } from '../../backend/useDownloadRSForm'; import { useInlineSynthesis } from '../../backend/useInlineSynthesis'; import { useMutatingRSForm } from '../../backend/useMutatingRSForm'; @@ -66,7 +65,6 @@ function MenuRSTabs() { const { restoreOrder } = useRestoreOrder(); const { produceStructure } = useProduceStructure(); const { inlineSynthesis } = useInlineSynthesis(); - const { cstSubstitute } = useCstSubstitute(); const { download } = useDownloadRSForm(); const showInlineSynthesis = useDialogsStore(state => state.showInlineSynthesis); @@ -167,9 +165,7 @@ function MenuRSTabs() { showSubstituteCst({ schema: controller.schema, onSubstitute: data => - void cstSubstitute({ itemID: controller.schema.id, data }).then(() => - controller.setSelected(prev => prev.filter(id => !data.substitutions.find(sub => sub.original === id))) - ) + controller.setSelected(prev => prev.filter(id => !data.substitutions.find(sub => sub.original === id))) }); } diff --git a/rsconcept/frontend/src/utils/labels.ts b/rsconcept/frontend/src/utils/labels.ts index 5297a583..d72931f9 100644 --- a/rsconcept/frontend/src/utils/labels.ts +++ b/rsconcept/frontend/src/utils/labels.ts @@ -139,7 +139,8 @@ export const errorMsg = { privacyNotAccepted: 'Примите политику обработки персональных данных', loginFormat: 'Имя пользователя должно содержать только буквы и цифры', invalidLocation: 'Некорректный формат пути', - versionTaken: 'Версия с таким шифром уже существует' + versionTaken: 'Версия с таким шифром уже существует', + emptySubstitutions: 'Выберите хотя бы одно отождествление' }; /**