F: Rework cst create and template dialogs
This commit is contained in:
parent
3c92e07d0f
commit
d5854366a9
|
@ -4,7 +4,7 @@ import { ILibraryItemReference, ILibraryItemVersioned } from '@/features/library
|
|||
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
|
||||
import { CstType, IConstituentaMeta, IInheritanceInfo, TermForm } from '../models/rsform';
|
||||
import { CstType, IConstituentaMeta, IInheritanceInfo } from '../models/rsform';
|
||||
import { IArgumentInfo, ParsingStatus, ValueClass } from '../models/rslang';
|
||||
|
||||
/**
|
||||
|
@ -42,17 +42,21 @@ export interface IRSFormUploadDTO {
|
|||
/**
|
||||
* Represents {@link IConstituenta} data, used in creation process.
|
||||
*/
|
||||
export interface ICstCreateDTO {
|
||||
alias: string;
|
||||
cst_type: CstType;
|
||||
definition_raw: string;
|
||||
term_raw: string;
|
||||
convention: string;
|
||||
definition_formal: string;
|
||||
term_forms: TermForm[];
|
||||
export const schemaCstCreate = z.object({
|
||||
cst_type: z.nativeEnum(CstType),
|
||||
alias: z.string().nonempty(errorMsg.requiredField),
|
||||
convention: z.string(),
|
||||
definition_formal: z.string(),
|
||||
definition_raw: z.string(),
|
||||
term_raw: z.string(),
|
||||
term_forms: z.array(z.object({ text: z.string(), tags: z.string() })),
|
||||
insert_after: z.number().nullable()
|
||||
});
|
||||
|
||||
insert_after: number | null;
|
||||
}
|
||||
/**
|
||||
* Represents {@link IConstituenta} data, used in creation process.
|
||||
*/
|
||||
export type ICstCreateDTO = z.infer<typeof schemaCstCreate>;
|
||||
|
||||
/**
|
||||
* Represents data response when creating {@link IConstituenta}.
|
||||
|
|
|
@ -1,54 +1,53 @@
|
|||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { FormProvider, useForm, useWatch } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
|
||||
import { ModalForm } from '@/components/Modal';
|
||||
import usePartialUpdate from '@/hooks/usePartialUpdate';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
|
||||
import { ICstCreateDTO } from '../../backend/types';
|
||||
import { CstType, IRSForm } from '../../models/rsform';
|
||||
import { generateAlias } from '../../models/rsformAPI';
|
||||
import { ICstCreateDTO, schemaCstCreate } from '../../backend/types';
|
||||
import { useCstCreate } from '../../backend/useCstCreate';
|
||||
import { IConstituentaMeta, IRSForm } from '../../models/rsform';
|
||||
import { validateNewAlias } from '../../models/rsformAPI';
|
||||
|
||||
import FormCreateCst from './FormCreateCst';
|
||||
|
||||
export interface DlgCreateCstProps {
|
||||
initial?: ICstCreateDTO;
|
||||
initial: ICstCreateDTO;
|
||||
schema: IRSForm;
|
||||
onCreate: (data: ICstCreateDTO) => void;
|
||||
onCreate: (data: IConstituentaMeta) => void;
|
||||
}
|
||||
|
||||
function DlgCreateCst() {
|
||||
const { initial, schema, onCreate } = useDialogsStore(state => state.props as DlgCreateCstProps);
|
||||
const { cstCreate } = useCstCreate();
|
||||
|
||||
const [validated, setValidated] = useState(false);
|
||||
const [cstData, updateCstData] = usePartialUpdate(
|
||||
initial || {
|
||||
cst_type: CstType.BASE,
|
||||
insert_after: null,
|
||||
alias: generateAlias(CstType.BASE, schema),
|
||||
convention: '',
|
||||
definition_formal: '',
|
||||
definition_raw: '',
|
||||
term_raw: '',
|
||||
term_forms: []
|
||||
const methods = useForm<ICstCreateDTO>({
|
||||
resolver: zodResolver(schemaCstCreate),
|
||||
defaultValues: { ...initial }
|
||||
});
|
||||
const alias = useWatch({ control: methods.control, name: 'alias' });
|
||||
const cst_type = useWatch({ control: methods.control, name: 'cst_type' });
|
||||
const isValid = alias !== initial.alias && validateNewAlias(alias, cst_type, schema);
|
||||
|
||||
function onSubmit(data: ICstCreateDTO) {
|
||||
return cstCreate({ itemID: schema.id, data }).then(onCreate);
|
||||
}
|
||||
);
|
||||
|
||||
const handleSubmit = () => {
|
||||
onCreate(cstData);
|
||||
return true;
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalForm
|
||||
header='Создание конституенты'
|
||||
canSubmit={validated}
|
||||
onSubmit={handleSubmit}
|
||||
canSubmit={isValid}
|
||||
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
|
||||
submitInvalidTooltip={errorMsg.aliasInvalid}
|
||||
submitText='Создать'
|
||||
className='cc-column w-[35rem] max-h-[30rem] py-2 px-6'
|
||||
>
|
||||
<FormCreateCst schema={schema} state={cstData} partialUpdate={updateCstData} setValidated={setValidated} />
|
||||
<FormProvider {...methods}>
|
||||
<FormCreateCst schema={schema} />
|
||||
</FormProvider>
|
||||
</ModalForm>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Controller, useFormContext, useWatch } from 'react-hook-form';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { BadgeHelp, HelpTopic } from '@/features/help';
|
||||
|
@ -12,48 +13,45 @@ import { ICstCreateDTO } from '../../backend/types';
|
|||
import RSInput from '../../components/RSInput';
|
||||
import { SelectCstType } from '../../components/SelectCstType';
|
||||
import { CstType, IRSForm } from '../../models/rsform';
|
||||
import { generateAlias, isBaseSet, isBasicConcept, isFunctional, validateNewAlias } from '../../models/rsformAPI';
|
||||
import { generateAlias, isBaseSet, isBasicConcept, isFunctional } from '../../models/rsformAPI';
|
||||
|
||||
interface FormCreateCstProps {
|
||||
schema: IRSForm;
|
||||
state: ICstCreateDTO;
|
||||
|
||||
partialUpdate: React.Dispatch<Partial<ICstCreateDTO>>;
|
||||
setValidated?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreateCstProps) {
|
||||
function FormCreateCst({ schema }: FormCreateCstProps) {
|
||||
const {
|
||||
setValue,
|
||||
register,
|
||||
control,
|
||||
formState: { errors }
|
||||
} = useFormContext<ICstCreateDTO>();
|
||||
const [forceComment, setForceComment] = useState(false);
|
||||
|
||||
const isBasic = isBasicConcept(state.cst_type);
|
||||
const isElementary = isBaseSet(state.cst_type);
|
||||
const showConvention = !!state.convention || forceComment || isBasic;
|
||||
|
||||
useEffect(() => {
|
||||
setForceComment(false);
|
||||
}, [state.cst_type, partialUpdate, schema]);
|
||||
|
||||
useEffect(() => {
|
||||
if (setValidated) {
|
||||
setValidated(validateNewAlias(state.alias, state.cst_type, schema));
|
||||
}
|
||||
}, [state.alias, state.cst_type, schema, setValidated]);
|
||||
const cst_type = useWatch({ control, name: 'cst_type' });
|
||||
const convention = useWatch({ control, name: 'convention' });
|
||||
const isBasic = isBasicConcept(cst_type);
|
||||
const isElementary = isBaseSet(cst_type);
|
||||
const isFunction = isFunctional(cst_type);
|
||||
const showConvention = !!convention || forceComment || isBasic;
|
||||
|
||||
function handleTypeChange(target: CstType) {
|
||||
return partialUpdate({ cst_type: target, alias: generateAlias(target, schema) });
|
||||
setValue('cst_type', target);
|
||||
setValue('alias', generateAlias(target, schema));
|
||||
setForceComment(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='flex items-center self-center gap-3'>
|
||||
<SelectCstType id='dlg_cst_type' className='w-[16rem]' value={state.cst_type} onChange={handleTypeChange} />
|
||||
<SelectCstType id='dlg_cst_type' className='w-[16rem]' value={cst_type} onChange={handleTypeChange} />
|
||||
<TextInput
|
||||
id='dlg_cst_alias'
|
||||
dense
|
||||
label='Имя'
|
||||
className='w-[7rem]'
|
||||
value={state.alias}
|
||||
onChange={event => partialUpdate({ alias: event.target.value })}
|
||||
{...register('alias')}
|
||||
error={errors.alias}
|
||||
/>
|
||||
<BadgeHelp
|
||||
topic={HelpTopic.CC_CONSTITUENTA}
|
||||
|
@ -69,31 +67,43 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
|
|||
label='Термин'
|
||||
placeholder='Обозначение для текстовых определений'
|
||||
className='max-h-[3.6rem]'
|
||||
value={state.term_raw}
|
||||
onChange={event => partialUpdate({ term_raw: event.target.value })}
|
||||
{...register('term_raw')}
|
||||
error={errors.term_raw}
|
||||
/>
|
||||
|
||||
{!!state.definition_formal || !isElementary ? (
|
||||
<Controller
|
||||
control={control}
|
||||
name='definition_formal'
|
||||
render={({ field }) =>
|
||||
!!field.value || !isElementary ? (
|
||||
<RSInput
|
||||
id='dlg_cst_expression'
|
||||
noTooltip
|
||||
label={
|
||||
state.cst_type === CstType.STRUCTURED
|
||||
cst_type === CstType.STRUCTURED
|
||||
? 'Область определения'
|
||||
: isFunctional(state.cst_type)
|
||||
: isFunction
|
||||
? 'Определение функции'
|
||||
: 'Формальное определение'
|
||||
}
|
||||
placeholder={
|
||||
state.cst_type !== CstType.STRUCTURED ? 'Родоструктурное выражение' : 'Типизация родовой структуры'
|
||||
cst_type !== CstType.STRUCTURED ? 'Родоструктурное выражение' : 'Типизация родовой структуры'
|
||||
}
|
||||
value={state.definition_formal}
|
||||
onChange={value => partialUpdate({ definition_formal: value })}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
schema={schema}
|
||||
/>
|
||||
) : null}
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
{!!state.definition_raw || !isElementary ? (
|
||||
<Controller
|
||||
control={control}
|
||||
name='definition_raw'
|
||||
render={({ field }) =>
|
||||
!!field.value || !isElementary ? (
|
||||
<TextArea
|
||||
id='dlg_cst_definition'
|
||||
spellCheck
|
||||
|
@ -101,10 +111,14 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
|
|||
label='Текстовое определение'
|
||||
placeholder='Текстовая интерпретация формального выражения'
|
||||
className='max-h-[3.6rem]'
|
||||
value={state.definition_raw}
|
||||
onChange={event => partialUpdate({ definition_raw: event.target.value })}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!showConvention ? (
|
||||
<button
|
||||
|
@ -124,8 +138,7 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
|
|||
label={isBasic ? 'Конвенция' : 'Комментарий'}
|
||||
placeholder={isBasic ? 'Договоренность об интерпретации' : 'Пояснение разработчика'}
|
||||
className='max-h-[5.4rem]'
|
||||
value={state.convention}
|
||||
onChange={event => partialUpdate({ convention: event.target.value })}
|
||||
{...register('convention')}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
'use client';
|
||||
|
||||
import { Suspense, useEffect, useState } from 'react';
|
||||
import { Suspense, useState } from 'react';
|
||||
import { FormProvider, useForm, useWatch } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { HelpTopic } from '@/features/help';
|
||||
|
@ -8,23 +10,21 @@ import { HelpTopic } from '@/features/help';
|
|||
import { Loader } from '@/components/Loader';
|
||||
import { ModalForm } from '@/components/Modal';
|
||||
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/Tabs';
|
||||
import usePartialUpdate from '@/hooks/usePartialUpdate';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { promptText } from '@/utils/labels';
|
||||
|
||||
import { ICstCreateDTO } from '../../backend/types';
|
||||
import { useRSForm } from '../../backend/useRSForm';
|
||||
import { CstType, IRSForm } from '../../models/rsform';
|
||||
import { ICstCreateDTO, schemaCstCreate } from '../../backend/types';
|
||||
import { useCstCreate } from '../../backend/useCstCreate';
|
||||
import { CstType, IConstituentaMeta, IRSForm } from '../../models/rsform';
|
||||
import { generateAlias, validateNewAlias } from '../../models/rsformAPI';
|
||||
import { inferTemplatedType, substituteTemplateArgs } from '../../models/rslangAPI';
|
||||
import FormCreateCst from '../DlgCreateCst/FormCreateCst';
|
||||
|
||||
import TabArguments, { IArgumentsState } from './TabArguments';
|
||||
import TabTemplate, { ITemplateState } from './TabTemplate';
|
||||
import TabArguments from './TabArguments';
|
||||
import TabTemplate from './TabTemplate';
|
||||
import { TemplateState } from './TemplateContext';
|
||||
|
||||
export interface DlgCstTemplateProps {
|
||||
schema: IRSForm;
|
||||
onCreate: (data: ICstCreateDTO) => void;
|
||||
onCreate: (data: IConstituentaMeta) => void;
|
||||
insertAfter?: number;
|
||||
}
|
||||
|
||||
|
@ -36,15 +36,11 @@ export enum TabID {
|
|||
|
||||
function DlgCstTemplate() {
|
||||
const { schema, onCreate, insertAfter } = useDialogsStore(state => state.props as DlgCstTemplateProps);
|
||||
const [activeTab, setActiveTab] = useState(TabID.TEMPLATE);
|
||||
const { cstCreate } = useCstCreate();
|
||||
|
||||
const [template, updateTemplate] = usePartialUpdate<ITemplateState>({});
|
||||
const { schema: templateSchema } = useRSForm({ itemID: template.templateID });
|
||||
const [substitutes, updateSubstitutes] = usePartialUpdate<IArgumentsState>({
|
||||
definition: '',
|
||||
arguments: []
|
||||
});
|
||||
const [constituenta, updateConstituenta] = usePartialUpdate<ICstCreateDTO>({
|
||||
const methods = useForm<ICstCreateDTO>({
|
||||
resolver: zodResolver(schemaCstCreate),
|
||||
defaultValues: {
|
||||
cst_type: CstType.TERM,
|
||||
insert_after: insertAfter ?? null,
|
||||
alias: generateAlias(CstType.TERM, schema),
|
||||
|
@ -53,81 +49,25 @@ function DlgCstTemplate() {
|
|||
definition_raw: '',
|
||||
term_raw: '',
|
||||
term_forms: []
|
||||
});
|
||||
|
||||
const [validated, setValidated] = useState(false);
|
||||
|
||||
function handleSubmit() {
|
||||
onCreate(constituenta);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
const alias = useWatch({ control: methods.control, name: 'alias' });
|
||||
const cst_type = useWatch({ control: methods.control, name: 'cst_type' });
|
||||
const isValid = validateNewAlias(alias, cst_type, schema);
|
||||
|
||||
function handlePrompt(): boolean {
|
||||
const definedSomeArgs = substitutes.arguments.some(arg => !!arg.value);
|
||||
if (!definedSomeArgs && !window.confirm(promptText.templateUndefined)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const [activeTab, setActiveTab] = useState(TabID.TEMPLATE);
|
||||
|
||||
useEffect(() => {
|
||||
if (!template.prototype) {
|
||||
updateConstituenta({
|
||||
definition_raw: '',
|
||||
definition_formal: '',
|
||||
term_raw: ''
|
||||
});
|
||||
updateSubstitutes({
|
||||
definition: '',
|
||||
arguments: []
|
||||
});
|
||||
} else {
|
||||
updateConstituenta({
|
||||
cst_type: template.prototype.cst_type,
|
||||
alias: generateAlias(template.prototype.cst_type, schema),
|
||||
definition_raw: template.prototype.definition_raw,
|
||||
definition_formal: template.prototype.definition_formal,
|
||||
term_raw: template.prototype.term_raw
|
||||
});
|
||||
updateSubstitutes({
|
||||
definition: template.prototype.definition_formal,
|
||||
arguments: template.prototype.parse.args.map(arg => ({
|
||||
alias: arg.alias,
|
||||
typification: arg.typification,
|
||||
value: ''
|
||||
}))
|
||||
});
|
||||
function onSubmit(data: ICstCreateDTO) {
|
||||
return cstCreate({ itemID: schema.id, data }).then(onCreate);
|
||||
}
|
||||
}, [template.prototype, updateConstituenta, updateSubstitutes, schema]);
|
||||
|
||||
useEffect(() => {
|
||||
if (substitutes.arguments.length === 0 || !template.prototype) {
|
||||
return;
|
||||
}
|
||||
const definition = substituteTemplateArgs(template.prototype.definition_formal, substitutes.arguments);
|
||||
const type = inferTemplatedType(template.prototype.cst_type, substitutes.arguments);
|
||||
updateConstituenta({
|
||||
cst_type: type,
|
||||
alias: generateAlias(type, schema),
|
||||
definition_formal: definition
|
||||
});
|
||||
updateSubstitutes({
|
||||
definition: definition
|
||||
});
|
||||
}, [substitutes.arguments, template.prototype, updateConstituenta, updateSubstitutes, schema]);
|
||||
|
||||
useEffect(() => {
|
||||
setValidated(!!template.prototype && validateNewAlias(constituenta.alias, constituenta.cst_type, schema));
|
||||
}, [constituenta.alias, constituenta.cst_type, schema, template.prototype]);
|
||||
|
||||
return (
|
||||
<ModalForm
|
||||
header='Создание конституенты из шаблона'
|
||||
submitText='Создать'
|
||||
className='w-[43rem] h-[35rem] px-6'
|
||||
canSubmit={validated}
|
||||
beforeSubmit={handlePrompt}
|
||||
onSubmit={handleSubmit}
|
||||
canSubmit={isValid}
|
||||
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
|
||||
helpTopic={HelpTopic.RSL_TEMPLATES}
|
||||
>
|
||||
<Tabs
|
||||
|
@ -142,21 +82,25 @@ function DlgCstTemplate() {
|
|||
<TabLabel label='Конституента' title='Редактирование конституенты' className='w-[8rem]' />
|
||||
</TabList>
|
||||
|
||||
<FormProvider {...methods}>
|
||||
<TemplateState>
|
||||
<TabPanel>
|
||||
<Suspense fallback={<Loader />}>
|
||||
<TabTemplate state={template} partialUpdate={updateTemplate} templateSchema={templateSchema} />
|
||||
<TabTemplate />
|
||||
</Suspense>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<TabArguments schema={schema} state={substitutes} partialUpdate={updateSubstitutes} />
|
||||
<TabArguments />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<div className='cc-fade-in cc-column'>
|
||||
<FormCreateCst state={constituenta} partialUpdate={updateConstituenta} schema={schema} />
|
||||
<FormCreateCst schema={schema} />
|
||||
</div>
|
||||
</TabPanel>
|
||||
</TemplateState>
|
||||
</FormProvider>
|
||||
</Tabs>
|
||||
</ModalForm>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
import clsx from 'clsx';
|
||||
|
||||
|
@ -8,37 +9,32 @@ import { MiniButton } from '@/components/Control';
|
|||
import DataTable, { IConditionalStyle } from '@/components/DataTable';
|
||||
import { IconAccept, IconRemove, IconReset } from '@/components/Icons';
|
||||
import { NoData } from '@/components/View';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { APP_COLORS } from '@/styling/colors';
|
||||
|
||||
import { ICstCreateDTO } from '../../backend/types';
|
||||
import { PickConstituenta } from '../../components/PickConstituenta';
|
||||
import RSInput from '../../components/RSInput';
|
||||
import { IConstituenta, IRSForm } from '../../models/rsform';
|
||||
import { IConstituenta } from '../../models/rsform';
|
||||
import { IArgumentValue } from '../../models/rslang';
|
||||
|
||||
interface TabArgumentsProps {
|
||||
state: IArgumentsState;
|
||||
schema: IRSForm;
|
||||
partialUpdate: React.Dispatch<Partial<IArgumentsState>>;
|
||||
}
|
||||
|
||||
export interface IArgumentsState {
|
||||
arguments: IArgumentValue[];
|
||||
definition: string;
|
||||
}
|
||||
import { DlgCstTemplateProps } from './DlgCstTemplate';
|
||||
import { useTemplateContext } from './TemplateContext';
|
||||
|
||||
const argumentsHelper = createColumnHelper<IArgumentValue>();
|
||||
|
||||
function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
|
||||
const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined);
|
||||
const [selectedArgument, setSelectedArgument] = useState<IArgumentValue | undefined>(undefined);
|
||||
const [argumentValue, setArgumentValue] = useState('');
|
||||
const isModified = selectedArgument && argumentValue !== selectedArgument.value;
|
||||
function TabArguments() {
|
||||
const { schema } = useDialogsStore(state => state.props as DlgCstTemplateProps);
|
||||
const { control } = useFormContext<ICstCreateDTO>();
|
||||
const { args, onChangeArguments } = useTemplateContext();
|
||||
const definition = useWatch({ control, name: 'definition_formal' });
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedArgument && state.arguments.length > 0) {
|
||||
setSelectedArgument(state.arguments[0]);
|
||||
}
|
||||
}, [state.arguments, selectedArgument]);
|
||||
const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined);
|
||||
const [selectedArgument, setSelectedArgument] = useState<IArgumentValue | undefined>(
|
||||
args.length > 0 ? args[0] : undefined
|
||||
);
|
||||
|
||||
const [argumentValue, setArgumentValue] = useState('');
|
||||
|
||||
function handleSelectArgument(arg: IArgumentValue) {
|
||||
setSelectedArgument(arg);
|
||||
|
@ -54,9 +50,7 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
|
|||
|
||||
function handleClearArgument(target: IArgumentValue) {
|
||||
const newArg = { ...target, value: '' };
|
||||
partialUpdate({
|
||||
arguments: state.arguments.map(arg => (arg.alias !== target.alias ? arg : newArg))
|
||||
});
|
||||
onChangeArguments(args.map(arg => (arg.alias !== target.alias ? arg : newArg)));
|
||||
setSelectedArgument(newArg);
|
||||
}
|
||||
|
||||
|
@ -64,11 +58,9 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
|
|||
setArgumentValue(selectedArgument?.value ?? '');
|
||||
}
|
||||
|
||||
function handleAssignArgument(target: IArgumentValue, value: string) {
|
||||
const newArg = { ...target, value: value };
|
||||
partialUpdate({
|
||||
arguments: state.arguments.map(arg => (arg.alias !== target.alias ? arg : newArg))
|
||||
});
|
||||
function handleAssignArgument(target: IArgumentValue, argValue: string) {
|
||||
const newArg = { ...target, value: argValue };
|
||||
onChangeArguments(args.map(arg => (arg.alias !== target.alias ? arg : newArg)));
|
||||
setSelectedArgument(newArg);
|
||||
}
|
||||
|
||||
|
@ -139,7 +131,7 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
|
|||
'border',
|
||||
'select-none'
|
||||
)}
|
||||
data={state.arguments}
|
||||
data={args}
|
||||
columns={columns}
|
||||
conditionalRowStyles={conditionalRowStyles}
|
||||
noDataComponent={<NoData className='min-h-[3.6rem]'>Аргументы отсутствуют</NoData>}
|
||||
|
@ -179,7 +171,6 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
|
|||
title='Очистить поле'
|
||||
noHover
|
||||
className='py-0'
|
||||
disabled={!isModified}
|
||||
onClick={handleReset}
|
||||
icon={<IconReset size='1.5rem' className='icon-primary' />}
|
||||
/>
|
||||
|
@ -201,7 +192,7 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
|
|||
placeholder='Итоговое определение'
|
||||
className='mt-[1.2rem]'
|
||||
height='5.1rem'
|
||||
value={state.definition}
|
||||
value={definition}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,36 +1,44 @@
|
|||
'use client';
|
||||
|
||||
import { Dispatch, useEffect, useState } from 'react';
|
||||
|
||||
import { useTemplatesSuspense } from '@/features/library';
|
||||
|
||||
import { SelectSingle, TextArea } from '@/components/Input';
|
||||
|
||||
import { useRSForm } from '../../backend/useRSForm';
|
||||
import { PickConstituenta } from '../../components/PickConstituenta';
|
||||
import RSInput from '../../components/RSInput';
|
||||
import { CATEGORY_CST_TYPE, IConstituenta, IRSForm } from '../../models/rsform';
|
||||
import { CATEGORY_CST_TYPE } from '../../models/rsform';
|
||||
import { applyFilterCategory } from '../../models/rsformAPI';
|
||||
|
||||
export interface ITemplateState {
|
||||
templateID?: number;
|
||||
prototype?: IConstituenta;
|
||||
filterCategory?: IConstituenta;
|
||||
}
|
||||
import { useTemplateContext } from './TemplateContext';
|
||||
|
||||
interface TabTemplateProps {
|
||||
state: ITemplateState;
|
||||
partialUpdate: Dispatch<Partial<ITemplateState>>;
|
||||
templateSchema?: IRSForm;
|
||||
}
|
||||
function TabTemplate() {
|
||||
const {
|
||||
templateID, //
|
||||
filterCategory,
|
||||
prototype,
|
||||
onChangePrototype,
|
||||
onChangeTemplateID,
|
||||
onChangeFilterCategory
|
||||
} = useTemplateContext();
|
||||
|
||||
function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps) {
|
||||
const { templates } = useTemplatesSuspense();
|
||||
const { schema: templateSchema } = useRSForm({ itemID: templateID });
|
||||
|
||||
const [filteredData, setFilteredData] = useState<IConstituenta[]>([]);
|
||||
if (!templateID) {
|
||||
onChangeTemplateID(templates[0].id);
|
||||
return null;
|
||||
}
|
||||
|
||||
const prototypeInfo = !state.prototype
|
||||
const filteredData = !templateSchema
|
||||
? []
|
||||
: !filterCategory
|
||||
? templateSchema.items
|
||||
: applyFilterCategory(filterCategory, templateSchema);
|
||||
|
||||
const prototypeInfo = !prototype
|
||||
? ''
|
||||
: `${state.prototype?.term_raw}${state.prototype?.definition_raw ? ` — ${state.prototype?.definition_raw}` : ''}`;
|
||||
: `${prototype?.term_raw}${prototype?.definition_raw ? ` — ${prototype?.definition_raw}` : ''}`;
|
||||
|
||||
const templateSelector = templates.map(template => ({
|
||||
value: template.id,
|
||||
|
@ -46,23 +54,6 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
|
|||
label: cst.term_raw
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (templates.length > 0 && !state.templateID) {
|
||||
partialUpdate({ templateID: templates[0].id });
|
||||
}
|
||||
}, [templates, state.templateID, partialUpdate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!templateSchema) {
|
||||
return;
|
||||
}
|
||||
let data = templateSchema.items;
|
||||
if (state.filterCategory) {
|
||||
data = applyFilterCategory(state.filterCategory, templateSchema);
|
||||
}
|
||||
setFilteredData(data);
|
||||
}, [state.filterCategory, templateSchema]);
|
||||
|
||||
return (
|
||||
<div className='cc-fade-in'>
|
||||
<div className='flex border-t border-x rounded-t-md clr-input'>
|
||||
|
@ -71,12 +62,8 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
|
|||
placeholder='Источник'
|
||||
className='w-[12rem]'
|
||||
options={templateSelector}
|
||||
value={
|
||||
state.templateID
|
||||
? { value: state.templateID, label: templates.find(item => item.id == state.templateID)!.title }
|
||||
: null
|
||||
}
|
||||
onChange={data => partialUpdate({ templateID: data ? data.value : undefined })}
|
||||
value={templateID ? { value: templateID, label: templates.find(item => item.id == templateID)!.title } : null}
|
||||
onChange={data => onChangeTemplateID(data ? data.value : undefined)}
|
||||
/>
|
||||
<SelectSingle
|
||||
noBorder
|
||||
|
@ -85,24 +72,22 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
|
|||
className='flex-grow ml-1 border-none'
|
||||
options={categorySelector}
|
||||
value={
|
||||
state.filterCategory && templateSchema
|
||||
filterCategory && templateSchema
|
||||
? {
|
||||
value: state.filterCategory.id,
|
||||
label: state.filterCategory.term_raw
|
||||
value: filterCategory.id,
|
||||
label: filterCategory.term_raw
|
||||
}
|
||||
: null
|
||||
}
|
||||
onChange={data =>
|
||||
partialUpdate({ filterCategory: data ? templateSchema?.cstByID.get(data?.value) : undefined })
|
||||
}
|
||||
onChange={data => onChangeFilterCategory(data ? templateSchema?.cstByID.get(data?.value) : undefined)}
|
||||
isClearable
|
||||
/>
|
||||
</div>
|
||||
<PickConstituenta
|
||||
id='dlg_template_picker'
|
||||
value={state.prototype}
|
||||
value={prototype}
|
||||
items={filteredData}
|
||||
onChange={cst => partialUpdate({ prototype: cst })}
|
||||
onChange={onChangePrototype}
|
||||
className='rounded-t-none'
|
||||
rows={8}
|
||||
/>
|
||||
|
@ -121,7 +106,7 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
|
|||
disabled
|
||||
placeholder='Выберите шаблон из списка'
|
||||
height='5.1rem'
|
||||
value={state.prototype?.definition_formal}
|
||||
value={prototype?.definition_formal}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
'use client';
|
||||
|
||||
import { createContext, useContext, useState } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import { ICstCreateDTO } from '../../backend/types';
|
||||
import { IConstituenta } from '../../models/rsform';
|
||||
import { generateAlias } from '../../models/rsformAPI';
|
||||
import { IArgumentValue } from '../../models/rslang';
|
||||
import { inferTemplatedType, substituteTemplateArgs } from '../../models/rslangAPI';
|
||||
|
||||
import { DlgCstTemplateProps } from './DlgCstTemplate';
|
||||
|
||||
export interface ITemplateContext {
|
||||
args: IArgumentValue[];
|
||||
prototype?: IConstituenta;
|
||||
templateID?: number;
|
||||
filterCategory?: IConstituenta;
|
||||
|
||||
onChangeArguments: (newArgs: IArgumentValue[]) => void;
|
||||
onChangePrototype: (newPrototype: IConstituenta) => void;
|
||||
onChangeTemplateID: (newTemplateID: number | undefined) => void;
|
||||
onChangeFilterCategory: (newFilterCategory: IConstituenta | undefined) => void;
|
||||
}
|
||||
|
||||
const TemplateContext = createContext<ITemplateContext | null>(null);
|
||||
export const useTemplateContext = () => {
|
||||
const context = useContext(TemplateContext);
|
||||
if (context === null) {
|
||||
throw new Error('useTemplateContext has to be used within <TemplateState>');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export const TemplateState = ({ children }: React.PropsWithChildren) => {
|
||||
const { schema } = useDialogsStore(state => state.props as DlgCstTemplateProps);
|
||||
const { setValue } = useFormContext<ICstCreateDTO>();
|
||||
const [templateID, setTemplateID] = useState<number | undefined>(undefined);
|
||||
const [args, setArguments] = useState<IArgumentValue[]>([]);
|
||||
const [prototype, setPrototype] = useState<IConstituenta | undefined>(undefined);
|
||||
const [filterCategory, setFilterCategory] = useState<IConstituenta | undefined>(undefined);
|
||||
|
||||
function onChangeArguments(newArgs: IArgumentValue[]) {
|
||||
setArguments(newArgs);
|
||||
if (newArgs.length === 0 || !prototype) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newType = inferTemplatedType(prototype.cst_type, newArgs);
|
||||
setValue('definition_formal', substituteTemplateArgs(prototype.definition_formal, newArgs));
|
||||
setValue('cst_type', newType);
|
||||
setValue('alias', generateAlias(newType, schema));
|
||||
}
|
||||
|
||||
function onChangePrototype(newPrototype: IConstituenta) {
|
||||
setPrototype(newPrototype);
|
||||
setArguments(
|
||||
newPrototype.parse.args.map(arg => ({
|
||||
alias: arg.alias,
|
||||
typification: arg.typification,
|
||||
value: ''
|
||||
}))
|
||||
);
|
||||
setValue('cst_type', newPrototype.cst_type);
|
||||
setValue('alias', generateAlias(newPrototype.cst_type, schema));
|
||||
setValue('definition_formal', newPrototype.definition_formal);
|
||||
setValue('term_raw', newPrototype.term_raw);
|
||||
setValue('definition_raw', newPrototype.definition_raw);
|
||||
}
|
||||
|
||||
function onChangeTemplateID(newTemplateID: number | undefined) {
|
||||
setTemplateID(newTemplateID);
|
||||
setPrototype(undefined);
|
||||
setArguments([]);
|
||||
}
|
||||
|
||||
return (
|
||||
<TemplateContext
|
||||
value={{
|
||||
templateID,
|
||||
prototype,
|
||||
filterCategory,
|
||||
args,
|
||||
onChangeArguments,
|
||||
onChangePrototype,
|
||||
onChangeFilterCategory: setFilterCategory,
|
||||
onChangeTemplateID
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TemplateContext>
|
||||
);
|
||||
};
|
|
@ -18,7 +18,7 @@ import { ICstCreateDTO } from '../../backend/types';
|
|||
import { useCstCreate } from '../../backend/useCstCreate';
|
||||
import { useCstMove } from '../../backend/useCstMove';
|
||||
import { useRSFormSuspense } from '../../backend/useRSForm';
|
||||
import { CstType, IConstituenta, IRSForm } from '../../models/rsform';
|
||||
import { CstType, IConstituenta, IConstituentaMeta, IRSForm } from '../../models/rsform';
|
||||
import { generateAlias } from '../../models/rsformAPI';
|
||||
|
||||
export enum RSTabID {
|
||||
|
@ -177,9 +177,7 @@ export const RSEditState = ({
|
|||
});
|
||||
}
|
||||
|
||||
function handleCreateCst(data: ICstCreateDTO) {
|
||||
data.alias = data.alias || generateAlias(data.cst_type, schema);
|
||||
void cstCreate({ itemID: itemID, data }).then(newCst => {
|
||||
function onCreateCst(newCst: IConstituentaMeta) {
|
||||
setSelected([newCst.id]);
|
||||
navigateRSForm({ tab: activeTab, activeID: newCst.id });
|
||||
if (activeTab === RSTabID.CST_LIST) {
|
||||
|
@ -194,7 +192,6 @@ export const RSEditState = ({
|
|||
}
|
||||
}, PARAMETER.refreshTimeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function moveUp() {
|
||||
|
@ -258,9 +255,9 @@ export const RSEditState = ({
|
|||
term_forms: []
|
||||
};
|
||||
if (skipDialog) {
|
||||
handleCreateCst(data);
|
||||
void cstCreate({ itemID: schema.id, data }).then(onCreateCst);
|
||||
} else {
|
||||
showCreateCst({ schema: schema, onCreate: handleCreateCst, initial: data });
|
||||
showCreateCst({ schema: schema, onCreate: onCreateCst, initial: data });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,7 +265,9 @@ export const RSEditState = ({
|
|||
if (!activeCst) {
|
||||
return;
|
||||
}
|
||||
const data: ICstCreateDTO = {
|
||||
void cstCreate({
|
||||
itemID: schema.id,
|
||||
data: {
|
||||
insert_after: activeCst.id,
|
||||
cst_type: activeCst.cst_type,
|
||||
alias: generateAlias(activeCst.cst_type, schema),
|
||||
|
@ -277,8 +276,8 @@ export const RSEditState = ({
|
|||
definition_raw: activeCst.definition_raw,
|
||||
convention: activeCst.convention,
|
||||
term_forms: activeCst.term_forms
|
||||
};
|
||||
handleCreateCst(data);
|
||||
}
|
||||
}).then(onCreateCst);
|
||||
}
|
||||
|
||||
function promptDeleteCst() {
|
||||
|
@ -299,11 +298,12 @@ export const RSEditState = ({
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
function promptTemplate() {
|
||||
if (isModified && !promptUnsaved()) {
|
||||
return;
|
||||
}
|
||||
showCstTemplate({ schema: schema, onCreate: handleCreateCst, insertAfter: activeCst?.id });
|
||||
showCstTemplate({ schema: schema, onCreate: onCreateCst, insertAfter: activeCst?.id });
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -140,7 +140,8 @@ export const errorMsg = {
|
|||
loginFormat: 'Имя пользователя должно содержать только буквы и цифры',
|
||||
invalidLocation: 'Некорректный формат пути',
|
||||
versionTaken: 'Версия с таким шифром уже существует',
|
||||
emptySubstitutions: 'Выберите хотя бы одно отождествление'
|
||||
emptySubstitutions: 'Выберите хотя бы одно отождествление',
|
||||
aliasInvalid: 'Введите незанятое имя, соответствующее типу'
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -162,7 +163,6 @@ export const promptText = {
|
|||
'Внимание!!\nУдаление операционной схемы приведет к удалению всех операций и собственных концептуальных схем.\nДанное действие нельзя отменить.\nВы уверены, что хотите удалить данную ОСС?',
|
||||
generateWordforms: 'Данное действие приведет к перезаписи словоформ при совпадении граммем. Продолжить?',
|
||||
restoreArchive: 'При восстановлении архивной версии актуальная схему будет заменена. Продолжить?',
|
||||
templateUndefined: 'Вы уверены, что хотите создать шаблонную конституенту не фиксируя аргументы?',
|
||||
ownerChange:
|
||||
'Вы уверены, что хотите изменить владельца? Вы потеряете право управления данной схемой. Данное действие отменить нельзя'
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user