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}. * Represents update data for editing {@link ILibraryItem}.
*/ */
export interface IUpdateLibraryItemDTO export const UpdateLibraryItemSchema = z.object({
extends Omit<ILibraryItem, 'time_create' | 'time_update' | 'access_policy' | 'location' | 'owner'> {} 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. * Create version metadata in persistent storage.

View File

@ -32,6 +32,6 @@ export const useUpdateItem = () => {
} }
}); });
return { 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'; 'use client';
import { zodResolver } from '@hookform/resolvers/zod'; 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 { Controller, useForm } from 'react-hook-form';
import { toast } from 'react-toastify'; 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 [localParse, setLocalParse] = useState<IExpressionParse | undefined>(undefined);
const typification = localParse const typification = useMemo(
? labelTypification({ () =>
isValid: localParse.parseResult, localParse
resultType: localParse.typification, ? labelTypification({
args: localParse.args isValid: localParse.parseResult,
}) resultType: localParse.typification,
: labelCstTypification(activeCst); args: localParse.args
})
: labelCstTypification(activeCst),
[localParse, activeCst]
);
const typeInfo = { const typeInfo = useMemo(
alias: activeCst.alias, () => ({
result: localParse ? localParse.typification : activeCst.parse.typification, alias: activeCst.alias,
args: localParse ? localParse.args : activeCst.parse.args result: localParse ? localParse.typification : activeCst.parse.typification,
}; args: localParse ? localParse.args : activeCst.parse.args
}),
[activeCst, localParse]
);
const [forceComment, setForceComment] = useState(false); const [forceComment, setForceComment] = useState(false);
const isBasic = isBasicConcept(activeCst.cst_type); 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'; 'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import clsx from 'clsx'; 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 { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls'; 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 { useUpdateItem } from '@/backend/library/useUpdateItem';
import { useMutatingRSForm } from '@/backend/rsform/useMutatingRSForm'; import { useMutatingRSForm } from '@/backend/rsform/useMutatingRSForm';
import { IconSave } from '@/components/Icons'; import { IconSave } from '@/components/Icons';
@ -28,106 +31,82 @@ interface FormRSFormProps {
function FormRSForm({ id }: FormRSFormProps) { function FormRSForm({ id }: FormRSFormProps) {
const controller = useRSEdit(); const controller = useRSEdit();
const router = useConceptNavigation(); const router = useConceptNavigation();
const schema = controller.schema; const { updateItem: updateSchema } = useUpdateItem();
const { updateItem: update } = useUpdateItem(); const { setIsModified } = useModificationStore();
const { isModified, setIsModified } = useModificationStore();
const isProcessing = useMutatingRSForm(); const isProcessing = useMutatingRSForm();
const [title, setTitle] = useState(schema.title); const {
const [alias, setAlias] = useState(schema.alias); register,
const [comment, setComment] = useState(schema.comment); handleSubmit,
const [visible, setVisible] = useState(schema.visible); control,
const [readOnly, setReadOnly] = useState(schema.read_only); 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) { function handleSelectVersion(version?: VersionID) {
router.push(urls.schema(schema.id, version)); router.push(urls.schema(controller.schema.id, version));
} }
useEffect(() => { function onSubmit(data: IUpdateLibraryItemDTO) {
if (schema) { updateSchema(data, () => reset({ ...data }));
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);
};
return ( 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 <TextInput
id='schema_title' id='schema_title'
required {...register('title')}
label='Полное название' label='Полное название'
className='mb-3' className='mb-3'
value={title}
disabled={!controller.isContentEditable} disabled={!controller.isContentEditable}
onChange={event => setTitle(event.target.value)} error={errors.title}
/> />
<div className='flex justify-between gap-3 mb-3'> <div className='flex justify-between gap-3 mb-3'>
<TextInput <TextInput
id='schema_alias' id='schema_alias'
required {...register('alias')}
label='Сокращение' label='Сокращение'
className='w-[16rem]' className='w-[16rem]'
disabled={!controller.isContentEditable} disabled={!controller.isContentEditable}
value={alias} error={errors.alias}
onChange={event => setAlias(event.target.value)}
/> />
<div className='flex flex-col'> <div className='flex flex-col'>
<ToolbarVersioning blockReload={schema.oss.length > 0} /> <ToolbarVersioning blockReload={controller.schema.oss.length > 0} />
<ToolbarItemAccess <ToolbarItemAccess
visible={visible} visible={visible}
toggleVisible={() => setVisible(prev => !prev)} toggleVisible={() => setValue('visible', !visible, { shouldDirty: true })}
readOnly={readOnly} readOnly={readOnly}
toggleReadOnly={() => setReadOnly(prev => !prev)} toggleReadOnly={() => setValue('read_only', !readOnly, { shouldDirty: true })}
controller={controller} controller={controller}
/> />
<Label text='Версия' className='mb-2 select-none' /> <Label text='Версия' className='mb-2 select-none' />
<SelectVersion <SelectVersion
id='schema_version' id='schema_version'
className='select-none' className='select-none'
value={schema.version} // value={controller.schema.version} //
items={schema.versions} items={controller.schema.versions}
onChange={handleSelectVersion} onChange={handleSelectVersion}
/> />
</div> </div>
@ -135,18 +114,18 @@ function FormRSForm({ id }: FormRSFormProps) {
<TextArea <TextArea
id='schema_comment' id='schema_comment'
{...register('comment')}
label='Описание' label='Описание'
rows={3} rows={3}
value={comment}
disabled={!controller.isContentEditable || isProcessing} disabled={!controller.isContentEditable || isProcessing}
onChange={event => setComment(event.target.value)} error={errors.comment}
/> />
{controller.isContentEditable || isModified ? ( {controller.isContentEditable || isDirty ? (
<SubmitButton <SubmitButton
text='Сохранить изменения' text='Сохранить изменения'
className='self-center mt-4' className='self-center mt-4'
loading={isProcessing} loading={isProcessing}
disabled={!isModified} disabled={!isDirty}
icon={<IconSave size='1.25rem' />} icon={<IconSave size='1.25rem' />}
/> />
) : null} ) : null}

View File

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

View File

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