F: Fix forms that are subject to react compiler problems

This commit is contained in:
Ivan 2025-02-06 00:26:41 +03:00
parent ddc5c7e767
commit 9b0cc3107b
6 changed files with 91 additions and 99 deletions

View File

@ -72,8 +72,20 @@ export type ICreateLibraryItemDTO = z.infer<typeof CreateLibraryItemSchema>;
/**
* Represents update data for editing {@link ILibraryItem}.
*/
export interface IUpdateLibraryItemDTO
extends Omit<ILibraryItem, 'time_create' | 'time_update' | 'access_policy' | 'location' | 'owner'> {}
export const UpdateLibraryItemSchema = z.object({
id: z.number(),
item_type: z.nativeEnum(LibraryItemType),
title: z.string().nonempty(errors.requiredField),
alias: z.string().nonempty(errors.requiredField),
comment: z.string(),
visible: z.boolean(),
read_only: z.boolean()
});
/**
* Represents update data for editing {@link ILibraryItem}.
*/
export type IUpdateLibraryItemDTO = z.infer<typeof UpdateLibraryItemSchema>;
/**
* Create version metadata in persistent storage.

View File

@ -32,6 +32,6 @@ export const useUpdateItem = () => {
}
});
return {
updateItem: (data: IUpdateLibraryItemDTO) => mutation.mutate(data)
updateItem: (data: IUpdateLibraryItemDTO, onSuccess?: () => void) => mutation.mutate(data, { onSuccess })
};
};

View File

@ -1,7 +1,8 @@
'use no memo'; // TODO: remove when react hook forms are compliant with react compiler
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useEffect, useLayoutEffect, useState } from 'react';
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { toast } from 'react-toastify';
@ -50,19 +51,26 @@ function FormConstituenta({ disabled, id, toggleReset, schema, activeCst, onOpen
const [localParse, setLocalParse] = useState<IExpressionParse | undefined>(undefined);
const typification = localParse
? labelTypification({
isValid: localParse.parseResult,
resultType: localParse.typification,
args: localParse.args
})
: labelCstTypification(activeCst);
const typification = useMemo(
() =>
localParse
? labelTypification({
isValid: localParse.parseResult,
resultType: localParse.typification,
args: localParse.args
})
: labelCstTypification(activeCst),
[localParse, activeCst]
);
const typeInfo = {
alias: activeCst.alias,
result: localParse ? localParse.typification : activeCst.parse.typification,
args: localParse ? localParse.args : activeCst.parse.args
};
const typeInfo = useMemo(
() => ({
alias: activeCst.alias,
result: localParse ? localParse.typification : activeCst.parse.typification,
args: localParse ? localParse.args : activeCst.parse.args
}),
[activeCst, localParse]
);
const [forceComment, setForceComment] = useState(false);
const isBasic = isBasicConcept(activeCst.cst_type);

View File

@ -1,11 +1,14 @@
'use no memo'; // TODO: remove when react hook forms are compliant with react compiler
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import clsx from 'clsx';
import { useEffect, useState } from 'react';
import { useEffect } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls';
import { IUpdateLibraryItemDTO } from '@/backend/library/api';
import { IUpdateLibraryItemDTO, UpdateLibraryItemSchema } from '@/backend/library/api';
import { useUpdateItem } from '@/backend/library/useUpdateItem';
import { useMutatingRSForm } from '@/backend/rsform/useMutatingRSForm';
import { IconSave } from '@/components/Icons';
@ -28,106 +31,82 @@ interface FormRSFormProps {
function FormRSForm({ id }: FormRSFormProps) {
const controller = useRSEdit();
const router = useConceptNavigation();
const schema = controller.schema;
const { updateItem: update } = useUpdateItem();
const { isModified, setIsModified } = useModificationStore();
const { updateItem: updateSchema } = useUpdateItem();
const { setIsModified } = useModificationStore();
const isProcessing = useMutatingRSForm();
const [title, setTitle] = useState(schema.title);
const [alias, setAlias] = useState(schema.alias);
const [comment, setComment] = useState(schema.comment);
const [visible, setVisible] = useState(schema.visible);
const [readOnly, setReadOnly] = useState(schema.read_only);
const {
register,
handleSubmit,
control,
setValue,
reset,
formState: { isDirty, errors }
} = useForm<IUpdateLibraryItemDTO>({
resolver: zodResolver(UpdateLibraryItemSchema),
defaultValues: {
id: controller.schema.id,
item_type: LibraryItemType.RSFORM,
title: controller.schema.title,
alias: controller.schema.alias,
comment: controller.schema.comment,
visible: controller.schema.visible,
read_only: controller.schema.read_only
}
});
const visible = useWatch({ control, name: 'visible' });
const readOnly = useWatch({ control, name: 'read_only' });
useEffect(() => {
setIsModified(isDirty);
}, [isDirty, controller.schema, setIsModified]);
function handleSelectVersion(version?: VersionID) {
router.push(urls.schema(schema.id, version));
router.push(urls.schema(controller.schema.id, version));
}
useEffect(() => {
if (schema) {
setTitle(schema.title);
setAlias(schema.alias);
setComment(schema.comment);
setVisible(schema.visible);
setReadOnly(schema.read_only);
}
}, [schema]);
useEffect(() => {
setIsModified(
schema.title !== title ||
schema.alias !== alias ||
schema.comment !== comment ||
schema.visible !== visible ||
schema.read_only !== readOnly
);
return () => setIsModified(false);
}, [
schema.title,
schema.alias,
schema.comment,
schema.visible,
schema.read_only,
title,
alias,
comment,
visible,
readOnly,
setIsModified
]);
const handleSubmit = (event?: React.FormEvent<HTMLFormElement>) => {
if (event) {
event.preventDefault();
}
const data: IUpdateLibraryItemDTO = {
id: schema.id,
item_type: LibraryItemType.RSFORM,
title: title,
alias: alias,
comment: comment,
visible: visible,
read_only: readOnly
};
update(data);
};
function onSubmit(data: IUpdateLibraryItemDTO) {
updateSchema(data, () => reset({ ...data }));
}
return (
<form id={id} className={clsx('mt-1 min-w-[22rem] sm:w-[30rem]', 'flex flex-col pt-1')} onSubmit={handleSubmit}>
<form
id={id}
className={clsx('mt-1 min-w-[22rem] sm:w-[30rem]', 'flex flex-col pt-1')}
onSubmit={event => void handleSubmit(onSubmit)(event)}
>
<TextInput
id='schema_title'
required
{...register('title')}
label='Полное название'
className='mb-3'
value={title}
disabled={!controller.isContentEditable}
onChange={event => setTitle(event.target.value)}
error={errors.title}
/>
<div className='flex justify-between gap-3 mb-3'>
<TextInput
id='schema_alias'
required
{...register('alias')}
label='Сокращение'
className='w-[16rem]'
disabled={!controller.isContentEditable}
value={alias}
onChange={event => setAlias(event.target.value)}
error={errors.alias}
/>
<div className='flex flex-col'>
<ToolbarVersioning blockReload={schema.oss.length > 0} />
<ToolbarVersioning blockReload={controller.schema.oss.length > 0} />
<ToolbarItemAccess
visible={visible}
toggleVisible={() => setVisible(prev => !prev)}
toggleVisible={() => setValue('visible', !visible, { shouldDirty: true })}
readOnly={readOnly}
toggleReadOnly={() => setReadOnly(prev => !prev)}
toggleReadOnly={() => setValue('read_only', !readOnly, { shouldDirty: true })}
controller={controller}
/>
<Label text='Версия' className='mb-2 select-none' />
<SelectVersion
id='schema_version'
className='select-none'
value={schema.version} //
items={schema.versions}
value={controller.schema.version} //
items={controller.schema.versions}
onChange={handleSelectVersion}
/>
</div>
@ -135,18 +114,18 @@ function FormRSForm({ id }: FormRSFormProps) {
<TextArea
id='schema_comment'
{...register('comment')}
label='Описание'
rows={3}
value={comment}
disabled={!controller.isContentEditable || isProcessing}
onChange={event => setComment(event.target.value)}
error={errors.comment}
/>
{controller.isContentEditable || isModified ? (
{controller.isContentEditable || isDirty ? (
<SubmitButton
text='Сохранить изменения'
className='self-center mt-4'
loading={isProcessing}
disabled={!isModified}
disabled={!isDirty}
icon={<IconSave size='1.25rem' />}
/>
) : null}

View File

@ -66,12 +66,6 @@ function FormSignup() {
>
<h1>
<span>Новый пользователь</span>
<Overlay id={globals.password_tooltip} position='top-[5.4rem] left-[3.5rem]'>
<IconHelp size='1.25rem' className='icon-primary' />
</Overlay>
<Tooltip anchorSelect={`#${globals.password_tooltip}`} offset={6}>
используйте уникальный пароль для каждого сайта
</Tooltip>
<Overlay id={globals.email_tooltip} position='top-[0.5rem] right-[1.75rem]'>
<IconHelp size='1.25rem' className='icon-primary' />
</Overlay>
@ -120,7 +114,7 @@ function FormSignup() {
required
spellCheck={false}
label='Электронная почта (email)'
title='электронная почта в корректном формате, например: i.petrov@mycompany.ru.com'
title='электронная почта в корректном формате'
error={errors.email}
/>
<TextInput

View File

@ -110,7 +110,6 @@ export const globals = {
tooltip: 'global_tooltip',
value_tooltip: 'value_tooltip',
constituenta_tooltip: 'cst_tooltip',
password_tooltip: 'password_tooltip',
email_tooltip: 'email_tooltip',
main_scroll: 'main_scroll',
library_item_editor: 'library_item_editor',