mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 21:10:38 +03:00
This commit is contained in:
parent
07c1d54f17
commit
a44c1cd170
|
@ -5,15 +5,7 @@ import { axiosDelete, axiosGet, axiosPatch, axiosPost } from '@/backend/apiTrans
|
|||
import { DELAYS } from '@/backend/configuration';
|
||||
import { ossApi } from '@/backend/oss/api';
|
||||
import { IRSFormDTO, rsformsApi } from '@/backend/rsform/api';
|
||||
import {
|
||||
AccessPolicy,
|
||||
ILibraryItem,
|
||||
IVersionData,
|
||||
IVersionInfo,
|
||||
LibraryItemID,
|
||||
LibraryItemType,
|
||||
VersionID
|
||||
} from '@/models/library';
|
||||
import { AccessPolicy, ILibraryItem, IVersionInfo, LibraryItemID, LibraryItemType, VersionID } from '@/models/library';
|
||||
import { validateLocation } from '@/models/libraryAPI';
|
||||
import { ConstituentaID } from '@/models/rsform';
|
||||
import { UserID } from '@/models/user';
|
||||
|
@ -90,7 +82,7 @@ export type IUpdateLibraryItemDTO = z.infer<typeof UpdateLibraryItemSchema>;
|
|||
/**
|
||||
* Create version metadata in persistent storage.
|
||||
*/
|
||||
export const CreateVersionSchema = z.object({
|
||||
export const VersionCreateSchema = z.object({
|
||||
version: z.string(),
|
||||
description: z.string(),
|
||||
items: z.array(z.number()).optional()
|
||||
|
@ -99,7 +91,7 @@ export const CreateVersionSchema = z.object({
|
|||
/**
|
||||
* Create version metadata in persistent storage.
|
||||
*/
|
||||
export type IVersionCreateDTO = z.infer<typeof CreateVersionSchema>;
|
||||
export type IVersionCreateDTO = z.infer<typeof VersionCreateSchema>;
|
||||
|
||||
/**
|
||||
* Represents data response when creating {@link IVersionInfo}.
|
||||
|
@ -109,6 +101,20 @@ export interface IVersionCreatedResponse {
|
|||
schema: IRSFormDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents version data, intended to update version metadata in persistent storage.
|
||||
*/
|
||||
export const VersionUpdateSchema = z.object({
|
||||
id: z.number(),
|
||||
version: z.string().nonempty(errors.requiredField),
|
||||
description: z.string()
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents version data, intended to update version metadata in persistent storage.
|
||||
*/
|
||||
export type IVersionUpdateDTO = z.infer<typeof VersionUpdateSchema>;
|
||||
|
||||
export const libraryApi = {
|
||||
baseKey: 'library',
|
||||
libraryListKey: ['library', 'list'],
|
||||
|
@ -234,9 +240,9 @@ export const libraryApi = {
|
|||
successMessage: information.versionRestored
|
||||
}
|
||||
}),
|
||||
versionUpdate: ({ versionID, data }: { versionID: VersionID; data: IVersionData }) =>
|
||||
axiosPatch<IVersionData, IVersionInfo>({
|
||||
endpoint: `/api/versions/${versionID}`,
|
||||
versionUpdate: (data: IVersionUpdateDTO) =>
|
||||
axiosPatch<IVersionUpdateDTO, IVersionInfo>({
|
||||
endpoint: `/api/versions/${data.id}`,
|
||||
request: {
|
||||
data: data,
|
||||
successMessage: information.changesSaved
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { IRSFormDTO, rsformsApi } from '@/backend/rsform/api';
|
||||
import { IVersionData, VersionID } from '@/models/library';
|
||||
|
||||
import { libraryApi } from './api';
|
||||
import { IVersionUpdateDTO, libraryApi } from './api';
|
||||
|
||||
export const useVersionUpdate = () => {
|
||||
const client = useQueryClient();
|
||||
|
@ -28,6 +27,6 @@ export const useVersionUpdate = () => {
|
|||
}
|
||||
});
|
||||
return {
|
||||
versionUpdate: (data: { versionID: VersionID; data: IVersionData }) => mutation.mutate(data)
|
||||
versionUpdate: (data: IVersionUpdateDTO, onSuccess?: () => void) => mutation.mutate(data, { onSuccess })
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,6 +4,9 @@ import { CProps } from '@/components/props';
|
|||
import { globals } from '@/utils/constants';
|
||||
|
||||
interface MiniButtonProps extends CProps.Button {
|
||||
/** Button type. */
|
||||
type?: 'button' | 'submit';
|
||||
|
||||
/** Icon to display in the button. */
|
||||
icon: React.ReactNode;
|
||||
|
||||
|
@ -25,12 +28,13 @@ export function MiniButton({
|
|||
title,
|
||||
titleHtml,
|
||||
hideTitle,
|
||||
type = 'button',
|
||||
className,
|
||||
...restProps
|
||||
}: MiniButtonProps) {
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
type={type}
|
||||
tabIndex={tabIndex ?? -1}
|
||||
className={clsx(
|
||||
'rounded-lg',
|
||||
|
|
|
@ -4,7 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||
import clsx from 'clsx';
|
||||
import { Controller, useForm, useWatch } from 'react-hook-form';
|
||||
|
||||
import { CreateVersionSchema, IVersionCreateDTO } from '@/backend/library/api';
|
||||
import { VersionCreateSchema, IVersionCreateDTO } from '@/backend/library/api';
|
||||
import { useVersionCreate } from '@/backend/library/useVersionCreate';
|
||||
import { Checkbox, TextArea, TextInput } from '@/components/ui/Input';
|
||||
import { ModalForm } from '@/components/ui/Modal';
|
||||
|
@ -33,7 +33,7 @@ function DlgCreateVersion() {
|
|||
const { versionCreate } = useVersionCreate();
|
||||
|
||||
const { register, handleSubmit, control } = useForm<IVersionCreateDTO>({
|
||||
resolver: zodResolver(CreateVersionSchema),
|
||||
resolver: zodResolver(VersionCreateSchema),
|
||||
defaultValues: {
|
||||
version: versions.length > 0 ? nextVersion(versions[0].version) : '1.0.0',
|
||||
description: '',
|
||||
|
|
|
@ -1,108 +1,119 @@
|
|||
'use no memo'; // TODO: remove when react hook forms are compliant with react compiler
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useMemo } from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
|
||||
import { IVersionUpdateDTO, VersionUpdateSchema } from '@/backend/library/api';
|
||||
import { useMutatingLibrary } from '@/backend/library/useMutatingLibrary';
|
||||
import { useVersionDelete } from '@/backend/library/useVersionDelete';
|
||||
import { useVersionUpdate } from '@/backend/library/useVersionUpdate';
|
||||
import { useRSFormSuspense } from '@/backend/rsform/useRSForm';
|
||||
import { IconReset, IconSave } from '@/components/Icons';
|
||||
import { MiniButton } from '@/components/ui/Control';
|
||||
import { TextArea, TextInput } from '@/components/ui/Input';
|
||||
import { ModalView } from '@/components/ui/Modal';
|
||||
import { ILibraryItemVersioned, IVersionInfo, VersionID } from '@/models/library';
|
||||
import { LibraryItemID, VersionID } from '@/models/library';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import TableVersions from './TableVersions';
|
||||
|
||||
export interface DlgEditVersionsProps {
|
||||
item: ILibraryItemVersioned;
|
||||
itemID: LibraryItemID;
|
||||
afterDelete: (targetVersion: VersionID) => void;
|
||||
}
|
||||
|
||||
function DlgEditVersions() {
|
||||
const { item, afterDelete } = useDialogsStore(state => state.props as DlgEditVersionsProps);
|
||||
const processing = useMutatingLibrary();
|
||||
const { itemID, afterDelete } = useDialogsStore(state => state.props as DlgEditVersionsProps);
|
||||
const hideDialog = useDialogsStore(state => state.hideDialog);
|
||||
const { schema } = useRSFormSuspense({ itemID });
|
||||
const isProcessing = useMutatingLibrary();
|
||||
const { versionDelete } = useVersionDelete();
|
||||
const { versionUpdate } = useVersionUpdate();
|
||||
|
||||
const [selected, setSelected] = useState<IVersionInfo | undefined>(undefined);
|
||||
const [version, setVersion] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
control,
|
||||
reset,
|
||||
formState: { isDirty }
|
||||
} = useForm<IVersionUpdateDTO>({
|
||||
resolver: zodResolver(VersionUpdateSchema),
|
||||
defaultValues: {
|
||||
id: schema.versions[0].id,
|
||||
version: schema.versions[0].version,
|
||||
description: schema.versions[0].description
|
||||
}
|
||||
});
|
||||
const versionID = useWatch({ control, name: 'id' });
|
||||
const versionName = useWatch({ control, name: 'version' });
|
||||
|
||||
const isValid = selected && item.versions.every(ver => ver.id === selected.id || ver.version != version);
|
||||
const isModified = selected && (selected.version != version || selected.description != description);
|
||||
const isValid = useMemo(
|
||||
() => schema.versions.every(ver => ver.id === versionID || ver.version != versionName),
|
||||
[schema, versionID, versionName]
|
||||
);
|
||||
|
||||
function handleDeleteVersion(targetVersion: VersionID) {
|
||||
versionDelete({ itemID: item.id, versionID: targetVersion }, () => afterDelete(targetVersion));
|
||||
}
|
||||
|
||||
function handleUpdate() {
|
||||
if (!isModified || !selected || processing || !isValid) {
|
||||
function handleSelectVersion(targetVersion: VersionID) {
|
||||
const ver = schema.versions.find(ver => ver.id === targetVersion);
|
||||
if (!ver) {
|
||||
return;
|
||||
}
|
||||
versionUpdate({
|
||||
versionID: selected.id,
|
||||
data: {
|
||||
version: version,
|
||||
description: description
|
||||
reset({ ...ver });
|
||||
}
|
||||
|
||||
function handleDeleteVersion(targetVersion: VersionID) {
|
||||
const nextVer = schema.versions.find(ver => ver.id !== targetVersion);
|
||||
versionDelete({ itemID: itemID, versionID: targetVersion }, () => {
|
||||
if (!nextVer) {
|
||||
hideDialog();
|
||||
} else if (targetVersion === versionID) {
|
||||
reset({ id: nextVer.id, version: nextVer.version, description: nextVer.description });
|
||||
}
|
||||
afterDelete(targetVersion);
|
||||
});
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
if (!selected) {
|
||||
return false;
|
||||
function onUpdate(data: IVersionUpdateDTO) {
|
||||
if (!isDirty || isProcessing || !isValid) {
|
||||
return;
|
||||
}
|
||||
setVersion(selected?.version ?? '');
|
||||
setDescription(selected?.description ?? '');
|
||||
versionUpdate(data, () => reset({ ...data }));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setVersion(selected?.version ?? '');
|
||||
setDescription(selected?.description ?? '');
|
||||
}, [selected]);
|
||||
|
||||
return (
|
||||
<ModalView header='Редактирование версий' className='flex flex-col w-[40rem] px-6 gap-3 pb-6'>
|
||||
<TableVersions
|
||||
processing={processing}
|
||||
items={item.versions}
|
||||
processing={isProcessing}
|
||||
items={schema.versions}
|
||||
onDelete={handleDeleteVersion}
|
||||
onSelect={versionID => setSelected(item.versions.find(ver => ver.id === versionID))}
|
||||
selected={selected?.id}
|
||||
onSelect={handleSelectVersion}
|
||||
selected={versionID}
|
||||
/>
|
||||
|
||||
<div className='flex'>
|
||||
<TextInput
|
||||
id='dlg_version'
|
||||
dense
|
||||
label='Версия'
|
||||
className='w-[16rem] mr-3'
|
||||
value={version}
|
||||
onChange={event => setVersion(event.target.value)}
|
||||
/>
|
||||
<form className='flex' onSubmit={event => void handleSubmit(onUpdate)(event)}>
|
||||
<TextInput id='dlg_version' {...register('version')} dense label='Версия' className='w-[16rem] mr-3' />
|
||||
<div className='cc-icons'>
|
||||
<MiniButton
|
||||
type='submit'
|
||||
title='Сохранить изменения'
|
||||
disabled={!isModified || !isValid || processing}
|
||||
disabled={!isDirty || !isValid || isProcessing}
|
||||
icon={<IconSave size='1.25rem' className='icon-primary' />}
|
||||
onClick={handleUpdate}
|
||||
/>
|
||||
<MiniButton
|
||||
title='Сбросить несохраненные изменения'
|
||||
disabled={!isModified}
|
||||
onClick={handleReset}
|
||||
disabled={!isDirty}
|
||||
onClick={() => reset()}
|
||||
icon={<IconReset size='1.25rem' className='icon-primary' />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<TextArea
|
||||
id='dlg_description'
|
||||
id='dlg_description' //
|
||||
{...register('description')}
|
||||
spellCheck
|
||||
label='Описание'
|
||||
rows={3}
|
||||
value={description}
|
||||
onChange={event => setDescription(event.target.value)}
|
||||
/>
|
||||
</ModalView>
|
||||
);
|
||||
|
|
|
@ -21,6 +21,13 @@ const columnHelper = createColumnHelper<IVersionInfo>();
|
|||
|
||||
function TableVersions({ processing, items, onDelete, selected, onSelect }: TableVersionsProps) {
|
||||
const intl = useIntl();
|
||||
|
||||
function handleDeleteVersion(event: React.MouseEvent, targetVersion: VersionID) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onDelete(targetVersion);
|
||||
}
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor('version', {
|
||||
id: 'version',
|
||||
|
@ -61,7 +68,7 @@ function TableVersions({ processing, items, onDelete, selected, onSelect }: Tabl
|
|||
noPadding
|
||||
disabled={processing}
|
||||
icon={<IconRemove size='1.25rem' className='icon-red' />}
|
||||
onClick={() => onDelete(props.row.original.id)}
|
||||
onClick={event => handleDeleteVersion(event, props.row.original.id)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -54,14 +54,6 @@ export interface IVersionInfo {
|
|||
time_create: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents version data, intended to update version metadata in persistent storage.
|
||||
*/
|
||||
export interface IVersionData {
|
||||
version: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents library item common data typical for all item types.
|
||||
*/
|
||||
|
|
|
@ -48,7 +48,7 @@ function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) {
|
|||
|
||||
function handleEditVersions() {
|
||||
showEditVersions({
|
||||
item: controller.schema,
|
||||
itemID: controller.schema.id,
|
||||
afterDelete: targetVersion => {
|
||||
if (targetVersion === controller.activeVersion) controller.navigateVersion(undefined);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user