R: Upgrade SubstituteCst dialog

This commit is contained in:
Ivan 2025-02-12 02:11:01 +03:00
parent 8346850cf6
commit acf0f4e9f6
11 changed files with 80 additions and 67 deletions

View File

@ -4,14 +4,8 @@ import { z } from 'zod';
import { axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport'; import { axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
import { DELAYS } from '@/backend/configuration'; import { DELAYS } from '@/backend/configuration';
import { ILibraryItem, ILibraryItemData, LibraryItemID } from '@/features/library/models/library'; import { ILibraryItem, ILibraryItemData, LibraryItemID } from '@/features/library/models/library';
import { import { IArgument, ICstSubstituteEx, IOperation, OperationID, OperationType } from '@/features/oss/models/oss';
IArgument, import { schemaCstSubstitute } from '@/features/rsform/backend/api';
ICstSubstituteEx,
IOperation,
OperationID,
OperationType,
schemaCstSubstitute
} from '@/features/oss/models/oss';
import { IConstituentaReference, ITargetCst } from '@/features/rsform/models/rsform'; import { IConstituentaReference, ITargetCst } from '@/features/rsform/models/rsform';
import { infoMsg } from '@/utils/labels'; import { infoMsg } from '@/utils/labels';

View File

@ -1,10 +1,8 @@
/** /**
* Module: Schema of Synthesis Operations. * Module: Schema of Synthesis Operations.
*/ */
import { z } from 'zod';
import { ILibraryItem, ILibraryItemData, LibraryItemID } from '@/features/library/models/library'; import { ILibraryItem, ILibraryItemData, LibraryItemID } from '@/features/library/models/library';
import { ICstSubstitute } from '@/features/rsform/backend/api';
import { IConstituenta } from '@/features/rsform/models/rsform'; import { IConstituenta } from '@/features/rsform/models/rsform';
import { Graph } from '../../../models/Graph'; import { Graph } from '../../../models/Graph';
@ -53,26 +51,6 @@ export interface IArgument {
argument: OperationID; 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<typeof schemaCstSubstitute>;
/**
* Represents data, used in merging multiple {@link IConstituenta}.
*/
export interface ICstSubstitutions {
substitutions: ICstSubstitute[];
}
/** /**
* Represents substitution for multi synthesis table. * Represents substitution for multi synthesis table.
*/ */

View File

@ -3,6 +3,7 @@
*/ */
import { ILibraryItem, LibraryItemID } from '@/features/library/models/library'; 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 { ConstituentaID, CstClass, CstType, IConstituenta, IRSForm } from '@/features/rsform/models/rsform';
import { AliasMapping, ParsingStatus } from '@/features/rsform/models/rslang'; import { AliasMapping, ParsingStatus } from '@/features/rsform/models/rslang';
import { import {
@ -18,7 +19,7 @@ import { TextMatcher } from '@/utils/utils';
import { Graph } from '../../../models/Graph'; import { Graph } from '../../../models/Graph';
import { IOperationPosition } from '../backend/api'; import { IOperationPosition } from '../backend/api';
import { describeSubstitutionError } from '../labels'; 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'; import { Position2D } from './ossLayout';
/** /**

View File

@ -9,8 +9,7 @@ import {
LibraryItemID, LibraryItemID,
VersionID VersionID
} from '@/features/library/models/library'; } from '@/features/library/models/library';
import { ICstSubstitute, ICstSubstitutions } from '@/features/oss/models/oss'; import { errorMsg, infoMsg } from '@/utils/labels';
import { infoMsg } from '@/utils/labels';
import { import {
ConstituentaID, ConstituentaID,
@ -127,6 +126,19 @@ export interface IProduceStructureResponse {
schema: IRSFormDTO; 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<typeof schemaCstSubstitute>;
/** /**
* Represents input data for inline synthesis. * Represents input data for inline synthesis.
*/ */
@ -146,6 +158,18 @@ export interface ICheckConstituentaDTO {
definition_formal: string; 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<typeof schemaCstSubstitutions>;
export const rsformsApi = { export const rsformsApi = {
baseKey: 'rsform', baseKey: 'rsform',
@ -214,8 +238,8 @@ export const rsformsApi = {
successMessage: infoMsg.changesSaved successMessage: infoMsg.changesSaved
} }
}), }),
cstSubstitute: ({ itemID, data }: { itemID: LibraryItemID; data: ICstSubstitutions }) => cstSubstitute: ({ itemID, data }: { itemID: LibraryItemID; data: ICstSubstitutionsDTO }) =>
axiosPatch<ICstSubstitutions, IRSFormDTO>({ axiosPatch<ICstSubstitutionsDTO, IRSFormDTO>({
endpoint: `/api/rsforms/${itemID}/substitute`, endpoint: `/api/rsforms/${itemID}/substitute`,
request: { request: {
data: data, data: data,
@ -229,7 +253,7 @@ export const rsformsApi = {
}), }),
produceStructure: ({ itemID, data }: { itemID: LibraryItemID; data: ITargetCst }) => produceStructure: ({ itemID, data }: { itemID: LibraryItemID; data: ITargetCst }) =>
axiosPost<ITargetCst, IProduceStructureResponse>({ axiosPatch<ITargetCst, IProduceStructureResponse>({
endpoint: `/api/rsforms/${itemID}/produce-structure`, endpoint: `/api/rsforms/${itemID}/produce-structure`,
request: { request: {
data: data, data: data,

View File

@ -3,9 +3,8 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useUpdateTimestamp } from '@/features/library/backend/useUpdateTimestamp'; import { useUpdateTimestamp } from '@/features/library/backend/useUpdateTimestamp';
import { LibraryItemID } from '@/features/library/models/library'; import { LibraryItemID } from '@/features/library/models/library';
import { ossApi } from '@/features/oss/backend/api'; 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 = () => { export const useCstSubstitute = () => {
const client = useQueryClient(); const client = useQueryClient();
@ -27,6 +26,6 @@ export const useCstSubstitute = () => {
} }
}); });
return { return {
cstSubstitute: (data: { itemID: LibraryItemID; data: ICstSubstitutions }) => mutation.mutateAsync(data) cstSubstitute: (data: { itemID: LibraryItemID; data: ICstSubstitutionsDTO }) => mutation.mutateAsync(data)
}; };
}; };

View File

@ -11,10 +11,11 @@ import { CProps } from '@/components/props';
import { NoData } from '@/components/View'; import { NoData } from '@/components/View';
import SelectLibraryItem from '@/features/library/components/SelectLibraryItem'; import SelectLibraryItem from '@/features/library/components/SelectLibraryItem';
import { ILibraryItem } from '@/features/library/models/library'; 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 { APP_COLORS } from '@/styling/colors';
import { errorMsg } from '@/utils/labels'; import { errorMsg } from '@/utils/labels';
import { ICstSubstitute } from '../backend/api';
import { ConstituentaID, IConstituenta, IRSForm } from '../models/rsform'; import { ConstituentaID, IConstituenta, IRSForm } from '../models/rsform';
import BadgeConstituenta from './BadgeConstituenta'; import BadgeConstituenta from './BadgeConstituenta';
import SelectConstituenta from './SelectConstituenta'; import SelectConstituenta from './SelectConstituenta';

View File

@ -7,11 +7,10 @@ import { Loader } from '@/components/Loader';
import { ModalForm } from '@/components/Modal'; import { ModalForm } from '@/components/Modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/Tabs'; import { TabLabel, TabList, TabPanel, Tabs } from '@/components/Tabs';
import { LibraryItemID } from '@/features/library/models/library'; import { LibraryItemID } from '@/features/library/models/library';
import { ICstSubstitute } from '@/features/oss/models/oss';
import { useRSForm } from '@/features/rsform/backend/useRSForm'; import { useRSForm } from '@/features/rsform/backend/useRSForm';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { IInlineSynthesisDTO } from '../../backend/api'; import { ICstSubstitute, IInlineSynthesisDTO } from '../../backend/api';
import { ConstituentaID, IRSForm } from '../../models/rsform'; import { ConstituentaID, IRSForm } from '../../models/rsform';
import TabConstituents from './TabConstituents'; import TabConstituents from './TabConstituents';
import TabSource from './TabSource'; import TabSource from './TabSource';

View File

@ -1,8 +1,8 @@
'use client'; 'use client';
import { LibraryItemID } from '@/features/library/models/library'; 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 { useRSFormSuspense } from '../../backend/useRSForm';
import PickSubstitutions from '../../components/PickSubstitutions'; import PickSubstitutions from '../../components/PickSubstitutions';
import { ConstituentaID, IRSForm } from '../../models/rsform'; import { ConstituentaID, IRSForm } from '../../models/rsform';

View File

@ -1,29 +1,42 @@
'use client'; 'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import clsx from 'clsx'; 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 { ModalForm } from '@/components/Modal';
import { HelpTopic } from '@/features/help/models/helpTopic'; 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 { 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'; import { IRSForm } from '../models/rsform';
export interface DlgSubstituteCstProps { export interface DlgSubstituteCstProps {
schema: IRSForm; schema: IRSForm;
onSubstitute: (data: ICstSubstitutions) => void; onSubstitute: (data: ICstSubstitutionsDTO) => void;
} }
function DlgSubstituteCst() { function DlgSubstituteCst() {
const { onSubstitute, schema } = useDialogsStore(state => state.props as DlgSubstituteCstProps); const { onSubstitute, schema } = useDialogsStore(state => state.props as DlgSubstituteCstProps);
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>([]); const { cstSubstitute } = useCstSubstitute();
const canSubmit = substitutions.length > 0;
function handleSubmit() { const {
onSubstitute({ substitutions: substitutions }); handleSubmit,
return true; control,
formState: { errors, isValid }
} = useForm<ICstSubstitutionsDTO>({
resolver: zodResolver(schemaCstSubstitutions),
defaultValues: {
substitutions: []
},
mode: 'onChange'
});
function onSubmit(data: ICstSubstitutionsDTO) {
return cstSubstitute({ itemID: schema.id, data: data }).then(() => onSubstitute(data));
} }
return ( return (
@ -31,18 +44,25 @@ function DlgSubstituteCst() {
header='Отождествление' header='Отождествление'
submitText='Отождествить' submitText='Отождествить'
submitInvalidTooltip='Выберите две различные конституенты' submitInvalidTooltip='Выберите две различные конституенты'
canSubmit={canSubmit} canSubmit={isValid}
onSubmit={handleSubmit} onSubmit={event => void handleSubmit(onSubmit)(event)}
className={clsx('w-[40rem]', 'px-6 pb-3')} className={clsx('w-[40rem]', 'px-6 pb-3')}
helpTopic={HelpTopic.UI_SUBSTITUTIONS} helpTopic={HelpTopic.UI_SUBSTITUTIONS}
> >
<Controller
control={control}
name='substitutions'
render={({ field }) => (
<PickSubstitutions <PickSubstitutions
allowSelfSubstitution allowSelfSubstitution
value={substitutions} value={field.value}
onChange={setSubstitutions} onChange={field.onChange}
rows={6} rows={6}
schemas={[schema]} schemas={[schema]}
/> />
)}
/>
<ErrorField className='mt-2' error={errors.substitutions} />
</ModalForm> </ModalForm>
); );
} }

View File

@ -42,7 +42,6 @@ import { EXTEOR_TRS_FILE } from '@/utils/constants';
import { describeAccessMode, labelAccessMode, tooltipText } from '@/utils/labels'; import { describeAccessMode, labelAccessMode, tooltipText } from '@/utils/labels';
import { generatePageQR, promptUnsaved, sharePage } from '@/utils/utils'; import { generatePageQR, promptUnsaved, sharePage } from '@/utils/utils';
import { useCstSubstitute } from '../../backend/useCstSubstitute';
import { useDownloadRSForm } from '../../backend/useDownloadRSForm'; import { useDownloadRSForm } from '../../backend/useDownloadRSForm';
import { useInlineSynthesis } from '../../backend/useInlineSynthesis'; import { useInlineSynthesis } from '../../backend/useInlineSynthesis';
import { useMutatingRSForm } from '../../backend/useMutatingRSForm'; import { useMutatingRSForm } from '../../backend/useMutatingRSForm';
@ -66,7 +65,6 @@ function MenuRSTabs() {
const { restoreOrder } = useRestoreOrder(); const { restoreOrder } = useRestoreOrder();
const { produceStructure } = useProduceStructure(); const { produceStructure } = useProduceStructure();
const { inlineSynthesis } = useInlineSynthesis(); const { inlineSynthesis } = useInlineSynthesis();
const { cstSubstitute } = useCstSubstitute();
const { download } = useDownloadRSForm(); const { download } = useDownloadRSForm();
const showInlineSynthesis = useDialogsStore(state => state.showInlineSynthesis); const showInlineSynthesis = useDialogsStore(state => state.showInlineSynthesis);
@ -167,9 +165,7 @@ function MenuRSTabs() {
showSubstituteCst({ showSubstituteCst({
schema: controller.schema, schema: controller.schema,
onSubstitute: data => 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)))
)
}); });
} }

View File

@ -139,7 +139,8 @@ export const errorMsg = {
privacyNotAccepted: 'Примите политику обработки персональных данных', privacyNotAccepted: 'Примите политику обработки персональных данных',
loginFormat: 'Имя пользователя должно содержать только буквы и цифры', loginFormat: 'Имя пользователя должно содержать только буквы и цифры',
invalidLocation: 'Некорректный формат пути', invalidLocation: 'Некорректный формат пути',
versionTaken: 'Версия с таким шифром уже существует' versionTaken: 'Версия с таким шифром уже существует',
emptySubstitutions: 'Выберите хотя бы одно отождествление'
}; };
/** /**