From 2ae957638473491f9d0caf0c53cbc9f0d50890c2 Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 20 Apr 2025 18:06:18 +0300 Subject: [PATCH] F: Implement frontend api calls --- .../backend/apps/oss/serializers/__init__.py | 10 +- .../apps/oss/serializers/data_access.py | 102 +++--- .../backend/apps/oss/serializers/responses.py | 6 +- rsconcept/backend/apps/oss/views/oss.py | 294 +++++++++--------- .../frontend/src/features/oss/backend/api.ts | 51 ++- .../src/features/oss/backend/types.ts | 93 ++++-- .../features/oss/backend/use-create-block.tsx | 25 ++ .../features/oss/backend/use-create-input.tsx | 2 +- .../oss/backend/use-create-operation.tsx | 2 +- .../features/oss/backend/use-delete-block.tsx | 25 ++ .../oss/backend/use-delete-operation.tsx | 2 +- .../oss/backend/use-execute-operation.tsx | 2 +- .../features/oss/backend/use-update-block.tsx | 37 +++ .../features/oss/backend/use-update-input.tsx | 2 +- .../oss/backend/use-update-operation.tsx | 2 +- .../dlg-edit-operation/dlg-edit-operation.tsx | 3 +- .../backend/use-create-constituenta.tsx | 2 +- .../backend/use-delete-constituents.tsx | 2 +- .../rsform/backend/use-move-constituents.tsx | 2 +- .../backend/use-rename-constituenta.tsx | 2 +- .../backend/use-substitute-constituents.tsx | 2 +- .../backend/use-update-constituenta.tsx | 2 +- 22 files changed, 419 insertions(+), 251 deletions(-) create mode 100644 rsconcept/frontend/src/features/oss/backend/use-create-block.tsx create mode 100644 rsconcept/frontend/src/features/oss/backend/use-delete-block.tsx create mode 100644 rsconcept/frontend/src/features/oss/backend/use-update-block.tsx diff --git a/rsconcept/backend/apps/oss/serializers/__init__.py b/rsconcept/backend/apps/oss/serializers/__init__.py index c0a07435..c965d3a6 100644 --- a/rsconcept/backend/apps/oss/serializers/__init__.py +++ b/rsconcept/backend/apps/oss/serializers/__init__.py @@ -7,18 +7,18 @@ from .data_access import ( CreateBlockSerializer, CreateOperationSerializer, DeleteBlockSerializer, - OperationDeleteSerializer, + DeleteOperationSerializer, OperationSchemaSerializer, OperationSerializer, - OperationTargetSerializer, RelocateConstituentsSerializer, SetOperationInputSerializer, + TargetOperationSerializer, UpdateBlockSerializer, UpdateOperationSerializer ) from .responses import ( + BlockCreatedResponse, ConstituentaReferenceResponse, - NewBlockResponse, - NewOperationResponse, - NewSchemaResponse + OperationCreatedResponse, + SchemaCreatedResponse ) diff --git a/rsconcept/backend/apps/oss/serializers/data_access.py b/rsconcept/backend/apps/oss/serializers/data_access.py index 05e37c86..45957698 100644 --- a/rsconcept/backend/apps/oss/serializers/data_access.py +++ b/rsconcept/backend/apps/oss/serializers/data_access.py @@ -83,6 +83,55 @@ class CreateBlockSerializer(serializers.Serializer): return attrs +class UpdateBlockSerializer(serializers.Serializer): + ''' Serializer: Block update. ''' + class UpdateBlockData(serializers.ModelSerializer): + ''' Serializer: Block update data. ''' + class Meta: + ''' serializer metadata. ''' + model = Block + fields = 'title', 'description', 'parent' + + layout = LayoutSerializer(required=False) + target = PKField(many=False, queryset=Block.objects.all()) + item_data = UpdateBlockData() + + def validate(self, attrs): + oss = cast(LibraryItem, self.context['oss']) + block = cast(Block, attrs['target']) + if block.oss_id != oss.pk: + raise serializers.ValidationError({ + 'target': msg.blockNotInOSS() + }) + + if 'parent' in attrs['item_data'] and \ + attrs['item_data']['parent'] is not None: + if attrs['item_data']['parent'].oss_id != oss.pk: + raise serializers.ValidationError({ + 'parent': msg.parentNotInOSS() + }) + if attrs['item_data']['parent'] == attrs['target']: + raise serializers.ValidationError({ + 'parent': msg.blockSelfParent() + }) + return attrs + + +class DeleteBlockSerializer(serializers.Serializer): + ''' Serializer: Delete block. ''' + layout = LayoutSerializer() + target = PKField(many=False, queryset=Block.objects.all().only('oss_id')) + + def validate(self, attrs): + oss = cast(LibraryItem, self.context['oss']) + block = cast(Block, attrs['target']) + if block.oss_id != oss.pk: + raise serializers.ValidationError({ + 'target': msg.blockNotInOSS() + }) + return attrs + + class CreateOperationSerializer(serializers.Serializer): ''' Serializer: Operation creation. ''' class CreateOperationData(serializers.ModelSerializer): @@ -196,56 +245,7 @@ class UpdateOperationSerializer(serializers.Serializer): return attrs -class UpdateBlockSerializer(serializers.Serializer): - ''' Serializer: Block update. ''' - class UpdateBlockData(serializers.ModelSerializer): - ''' Serializer: Block update data. ''' - class Meta: - ''' serializer metadata. ''' - model = Block - fields = 'title', 'description', 'parent' - - layout = LayoutSerializer(required=False) - target = PKField(many=False, queryset=Block.objects.all()) - item_data = UpdateBlockData() - - def validate(self, attrs): - oss = cast(LibraryItem, self.context['oss']) - block = cast(Block, attrs['target']) - if block.oss_id != oss.pk: - raise serializers.ValidationError({ - 'target': msg.blockNotInOSS() - }) - - if 'parent' in attrs['item_data'] and \ - attrs['item_data']['parent'] is not None: - if attrs['item_data']['parent'].oss_id != oss.pk: - raise serializers.ValidationError({ - 'parent': msg.parentNotInOSS() - }) - if attrs['item_data']['parent'] == attrs['target']: - raise serializers.ValidationError({ - 'parent': msg.blockSelfParent() - }) - return attrs - - -class DeleteBlockSerializer(serializers.Serializer): - ''' Serializer: Delete block. ''' - layout = LayoutSerializer() - target = PKField(many=False, queryset=Block.objects.all().only('oss_id')) - - def validate(self, attrs): - oss = cast(LibraryItem, self.context['oss']) - block = cast(Block, attrs['target']) - if block.oss_id != oss.pk: - raise serializers.ValidationError({ - 'target': msg.blockNotInOSS() - }) - return attrs - - -class OperationDeleteSerializer(serializers.Serializer): +class DeleteOperationSerializer(serializers.Serializer): ''' Serializer: Delete operation. ''' layout = LayoutSerializer() target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result')) @@ -262,7 +262,7 @@ class OperationDeleteSerializer(serializers.Serializer): return attrs -class OperationTargetSerializer(serializers.Serializer): +class TargetOperationSerializer(serializers.Serializer): ''' Serializer: Target single operation. ''' layout = LayoutSerializer() target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result_id')) diff --git a/rsconcept/backend/apps/oss/serializers/responses.py b/rsconcept/backend/apps/oss/serializers/responses.py index 9320d03a..539aa9c4 100644 --- a/rsconcept/backend/apps/oss/serializers/responses.py +++ b/rsconcept/backend/apps/oss/serializers/responses.py @@ -6,19 +6,19 @@ from apps.library.serializers import LibraryItemSerializer from .data_access import BlockSerializer, OperationSchemaSerializer, OperationSerializer -class NewOperationResponse(serializers.Serializer): +class OperationCreatedResponse(serializers.Serializer): ''' Serializer: Create operation response. ''' new_operation = OperationSerializer() oss = OperationSchemaSerializer() -class NewBlockResponse(serializers.Serializer): +class BlockCreatedResponse(serializers.Serializer): ''' Serializer: Create block response. ''' new_block = BlockSerializer() oss = OperationSchemaSerializer() -class NewSchemaResponse(serializers.Serializer): +class SchemaCreatedResponse(serializers.Serializer): ''' Serializer: Create RSForm for input operation response. ''' new_schema = LibraryItemSerializer() oss = OperationSchemaSerializer() diff --git a/rsconcept/backend/apps/oss/views/oss.py b/rsconcept/backend/apps/oss/views/oss.py index 1afc0e95..9780a3b7 100644 --- a/rsconcept/backend/apps/oss/views/oss.py +++ b/rsconcept/backend/apps/oss/views/oss.py @@ -37,12 +37,12 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev ''' Determine permission class. ''' if self.action in [ 'update_layout', - 'create_operation', 'create_block', - 'delete_operation', - 'delete_block', - 'update_operation', 'update_block', + 'delete_block', + 'create_operation', + 'update_operation', + 'delete_operation', 'create_input', 'set_input', 'execute_operation', @@ -93,12 +93,130 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev m.OperationSchema(self.get_object()).update_layout(serializer.validated_data) return Response(status=c.HTTP_200_OK) + @extend_schema( + summary='create block', + tags=['OSS'], + request=s.CreateBlockSerializer(), + responses={ + c.HTTP_201_CREATED: s.BlockCreatedResponse, + c.HTTP_400_BAD_REQUEST: None, + c.HTTP_403_FORBIDDEN: None, + c.HTTP_404_NOT_FOUND: None + } + ) + @action(detail=True, methods=['post'], url_path='create-block') + def create_block(self, request: Request, pk) -> HttpResponse: + ''' Create Block. ''' + serializer = s.CreateBlockSerializer( + data=request.data, + context={'oss': self.get_object()} + ) + serializer.is_valid(raise_exception=True) + + oss = m.OperationSchema(self.get_object()) + layout = serializer.validated_data['layout'] + children_blocks: list[m.Block] = serializer.validated_data['children_blocks'] + children_operations: list[m.Operation] = serializer.validated_data['children_operations'] + with transaction.atomic(): + new_block = oss.create_block(**serializer.validated_data['item_data']) + layout['blocks'].append({ + 'id': new_block.pk, + 'x': serializer.validated_data['position_x'], + 'y': serializer.validated_data['position_y'], + 'width': serializer.validated_data['width'], + 'height': serializer.validated_data['height'], + }) + oss.update_layout(layout) + if len(children_blocks) > 0: + for block in children_blocks: + block.parent = new_block + m.Block.objects.bulk_update(children_blocks, ['parent']) + if len(children_operations) > 0: + for operation in children_operations: + operation.parent = new_block + m.Operation.objects.bulk_update(children_operations, ['parent']) + + return Response( + status=c.HTTP_201_CREATED, + data={ + 'new_block': s.BlockSerializer(new_block).data, + 'oss': s.OperationSchemaSerializer(oss.model).data + } + ) + + @extend_schema( + summary='update block', + tags=['OSS'], + request=s.UpdateBlockSerializer(), + responses={ + c.HTTP_200_OK: s.OperationSchemaSerializer, + c.HTTP_400_BAD_REQUEST: None, + c.HTTP_403_FORBIDDEN: None, + c.HTTP_404_NOT_FOUND: None + } + ) + @action(detail=True, methods=['patch'], url_path='update-block') + def update_block(self, request: Request, pk) -> HttpResponse: + ''' Update Block. ''' + serializer = s.UpdateBlockSerializer( + data=request.data, + context={'oss': self.get_object()} + ) + serializer.is_valid(raise_exception=True) + + block: m.Block = cast(m.Block, serializer.validated_data['target']) + oss = m.OperationSchema(self.get_object()) + with transaction.atomic(): + if 'layout' in serializer.validated_data: + oss.update_layout(serializer.validated_data['layout']) + block.title = serializer.validated_data['item_data']['title'] + block.description = serializer.validated_data['item_data']['description'] + block.parent = serializer.validated_data['item_data']['parent'] + block.save(update_fields=['title', 'description', 'parent']) + return Response( + status=c.HTTP_200_OK, + data=s.OperationSchemaSerializer(oss.model).data + ) + + @extend_schema( + summary='delete block', + tags=['OSS'], + request=s.DeleteBlockSerializer, + responses={ + c.HTTP_200_OK: s.OperationSchemaSerializer, + c.HTTP_400_BAD_REQUEST: None, + c.HTTP_403_FORBIDDEN: None, + c.HTTP_404_NOT_FOUND: None + } + ) + @action(detail=True, methods=['patch'], url_path='delete-block') + def delete_block(self, request: Request, pk) -> HttpResponse: + ''' Endpoint: Delete Block. ''' + serializer = s.DeleteBlockSerializer( + data=request.data, + context={'oss': self.get_object()} + ) + serializer.is_valid(raise_exception=True) + + oss = m.OperationSchema(self.get_object()) + block = cast(m.Block, serializer.validated_data['target']) + layout = serializer.validated_data['layout'] + layout['blocks'] = [x for x in layout['blocks'] if x['id'] != block.pk] + with transaction.atomic(): + oss.delete_block(block) + oss.update_layout(layout) + + return Response( + status=c.HTTP_200_OK, + data=s.OperationSchemaSerializer(oss.model).data + ) + @extend_schema( summary='create operation', tags=['OSS'], request=s.CreateOperationSerializer(), responses={ - c.HTTP_201_CREATED: s.NewOperationResponse, + c.HTTP_201_CREATED: s.OperationCreatedResponse, c.HTTP_400_BAD_REQUEST: None, c.HTTP_403_FORBIDDEN: None, c.HTTP_404_NOT_FOUND: None @@ -154,130 +272,6 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev } ) - @extend_schema( - summary='create block', - tags=['OSS'], - request=s.CreateBlockSerializer(), - responses={ - c.HTTP_201_CREATED: s.NewBlockResponse, - c.HTTP_400_BAD_REQUEST: None, - c.HTTP_403_FORBIDDEN: None, - c.HTTP_404_NOT_FOUND: None - } - ) - @action(detail=True, methods=['post'], url_path='create-block') - def create_block(self, request: Request, pk) -> HttpResponse: - ''' Create Block. ''' - serializer = s.CreateBlockSerializer( - data=request.data, - context={'oss': self.get_object()} - ) - serializer.is_valid(raise_exception=True) - - oss = m.OperationSchema(self.get_object()) - layout = serializer.validated_data['layout'] - children_blocks: list[m.Block] = serializer.validated_data['children_blocks'] - children_operations: list[m.Operation] = serializer.validated_data['children_operations'] - with transaction.atomic(): - new_block = oss.create_block(**serializer.validated_data['item_data']) - layout['blocks'].append({ - 'id': new_block.pk, - 'x': serializer.validated_data['position_x'], - 'y': serializer.validated_data['position_y'], - 'width': serializer.validated_data['width'], - 'height': serializer.validated_data['height'], - }) - oss.update_layout(layout) - if len(children_blocks) > 0: - for block in children_blocks: - block.parent = new_block - m.Block.objects.bulk_update(children_blocks, ['parent']) - if len(children_operations) > 0: - for operation in children_operations: - operation.parent = new_block - m.Operation.objects.bulk_update(children_operations, ['parent']) - - return Response( - status=c.HTTP_201_CREATED, - data={ - 'new_block': s.BlockSerializer(new_block).data, - 'oss': s.OperationSchemaSerializer(oss.model).data - } - ) - - @extend_schema( - summary='delete operation', - tags=['OSS'], - request=s.OperationDeleteSerializer, - responses={ - c.HTTP_200_OK: s.OperationSchemaSerializer, - c.HTTP_400_BAD_REQUEST: None, - c.HTTP_403_FORBIDDEN: None, - c.HTTP_404_NOT_FOUND: None - } - ) - @action(detail=True, methods=['patch'], url_path='delete-operation') - def delete_operation(self, request: Request, pk) -> HttpResponse: - ''' Endpoint: Delete Operation. ''' - serializer = s.OperationDeleteSerializer( - data=request.data, - context={'oss': self.get_object()} - ) - serializer.is_valid(raise_exception=True) - - oss = m.OperationSchema(self.get_object()) - operation = cast(m.Operation, serializer.validated_data['target']) - old_schema = operation.result - layout = serializer.validated_data['layout'] - layout['operations'] = [x for x in layout['operations'] if x['id'] != operation.pk] - with transaction.atomic(): - oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents']) - oss.update_layout(layout) - if old_schema is not None: - if serializer.validated_data['delete_schema']: - m.PropagationFacade.before_delete_schema(old_schema) - old_schema.delete() - elif old_schema.is_synced(oss.model): - old_schema.visible = True - old_schema.save(update_fields=['visible']) - return Response( - status=c.HTTP_200_OK, - data=s.OperationSchemaSerializer(oss.model).data - ) - - @extend_schema( - summary='delete block', - tags=['OSS'], - request=s.DeleteBlockSerializer, - responses={ - c.HTTP_200_OK: s.OperationSchemaSerializer, - c.HTTP_400_BAD_REQUEST: None, - c.HTTP_403_FORBIDDEN: None, - c.HTTP_404_NOT_FOUND: None - } - ) - @action(detail=True, methods=['patch'], url_path='delete-block') - def delete_block(self, request: Request, pk) -> HttpResponse: - ''' Endpoint: Delete Block. ''' - serializer = s.DeleteBlockSerializer( - data=request.data, - context={'oss': self.get_object()} - ) - serializer.is_valid(raise_exception=True) - - oss = m.OperationSchema(self.get_object()) - block = cast(m.Block, serializer.validated_data['target']) - layout = serializer.validated_data['layout'] - layout['blocks'] = [x for x in layout['blocks'] if x['id'] != block.pk] - with transaction.atomic(): - oss.delete_block(block) - oss.update_layout(layout) - - return Response( - status=c.HTTP_200_OK, - data=s.OperationSchemaSerializer(oss.model).data - ) - @extend_schema( summary='update operation', tags=['OSS'], @@ -325,9 +319,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev ) @extend_schema( - summary='update block', + summary='delete operation', tags=['OSS'], - request=s.UpdateBlockSerializer(), + request=s.DeleteOperationSerializer, responses={ c.HTTP_200_OK: s.OperationSchemaSerializer, c.HTTP_400_BAD_REQUEST: None, @@ -335,24 +329,30 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev c.HTTP_404_NOT_FOUND: None } ) - @action(detail=True, methods=['patch'], url_path='update-block') - def update_block(self, request: Request, pk) -> HttpResponse: - ''' Update Block. ''' - serializer = s.UpdateBlockSerializer( + @action(detail=True, methods=['patch'], url_path='delete-operation') + def delete_operation(self, request: Request, pk) -> HttpResponse: + ''' Endpoint: Delete Operation. ''' + serializer = s.DeleteOperationSerializer( data=request.data, context={'oss': self.get_object()} ) serializer.is_valid(raise_exception=True) - block: m.Block = cast(m.Block, serializer.validated_data['target']) oss = m.OperationSchema(self.get_object()) + operation = cast(m.Operation, serializer.validated_data['target']) + old_schema = operation.result + layout = serializer.validated_data['layout'] + layout['operations'] = [x for x in layout['operations'] if x['id'] != operation.pk] with transaction.atomic(): - if 'layout' in serializer.validated_data: - oss.update_layout(serializer.validated_data['layout']) - block.title = serializer.validated_data['item_data']['title'] - block.description = serializer.validated_data['item_data']['description'] - block.parent = serializer.validated_data['item_data']['parent'] - block.save(update_fields=['title', 'description', 'parent']) + oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents']) + oss.update_layout(layout) + if old_schema is not None: + if serializer.validated_data['delete_schema']: + m.PropagationFacade.before_delete_schema(old_schema) + old_schema.delete() + elif old_schema.is_synced(oss.model): + old_schema.visible = True + old_schema.save(update_fields=['visible']) return Response( status=c.HTTP_200_OK, data=s.OperationSchemaSerializer(oss.model).data @@ -361,9 +361,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev @extend_schema( summary='create input schema for target operation', tags=['OSS'], - request=s.OperationTargetSerializer(), + request=s.TargetOperationSerializer(), responses={ - c.HTTP_200_OK: s.NewSchemaResponse, + c.HTTP_200_OK: s.SchemaCreatedResponse, c.HTTP_400_BAD_REQUEST: None, c.HTTP_403_FORBIDDEN: None, c.HTTP_404_NOT_FOUND: None @@ -372,7 +372,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev @action(detail=True, methods=['patch'], url_path='create-input') def create_input(self, request: Request, pk) -> HttpResponse: ''' Create input RSForm. ''' - serializer = s.OperationTargetSerializer( + serializer = s.TargetOperationSerializer( data=request.data, context={'oss': self.get_object()} ) @@ -451,7 +451,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev @extend_schema( summary='execute operation', tags=['OSS'], - request=s.OperationTargetSerializer(), + request=s.TargetOperationSerializer(), responses={ c.HTTP_200_OK: s.OperationSchemaSerializer, c.HTTP_400_BAD_REQUEST: None, @@ -462,7 +462,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev @action(detail=True, methods=['post'], url_path='execute-operation') def execute_operation(self, request: Request, pk) -> HttpResponse: ''' Execute operation. ''' - serializer = s.OperationTargetSerializer( + serializer = s.TargetOperationSerializer( data=request.data, context={'oss': self.get_object()} ) diff --git a/rsconcept/frontend/src/features/oss/backend/api.ts b/rsconcept/frontend/src/features/oss/backend/api.ts index 61047d5e..2dfde8a2 100644 --- a/rsconcept/frontend/src/features/oss/backend/api.ts +++ b/rsconcept/frontend/src/features/oss/backend/api.ts @@ -5,8 +5,11 @@ import { DELAYS, KEYS } from '@/backend/configuration'; import { infoMsg } from '@/utils/labels'; import { + type IBlockCreatedResponse, type IConstituentaReference, + type ICreateBlockDTO, type ICreateOperationDTO, + type IDeleteBlockDTO, type IDeleteOperationDTO, type IInputCreatedResponse, type IOperationCreatedResponse, @@ -14,6 +17,7 @@ import { type IOssLayout, type IRelocateConstituentsDTO, type ITargetOperation, + type IUpdateBlockDTO, type IUpdateInputDTO, type IUpdateOperationDTO, schemaConstituentaReference, @@ -49,6 +53,34 @@ export const ossApi = { } }), + createBlock: ({ itemID, data }: { itemID: number; data: ICreateBlockDTO }) => + axiosPost({ + schema: schemaOperationCreatedResponse, + endpoint: `/api/oss/${itemID}/create-block`, + request: { + data: data, + successMessage: infoMsg.changesSaved + } + }), + updateBlock: ({ itemID, data }: { itemID: number; data: IUpdateBlockDTO }) => + axiosPatch({ + schema: schemaOperationSchema, + endpoint: `/api/oss/${itemID}/update-operation`, + request: { + data: data, + successMessage: infoMsg.changesSaved + } + }), + deleteBlock: ({ itemID, data }: { itemID: number; data: IDeleteBlockDTO }) => + axiosPatch({ + schema: schemaOperationSchema, + endpoint: `/api/oss/${itemID}/delete-operation`, + request: { + data: data, + successMessage: infoMsg.operationDestroyed + } + }), + createOperation: ({ itemID, data }: { itemID: number; data: ICreateOperationDTO }) => axiosPost({ schema: schemaOperationCreatedResponse, @@ -58,6 +90,15 @@ export const ossApi = { successMessage: response => infoMsg.newOperation(response.new_operation.alias) } }), + updateOperation: ({ itemID, data }: { itemID: number; data: IUpdateOperationDTO }) => + axiosPatch({ + schema: schemaOperationSchema, + endpoint: `/api/oss/${itemID}/update-operation`, + request: { + data: data, + successMessage: infoMsg.changesSaved + } + }), deleteOperation: ({ itemID, data }: { itemID: number; data: IDeleteOperationDTO }) => axiosPatch({ schema: schemaOperationSchema, @@ -67,6 +108,7 @@ export const ossApi = { successMessage: infoMsg.operationDestroyed } }), + createInput: ({ itemID, data }: { itemID: number; data: ITargetOperation }) => axiosPatch({ schema: schemaInputCreatedResponse, @@ -85,15 +127,6 @@ export const ossApi = { successMessage: infoMsg.changesSaved } }), - updateOperation: ({ itemID, data }: { itemID: number; data: IUpdateOperationDTO }) => - axiosPatch({ - schema: schemaOperationSchema, - endpoint: `/api/oss/${itemID}/update-operation`, - request: { - data: data, - successMessage: infoMsg.changesSaved - } - }), executeOperation: ({ itemID, data }: { itemID: number; data: ITargetOperation }) => axiosPost({ schema: schemaOperationSchema, diff --git a/rsconcept/frontend/src/features/oss/backend/types.ts b/rsconcept/frontend/src/features/oss/backend/types.ts index f044aee1..c74d3b6d 100644 --- a/rsconcept/frontend/src/features/oss/backend/types.ts +++ b/rsconcept/frontend/src/features/oss/backend/types.ts @@ -27,31 +27,42 @@ export type IOperationSchemaDTO = z.infer; /** Represents {@link IOperationSchema} layout. */ export type IOssLayout = z.infer; -/** Represents {@link IOperation} data, used in creation process. */ +/** Represents {@link IBlock} data, used in Create action. */ +export type ICreateBlockDTO = z.infer; + +/** Represents data response when creating {@link IBlock}. */ +export type IBlockCreatedResponse = z.infer; + +/** Represents {@link IBlock} data, used in Update action. */ +export type IUpdateBlockDTO = z.infer; + +/** Represents {@link IBlock} data, used in Delete action. */ +export type IDeleteBlockDTO = z.infer; + +/** Represents {@link IOperation} data, used in Create action. */ export type ICreateOperationDTO = z.infer; /** Represents data response when creating {@link IOperation}. */ export type IOperationCreatedResponse = z.infer; -/** - * Represents target {@link IOperation}. - */ + +/** Represents {@link IOperation} data, used in Update action. */ +export type IUpdateOperationDTO = z.infer; + +/** Represents {@link IOperation} data, used in Delete action. */ +export type IDeleteOperationDTO = z.infer; + +/** Represents target {@link IOperation}. */ export interface ITargetOperation { layout: IOssLayout; target: number; } -/** Represents {@link IOperation} data, used in destruction process. */ -export type IDeleteOperationDTO = z.infer; - /** Represents data response when creating {@link IRSForm} for Input {@link IOperation}. */ export type IInputCreatedResponse = z.infer; -/** Represents {@link IOperation} data, used in setInput process. */ +/** Represents {@link IOperation} data, used in setInput action. */ export type IUpdateInputDTO = z.infer; -/** Represents {@link IOperation} data, used in update process. */ -export type IUpdateOperationDTO = z.infer; - /** Represents data, used relocating {@link IConstituenta}s between {@link ILibraryItem}s. */ export type IRelocateConstituentsDTO = z.infer; @@ -121,6 +132,41 @@ export const schemaOperationSchema = schemaLibraryItem.extend({ substitutions: z.array(schemaCstSubstituteInfo) }); +export const schemaCreateBlock = z.strictObject({ + layout: schemaOssLayout, + item_data: z.strictObject({ + title: z.string(), + description: z.string(), + parent: z.number().nullable() + }), + position_x: z.number(), + position_y: z.number(), + width: z.number(), + height: z.number(), + children_operations: z.array(z.number()), + children_blocks: z.array(z.number()) +}); + +export const schemaBlockCreatedResponse = z.strictObject({ + new_block: schemaBlock, + oss: schemaOperationSchema +}); + +export const schemaUpdateBlock = z.strictObject({ + target: z.number(), + layout: schemaOssLayout, + item_data: z.strictObject({ + title: z.string(), + description: z.string(), + parent: z.number().nullable() + }) +}); + +export const schemaDeleteBlock = z.strictObject({ + target: z.number(), + layout: schemaOssLayout +}); + export const schemaCreateOperation = z.strictObject({ layout: schemaOssLayout, item_data: z.strictObject({ @@ -142,6 +188,19 @@ export const schemaOperationCreatedResponse = z.strictObject({ oss: schemaOperationSchema }); +export const schemaUpdateOperation = z.strictObject({ + target: z.number(), + layout: schemaOssLayout, + item_data: z.strictObject({ + alias: z.string().nonempty(errorMsg.requiredField), + title: z.string(), + description: z.string(), + parent: z.number().nullable() + }), + arguments: z.array(z.number()), + substitutions: z.array(schemaSubstituteConstituents) +}); + export const schemaDeleteOperation = z.strictObject({ target: z.number(), layout: schemaOssLayout, @@ -160,18 +219,6 @@ export const schemaInputCreatedResponse = z.strictObject({ oss: schemaOperationSchema }); -export const schemaUpdateOperation = z.strictObject({ - target: z.number(), - layout: schemaOssLayout, - item_data: z.strictObject({ - alias: z.string().nonempty(errorMsg.requiredField), - title: z.string(), - description: z.string() - }), - arguments: z.array(z.number()), - substitutions: z.array(schemaSubstituteConstituents) -}); - export const schemaRelocateConstituents = z.strictObject({ destination: z.number().nullable(), items: z.array(z.number()).refine(data => data.length > 0) diff --git a/rsconcept/frontend/src/features/oss/backend/use-create-block.tsx b/rsconcept/frontend/src/features/oss/backend/use-create-block.tsx new file mode 100644 index 00000000..e8d017db --- /dev/null +++ b/rsconcept/frontend/src/features/oss/backend/use-create-block.tsx @@ -0,0 +1,25 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp'; + +import { KEYS } from '@/backend/configuration'; + +import { ossApi } from './api'; +import { type ICreateBlockDTO } from './types'; + +export const useCreateBlock = () => { + const client = useQueryClient(); + const { updateTimestamp } = useUpdateTimestamp(); + const mutation = useMutation({ + mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'create-block'], + mutationFn: ossApi.createBlock, + onSuccess: response => { + client.setQueryData(ossApi.getOssQueryOptions({ itemID: response.oss.id }).queryKey, response.oss); + updateTimestamp(response.oss.id); + }, + onError: () => client.invalidateQueries() + }); + return { + createBlock: (data: { itemID: number; data: ICreateBlockDTO }) => mutation.mutateAsync(data) + }; +}; diff --git a/rsconcept/frontend/src/features/oss/backend/use-create-input.tsx b/rsconcept/frontend/src/features/oss/backend/use-create-input.tsx index 7a43d7f2..7302c14d 100644 --- a/rsconcept/frontend/src/features/oss/backend/use-create-input.tsx +++ b/rsconcept/frontend/src/features/oss/backend/use-create-input.tsx @@ -8,7 +8,7 @@ import { type ITargetOperation } from './types'; export const useCreateInput = () => { const client = useQueryClient(); const mutation = useMutation({ - mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'input-create'], + mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'create-input'], mutationFn: ossApi.createInput, onSuccess: data => { client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.oss.id }).queryKey, data.oss); diff --git a/rsconcept/frontend/src/features/oss/backend/use-create-operation.tsx b/rsconcept/frontend/src/features/oss/backend/use-create-operation.tsx index cb32bd89..cec0fb2a 100644 --- a/rsconcept/frontend/src/features/oss/backend/use-create-operation.tsx +++ b/rsconcept/frontend/src/features/oss/backend/use-create-operation.tsx @@ -11,7 +11,7 @@ export const useCreateOperation = () => { const client = useQueryClient(); const { updateTimestamp } = useUpdateTimestamp(); const mutation = useMutation({ - mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'operation-create'], + mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'create-operation'], mutationFn: ossApi.createOperation, onSuccess: response => { client.setQueryData(ossApi.getOssQueryOptions({ itemID: response.oss.id }).queryKey, response.oss); diff --git a/rsconcept/frontend/src/features/oss/backend/use-delete-block.tsx b/rsconcept/frontend/src/features/oss/backend/use-delete-block.tsx new file mode 100644 index 00000000..7efe2414 --- /dev/null +++ b/rsconcept/frontend/src/features/oss/backend/use-delete-block.tsx @@ -0,0 +1,25 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { KEYS } from '@/backend/configuration'; + +import { ossApi } from './api'; +import { type IDeleteBlockDTO } from './types'; + +export const useDeleteBlock = () => { + const client = useQueryClient(); + const mutation = useMutation({ + mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-block'], + mutationFn: ossApi.deleteBlock, + onSuccess: data => { + client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data); + return Promise.allSettled([ + client.invalidateQueries({ queryKey: KEYS.composite.libraryList }), + client.invalidateQueries({ queryKey: [KEYS.rsform] }) + ]); + }, + onError: () => client.invalidateQueries() + }); + return { + deleteBlock: (data: { itemID: number; data: IDeleteBlockDTO }) => mutation.mutateAsync(data) + }; +}; diff --git a/rsconcept/frontend/src/features/oss/backend/use-delete-operation.tsx b/rsconcept/frontend/src/features/oss/backend/use-delete-operation.tsx index 8755541f..8d8d9b30 100644 --- a/rsconcept/frontend/src/features/oss/backend/use-delete-operation.tsx +++ b/rsconcept/frontend/src/features/oss/backend/use-delete-operation.tsx @@ -8,7 +8,7 @@ import { type IDeleteOperationDTO } from './types'; export const useDeleteOperation = () => { const client = useQueryClient(); const mutation = useMutation({ - mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'operation-delete'], + mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-operation'], mutationFn: ossApi.deleteOperation, onSuccess: data => { client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data); diff --git a/rsconcept/frontend/src/features/oss/backend/use-execute-operation.tsx b/rsconcept/frontend/src/features/oss/backend/use-execute-operation.tsx index ba24ec35..f0c99cd6 100644 --- a/rsconcept/frontend/src/features/oss/backend/use-execute-operation.tsx +++ b/rsconcept/frontend/src/features/oss/backend/use-execute-operation.tsx @@ -8,7 +8,7 @@ import { type ITargetOperation } from './types'; export const useExecuteOperation = () => { const client = useQueryClient(); const mutation = useMutation({ - mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'operation-execute'], + mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'execute-operation'], mutationFn: ossApi.executeOperation, onSuccess: data => { client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data); diff --git a/rsconcept/frontend/src/features/oss/backend/use-update-block.tsx b/rsconcept/frontend/src/features/oss/backend/use-update-block.tsx new file mode 100644 index 00000000..b0e93dfa --- /dev/null +++ b/rsconcept/frontend/src/features/oss/backend/use-update-block.tsx @@ -0,0 +1,37 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { type ILibraryItem } from '@/features/library'; + +import { KEYS } from '@/backend/configuration'; + +import { ossApi } from './api'; +import { type IUpdateBlockDTO } from './types'; + +export const useUpdateBlock = () => { + const client = useQueryClient(); + const mutation = useMutation({ + mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'update-block'], + mutationFn: ossApi.updateBlock, + onSuccess: (data, variables) => { + client.setQueryData(KEYS.composite.ossItem({ itemID: data.id }), data); + const schemaID = data.operations.find(item => item.id === variables.data.target)?.result; + if (!schemaID) { + return; + } + client.setQueryData(KEYS.composite.libraryList, (prev: ILibraryItem[] | undefined) => + !prev + ? undefined + : prev.map(item => + item.id === schemaID ? { ...item, ...variables.data.item_data, time_update: Date() } : item + ) + ); + return client.invalidateQueries({ + queryKey: KEYS.composite.rsItem({ itemID: schemaID }) + }); + }, + onError: () => client.invalidateQueries() + }); + return { + updateBlock: (data: { itemID: number; data: IUpdateBlockDTO }) => mutation.mutateAsync(data) + }; +}; diff --git a/rsconcept/frontend/src/features/oss/backend/use-update-input.tsx b/rsconcept/frontend/src/features/oss/backend/use-update-input.tsx index f172f668..e195b9ae 100644 --- a/rsconcept/frontend/src/features/oss/backend/use-update-input.tsx +++ b/rsconcept/frontend/src/features/oss/backend/use-update-input.tsx @@ -8,7 +8,7 @@ import { type IUpdateInputDTO } from './types'; export const useUpdateInput = () => { const client = useQueryClient(); const mutation = useMutation({ - mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'input-update'], + mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'update-input'], mutationFn: ossApi.updateInput, onSuccess: data => { client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data); diff --git a/rsconcept/frontend/src/features/oss/backend/use-update-operation.tsx b/rsconcept/frontend/src/features/oss/backend/use-update-operation.tsx index aad637ca..215a2cdd 100644 --- a/rsconcept/frontend/src/features/oss/backend/use-update-operation.tsx +++ b/rsconcept/frontend/src/features/oss/backend/use-update-operation.tsx @@ -10,7 +10,7 @@ import { type IUpdateOperationDTO } from './types'; export const useUpdateOperation = () => { const client = useQueryClient(); const mutation = useMutation({ - mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'operation-update'], + mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'update-operation'], mutationFn: ossApi.updateOperation, onSuccess: (data, variables) => { client.setQueryData(KEYS.composite.ossItem({ itemID: data.id }), data); diff --git a/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/dlg-edit-operation.tsx b/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/dlg-edit-operation.tsx index 3e32b549..388c5ef3 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/dlg-edit-operation.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/dlg-edit-operation.tsx @@ -43,7 +43,8 @@ export function DlgEditOperation() { item_data: { alias: target.alias, title: target.title, - description: target.description + description: target.description, + parent: target.parent }, arguments: target.arguments, substitutions: target.substitutions.map(sub => ({ diff --git a/rsconcept/frontend/src/features/rsform/backend/use-create-constituenta.tsx b/rsconcept/frontend/src/features/rsform/backend/use-create-constituenta.tsx index 48cb7a5f..86ab18c6 100644 --- a/rsconcept/frontend/src/features/rsform/backend/use-create-constituenta.tsx +++ b/rsconcept/frontend/src/features/rsform/backend/use-create-constituenta.tsx @@ -11,7 +11,7 @@ export const useCreateConstituenta = () => { const client = useQueryClient(); const { updateTimestamp } = useUpdateTimestamp(); const mutation = useMutation({ - mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'create-cst'], + mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'create-constituenta'], mutationFn: rsformsApi.createConstituenta, onSuccess: data => { client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema); diff --git a/rsconcept/frontend/src/features/rsform/backend/use-delete-constituents.tsx b/rsconcept/frontend/src/features/rsform/backend/use-delete-constituents.tsx index 0d504d63..063d2e6a 100644 --- a/rsconcept/frontend/src/features/rsform/backend/use-delete-constituents.tsx +++ b/rsconcept/frontend/src/features/rsform/backend/use-delete-constituents.tsx @@ -11,7 +11,7 @@ export const useDeleteConstituents = () => { const client = useQueryClient(); const { updateTimestamp } = useUpdateTimestamp(); const mutation = useMutation({ - mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'delete-multiple-cst'], + mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'delete-constituents'], mutationFn: rsformsApi.deleteConstituents, onSuccess: data => { client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data); diff --git a/rsconcept/frontend/src/features/rsform/backend/use-move-constituents.tsx b/rsconcept/frontend/src/features/rsform/backend/use-move-constituents.tsx index 27cae747..55a13b22 100644 --- a/rsconcept/frontend/src/features/rsform/backend/use-move-constituents.tsx +++ b/rsconcept/frontend/src/features/rsform/backend/use-move-constituents.tsx @@ -11,7 +11,7 @@ export const useMoveConstituents = () => { const client = useQueryClient(); const { updateTimestamp } = useUpdateTimestamp(); const mutation = useMutation({ - mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'move-cst'], + mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'move-constituents'], mutationFn: rsformsApi.moveConstituents, onSuccess: data => { client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data); diff --git a/rsconcept/frontend/src/features/rsform/backend/use-rename-constituenta.tsx b/rsconcept/frontend/src/features/rsform/backend/use-rename-constituenta.tsx index 1dfd6d8b..ec17b1b5 100644 --- a/rsconcept/frontend/src/features/rsform/backend/use-rename-constituenta.tsx +++ b/rsconcept/frontend/src/features/rsform/backend/use-rename-constituenta.tsx @@ -11,7 +11,7 @@ export const useRenameConstituenta = () => { const client = useQueryClient(); const { updateTimestamp } = useUpdateTimestamp(); const mutation = useMutation({ - mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'rename-cst'], + mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'rename-constituenta'], mutationFn: rsformsApi.renameConstituenta, onSuccess: data => { client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema); diff --git a/rsconcept/frontend/src/features/rsform/backend/use-substitute-constituents.tsx b/rsconcept/frontend/src/features/rsform/backend/use-substitute-constituents.tsx index 8128357b..061e19e8 100644 --- a/rsconcept/frontend/src/features/rsform/backend/use-substitute-constituents.tsx +++ b/rsconcept/frontend/src/features/rsform/backend/use-substitute-constituents.tsx @@ -11,7 +11,7 @@ export const useSubstituteConstituents = () => { const client = useQueryClient(); const { updateTimestamp } = useUpdateTimestamp(); const mutation = useMutation({ - mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'substitute-cst'], + mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'substitute-constituents'], mutationFn: rsformsApi.substituteConstituents, onSuccess: data => { client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data); diff --git a/rsconcept/frontend/src/features/rsform/backend/use-update-constituenta.tsx b/rsconcept/frontend/src/features/rsform/backend/use-update-constituenta.tsx index f3bb6738..130c57fd 100644 --- a/rsconcept/frontend/src/features/rsform/backend/use-update-constituenta.tsx +++ b/rsconcept/frontend/src/features/rsform/backend/use-update-constituenta.tsx @@ -11,7 +11,7 @@ export const useUpdateConstituenta = () => { const client = useQueryClient(); const { updateTimestamp } = useUpdateTimestamp(); const mutation = useMutation({ - mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'update-cst'], + mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'update-constituenta'], mutationFn: rsformsApi.updateConstituenta, onSuccess: (_, variables) => { updateTimestamp(variables.itemID);