F: rework CloneLibraryItem dialog
This commit is contained in:
parent
6f03dba4e7
commit
8d611d5471
|
@ -7,7 +7,6 @@ import { ossApi } from '@/backend/oss/api';
|
||||||
import { IRSFormDTO, rsformsApi } from '@/backend/rsform/api';
|
import { IRSFormDTO, rsformsApi } from '@/backend/rsform/api';
|
||||||
import { AccessPolicy, ILibraryItem, IVersionInfo, LibraryItemID, LibraryItemType, VersionID } from '@/models/library';
|
import { AccessPolicy, ILibraryItem, IVersionInfo, LibraryItemID, LibraryItemType, VersionID } from '@/models/library';
|
||||||
import { validateLocation } from '@/models/libraryAPI';
|
import { validateLocation } from '@/models/libraryAPI';
|
||||||
import { ConstituentaID } from '@/models/rsform';
|
|
||||||
import { UserID } from '@/models/user';
|
import { UserID } from '@/models/user';
|
||||||
import { errors, information } from '@/utils/labels';
|
import { errors, information } from '@/utils/labels';
|
||||||
|
|
||||||
|
@ -22,9 +21,24 @@ export interface IRenameLocationDTO {
|
||||||
/**
|
/**
|
||||||
* Represents data, used for cloning {@link IRSForm}.
|
* Represents data, used for cloning {@link IRSForm}.
|
||||||
*/
|
*/
|
||||||
export interface ICloneLibraryItemDTO extends Omit<ILibraryItem, 'time_create' | 'time_update' | 'owner'> {
|
export const CloneLibraryItemSchema = z.object({
|
||||||
items?: ConstituentaID[];
|
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(),
|
||||||
|
location: z.string().refine(data => validateLocation(data), { message: errors.invalidLocation }),
|
||||||
|
access_policy: z.nativeEnum(AccessPolicy),
|
||||||
|
|
||||||
|
items: z.array(z.number()).optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data, used for cloning {@link IRSForm}.
|
||||||
|
*/
|
||||||
|
export type ICloneLibraryItemDTO = z.infer<typeof CloneLibraryItemSchema>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents data, used for creating {@link IRSForm}.
|
* Represents data, used for creating {@link IRSForm}.
|
||||||
|
@ -37,16 +51,12 @@ export const CreateLibraryItemSchema = z
|
||||||
comment: z.string(),
|
comment: z.string(),
|
||||||
visible: z.boolean(),
|
visible: z.boolean(),
|
||||||
read_only: z.boolean(),
|
read_only: z.boolean(),
|
||||||
location: z.string(),
|
location: z.string().refine(data => validateLocation(data), { message: errors.invalidLocation }),
|
||||||
access_policy: z.nativeEnum(AccessPolicy),
|
access_policy: z.nativeEnum(AccessPolicy),
|
||||||
|
|
||||||
file: z.instanceof(File).optional(),
|
file: z.instanceof(File).optional(),
|
||||||
fileName: z.string().optional()
|
fileName: z.string().optional()
|
||||||
})
|
})
|
||||||
.refine(data => validateLocation(data.location), {
|
|
||||||
path: ['location'],
|
|
||||||
message: errors.invalidLocation
|
|
||||||
})
|
|
||||||
.refine(data => !!data.file || !!data.title, {
|
.refine(data => !!data.file || !!data.title, {
|
||||||
path: ['title'],
|
path: ['title'],
|
||||||
message: errors.requiredField
|
message: errors.requiredField
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
'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 { useConceptNavigation } from '@/app/Navigation/NavigationContext';
|
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
|
||||||
import { urls } from '@/app/urls';
|
import { urls } from '@/app/urls';
|
||||||
import { useAuthSuspense } from '@/backend/auth/useAuth';
|
import { useAuthSuspense } from '@/backend/auth/useAuth';
|
||||||
import { ICloneLibraryItemDTO } from '@/backend/library/api';
|
import { CloneLibraryItemSchema, ICloneLibraryItemDTO } from '@/backend/library/api';
|
||||||
import { useCloneItem } from '@/backend/library/useCloneItem';
|
import { useCloneItem } from '@/backend/library/useCloneItem';
|
||||||
import { VisibilityIcon } from '@/components/DomainIcons';
|
import { VisibilityIcon } from '@/components/DomainIcons';
|
||||||
import SelectAccessPolicy from '@/components/select/SelectAccessPolicy';
|
import SelectAccessPolicy from '@/components/select/SelectAccessPolicy';
|
||||||
|
@ -16,7 +17,7 @@ import { MiniButton } from '@/components/ui/Control';
|
||||||
import { Checkbox, Label, TextArea, TextInput } from '@/components/ui/Input';
|
import { Checkbox, Label, TextArea, TextInput } from '@/components/ui/Input';
|
||||||
import { ModalForm } from '@/components/ui/Modal';
|
import { ModalForm } from '@/components/ui/Modal';
|
||||||
import { AccessPolicy, ILibraryItem, LocationHead } from '@/models/library';
|
import { AccessPolicy, ILibraryItem, LocationHead } from '@/models/library';
|
||||||
import { cloneTitle, combineLocation, validateLocation } from '@/models/libraryAPI';
|
import { cloneTitle, combineLocation } from '@/models/libraryAPI';
|
||||||
import { ConstituentaID } from '@/models/rsform';
|
import { ConstituentaID } from '@/models/rsform';
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
|
|
||||||
|
@ -33,82 +34,81 @@ function DlgCloneLibraryItem() {
|
||||||
);
|
);
|
||||||
const router = useConceptNavigation();
|
const router = useConceptNavigation();
|
||||||
const { user } = useAuthSuspense();
|
const { user } = useAuthSuspense();
|
||||||
|
|
||||||
const [title, setTitle] = useState(cloneTitle(base));
|
|
||||||
const [alias, setAlias] = useState(base.alias);
|
|
||||||
const [comment, setComment] = useState(base.comment);
|
|
||||||
const [visible, setVisible] = useState(true);
|
|
||||||
const [policy, setPolicy] = useState(AccessPolicy.PUBLIC);
|
|
||||||
|
|
||||||
const [onlySelected, setOnlySelected] = useState(false);
|
|
||||||
|
|
||||||
const [head, setHead] = useState(initialLocation.substring(0, 2) as LocationHead);
|
|
||||||
const [body, setBody] = useState(initialLocation.substring(3));
|
|
||||||
const location = combineLocation(head, body);
|
|
||||||
|
|
||||||
const { cloneItem } = useCloneItem();
|
const { cloneItem } = useCloneItem();
|
||||||
|
|
||||||
const canSubmit = title !== '' && alias !== '' && validateLocation(location);
|
const {
|
||||||
|
register,
|
||||||
function handleSelectLocation(newValue: string) {
|
handleSubmit,
|
||||||
setHead(newValue.substring(0, 2) as LocationHead);
|
control,
|
||||||
setBody(newValue.length > 3 ? newValue.substring(3) : '');
|
formState: { errors, isValid }
|
||||||
}
|
} = useForm<ICloneLibraryItemDTO>({
|
||||||
|
resolver: zodResolver(CloneLibraryItemSchema),
|
||||||
function handleSubmit() {
|
defaultValues: {
|
||||||
const data: ICloneLibraryItemDTO = {
|
|
||||||
id: base.id,
|
id: base.id,
|
||||||
item_type: base.item_type,
|
item_type: base.item_type,
|
||||||
title: title,
|
title: cloneTitle(base),
|
||||||
alias: alias,
|
alias: base.alias,
|
||||||
comment: comment,
|
comment: base.comment,
|
||||||
|
visible: true,
|
||||||
read_only: false,
|
read_only: false,
|
||||||
visible: visible,
|
access_policy: AccessPolicy.PUBLIC,
|
||||||
access_policy: policy,
|
location: initialLocation,
|
||||||
location: location
|
items: undefined
|
||||||
};
|
},
|
||||||
if (onlySelected) {
|
mode: 'onChange',
|
||||||
data.items = selected;
|
reValidateMode: 'onChange'
|
||||||
}
|
});
|
||||||
|
|
||||||
|
function onSubmit(data: ICloneLibraryItemDTO) {
|
||||||
cloneItem(data, newSchema => router.push(urls.schema(newSchema.id)));
|
cloneItem(data, newSchema => router.push(urls.schema(newSchema.id)));
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalForm
|
<ModalForm
|
||||||
header='Создание копии концептуальной схемы'
|
header='Создание копии концептуальной схемы'
|
||||||
canSubmit={canSubmit}
|
|
||||||
submitText='Создать'
|
submitText='Создать'
|
||||||
onSubmit={handleSubmit}
|
canSubmit={isValid}
|
||||||
|
onSubmit={event => void handleSubmit(onSubmit)(event)}
|
||||||
className={clsx('px-6 py-2', 'cc-column', 'max-h-full w-[30rem]')}
|
className={clsx('px-6 py-2', 'cc-column', 'max-h-full w-[30rem]')}
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
id='dlg_full_name'
|
id='dlg_full_name' //
|
||||||
label='Полное название'
|
label='Полное название'
|
||||||
value={title}
|
{...register('title')}
|
||||||
onChange={event => setTitle(event.target.value)}
|
error={errors.title}
|
||||||
/>
|
/>
|
||||||
<div className='flex justify-between gap-3'>
|
<div className='flex justify-between gap-3'>
|
||||||
<TextInput
|
<TextInput
|
||||||
id='dlg_alias'
|
id='dlg_alias'
|
||||||
label='Сокращение'
|
label='Сокращение'
|
||||||
value={alias}
|
className='w-[16rem]'
|
||||||
className='w-[15rem]'
|
{...register('alias')}
|
||||||
onChange={event => setAlias(event.target.value)}
|
error={errors.alias}
|
||||||
/>
|
/>
|
||||||
<div className='flex flex-col gap-2'>
|
<div className='flex flex-col gap-2'>
|
||||||
<Label text='Доступ' className='self-center select-none' />
|
<Label text='Доступ' className='self-center select-none' />
|
||||||
<div className='ml-auto cc-icons'>
|
<div className='ml-auto cc-icons'>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name='access_policy'
|
||||||
|
render={({ field }) => (
|
||||||
<SelectAccessPolicy
|
<SelectAccessPolicy
|
||||||
stretchLeft // prettier: split-lines
|
value={field.value} //
|
||||||
value={policy}
|
onChange={field.onChange}
|
||||||
onChange={newPolicy => setPolicy(newPolicy)}
|
stretchLeft
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name='visible'
|
||||||
|
render={({ field }) => (
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title={visible ? 'Библиотека: отображать' : 'Библиотека: скрывать'}
|
title={field.value ? 'Библиотека: отображать' : 'Библиотека: скрывать'}
|
||||||
icon={<VisibilityIcon value={visible} />}
|
icon={<VisibilityIcon value={field.value} />}
|
||||||
onClick={() => setVisible(prev => !prev)}
|
onClick={() => field.onChange(!field.value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -117,26 +117,60 @@ function DlgCloneLibraryItem() {
|
||||||
<div className='flex justify-between gap-3'>
|
<div className='flex justify-between gap-3'>
|
||||||
<div className='flex flex-col gap-2 w-[7rem] h-min'>
|
<div className='flex flex-col gap-2 w-[7rem] h-min'>
|
||||||
<Label text='Корень' />
|
<Label text='Корень' />
|
||||||
<SelectLocationHead value={head} onChange={setHead} excluded={!user.is_staff ? [LocationHead.LIBRARY] : []} />
|
<Controller
|
||||||
|
control={control} //
|
||||||
|
name='location'
|
||||||
|
render={({ field }) => (
|
||||||
|
<SelectLocationHead
|
||||||
|
value={field.value.substring(0, 2) as LocationHead}
|
||||||
|
onChange={newValue => field.onChange(combineLocation(newValue, field.value.substring(3)))}
|
||||||
|
excluded={!user.is_staff ? [LocationHead.LIBRARY] : []}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<SelectLocationContext value={location} onChange={handleSelectLocation} />
|
<Controller
|
||||||
|
control={control} //
|
||||||
|
name='location'
|
||||||
|
render={({ field }) => (
|
||||||
|
<SelectLocationContext
|
||||||
|
value={field.value} //
|
||||||
|
onChange={field.onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control} //
|
||||||
|
name='location'
|
||||||
|
render={({ field }) => (
|
||||||
<TextArea
|
<TextArea
|
||||||
id='dlg_cst_body'
|
id='dlg_location'
|
||||||
label='Путь'
|
label='Путь'
|
||||||
rows={3}
|
rows={3}
|
||||||
value={body}
|
value={field.value.substring(3)}
|
||||||
onChange={event => setBody(event.target.value)}
|
onChange={event => field.onChange(combineLocation(field.value.substring(0, 2), event.target.value))}
|
||||||
|
error={errors.location}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TextArea id='dlg_comment' label='Описание' value={comment} onChange={event => setComment(event.target.value)} />
|
<TextArea id='dlg_comment' {...register('comment')} label='Описание' error={errors.comment} />
|
||||||
|
|
||||||
|
{selected.length > 0 ? (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name='items'
|
||||||
|
render={({ field }) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id='dlg_only_selected'
|
id='dlg_only_selected'
|
||||||
label={`Только выбранные конституенты [${selected.length} из ${totalCount}]`}
|
label={`Только выбранные конституенты [${selected.length} из ${totalCount}]`}
|
||||||
value={onlySelected}
|
value={field.value !== undefined}
|
||||||
onChange={value => setOnlySelected(value)}
|
onChange={value => field.onChange(value ? selected : undefined)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</ModalForm>
|
</ModalForm>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,12 +166,13 @@ function FormCreateItem() {
|
||||||
<Label text='Доступ' className='self-center select-none' />
|
<Label text='Доступ' className='self-center select-none' />
|
||||||
<div className='ml-auto cc-icons'>
|
<div className='ml-auto cc-icons'>
|
||||||
<Controller
|
<Controller
|
||||||
control={control} //
|
control={control}
|
||||||
name='access_policy'
|
name='access_policy'
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<SelectAccessPolicy
|
<SelectAccessPolicy
|
||||||
value={field.value} //
|
value={field.value} //
|
||||||
onChange={field.onChange}
|
onChange={field.onChange}
|
||||||
|
stretchLeft
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -228,7 +229,7 @@ function FormCreateItem() {
|
||||||
name='location'
|
name='location'
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<TextArea
|
<TextArea
|
||||||
id='dlg_cst_body'
|
id='location'
|
||||||
label='Путь'
|
label='Путь'
|
||||||
rows={4}
|
rows={4}
|
||||||
value={field.value.substring(3)}
|
value={field.value.substring(3)}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user