F: Improve dialogs validation hinting

This commit is contained in:
Ivan 2025-11-04 13:14:39 +03:00
parent 00f47c4b68
commit 5bf70ed3fa
26 changed files with 169 additions and 38 deletions

View File

@ -5,10 +5,11 @@ import { BadgeHelp } from '@/features/help/components/badge-help';
import { useEscapeKey } from '@/hooks/use-escape-key';
import { useDialogsStore } from '@/stores/dialogs';
import { globalIDs } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils';
import { Button, MiniButton, SubmitButton } from '../control';
import { IconClose } from '../icons';
import { IconAlert, IconClose } from '../icons';
import { type Styling } from '../props';
import { cn } from '../utils';
@ -33,7 +34,7 @@ interface ModalFormProps extends ModalProps {
submitText?: string;
/** Tooltip for the submit button when the form is invalid. */
submitInvalidTooltip?: string;
validationHint?: string;
/** Indicates that submit button is enabled. */
canSubmit?: boolean;
@ -60,7 +61,7 @@ export function ModalForm({
canSubmit = true,
submitText = 'Продолжить',
submitInvalidTooltip,
validationHint,
beforeSubmit,
onSubmit,
onCancel,
@ -121,7 +122,7 @@ export function ModalForm({
<div
className={cn(
'@container/modal',
'max-h-[calc(100svh-8rem)] max-w-[100svw] xs:max-w-[calc(100svw-2rem)]',
'max-h-[calc(100svh-8rem)] max-w-svw xs:max-w-[calc(100svw-2rem)]',
'overscroll-contain outline-hidden',
overflowVisible ? 'overflow-visible' : 'overflow-auto',
className
@ -131,14 +132,18 @@ export function ModalForm({
{children}
</div>
<div className='z-pop my-2 flex gap-12 justify-center text-sm'>
<SubmitButton
autoFocus
text={submitText}
title={!canSubmit ? submitInvalidTooltip : ''}
className='min-w-28'
disabled={!canSubmit}
/>
<div className='z-pop my-2 flex gap-12 justify-center text-sm relative'>
{validationHint ? (
<div className='absolute top-0.5 text-muted-foreground'>
<IconAlert
size='1.5rem'
className={canSubmit ? 'hover:icon-green' : 'hover:icon-red'}
data-tooltip-id={globalIDs.tooltip}
data-tooltip-html={validationHint}
/>
</div>
) : null}
<SubmitButton autoFocus text={submitText} className='min-w-28' disabled={!canSubmit} />
<Button text='Отмена' aria-label='Закрыть' className='min-w-28' onClick={handleCancel} />
</div>
</form>

View File

@ -53,7 +53,7 @@ export function DlgCreatePromptTemplate() {
submitText='Создать'
canSubmit={canSubmit}
onSubmit={event => void handleSubmit(onSubmit)(event)}
submitInvalidTooltip='Введите уникальное название шаблона'
validationHint={canSubmit ? '' : 'Введите уникальное название шаблона'}
className='cc-column w-140 max-h-120 py-2 px-6'
helpTopic={HelpTopic.ASSISTANT}
>

View File

@ -47,7 +47,11 @@ export function DlgChangeLocation() {
overflowVisible
header='Изменение расположения'
submitText='Переместить'
submitInvalidTooltip={`Допустимы буквы, цифры, подчерк, пробел и "!". Сегмент пути не может начинаться и заканчиваться пробелом. Общая длина (с корнем) не должна превышать ${limits.len_location}`}
validationHint={
isValid
? ''
: `Допустимы буквы, цифры, подчерк, пробел и "!". Сегмент пути не может начинаться и заканчиваться пробелом. Общая длина (с корнем) не должна превышать ${limits.len_location}`
}
canSubmit={isValid && isDirty}
onSubmit={event => void handleSubmit(onSubmit)(event)}
className='w-130 pb-3 px-6 h-36'

View File

@ -6,7 +6,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { Checkbox, TextArea, TextInput } from '@/components/input';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { errorMsg } from '@/utils/labels';
import { hintMsg } from '@/utils/labels';
import { type ICreateVersionDTO, type IVersionInfo, schemaCreateVersion } from '../backend/types';
import { useCreateVersion } from '../backend/use-create-version';
@ -41,7 +41,15 @@ export function DlgCreateVersion() {
mode: 'onChange'
});
const version = useWatch({ control, name: 'version' });
const canSubmit = !!version && !versions.find(ver => ver.version === version);
const { canSubmit, hint } = (() => {
if (!version) {
return { canSubmit: false, hint: hintMsg.versionEmpty };
} else if (versions.find(ver => ver.version === version)) {
return { canSubmit: false, hint: hintMsg.versionTaken };
} else {
return { canSubmit: true, hint: '' };
}
})();
function onSubmit(data: ICreateVersionDTO) {
return versionCreate({ itemID, data }).then(onCreate);
@ -52,7 +60,7 @@ export function DlgCreateVersion() {
header='Создание версии'
className='cc-column w-120 py-2 px-6'
canSubmit={canSubmit}
submitInvalidTooltip={errorMsg.versionTaken}
validationHint={hint}
submitText='Создать'
onSubmit={event => void handleSubmit(onSubmit)(event)}
>

View File

@ -12,7 +12,7 @@ import { IconReset, IconSave } from '@/components/icons';
import { TextArea, TextInput } from '@/components/input';
import { ModalView } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { errorMsg } from '@/utils/labels';
import { hintMsg } from '@/utils/labels';
import { type IUpdateVersionDTO, schemaUpdateVersion } from '../../backend/types';
import { useDeleteVersion } from '../../backend/use-delete-version';
@ -106,7 +106,7 @@ export function DlgEditVersions() {
<div className='cc-icons h-fit'>
<MiniButton
type='submit'
title={isValid ? 'Сохранить изменения' : errorMsg.versionTaken}
title={isValid ? 'Сохранить изменения' : hintMsg.versionTaken}
aria-label='Сохранить изменения'
icon={<IconSave size='1.25rem' className='icon-primary' />}
disabled={!isDirty || !isValid || isProcessing}

View File

@ -9,6 +9,7 @@ import { HelpTopic } from '@/features/help';
import { ModalForm } from '@/components/modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
import { useDialogsStore } from '@/stores/dialogs';
import { hintMsg } from '@/utils/labels';
import { type ICreateBlockDTO, type IOssLayout, schemaCreateBlock } from '../../backend/types';
import { useCreateBlock } from '../../backend/use-create-block';
@ -77,7 +78,17 @@ export function DlgCreateBlock() {
const children_blocks = useWatch({ control: methods.control, name: 'children_blocks' });
const children_operations = useWatch({ control: methods.control, name: 'children_operations' });
const [activeTab, setActiveTab] = useState<TabID>(TabID.CARD);
const canSubmit = methods.formState.isValid && !!title && !manager.oss.blocks.some(block => block.title === title);
const { canSubmit, hint } = (() => {
if (!methods.formState.isValid) {
return { canSubmit: false, hint: hintMsg.formInvalid };
} else if (!title) {
return { canSubmit: false, hint: hintMsg.titleEmpty };
} else if (manager.oss.blocks.some(block => block.title === title)) {
return { canSubmit: false, hint: hintMsg.blockTitleTaken };
} else {
return { canSubmit: true, hint: '' };
}
})();
function onSubmit(data: ICreateBlockDTO) {
data.position = manager.newBlockPosition(data);
@ -90,6 +101,7 @@ export function DlgCreateBlock() {
header='Создание блока'
submitText='Создать'
canSubmit={canSubmit}
validationHint={hint}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
className='w-160 px-6 h-110'
helpTopic={HelpTopic.CC_STRUCTURING}

View File

@ -28,6 +28,7 @@ export function TabBlockCard({ oss }: TabBlockCardProps) {
<TextInput
id='operation_title' //
label='Название'
placeholder='Введите название'
{...register('item_data.title')}
error={errors.item_data?.title}
/>

View File

@ -8,6 +8,7 @@ import { HelpTopic } from '@/features/help';
import { TextArea, TextInput } from '@/components/input';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { hintMsg } from '@/utils/labels';
import { type ICreateSchemaDTO, type IOssLayout, schemaCreateSchema } from '../backend/types';
import { useCreateSchema } from '../backend/use-create-schema';
@ -64,7 +65,17 @@ export function DlgCreateSchema() {
mode: 'onChange'
});
const alias = useWatch({ control: control, name: 'item_data.alias' });
const canSubmit = isValid && !!alias && !manager.oss.operations.some(operation => operation.alias === alias);
const { canSubmit, hint } = (() => {
if (!isValid) {
return { canSubmit: false, hint: hintMsg.formInvalid };
} else if (!alias) {
return { canSubmit: false, hint: hintMsg.aliasEmpty };
} else if (manager.oss.operations.some(operation => operation.alias === alias)) {
return { canSubmit: false, hint: hintMsg.schemaAliasTaken };
} else {
return { canSubmit: true, hint: '' };
}
})();
function onSubmit(data: ICreateSchemaDTO) {
data.position = manager.newOperationPosition(data);
@ -77,6 +88,7 @@ export function DlgCreateSchema() {
header='Создание операции: Новая схема'
submitText='Создать'
canSubmit={canSubmit}
validationHint={hint}
onSubmit={event => void handleSubmit(onSubmit)(event)}
className='w-180 px-6 pb-3 cc-column'
helpTopic={HelpTopic.CC_OSS}
@ -84,6 +96,7 @@ export function DlgCreateSchema() {
<TextInput
id='operation_title' //
label='Название'
placeholder='Введите название'
{...register('item_data.title')}
error={errors.item_data?.title}
/>
@ -93,6 +106,7 @@ export function DlgCreateSchema() {
id='operation_alias' //
label='Сокращение'
className='w-80'
placeholder='Введите сокращение'
{...register('item_data.alias')}
error={errors.item_data?.alias}
/>

View File

@ -10,6 +10,7 @@ import { Loader } from '@/components/loader';
import { ModalForm } from '@/components/modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
import { useDialogsStore } from '@/stores/dialogs';
import { hintMsg } from '@/utils/labels';
import { type ICreateSynthesisDTO, type IOssLayout, schemaCreateSynthesis } from '../../backend/types';
import { useCreateSynthesis } from '../../backend/use-create-synthesis';
@ -73,8 +74,17 @@ export function DlgCreateSynthesis() {
});
const alias = useWatch({ control: methods.control, name: 'item_data.alias' });
const [activeTab, setActiveTab] = useState<TabID>(TabID.ARGUMENTS);
const canSubmit =
methods.formState.isValid && !!alias && !manager.oss.operations.some(operation => operation.alias === alias);
const { canSubmit, hint } = (() => {
if (!methods.formState.isValid) {
return { canSubmit: false, hint: hintMsg.formInvalid };
} else if (!alias) {
return { canSubmit: false, hint: hintMsg.aliasEmpty };
} else if (manager.oss.operations.some(operation => operation.alias === alias)) {
return { canSubmit: false, hint: hintMsg.schemaAliasTaken };
} else {
return { canSubmit: true, hint: '' };
}
})();
function onSubmit(data: ICreateSynthesisDTO) {
data.position = manager.newOperationPosition(data);
@ -87,6 +97,7 @@ export function DlgCreateSynthesis() {
header='Создание операции синтеза'
submitText='Создать'
canSubmit={canSubmit}
validationHint={hint}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
className='w-180 px-6 h-128'
helpTopic={HelpTopic.CC_OSS}

View File

@ -32,6 +32,7 @@ export function TabArguments({ oss }: TabArgumentsProps) {
<TextInput
id='operation_title'
label='Название'
placeholder='Введите название'
{...register('item_data.title')}
error={errors.item_data?.title}
/>
@ -40,6 +41,7 @@ export function TabArguments({ oss }: TabArgumentsProps) {
<TextInput
id='operation_alias' //
label='Сокращение'
placeholder='Введите сокращение'
className='w-80'
{...register('item_data.alias')}
error={errors.item_data?.alias}

View File

@ -45,7 +45,12 @@ export function TabSubstitutions({ oss }: TabSubstitutionsProps) {
)}
/>
<TextArea disabled value={validator.msg} rows={4} className={isCorrect ? '' : 'border-(--acc-fg-red) border-2'} />
<TextArea
disabled
value={validator.msg}
rows={4}
className={isCorrect ? '' : 'border-accent-red-foreground border-2'}
/>
</div>
);
}

View File

@ -67,6 +67,7 @@ export function DlgEditBlock() {
<TextInput
id='operation_title' //
label='Название'
placeholder='Введите название'
{...register('item_data.title')}
error={errors.item_data?.title}
/>

View File

@ -10,6 +10,7 @@ import { Loader } from '@/components/loader';
import { ModalForm } from '@/components/modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
import { useDialogsStore } from '@/stores/dialogs';
import { hintMsg } from '@/utils/labels';
import { type IOssLayout, type IUpdateOperationDTO, OperationType, schemaUpdateOperation } from '../../backend/types';
import { useOssSuspense } from '../../backend/use-oss';
@ -79,6 +80,7 @@ export function DlgEditOperation() {
header='Редактирование операции'
submitText='Сохранить'
canSubmit={canSubmit}
validationHint={hintMsg.formInvalid}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
className='w-160 px-6 h-128'
helpTopic={HelpTopic.UI_SUBSTITUTIONS}

View File

@ -22,6 +22,7 @@ export function TabOperation({ oss }: TabOperationProps) {
<TextInput
id='operation_title'
label='Название'
placeholder='Введите название'
{...register('item_data.title')}
error={errors.item_data?.title}
/>
@ -29,6 +30,7 @@ export function TabOperation({ oss }: TabOperationProps) {
<TextInput
id='operation_alias' //
label='Сокращение'
placeholder='Введите сокращение'
className='w-80'
{...register('item_data.alias')}
error={errors.item_data?.alias}

View File

@ -45,7 +45,12 @@ export function TabSubstitutions({ oss }: TabSubstitutionsProps) {
)}
/>
<TextArea disabled value={validator.msg} rows={4} className={isCorrect ? '' : 'border-(--acc-fg-red) border-2'} />
<TextArea
disabled
value={validator.msg}
rows={4}
className={isCorrect ? '' : 'border-accent-red-foreground border-2'}
/>
</div>
);
}

View File

@ -11,6 +11,7 @@ import { PickSchema } from '@/features/library/components/pick-schema';
import { Checkbox, TextArea, TextInput } from '@/components/input';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { hintMsg } from '@/utils/labels';
import { type IImportSchemaDTO, type IOssLayout, schemaImportSchema } from '../backend/types';
import { useImportSchema } from '../backend/use-import-schema';
@ -73,7 +74,17 @@ export function DlgImportSchema() {
});
const alias = useWatch({ control: control, name: 'item_data.alias' });
const clone_source = useWatch({ control: control, name: 'clone_source' });
const canSubmit = isValid && !!alias && !manager.oss.operations.some(operation => operation.alias === alias);
const { canSubmit, hint } = (() => {
if (!isValid) {
return { canSubmit: false, hint: hintMsg.formInvalid };
} else if (!alias) {
return { canSubmit: false, hint: hintMsg.aliasEmpty };
} else if (manager.oss.operations.some(operation => operation.alias === alias)) {
return { canSubmit: false, hint: hintMsg.schemaAliasTaken };
} else {
return { canSubmit: true, hint: '' };
}
})();
function onSubmit(data: IImportSchemaDTO) {
data.position = manager.newOperationPosition(data);
@ -101,6 +112,7 @@ export function DlgImportSchema() {
header='Создание операции: Загрузка'
submitText='Создать'
canSubmit={canSubmit}
validationHint={hint}
onSubmit={event => void handleSubmit(onSubmit)(event)}
className='w-180 px-6 pb-3 cc-column'
helpTopic={HelpTopic.CC_OSS}

View File

@ -15,6 +15,7 @@ import { MiniButton } from '@/components/control';
import { Loader } from '@/components/loader';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { hintMsg } from '@/utils/labels';
import { type IOssLayout, type IRelocateConstituentsDTO, schemaRelocateConstituents } from '../backend/types';
import { useOssSuspense } from '../backend/use-oss';
@ -120,7 +121,7 @@ export function DlgRelocateConstituents() {
header='Перенос конституент'
submitText='Переместить'
canSubmit={canSubmit}
submitInvalidTooltip='Необходимо выбрать хотя бы одну собственную конституенту'
validationHint={canSubmit ? '' : hintMsg.relocateEmpty}
onSubmit={event => void handleSubmit(onSubmit)(event)}
className='w-160 h-132 py-3 px-6'
helpTopic={HelpTopic.UI_RELOCATE_CST}

View File

@ -5,7 +5,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { errorMsg } from '@/utils/labels';
import { hintMsg } from '@/utils/labels';
import { type RO } from '@/utils/meta';
import {
@ -37,7 +37,15 @@ export function DlgCreateCst() {
});
const alias = useWatch({ control: methods.control, name: 'alias' });
const cst_type = useWatch({ control: methods.control, name: 'cst_type' });
const canSubmit = methods.formState.isValid && validateNewAlias(alias, cst_type, schema);
const { canSubmit, hint } = (() => {
if (!methods.formState.isValid) {
return { canSubmit: false, hint: hintMsg.formInvalid };
} else if (!validateNewAlias(alias, cst_type, schema)) {
return { canSubmit: false, hint: hintMsg.aliasInvalid };
} else {
return { canSubmit: true, hint: '' };
}
})();
function onSubmit(data: ICreateConstituentaDTO) {
return cstCreate({ itemID: schema.id, data }).then(onCreate);
@ -48,7 +56,7 @@ export function DlgCreateCst() {
header='Создание конституенты'
canSubmit={canSubmit}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
submitInvalidTooltip={errorMsg.aliasInvalid}
validationHint={hint}
submitText='Создать'
className='cc-column w-140 max-h-120 py-2 px-6'
>

View File

@ -10,6 +10,7 @@ import { Loader } from '@/components/loader';
import { ModalForm } from '@/components/modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
import { useDialogsStore } from '@/stores/dialogs';
import { hintMsg } from '@/utils/labels';
import { type RO } from '@/utils/meta';
import {
@ -61,7 +62,17 @@ export function DlgCstTemplate() {
});
const alias = useWatch({ control: methods.control, name: 'alias' });
const cst_type = useWatch({ control: methods.control, name: 'cst_type' });
const canSubmit = methods.formState.isValid && validateNewAlias(alias, cst_type, schema);
const { canSubmit, hint } = (() => {
if (!cst_type) {
return { canSubmit: false, hint: hintMsg.templateInvalid };
} else if (!methods.formState.isValid) {
return { canSubmit: false, hint: hintMsg.formInvalid };
} else if (!validateNewAlias(alias, cst_type, schema)) {
return { canSubmit: false, hint: hintMsg.aliasInvalid };
} else {
return { canSubmit: true, hint: '' };
}
})();
const [activeTab, setActiveTab] = useState<TabID>(TabID.TEMPLATE);
@ -76,6 +87,7 @@ export function DlgCstTemplate() {
className='w-172 h-140 px-6'
canSubmit={canSubmit}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
validationHint={hint}
helpTopic={HelpTopic.RSL_TEMPLATES}
>
<Tabs className='grid' selectedIndex={activeTab} onSelect={index => setActiveTab(index as TabID)}>

View File

@ -10,7 +10,7 @@ import { MiniButton } from '@/components/control';
import { IconChild, IconRSForm } from '@/components/icons';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { errorMsg } from '@/utils/labels';
import { hintMsg } from '@/utils/labels';
import { type IUpdateConstituentaDTO, schemaUpdateConstituenta } from '../../backend/types';
import { useRSFormSuspense } from '../../backend/use-rsform';
@ -91,7 +91,7 @@ export function DlgEditCst() {
header='Редактирование конституенты'
canSubmit={canSubmit}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
submitInvalidTooltip={errorMsg.aliasInvalid}
validationHint={canSubmit ? '' : hintMsg.aliasInvalid}
submitText='Сохранить'
className='cc-column w-140 max-h-120 py-2 px-6'
>

View File

@ -10,6 +10,7 @@ import { HelpTopic } from '@/features/help';
import { ModalForm } from '@/components/modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
import { useDialogsStore } from '@/stores/dialogs';
import { hintMsg } from '@/utils/labels';
import { labelReferenceType } from '../../labels';
import {
@ -116,6 +117,7 @@ export function DlgEditReference() {
header='Редактирование ссылки'
submitText='Сохранить ссылку'
canSubmit={canSubmit}
validationHint={canSubmit ? '' : hintMsg.referenceInvalid}
onCancel={onCancel}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
className='w-160 px-6 h-128'

View File

@ -8,6 +8,7 @@ import { Loader } from '@/components/loader';
import { ModalForm } from '@/components/modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
import { useDialogsStore } from '@/stores/dialogs';
import { hintMsg } from '@/utils/labels';
import { type IInlineSynthesisDTO, schemaInlineSynthesis } from '../../backend/types';
import { useInlineSynthesis } from '../../backend/use-inline-synthesis';
@ -58,6 +59,7 @@ export function DlgInlineSynthesis() {
submitText='Добавить конституенты'
className='w-160 h-132 px-6'
canSubmit={canSubmit}
validationHint={canSubmit ? '' : hintMsg.sourceEmpty}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
>
<Tabs className='grid' selectedIndex={activeTab} onSelect={index => setActiveTab(index as TabID)}>

View File

@ -8,6 +8,7 @@ import { HelpTopic } from '@/features/help';
import { TextInput } from '@/components/input';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { hintMsg } from '@/utils/labels';
import { type CstType, type IUpdateConstituentaDTO, schemaUpdateConstituenta } from '../backend/types';
import { useRSFormSuspense } from '../backend/use-rsform';
@ -60,8 +61,8 @@ export function DlgRenameCst() {
<ModalForm
header='Переименование конституенты'
submitText='Переименовать'
submitInvalidTooltip='Введите незанятое имя, соответствующее типу'
canSubmit={canSubmit}
validationHint={canSubmit ? '' : hintMsg.aliasInvalid}
onSubmit={event => void handleSubmit(onSubmit)(event)}
className='w-120 py-6 pr-3 pl-6 flex gap-3 justify-center items-center'
helpTopic={HelpTopic.CC_CONSTITUENTA}

View File

@ -10,6 +10,7 @@ import { SubstitutionValidator } from '@/features/oss/models/oss-api';
import { ErrorField, TextArea } from '@/components/input';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { hintMsg } from '@/utils/labels';
import { type ISubstitutionsDTO, schemaSubstitutions } from '../backend/types';
import { useRSFormSuspense } from '../backend/use-rsform';
@ -50,8 +51,8 @@ export function DlgSubstituteCst() {
<ModalForm
header='Отождествление'
submitText='Отождествить'
submitInvalidTooltip='Выберите две различные конституенты'
canSubmit={isValid}
validationHint={isValid ? '' : hintMsg.substitutionsEmpty}
onSubmit={event => void handleSubmit(onSubmit)(event)}
className='w-160 px-6 pb-3'
helpTopic={HelpTopic.UI_SUBSTITUTIONS}
@ -74,7 +75,7 @@ export function DlgSubstituteCst() {
disabled
value={validator.msg}
rows={4}
className={clsx('mt-3', isCorrect ? '' : 'border-(--acc-fg-red) border-2')}
className={clsx('mt-3', isCorrect ? '' : 'border-accent-red-foreground border-2')}
/>
</ModalForm>
);

View File

@ -6,6 +6,7 @@ import { Checkbox, FileInput } from '@/components/input';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { EXTEOR_TRS_FILE } from '@/utils/constants';
import { hintMsg } from '@/utils/labels';
import { useUploadTRS } from '../backend/use-upload-trs';
@ -42,6 +43,7 @@ export function DlgUploadRSForm() {
<ModalForm
header='Импорт схемы из Экстеора'
canSubmit={!!file}
validationHint={!!file ? '' : hintMsg.fileEmpty}
onSubmit={handleSubmit}
submitText='Загрузить'
className='w-100 px-6'

View File

@ -68,12 +68,30 @@ export const errorMsg = {
privacyNotAccepted: 'Примите политику обработки персональных данных',
loginFormat: 'Имя пользователя должно содержать только буквы и цифры',
invalidLocation: 'Некорректный формат пути',
versionTaken: 'Версия с таким шифром уже существует',
emptySubstitutions: 'Выберите хотя бы одно отождествление',
aliasInvalid: 'Введите незанятое имя, соответствующее типу',
invalidResponse: 'Некорректный ответ сервера'
} as const;
/**
* UI hint descriptors.
*/
export const hintMsg = {
templateInvalid: 'Выберите шаблон конституенты',
formInvalid: 'Форма заполнена некорректно',
aliasInvalid: 'Введите незанятое имя, соответствующее типу',
aliasEmpty: 'Введите сокращение',
titleEmpty: 'Введите название',
blockTitleTaken: 'Блок с таким названием уже существует',
schemaAliasTaken: 'Схема с таким именем уже существует',
versionEmpty: 'Введите шифр версии',
versionTaken: 'Версия с таким шифром уже существует',
relocateEmpty: 'Необходимо выбрать хотя бы одну собственную конституенту',
substitutionsEmpty: 'Выберите две различные конституенты',
sourceEmpty: 'Выберите схему источник',
referenceInvalid: 'Выберите тип ссылки и заполните все поля',
fileEmpty: 'Выберите файл для загрузки'
};
/**
* UI tooltip descriptors.
*/