F: Rework Edit Operation dialog

This commit is contained in:
Ivan 2025-02-12 00:14:18 +03:00
parent c0b22f5ca1
commit b6f1ff3337
15 changed files with 194 additions and 242 deletions

View File

@ -6,11 +6,11 @@ import { DELAYS } from '@/backend/configuration';
import { ILibraryItem, ILibraryItemData, LibraryItemID } from '@/features/library/models/library'; import { ILibraryItem, ILibraryItemData, LibraryItemID } from '@/features/library/models/library';
import { import {
IArgument, IArgument,
ICstSubstitute,
ICstSubstituteEx, ICstSubstituteEx,
IOperation, IOperation,
OperationID, OperationID,
OperationType OperationType,
schemaCstSubstitute
} from '@/features/oss/models/oss'; } from '@/features/oss/models/oss';
import { 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';
@ -122,15 +122,22 @@ export type IInputUpdateDTO = z.infer<typeof schemaInputUpdate>;
/** /**
* Represents {@link IOperation} data, used in update process. * Represents {@link IOperation} data, used in update process.
*/ */
export interface IOperationUpdateDTO extends ITargetOperation { export const schemaOperationUpdate = z.object({
item_data: { target: z.number(),
alias: string; positions: z.array(schemaOperationPosition),
title: string; item_data: z.object({
comment: string; alias: z.string().nonempty(),
}; title: z.string(),
arguments: OperationID[] | undefined; comment: z.string()
substitutions: ICstSubstitute[] | undefined; }),
} arguments: z.array(z.number()),
substitutions: z.array(schemaCstSubstitute)
});
/**
* Represents {@link IOperation} data, used in update process.
*/
export type IOperationUpdateDTO = z.infer<typeof schemaOperationUpdate>;
/** /**
* Represents data, used relocating {@link IConstituenta}s between {@link ILibraryItem}s. * Represents data, used relocating {@link IConstituenta}s between {@link ILibraryItem}s.

View File

@ -21,7 +21,7 @@ interface PickMultiOperationProps extends CProps.Styling {
const columnHelper = createColumnHelper<IOperation>(); const columnHelper = createColumnHelper<IOperation>();
function PickMultiOperation({ rows, items, value, onChange, className, ...restProps }: PickMultiOperationProps) { export function PickMultiOperation({ rows, items, value, onChange, className, ...restProps }: PickMultiOperationProps) {
const selectedItems = value.map(itemID => items.find(item => item.id === itemID)!); const selectedItems = value.map(itemID => items.find(item => item.id === itemID)!);
const nonSelectedItems = items.filter(item => !value.includes(item.id)); const nonSelectedItems = items.filter(item => !value.includes(item.id));
const [lastSelected, setLastSelected] = useState<IOperation | undefined>(undefined); const [lastSelected, setLastSelected] = useState<IOperation | undefined>(undefined);
@ -134,5 +134,3 @@ function PickMultiOperation({ rows, items, value, onChange, className, ...restPr
</div> </div>
); );
} }
export default PickMultiOperation;

View File

@ -115,11 +115,11 @@ function DlgCreateOperation() {
</TabList> </TabList>
<FormProvider {...methods}> <FormProvider {...methods}>
<TabPanel> <TabPanel>
<TabInputOperation oss={oss} /> <TabInputOperation />
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<TabSynthesisOperation oss={oss} /> <TabSynthesisOperation />
</TabPanel> </TabPanel>
</FormProvider> </FormProvider>
</Tabs> </Tabs>

View File

@ -7,17 +7,15 @@ import { IconReset } from '@/components/Icons';
import { Checkbox, Label, TextArea, TextInput } from '@/components/Input'; import { Checkbox, Label, TextArea, TextInput } from '@/components/Input';
import { useLibrary } from '@/features/library/backend/useLibrary'; import { useLibrary } from '@/features/library/backend/useLibrary';
import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/features/library/models/library'; import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/features/library/models/library';
import { IOperationSchema } from '@/features/oss/models/oss';
import { sortItemsForOSS } from '@/features/oss/models/ossAPI'; import { sortItemsForOSS } from '@/features/oss/models/ossAPI';
import PickSchema from '@/features/rsform/components/PickSchema'; import PickSchema from '@/features/rsform/components/PickSchema';
import { useDialogsStore } from '@/stores/dialogs';
import { IOperationCreateDTO } from '../../backend/api'; import { IOperationCreateDTO } from '../../backend/api';
import { DlgCreateOperationProps } from './DlgCreateOperation';
interface TabInputOperationProps { function TabInputOperation() {
oss: IOperationSchema; const { oss } = useDialogsStore(state => state.props as DlgCreateOperationProps);
}
function TabInputOperation({ oss }: TabInputOperationProps) {
const { items: libraryItems } = useLibrary(); const { items: libraryItems } = useLibrary();
const sortedItems = sortItemsForOSS(oss, libraryItems); const sortedItems = sortItemsForOSS(oss, libraryItems);

View File

@ -2,16 +2,14 @@ import { Controller, useFormContext, useWatch } from 'react-hook-form';
import { FlexColumn } from '@/components/Container'; import { FlexColumn } from '@/components/Container';
import { Label, TextArea, TextInput } from '@/components/Input'; import { Label, TextArea, TextInput } from '@/components/Input';
import { useDialogsStore } from '@/stores/dialogs';
import { IOperationCreateDTO } from '../../backend/api'; import { IOperationCreateDTO } from '../../backend/api';
import PickMultiOperation from '../../components/PickMultiOperation'; import { PickMultiOperation } from '../../components/PickMultiOperation';
import { IOperationSchema } from '../../models/oss'; import { DlgCreateOperationProps } from './DlgCreateOperation';
interface TabSynthesisOperationProps { function TabSynthesisOperation() {
oss: IOperationSchema; const { oss } = useDialogsStore(state => state.props as DlgCreateOperationProps);
}
function TabSynthesisOperation({ oss }: TabSynthesisOperationProps) {
const { const {
register, register,
control, control,

View File

@ -1,19 +1,19 @@
'use client'; 'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useEffect, useState } from 'react'; import { Suspense, useState } from 'react';
import { FormProvider, useForm, useWatch } from 'react-hook-form';
import { Loader } from '@/components/Loader';
import { ModalForm } from '@/components/Modal'; import { ModalForm } from '@/components/Modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/Tabs'; import { TabLabel, TabList, TabPanel, Tabs } from '@/components/Tabs';
import { HelpTopic } from '@/features/help/models/helpTopic'; import { HelpTopic } from '@/features/help/models/helpTopic';
import { LibraryItemID } from '@/features/library/models/library';
import { useRSForms } from '@/features/rsform/backend/useRSForms';
import { ConstituentaID } from '@/features/rsform/models/rsform';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { IOperationUpdateDTO } from '../../backend/api'; import { IOperationPosition, IOperationUpdateDTO, schemaOperationUpdate } from '../../backend/api';
import { ICstSubstitute, IOperation, IOperationSchema, OperationID, OperationType } from '../../models/oss'; import { useOperationUpdate } from '../../backend/useOperationUpdate';
import { SubstitutionValidator } from '../../models/ossAPI'; import { IOperation, IOperationSchema, OperationType } from '../../models/oss';
import TabArguments from './TabArguments'; import TabArguments from './TabArguments';
import TabOperation from './TabOperation'; import TabOperation from './TabOperation';
import TabSynthesis from './TabSynthesis'; import TabSynthesis from './TabSynthesis';
@ -21,7 +21,7 @@ import TabSynthesis from './TabSynthesis';
export interface DlgEditOperationProps { export interface DlgEditOperationProps {
oss: IOperationSchema; oss: IOperationSchema;
target: IOperation; target: IOperation;
onSubmit: (data: IOperationUpdateDTO) => void; positions: IOperationPosition[];
} }
export enum TabID { export enum TabID {
@ -31,94 +31,32 @@ export enum TabID {
} }
function DlgEditOperation() { function DlgEditOperation() {
const { oss, target, onSubmit } = useDialogsStore(state => state.props as DlgEditOperationProps); const { oss, target, positions } = useDialogsStore(state => state.props as DlgEditOperationProps);
const { operationUpdate } = useOperationUpdate();
const methods = useForm<IOperationUpdateDTO>({
resolver: zodResolver(schemaOperationUpdate),
defaultValues: {
item_data: {
alias: target.alias,
title: target.alias,
comment: target.comment
},
arguments: target.arguments,
substitutions: target.substitutions.map(sub => ({
original: sub.original,
substitution: sub.substitution
})),
positions: positions
}
});
const alias = useWatch({ control: methods.control, name: 'item_data.alias' });
const canSubmit = alias !== '';
const [activeTab, setActiveTab] = useState(TabID.CARD); const [activeTab, setActiveTab] = useState(TabID.CARD);
const [alias, setAlias] = useState(target.alias); function onSubmit(data: IOperationUpdateDTO) {
const [title, setTitle] = useState(target.title); return operationUpdate({ itemID: oss.id, data });
const [comment, setComment] = useState(target.comment);
const [isCorrect, setIsCorrect] = useState(true);
const [validationText, setValidationText] = useState('');
const initialInputs = oss.graph.expandInputs([target.id]);
const [inputs, setInputs] = useState<OperationID[]>(initialInputs);
const inputOperations = inputs.map(id => oss.operationByID.get(id)!);
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>(target.substitutions);
const [suggestions, setSuggestions] = useState<ICstSubstitute[]>([]);
const [schemasIDs, setSchemaIDs] = useState<LibraryItemID[]>([]);
const schemas = useRSForms(schemasIDs);
const isModified =
alias !== target.alias ||
title !== target.title ||
comment !== target.comment ||
JSON.stringify(initialInputs) !== JSON.stringify(inputs) ||
JSON.stringify(substitutions) !== JSON.stringify(target.substitutions);
const canSubmit = isModified && alias !== '';
const getSchemaByCst = useCallback(
(id: ConstituentaID) => {
for (const schema of schemas) {
const cst = schema.items.find(cst => cst.id === id);
if (cst) {
return schema;
}
}
return undefined;
},
[schemas]
);
useEffect(() => {
setSchemaIDs(inputOperations.map(operation => operation.result).filter(id => id !== null));
}, [inputOperations]);
useEffect(() => {
if (schemas.length !== schemasIDs.length || schemas.length === 0) {
return;
}
setSubstitutions(prev =>
prev.filter(sub => {
const original = getSchemaByCst(sub.original);
if (!original || !schemasIDs.includes(original.id)) {
return false;
}
const substitution = getSchemaByCst(sub.substitution);
if (!substitution || !schemasIDs.includes(substitution.id)) {
return false;
}
return true;
})
);
}, [schemasIDs, schemas, getSchemaByCst]);
useEffect(() => {
if (schemas.length !== schemasIDs.length || schemas.length === 0) {
return;
}
const validator = new SubstitutionValidator(schemas, substitutions);
setIsCorrect(validator.validate());
setValidationText(validator.msg);
setSuggestions(validator.suggestions);
}, [substitutions, schemas, schemasIDs.length]);
function handleSubmit() {
onSubmit({
target: target.id,
item_data: {
alias: alias,
title: title,
comment: comment
},
positions: [],
arguments: target.operation_type !== OperationType.SYNTHESIS ? undefined : inputs,
substitutions: target.operation_type !== OperationType.SYNTHESIS ? undefined : substitutions
});
return true;
} }
return ( return (
@ -126,7 +64,7 @@ function DlgEditOperation() {
header='Редактирование операции' header='Редактирование операции'
submitText='Сохранить' submitText='Сохранить'
canSubmit={canSubmit} canSubmit={canSubmit}
onSubmit={handleSubmit} onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
className='w-[40rem] px-6 h-[32rem]' className='w-[40rem] px-6 h-[32rem]'
helpTopic={HelpTopic.UI_SUBSTITUTIONS} helpTopic={HelpTopic.UI_SUBSTITUTIONS}
hideHelpWhen={() => activeTab !== TabID.SUBSTITUTION} hideHelpWhen={() => activeTab !== TabID.SUBSTITUTION}
@ -143,47 +81,28 @@ function DlgEditOperation() {
<TabLabel title='Выбор аргументов операции' label='Аргументы' className='w-[8rem]' /> <TabLabel title='Выбор аргументов операции' label='Аргументы' className='w-[8rem]' />
) : null} ) : null}
{target.operation_type === OperationType.SYNTHESIS ? ( {target.operation_type === OperationType.SYNTHESIS ? (
<TabLabel <TabLabel titleHtml='Таблица отождествлений' label='Отождествления' className='w-[8rem]' />
titleHtml={'Таблица отождествлений' + (isCorrect ? '' : '<br/>(не прошла проверку)')}
label={isCorrect ? 'Отождествления' : 'Отождествления*'}
className='w-[8rem]'
/>
) : null} ) : null}
</TabList> </TabList>
<FormProvider {...methods}>
<TabPanel> <TabPanel>
<TabOperation <TabOperation />
alias={alias}
onChangeAlias={setAlias}
comment={comment}
onChangeComment={setComment}
title={title}
onChangeTitle={setTitle}
/>
</TabPanel> </TabPanel>
{target.operation_type === OperationType.SYNTHESIS ? ( {target.operation_type === OperationType.SYNTHESIS ? (
<TabPanel> <TabPanel>
<TabArguments <TabArguments />
target={target.id} // prettier: split-lines
oss={oss}
inputs={inputs}
setInputs={setInputs}
/>
</TabPanel> </TabPanel>
) : null} ) : null}
{target.operation_type === OperationType.SYNTHESIS ? ( {target.operation_type === OperationType.SYNTHESIS ? (
<TabPanel> <TabPanel>
<TabSynthesis <Suspense fallback={<Loader />}>
schemas={schemas} <TabSynthesis />
validationText={validationText} </Suspense>
isCorrect={isCorrect}
substitutions={substitutions}
setSubstitutions={setSubstitutions}
suggestions={suggestions}
/>
</TabPanel> </TabPanel>
) : null} ) : null}
</FormProvider>
</Tabs> </Tabs>
</ModalForm> </ModalForm>
); );

View File

@ -1,25 +1,47 @@
'use client'; 'use client';
import { Controller, useFormContext } from 'react-hook-form';
import { FlexColumn } from '@/components/Container'; import { FlexColumn } from '@/components/Container';
import { Label } from '@/components/Input'; import { Label } from '@/components/Input';
import { LibraryItemID } from '@/features/library/models/library';
import { useDialogsStore } from '@/stores/dialogs';
import PickMultiOperation from '../../components/PickMultiOperation'; import { IOperationUpdateDTO } from '../../backend/api';
import { IOperationSchema, OperationID } from '../../models/oss'; import { PickMultiOperation } from '../../components/PickMultiOperation';
import { DlgEditOperationProps } from './DlgEditOperation';
interface TabArgumentsProps { function TabArguments() {
oss: IOperationSchema; const { control, setValue } = useFormContext<IOperationUpdateDTO>();
target: OperationID; const { oss, target } = useDialogsStore(state => state.props as DlgEditOperationProps);
inputs: OperationID[]; const potentialCycle = [target.id, ...oss.graph.expandAllOutputs([target.id])];
setInputs: React.Dispatch<React.SetStateAction<OperationID[]>>; const filtered = oss.items.filter(item => !potentialCycle.includes(item.id));
function handleChangeArguments(prev: LibraryItemID[], newValue: LibraryItemID[]) {
setValue('arguments', newValue);
if (prev.some(id => !newValue.includes(id))) {
setValue('substitutions', []);
}
} }
function TabArguments({ oss, inputs, target, setInputs }: TabArgumentsProps) {
const potentialCycle = [target, ...oss.graph.expandAllOutputs([target])];
const filtered = oss.items.filter(item => !potentialCycle.includes(item.id));
return ( return (
<div className='cc-fade-in cc-column'> <div className='cc-fade-in cc-column'>
<FlexColumn> <FlexColumn>
<Label text={`Выбор аргументов: [ ${inputs.length} ]`} /> <Controller
<PickMultiOperation items={filtered} value={inputs} onChange={setInputs} rows={8} /> name='arguments'
control={control}
defaultValue={[]}
render={({ field }) => (
<>
<Label text={`Выбор аргументов: [ ${field.value.length} ]`} />
<PickMultiOperation
items={filtered}
value={field.value}
onChange={newValue => handleChangeArguments(field.value, newValue)}
rows={8}
/>
</>
)}
/>
</FlexColumn> </FlexColumn>
</div> </div>
); );

View File

@ -1,30 +1,30 @@
import { useFormContext } from 'react-hook-form';
import { TextArea, TextInput } from '@/components/Input'; import { TextArea, TextInput } from '@/components/Input';
interface TabOperationProps { import { IOperationUpdateDTO } from '../../backend/api';
alias: string;
onChangeAlias: (newValue: string) => void; function TabOperation() {
title: string; const {
onChangeTitle: (newValue: string) => void; register,
comment: string; formState: { errors }
onChangeComment: (newValue: string) => void; } = useFormContext<IOperationUpdateDTO>();
}
function TabOperation({ alias, onChangeAlias, title, onChangeTitle, comment, onChangeComment }: TabOperationProps) {
return ( return (
<div className='cc-fade-in cc-column'> <div className='cc-fade-in cc-column'>
<TextInput <TextInput
id='operation_title' id='operation_title'
label='Полное название' label='Полное название'
value={title} {...register('item_data.title')}
onChange={event => onChangeTitle(event.target.value)} error={errors.item_data?.title}
/> />
<div className='flex gap-6'> <div className='flex gap-6'>
<TextInput <TextInput
id='operation_alias' id='operation_alias'
label='Сокращение' label='Сокращение'
className='w-[16rem]' className='w-[16rem]'
value={alias} {...register('item_data.alias')}
onChange={event => onChangeAlias(event.target.value)} error={errors.item_data?.alias}
/> />
<TextArea <TextArea
@ -32,8 +32,8 @@ function TabOperation({ alias, onChangeAlias, title, onChangeTitle, comment, onC
label='Описание' label='Описание'
noResize noResize
rows={3} rows={3}
value={comment} {...register('item_data.comment')}
onChange={event => onChangeComment(event.target.value)} error={errors.item_data?.comment}
/> />
</div> </div>
</div> </div>

View File

@ -1,40 +1,52 @@
'use client';
import { Controller, useFormContext, useWatch } from 'react-hook-form';
import { TextArea } from '@/components/Input'; import { TextArea } from '@/components/Input';
import { useRSForms } from '@/features/rsform/backend/useRSForms';
import PickSubstitutions from '@/features/rsform/components/PickSubstitutions'; import PickSubstitutions from '@/features/rsform/components/PickSubstitutions';
import { IRSForm } from '@/features/rsform/models/rsform'; import { useDialogsStore } from '@/stores/dialogs';
import { APP_COLORS } from '@/styling/colors'; import { APP_COLORS } from '@/styling/colors';
import { ICstSubstitute } from '../../models/oss'; import { IOperationUpdateDTO } from '../../backend/api';
import { SubstitutionValidator } from '../../models/ossAPI';
import { DlgEditOperationProps } from './DlgEditOperation';
interface TabSynthesisProps { function TabSynthesis() {
validationText: string; const { oss } = useDialogsStore(state => state.props as DlgEditOperationProps);
isCorrect: boolean; const { control } = useFormContext<IOperationUpdateDTO>();
const inputs = useWatch({ control, name: 'arguments' });
const substitutions = useWatch({ control, name: 'substitutions' });
schemas: IRSForm[]; const schemasIDs = inputs
substitutions: ICstSubstitute[]; .map(id => oss.operationByID.get(id)!)
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>; .map(operation => operation.result)
suggestions: ICstSubstitute[]; .filter(id => id !== null);
} const schemas = useRSForms(schemasIDs);
const validator = new SubstitutionValidator(schemas, substitutions);
const isCorrect = validator.validate();
function TabSynthesis({
schemas,
validationText,
isCorrect,
substitutions,
setSubstitutions,
suggestions
}: TabSynthesisProps) {
return ( return (
<div className='cc-fade-in cc-column mt-3'> <div className='cc-fade-in cc-column mt-3'>
<Controller
name='substitutions'
control={control}
defaultValue={[]}
render={({ field }) => (
<PickSubstitutions <PickSubstitutions
schemas={schemas} schemas={schemas}
rows={8} rows={8}
value={substitutions} value={field.value}
onChange={setSubstitutions} onChange={field.onChange}
suggestions={suggestions} suggestions={validator.suggestions}
/> />
)}
/>
<TextArea <TextArea
disabled disabled
value={validationText} value={validator.msg}
rows={4} rows={4}
style={{ borderColor: isCorrect ? undefined : APP_COLORS.fgRed, borderWidth: isCorrect ? undefined : '2px' }} style={{ borderColor: isCorrect ? undefined : APP_COLORS.fgRed, borderWidth: isCorrect ? undefined : '2px' }}
/> />

View File

@ -2,8 +2,10 @@
* Module: Schema of Synthesis Operations. * Module: Schema of Synthesis Operations.
*/ */
import { z } from 'zod';
import { ILibraryItem, ILibraryItemData, LibraryItemID } from '@/features/library/models/library'; import { ILibraryItem, ILibraryItemData, LibraryItemID } from '@/features/library/models/library';
import { ConstituentaID, IConstituenta } from '@/features/rsform/models/rsform'; import { IConstituenta } from '@/features/rsform/models/rsform';
import { Graph } from '../../../models/Graph'; import { Graph } from '../../../models/Graph';
@ -54,10 +56,15 @@ export interface IArgument {
/** /**
* Represents data, used in merging single {@link IConstituenta}. * Represents data, used in merging single {@link IConstituenta}.
*/ */
export interface ICstSubstitute { export const schemaCstSubstitute = z.object({
original: ConstituentaID; original: z.number(),
substitution: ConstituentaID; substitution: z.number()
} });
/**
* Represents data, used in merging single {@link IConstituenta}.
*/
export type ICstSubstitute = z.infer<typeof schemaCstSubstitute>;
/** /**
* Represents data, used in merging multiple {@link IConstituenta}. * Represents data, used in merging multiple {@link IConstituenta}.

View File

@ -120,7 +120,6 @@ export class SubstitutionValidator {
if (!this.checkSubstitutions()) { if (!this.checkSubstitutions()) {
return false; return false;
} }
return this.setValid(); return this.setValid();
} }

View File

@ -296,7 +296,10 @@ function OssFlow() {
return ( return (
<div tabIndex={-1} onKeyDown={handleKeyDown}> <div tabIndex={-1} onKeyDown={handleKeyDown}>
<Overlay position='top-[1.9rem] pt-1 right-1/2 translate-x-1/2' className='rounded-b-2xl cc-blur'> <Overlay
position='top-[1.9rem] pt-1 right-1/2 translate-x-1/2'
className='rounded-b-2xl cc-blur hover:bg-prim-100 hover:bg-opacity-50'
>
<ToolbarOssGraph <ToolbarOssGraph
onFitView={() => flow.fitView({ duration: PARAMETER.zoomDuration })} onFitView={() => flow.fitView({ duration: PARAMETER.zoomDuration })}
onCreate={() => handleCreateOperation(controller.selected)} onCreate={() => handleCreateOperation(controller.selected)}

View File

@ -15,7 +15,6 @@ import { useRoleStore } from '@/stores/role';
import { prompts } from '@/utils/labels'; import { prompts } from '@/utils/labels';
import { IOperationPosition } from '../../backend/api'; import { IOperationPosition } from '../../backend/api';
import { useOperationUpdate } from '../../backend/useOperationUpdate';
import { useOssSuspense } from '../../backend/useOSS'; import { useOssSuspense } from '../../backend/useOSS';
import { IOperationSchema, OperationID, OperationType } from '../../models/oss'; import { IOperationSchema, OperationID, OperationType } from '../../models/oss';
@ -95,7 +94,6 @@ 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 { operationUpdate } = useOperationUpdate();
useEffect( useEffect(
() => () =>
@ -166,10 +164,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
showEditOperation({ showEditOperation({
oss: schema, oss: schema,
target: operation, target: operation,
onSubmit: data => { positions: positions
data.positions = positions;
void operationUpdate({ itemID: schema.id, data });
}
}); });
} }

View File

@ -1,4 +1,4 @@
import { useQueries } from '@tanstack/react-query'; import { useSuspenseQueries } from '@tanstack/react-query';
import { LibraryItemID } from '@/features/library/models/library'; import { LibraryItemID } from '@/features/library/models/library';
@ -7,7 +7,7 @@ import { rsformsApi } from './api';
import { RSFormLoader } from './RSFormLoader'; import { RSFormLoader } from './RSFormLoader';
export function useRSForms(itemIDs: LibraryItemID[]) { export function useRSForms(itemIDs: LibraryItemID[]) {
const results = useQueries({ const results = useSuspenseQueries({
queries: itemIDs.map(itemID => ({ queries: itemIDs.map(itemID => ({
...rsformsApi.getRSFormQueryOptions({ itemID }), ...rsformsApi.getRSFormQueryOptions({ itemID }),
enabled: itemIDs.length > 0, enabled: itemIDs.length > 0,

View File

@ -21,7 +21,7 @@ import SelectConstituenta from './SelectConstituenta';
interface PickSubstitutionsProps extends CProps.Styling { interface PickSubstitutionsProps extends CProps.Styling {
value: ICstSubstitute[]; value: ICstSubstitute[];
onChange: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>; onChange: (newValue: ICstSubstitute[]) => void;
suggestions?: ICstSubstitute[]; suggestions?: ICstSubstitute[];
@ -125,30 +125,24 @@ function PickSubstitutions({
return; return;
} }
} }
onChange(prev => [...prev, newSubstitution]); onChange([...value, newSubstitution]);
setLeftCst(undefined); setLeftCst(undefined);
setRightCst(undefined); setRightCst(undefined);
} }
function handleDeclineSuggestion(item: IMultiSubstitution) { function handleDeclineSuggestion(item: IMultiSubstitution) {
setIgnores(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]); setIgnores([...value, { original: item.original.id, substitution: item.substitution.id }]);
} }
function handleAcceptSuggestion(item: IMultiSubstitution) { function handleAcceptSuggestion(item: IMultiSubstitution) {
onChange(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]); onChange([...value, { original: item.original.id, substitution: item.substitution.id }]);
} }
function handleDeleteSubstitution(target: IMultiSubstitution) { function handleDeleteSubstitution(target: IMultiSubstitution) {
handleDeclineSuggestion(target); handleDeclineSuggestion(target);
onChange(prev => { onChange(
const newItems: ICstSubstitute[] = []; value.filter(item => item.original !== target.original.id || item.substitution !== target.substitution.id)
prev.forEach(item => { );
if (item.original !== target.original.id || item.substitution !== target.substitution.id) {
newItems.push(item);
}
});
return newItems;
});
} }
const columns = [ const columns = [