mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
F: Rework RelocateConstituents dialog
Some checks are pending
Frontend CI / build (22.x) (push) Waiting to run
Some checks are pending
Frontend CI / build (22.x) (push) Waiting to run
This commit is contained in:
parent
a967e957b3
commit
c5b73612f3
|
@ -12,7 +12,7 @@ import {
|
||||||
OperationID,
|
OperationID,
|
||||||
OperationType
|
OperationType
|
||||||
} from '@/features/oss/models/oss';
|
} from '@/features/oss/models/oss';
|
||||||
import { ConstituentaID, IConstituentaReference, ITargetCst } from '@/features/rsform/models/rsform';
|
import { IConstituentaReference, ITargetCst } from '@/features/rsform/models/rsform';
|
||||||
import { information } from '@/utils/labels';
|
import { information } from '@/utils/labels';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -130,10 +130,15 @@ export interface IOperationUpdateDTO extends ITargetOperation {
|
||||||
/**
|
/**
|
||||||
* Represents data, used relocating {@link IConstituenta}s between {@link ILibraryItem}s.
|
* Represents data, used relocating {@link IConstituenta}s between {@link ILibraryItem}s.
|
||||||
*/
|
*/
|
||||||
export interface ICstRelocateDTO {
|
export const schemaCstRelocate = z.object({
|
||||||
destination: LibraryItemID;
|
destination: z.number(),
|
||||||
items: ConstituentaID[];
|
items: z.array(z.number()).refine(data => data.length > 0)
|
||||||
}
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data, used relocating {@link IConstituenta}s between {@link ILibraryItem}s.
|
||||||
|
*/
|
||||||
|
export type ICstRelocateDTO = z.infer<typeof schemaCstRelocate>;
|
||||||
|
|
||||||
export const ossApi = {
|
export const ossApi = {
|
||||||
baseKey: 'oss',
|
baseKey: 'oss',
|
||||||
|
@ -218,9 +223,9 @@ export const ossApi = {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
relocateConstituents: ({ itemID, data }: { itemID: LibraryItemID; data: ICstRelocateDTO }) =>
|
relocateConstituents: (data: ICstRelocateDTO) =>
|
||||||
axiosPost<ICstRelocateDTO, IOperationSchemaDTO>({
|
axiosPost<ICstRelocateDTO, IOperationSchemaDTO>({
|
||||||
endpoint: `/api/oss/${itemID}/relocate-constituents`,
|
endpoint: `/api/oss/relocate-constituents`,
|
||||||
request: {
|
request: {
|
||||||
data: data,
|
data: data,
|
||||||
successMessage: information.changesSaved
|
successMessage: information.changesSaved
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { libraryApi } from '@/features/library/backend/api';
|
import { libraryApi } from '@/features/library/backend/api';
|
||||||
import { LibraryItemID } from '@/features/library/models/library';
|
|
||||||
import { rsformsApi } from '@/features/rsform/backend/api';
|
import { rsformsApi } from '@/features/rsform/backend/api';
|
||||||
|
|
||||||
import { ICstRelocateDTO, ossApi } from './api';
|
import { ICstRelocateDTO, ossApi } from './api';
|
||||||
|
@ -20,6 +19,6 @@ export const useRelocateConstituents = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
relocateConstituents: (data: { itemID: LibraryItemID; data: ICstRelocateDTO }) => mutation.mutate(data)
|
relocateConstituents: (data: ICstRelocateDTO) => mutation.mutate(data)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -103,7 +103,7 @@ function TabInputOperation({
|
||||||
{!createSchema ? (
|
{!createSchema ? (
|
||||||
<PickSchema
|
<PickSchema
|
||||||
items={sortedItems}
|
items={sortedItems}
|
||||||
value={attachedID}
|
value={attachedID ?? null}
|
||||||
itemType={LibraryItemType.RSFORM}
|
itemType={LibraryItemType.RSFORM}
|
||||||
onChange={onChangeAttachedID}
|
onChange={onChangeAttachedID}
|
||||||
rows={8}
|
rows={8}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { Controller, useForm, useWatch } from 'react-hook-form';
|
||||||
|
|
||||||
import { MiniButton } from '@/components/Control';
|
import { MiniButton } from '@/components/Control';
|
||||||
import { RelocateUpIcon } from '@/components/DomainIcons';
|
import { RelocateUpIcon } from '@/components/DomainIcons';
|
||||||
|
@ -11,32 +13,50 @@ import { HelpTopic } from '@/features/help/models/helpTopic';
|
||||||
import { useLibrary } from '@/features/library/backend/useLibrary';
|
import { useLibrary } from '@/features/library/backend/useLibrary';
|
||||||
import SelectLibraryItem from '@/features/library/components/SelectLibraryItem';
|
import SelectLibraryItem from '@/features/library/components/SelectLibraryItem';
|
||||||
import { ILibraryItem, LibraryItemID } from '@/features/library/models/library';
|
import { ILibraryItem, LibraryItemID } from '@/features/library/models/library';
|
||||||
import { ICstRelocateDTO } from '@/features/oss/backend/api';
|
|
||||||
import { useRSForm } from '@/features/rsform/backend/useRSForm';
|
import { useRSForm } from '@/features/rsform/backend/useRSForm';
|
||||||
import PickMultiConstituenta from '@/features/rsform/components/PickMultiConstituenta';
|
import PickMultiConstituenta from '@/features/rsform/components/PickMultiConstituenta';
|
||||||
import { ConstituentaID } from '@/features/rsform/models/rsform';
|
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
|
|
||||||
|
import { ICstRelocateDTO, IOperationPosition, schemaCstRelocate } from '../backend/api';
|
||||||
|
import { useRelocateConstituents } from '../backend/useRelocateConstituents';
|
||||||
|
import { useUpdatePositions } from '../backend/useUpdatePositions';
|
||||||
import { IOperation, IOperationSchema } from '../models/oss';
|
import { IOperation, IOperationSchema } from '../models/oss';
|
||||||
import { getRelocateCandidates } from '../models/ossAPI';
|
import { getRelocateCandidates } from '../models/ossAPI';
|
||||||
|
|
||||||
export interface DlgRelocateConstituentsProps {
|
export interface DlgRelocateConstituentsProps {
|
||||||
oss: IOperationSchema;
|
oss: IOperationSchema;
|
||||||
initialTarget?: IOperation;
|
initialTarget?: IOperation;
|
||||||
onSubmit: (data: ICstRelocateDTO) => void;
|
positions: IOperationPosition[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function DlgRelocateConstituents() {
|
function DlgRelocateConstituents() {
|
||||||
const { oss, initialTarget, onSubmit } = useDialogsStore(state => state.props as DlgRelocateConstituentsProps);
|
const { oss, initialTarget, positions } = useDialogsStore(state => state.props as DlgRelocateConstituentsProps);
|
||||||
const { items: libraryItems } = useLibrary();
|
const { items: libraryItems } = useLibrary();
|
||||||
|
const { updatePositions } = useUpdatePositions();
|
||||||
|
const { relocateConstituents } = useRelocateConstituents();
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
control,
|
||||||
|
setValue,
|
||||||
|
resetField,
|
||||||
|
formState: { isValid }
|
||||||
|
} = useForm<ICstRelocateDTO>({
|
||||||
|
resolver: zodResolver(schemaCstRelocate),
|
||||||
|
defaultValues: {
|
||||||
|
items: []
|
||||||
|
},
|
||||||
|
mode: 'onChange'
|
||||||
|
});
|
||||||
|
const destination = useWatch({ control, name: 'destination' });
|
||||||
|
const destinationItem = destination ? libraryItems.find(item => item.id === destination) : undefined;
|
||||||
|
|
||||||
const [directionUp, setDirectionUp] = useState(true);
|
const [directionUp, setDirectionUp] = useState(true);
|
||||||
const [destination, setDestination] = useState<ILibraryItem | undefined>(undefined);
|
|
||||||
const [selected, setSelected] = useState<ConstituentaID[]>([]);
|
|
||||||
const [source, setSource] = useState<ILibraryItem | undefined>(
|
const [source, setSource] = useState<ILibraryItem | undefined>(
|
||||||
libraryItems.find(item => item.id === initialTarget?.result)
|
libraryItems.find(item => item.id === initialTarget?.result)
|
||||||
);
|
);
|
||||||
const isValid = !!destination && selected.length > 0;
|
|
||||||
|
console.log(isValid);
|
||||||
|
|
||||||
const operation = oss.items.find(item => item.result === source?.id);
|
const operation = oss.items.find(item => item.result === source?.id);
|
||||||
const sourceSchemas = libraryItems.filter(item => oss.schemas.includes(item.id));
|
const sourceSchemas = libraryItems.filter(item => oss.schemas.includes(item.id));
|
||||||
|
@ -53,37 +73,50 @@ function DlgRelocateConstituents() {
|
||||||
|
|
||||||
const sourceData = useRSForm({ itemID: source?.id });
|
const sourceData = useRSForm({ itemID: source?.id });
|
||||||
const filteredConstituents = (() => {
|
const filteredConstituents = (() => {
|
||||||
if (!sourceData.schema || !destination || !operation) {
|
if (!sourceData.schema || !destinationItem || !operation) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const destinationOperation = oss.items.find(item => item.result === destination.id);
|
const destinationOperation = oss.items.find(item => item.result === destination);
|
||||||
return getRelocateCandidates(operation.id, destinationOperation!.id, sourceData.schema, oss);
|
return getRelocateCandidates(operation.id, destinationOperation!.id, sourceData.schema, oss);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
function toggleDirection() {
|
function toggleDirection() {
|
||||||
setDirectionUp(prev => !prev);
|
setDirectionUp(prev => !prev);
|
||||||
setDestination(undefined);
|
resetField('destination');
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSelectSource(newValue: ILibraryItem | undefined) {
|
function handleSelectSource(newValue: ILibraryItem | undefined) {
|
||||||
setSource(newValue);
|
setSource(newValue);
|
||||||
setDestination(undefined);
|
resetField('destination');
|
||||||
setSelected([]);
|
resetField('items');
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSelectDestination(newValue: ILibraryItem | undefined) {
|
function handleSelectDestination(newValue: ILibraryItem | undefined) {
|
||||||
setDestination(newValue);
|
if (newValue) {
|
||||||
setSelected([]);
|
setValue('destination', newValue.id);
|
||||||
|
} else {
|
||||||
|
resetField('destination');
|
||||||
|
}
|
||||||
|
resetField('items');
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit() {
|
function onSubmit(data: ICstRelocateDTO) {
|
||||||
if (destination) {
|
const positionsUnchanged = positions.every(item => {
|
||||||
onSubmit({
|
const operation = oss.operationByID.get(item.id)!;
|
||||||
destination: destination.id,
|
return operation.position_x === item.position_x && operation.position_y === item.position_y;
|
||||||
items: selected
|
});
|
||||||
});
|
if (positionsUnchanged) {
|
||||||
|
relocateConstituents(data);
|
||||||
|
} else {
|
||||||
|
updatePositions(
|
||||||
|
{
|
||||||
|
isSilent: true,
|
||||||
|
itemID: oss.id,
|
||||||
|
positions: positions
|
||||||
|
},
|
||||||
|
() => relocateConstituents(data)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -91,7 +124,7 @@ function DlgRelocateConstituents() {
|
||||||
header='Перенос конституент'
|
header='Перенос конституент'
|
||||||
submitText='Переместить'
|
submitText='Переместить'
|
||||||
canSubmit={isValid}
|
canSubmit={isValid}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={event => void handleSubmit(onSubmit)(event)}
|
||||||
className={clsx('w-[40rem] h-[33rem]', 'py-3 px-6')}
|
className={clsx('w-[40rem] h-[33rem]', 'py-3 px-6')}
|
||||||
helpTopic={HelpTopic.UI_RELOCATE_CST}
|
helpTopic={HelpTopic.UI_RELOCATE_CST}
|
||||||
>
|
>
|
||||||
|
@ -115,19 +148,25 @@ function DlgRelocateConstituents() {
|
||||||
className='w-1/2'
|
className='w-1/2'
|
||||||
placeholder='Выберите целевую схему'
|
placeholder='Выберите целевую схему'
|
||||||
items={destinationSchemas}
|
items={destinationSchemas}
|
||||||
value={destination}
|
value={destinationItem}
|
||||||
onChange={handleSelectDestination}
|
onChange={handleSelectDestination}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{sourceData.isLoading ? <Loader /> : null}
|
{sourceData.isLoading ? <Loader /> : null}
|
||||||
{!sourceData.isLoading && sourceData.schema ? (
|
{!sourceData.isLoading && sourceData.schema ? (
|
||||||
<PickMultiConstituenta
|
<Controller
|
||||||
noBorder
|
name='items'
|
||||||
schema={sourceData.schema}
|
control={control}
|
||||||
items={filteredConstituents}
|
render={({ field }) => (
|
||||||
rows={12}
|
<PickMultiConstituenta
|
||||||
value={selected}
|
noBorder
|
||||||
onChange={setSelected}
|
schema={sourceData.schema!}
|
||||||
|
items={filteredConstituents}
|
||||||
|
rows={12}
|
||||||
|
value={field.value}
|
||||||
|
onChange={field.onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,8 +19,6 @@ import { IOperationPosition } from '../../backend/api';
|
||||||
import { useOperationCreate } from '../../backend/useOperationCreate';
|
import { useOperationCreate } from '../../backend/useOperationCreate';
|
||||||
import { useOperationUpdate } from '../../backend/useOperationUpdate';
|
import { useOperationUpdate } from '../../backend/useOperationUpdate';
|
||||||
import { useOssSuspense } from '../../backend/useOSS';
|
import { useOssSuspense } from '../../backend/useOSS';
|
||||||
import { useRelocateConstituents } from '../../backend/useRelocateConstituents';
|
|
||||||
import { useUpdatePositions } from '../../backend/useUpdatePositions';
|
|
||||||
import { IOperationSchema, OperationID, OperationType } from '../../models/oss';
|
import { IOperationSchema, OperationID, OperationType } from '../../models/oss';
|
||||||
import { calculateInsertPosition } from '../../models/ossAPI';
|
import { calculateInsertPosition } from '../../models/ossAPI';
|
||||||
|
|
||||||
|
@ -100,10 +98,8 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
|
||||||
const showCreateOperation = useDialogsStore(state => state.showCreateOperation);
|
const showCreateOperation = useDialogsStore(state => state.showCreateOperation);
|
||||||
|
|
||||||
const { deleteItem } = useDeleteItem();
|
const { deleteItem } = useDeleteItem();
|
||||||
const { updatePositions } = useUpdatePositions();
|
|
||||||
const { operationCreate } = useOperationCreate();
|
const { operationCreate } = useOperationCreate();
|
||||||
const { operationUpdate } = useOperationUpdate();
|
const { operationUpdate } = useOperationUpdate();
|
||||||
const { relocateConstituents } = useRelocateConstituents();
|
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() =>
|
() =>
|
||||||
|
@ -220,25 +216,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
|
||||||
showRelocateConstituents({
|
showRelocateConstituents({
|
||||||
oss: schema,
|
oss: schema,
|
||||||
initialTarget: operation,
|
initialTarget: operation,
|
||||||
onSubmit: data => {
|
positions: positions
|
||||||
if (
|
|
||||||
positions.every(item => {
|
|
||||||
const operation = schema.operationByID.get(item.id)!;
|
|
||||||
return operation.position_x === item.position_x && operation.position_y === item.position_y;
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
relocateConstituents({ itemID: schema.id, data });
|
|
||||||
} else {
|
|
||||||
updatePositions(
|
|
||||||
{
|
|
||||||
isSilent: true,
|
|
||||||
itemID: schema.id, //
|
|
||||||
positions: positions
|
|
||||||
},
|
|
||||||
() => relocateConstituents({ itemID: schema.id, data })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import ToolbarGraphSelection from './ToolbarGraphSelection';
|
||||||
interface PickMultiConstituentaProps extends CProps.Styling {
|
interface PickMultiConstituentaProps extends CProps.Styling {
|
||||||
id?: string;
|
id?: string;
|
||||||
value: ConstituentaID[];
|
value: ConstituentaID[];
|
||||||
onChange: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
|
onChange: (newValue: ConstituentaID[]) => void;
|
||||||
|
|
||||||
schema: IRSForm;
|
schema: IRSForm;
|
||||||
items: IConstituenta[];
|
items: IConstituenta[];
|
||||||
|
@ -78,7 +78,7 @@ function PickMultiConstituenta({
|
||||||
newSelection.push(cst.id);
|
newSelection.push(cst.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
onChange(prev => [...prev.filter(cst_id => !filtered.find(cst => cst.id === cst_id)), ...newSelection]);
|
onChange([...value.filter(cst_id => !filtered.find(cst => cst.id === cst_id)), ...newSelection]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ function TabSource({ selected, receiver, setSelected }: TabSourceProps) {
|
||||||
items={sortedItems}
|
items={sortedItems}
|
||||||
itemType={LibraryItemType.RSFORM}
|
itemType={LibraryItemType.RSFORM}
|
||||||
rows={14}
|
rows={14}
|
||||||
value={selected}
|
value={selected ?? null}
|
||||||
onChange={setSelected}
|
onChange={setSelected}
|
||||||
/>
|
/>
|
||||||
<div className='flex items-center gap-6 '>
|
<div className='flex items-center gap-6 '>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user