From fa1eaa570c9158852a394dc5f4a957bf2717580a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Jul 2025 10:01:43 +0300 Subject: [PATCH 01/12] Refactor Space Update Logic: Introduced UpdateSpaceParam for better parameter handling in update operations. Enhanced SpaceDetailsDialogHelper to manage loading and error states during space updates. Updated RemoteUpdateSpaceService to construct dynamic URLs for space updates based on community UUID. Improved CommunitiesTreeFailureWidget UI with SelectableText and added spacing for better layout. --- .../widgets/community_structure_header.dart | 1 + .../communities_tree_failure_widget.dart | 6 +- .../helpers/space_details_dialog_helper.dart | 112 +++++++++++++++--- .../services/remote_update_space_service.dart | 28 ++++- .../domain/params/update_space_param.dart | 11 ++ .../domain/services/update_space_service.dart | 5 +- .../presentation/bloc/update_space_bloc.dart | 3 +- .../presentation/bloc/update_space_event.dart | 6 +- .../presentation/bloc/update_space_state.dart | 6 +- 9 files changed, 142 insertions(+), 36 deletions(-) create mode 100644 lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart index 4f71075b..5b790514 100644 --- a/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart @@ -97,6 +97,7 @@ class CommunityStructureHeader extends StatelessWidget { SpaceDetailsDialogHelper.showEdit( context, spaceModel: selectedSpace!, + communityUuid: selectedCommunity.uuid, ); }, selectedSpace: selectedSpace, diff --git a/lib/pages/space_management_v2/modules/communities/presentation/widgets/communities_tree_failure_widget.dart b/lib/pages/space_management_v2/modules/communities/presentation/widgets/communities_tree_failure_widget.dart index cfd32f52..277347df 100644 --- a/lib/pages/space_management_v2/modules/communities/presentation/widgets/communities_tree_failure_widget.dart +++ b/lib/pages/space_management_v2/modules/communities/presentation/widgets/communities_tree_failure_widget.dart @@ -13,14 +13,14 @@ class CommunitiesTreeFailureWidget extends StatelessWidget { return Expanded( child: Center( child: Column( + spacing: 16, mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( + SelectableText( errorMessage ?? 'Something went wrong', textAlign: TextAlign.center, ), - const SizedBox(height: 16), - ElevatedButton( + FilledButton( onPressed: () => context.read().add( LoadCommunities( LoadCommunitiesParam( diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart b/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart index 6b95556a..229d0dca 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart @@ -2,23 +2,38 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_bloc.dart'; import 'package:syncrow_web/services/api/http_service.dart'; abstract final class SpaceDetailsDialogHelper { static void showCreate(BuildContext context) { showDialog( context: context, - builder: (_) => BlocProvider( - create: (context) => SpaceDetailsBloc( - RemoteSpaceDetailsService(httpService: HTTPService()), - ), - child: SpaceDetailsDialog( - context: context, - title: const SelectableText('Create Space'), - spaceModel: SpaceModel.empty(), - onSave: print, + builder: (_) => MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => SpaceDetailsBloc( + RemoteSpaceDetailsService(httpService: HTTPService()), + ), + ), + BlocProvider( + create: (context) => UpdateSpaceBloc( + RemoteUpdateSpaceService(HTTPService()), + ), + ), + ], + child: Builder( + builder: (context) => SpaceDetailsDialog( + context: context, + title: const SelectableText('Create Space'), + spaceModel: SpaceModel.empty(), + onSave: (space) {}, + ), ), ), ); @@ -27,20 +42,81 @@ abstract final class SpaceDetailsDialogHelper { static void showEdit( BuildContext context, { required SpaceModel spaceModel, + required String communityUuid, }) { showDialog( context: context, - builder: (_) => BlocProvider( - create: (context) => SpaceDetailsBloc( - RemoteSpaceDetailsService(httpService: HTTPService()), - ), - child: SpaceDetailsDialog( - context: context, - title: const SelectableText('Edit Space'), - spaceModel: spaceModel, - onSave: (space) {}, + builder: (_) => MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => SpaceDetailsBloc( + RemoteSpaceDetailsService(httpService: HTTPService()), + ), + ), + BlocProvider( + create: (context) => UpdateSpaceBloc( + RemoteUpdateSpaceService(HTTPService()), + ), + ), + ], + child: Builder( + builder: (context) => BlocListener( + listener: _updateListener, + child: SpaceDetailsDialog( + context: context, + title: const SelectableText('Edit Space'), + spaceModel: spaceModel, + onSave: (space) => context.read().add( + UpdateSpace( + UpdateSpaceParam( + communityUuid: communityUuid, + space: space, + ), + ), + ), + ), + ), ), ), ); } + + static void _updateListener(BuildContext context, UpdateSpaceState state) { + return switch (state) { + UpdateSpaceInitial() => null, + UpdateSpaceLoading() => _onLoading(context), + UpdateSpaceSuccess(:final space) => _onUpdateSuccess(context, space), + UpdateSpaceFailure(:final errorMessage) => _onError(context, errorMessage), + }; + } + + static void _onUpdateSuccess(BuildContext context, SpaceDetailsModel space) { + Navigator.of(context).pop(); + } + + static void _onLoading(BuildContext context) { + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const Center(child: CircularProgressIndicator()), + ); + } + + static void _onError(BuildContext context, String errorMessage) { + Navigator.of(context).pop(); + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => AlertDialog( + title: const Text('Error'), + content: Text(errorMessage), + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: const Text('OK'), + ), + ], + ), + ); + } } diff --git a/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart b/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart index b15e6095..9f6f65a6 100644 --- a/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart +++ b/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart @@ -1,5 +1,7 @@ import 'package:dio/dio.dart'; +import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart'; import 'package:syncrow_web/services/api/api_exception.dart'; import 'package:syncrow_web/services/api/http_service.dart'; @@ -12,14 +14,19 @@ class RemoteUpdateSpaceService implements UpdateSpaceService { static const _defaultErrorMessage = 'Failed to update space'; @override - Future updateSpace(SpaceDetailsModel space) async { + Future updateSpace(UpdateSpaceParam param) async { try { + final path = await _makeUrl(param); final response = await _httpService.put( - path: 'endpoint', - body: space.toJson(), - expectedResponseModel: (data) => SpaceDetailsModel.fromJson( - data as Map, - ), + path: path, + body: param.space.toJson(), + expectedResponseModel: (data) { + final response = data as Map; + final space = SpaceDetailsModel.fromJson( + response['data'] as Map, + ); + return space; + }, ); return response; @@ -37,4 +44,13 @@ class RemoteUpdateSpaceService implements UpdateSpaceService { throw APIException(formattedErrorMessage); } } + + Future _makeUrl(UpdateSpaceParam param) async { + final projectUuid = await ProjectManager.getProjectUUID(); + if (projectUuid == null || projectUuid.isEmpty) { + throw APIException('Project UUID is not set'); + } + + return '/projects/$projectUuid/communities/${param.communityUuid}/spaces/${param.space.uuid}'; + } } diff --git a/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart b/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart new file mode 100644 index 00000000..884976f7 --- /dev/null +++ b/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart @@ -0,0 +1,11 @@ +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; + +class UpdateSpaceParam { + UpdateSpaceParam({ + required this.space, + required this.communityUuid, + }); + + final SpaceDetailsModel space; + final String communityUuid; +} diff --git a/lib/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart b/lib/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart index 29bc9419..c75fc0d4 100644 --- a/lib/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart +++ b/lib/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart @@ -1,5 +1,6 @@ import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart'; -abstract class UpdateSpaceService { - Future updateSpace(SpaceDetailsModel space); +abstract interface class UpdateSpaceService { + Future updateSpace(UpdateSpaceParam param); } diff --git a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_bloc.dart b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_bloc.dart index 3bc4e187..0920b547 100644 --- a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_bloc.dart +++ b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_bloc.dart @@ -1,6 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart'; import 'package:syncrow_web/services/api/api_exception.dart'; @@ -20,7 +21,7 @@ class UpdateSpaceBloc extends Bloc { ) async { emit(UpdateSpaceLoading()); try { - final updatedSpace = await _updateSpaceService.updateSpace(event.space); + final updatedSpace = await _updateSpaceService.updateSpace(event.param); emit(UpdateSpaceSuccess(updatedSpace)); } on APIException catch (e) { emit(UpdateSpaceFailure(e.message)); diff --git a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_event.dart b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_event.dart index b7d476af..ec08cdd2 100644 --- a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_event.dart +++ b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_event.dart @@ -8,10 +8,10 @@ sealed class UpdateSpaceEvent extends Equatable { } final class UpdateSpace extends UpdateSpaceEvent { - const UpdateSpace(this.space); + const UpdateSpace(this.param); - final SpaceDetailsModel space; + final UpdateSpaceParam param; @override - List get props => [space]; + List get props => [param]; } diff --git a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_state.dart b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_state.dart index f0bc5a2b..437cca60 100644 --- a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_state.dart +++ b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_state.dart @@ -21,10 +21,10 @@ final class UpdateSpaceSuccess extends UpdateSpaceState { } final class UpdateSpaceFailure extends UpdateSpaceState { - final String message; + final String errorMessage; - const UpdateSpaceFailure(this.message); + const UpdateSpaceFailure(this.errorMessage); @override - List get props => [message]; + List get props => [errorMessage]; } From b001713ce439a392f6d5a1b6e83d16a30f4dfb26 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Jul 2025 11:10:22 +0300 Subject: [PATCH 02/12] Enhance Community Structure Widgets: Updated SpaceDetailsDialogHelper to accept community UUID for space creation and editing. Refactored CreateSpaceButton and CommunityStructureHeader to pass community UUID, improving data handling and consistency across the community structure features. --- .../widgets/community_structure_canvas.dart | 5 ++- .../widgets/community_structure_header.dart | 22 ++++++------ .../widgets/create_space_button.dart | 12 +++++-- .../space_management_community_structure.dart | 10 ++++-- .../helpers/space_details_dialog_helper.dart | 7 +++- .../widgets/space_details_dialog.dart | 9 ++--- .../domain/params/update_space_param.dart | 34 +++++++++++++++++++ 7 files changed, 76 insertions(+), 23 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart index 4aea103a..8ab0c97b 100644 --- a/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart @@ -241,7 +241,10 @@ class _CommunityStructureCanvasState extends State ), ); }, - onTap: () => SpaceDetailsDialogHelper.showCreate(context), + onTap: () => SpaceDetailsDialogHelper.showCreate( + context, + communityUuid: widget.community.uuid, + ), ), ), ); diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart index 5b790514..f27dc8b9 100644 --- a/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart @@ -55,8 +55,9 @@ class CommunityStructureHeader extends StatelessWidget { children: [ Text( 'Community Structure', - style: theme.textTheme.headlineLarge - ?.copyWith(color: ColorsManager.blackColor), + style: theme.textTheme.headlineLarge?.copyWith( + color: ColorsManager.blackColor, + ), ), if (selectedCommunity != null) Row( @@ -67,8 +68,9 @@ class CommunityStructureHeader extends StatelessWidget { Flexible( child: SelectableText( selectedCommunity.name, - style: theme.textTheme.bodyLarge - ?.copyWith(color: ColorsManager.blackColor), + style: theme.textTheme.bodyLarge?.copyWith( + color: ColorsManager.blackColor, + ), maxLines: 1, ), ), @@ -93,13 +95,11 @@ class CommunityStructureHeader extends StatelessWidget { CommunityStructureHeaderActionButtons( onDelete: (space) {}, onDuplicate: (space) {}, - onEdit: (space) { - SpaceDetailsDialogHelper.showEdit( - context, - spaceModel: selectedSpace!, - communityUuid: selectedCommunity.uuid, - ); - }, + onEdit: (space) => SpaceDetailsDialogHelper.showEdit( + context, + spaceModel: selectedSpace!, + communityUuid: selectedCommunity.uuid, + ), selectedSpace: selectedSpace, ), ], diff --git a/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart b/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart index 4cbfd7fd..b7259d21 100644 --- a/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart +++ b/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart @@ -3,12 +3,20 @@ import 'package:syncrow_web/pages/space_management_v2/modules/space_details/pres import 'package:syncrow_web/utils/color_manager.dart'; class CreateSpaceButton extends StatelessWidget { - const CreateSpaceButton({super.key}); + const CreateSpaceButton({ + required this.communityUuid, + super.key, + }); + + final String communityUuid; @override Widget build(BuildContext context) { return GestureDetector( - onTap: () => SpaceDetailsDialogHelper.showCreate(context), + onTap: () => SpaceDetailsDialogHelper.showCreate( + context, + communityUuid: communityUuid, + ), child: Container( height: 60, decoration: BoxDecoration( diff --git a/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart b/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart index e1f1fc00..4c588ec7 100644 --- a/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart +++ b/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart @@ -16,8 +16,14 @@ class SpaceManagementCommunityStructure extends StatelessWidget { const spacer = Spacer(flex: 10); return Visibility( visible: selectedCommunity!.spaces.isNotEmpty, - replacement: const Row( - children: [spacer, Expanded(child: CreateSpaceButton()), spacer], + replacement: Row( + children: [ + spacer, + Expanded( + child: CreateSpaceButton(communityUuid: selectedCommunity.uuid), + ), + spacer + ], ), child: Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart b/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart index 229d0dca..d66d28f4 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart @@ -11,7 +11,10 @@ import 'package:syncrow_web/pages/space_management_v2/modules/update_space/prese import 'package:syncrow_web/services/api/http_service.dart'; abstract final class SpaceDetailsDialogHelper { - static void showCreate(BuildContext context) { + static void showCreate( + BuildContext context, { + required String communityUuid, + }) { showDialog( context: context, builder: (_) => MultiBlocProvider( @@ -33,6 +36,7 @@ abstract final class SpaceDetailsDialogHelper { title: const SelectableText('Create Space'), spaceModel: SpaceModel.empty(), onSave: (space) {}, + communityUuid: communityUuid, ), ), ), @@ -74,6 +78,7 @@ abstract final class SpaceDetailsDialogHelper { ), ), ), + communityUuid: communityUuid, ), ), ), diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart index ae772036..d97442ec 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart'; @@ -15,6 +14,7 @@ class SpaceDetailsDialog extends StatefulWidget { required this.spaceModel, required this.onSave, required this.context, + required this.communityUuid, super.key, }); @@ -22,6 +22,7 @@ class SpaceDetailsDialog extends StatefulWidget { final SpaceModel spaceModel; final void Function(SpaceDetailsModel space) onSave; final BuildContext context; + final String communityUuid; @override State createState() => _SpaceDetailsDialogState(); @@ -35,11 +36,7 @@ class _SpaceDetailsDialogState extends State { if (!isCreateMode) { final param = LoadSpaceDetailsParam( spaceUuid: widget.spaceModel.uuid, - communityUuid: widget.context - .read() - .state - .selectedCommunity! - .uuid, + communityUuid: widget.communityUuid, ); widget.context.read().add(LoadSpaceDetails(param)); } diff --git a/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart b/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart index 884976f7..884cd581 100644 --- a/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart +++ b/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart @@ -8,4 +8,38 @@ class UpdateSpaceParam { final SpaceDetailsModel space; final String communityUuid; + + Map toJson() { + return { + 'spaceName': space.spaceName, + 'icon': space.icon, + 'subspaces': space.subspaces + .map( + (e) => { + 'subspaceName': e.name, + 'productAllocations': e.productAllocations + .map( + (e) => { + 'name': e.tag.name, + 'productUuid': e.product.uuid, + 'uuid': e.uuid, + }, + ) + .toList(), + 'uuid': e.uuid, + }, + ) + .toList(), + 'productAllocations': space.productAllocations + .map( + (e) => { + 'tagName': e.tag.name, + 'tagUuid': e.tag.uuid, + 'productUuid': e.product.uuid, + }, + ) + .toList(), + 'spaceModelUuid': space.uuid, + }; + } } From bcf62027bc38a171be12c5f1d6475fa4e846dd44 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Jul 2025 11:12:12 +0300 Subject: [PATCH 03/12] Validate UUIDs in RemoteUpdateSpaceService: Added checks for empty space and community UUIDs before constructing the update URL, improving error handling and robustness in the update space process. --- .../data/services/remote_update_space_service.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart b/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart index 9f6f65a6..452a7375 100644 --- a/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart +++ b/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart @@ -51,6 +51,16 @@ class RemoteUpdateSpaceService implements UpdateSpaceService { throw APIException('Project UUID is not set'); } - return '/projects/$projectUuid/communities/${param.communityUuid}/spaces/${param.space.uuid}'; + final spaceUuid = param.space.uuid; + if (spaceUuid.isEmpty) { + throw APIException('Space UUID is not set'); + } + + final communityUuid = param.communityUuid; + if (communityUuid.isEmpty) { + throw APIException('Community UUID is not set'); + } + + return '/projects/$projectUuid/communities/$communityUuid/spaces/$spaceUuid'; } } From 7c2aed2d580cf4898421a8f511b32209aec2f5cd Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Jul 2025 12:20:10 +0300 Subject: [PATCH 04/12] Refactor RemoteUpdateSpaceService: Improved error handling in updateSpace method by checking API response success before returning the updated space. This enhances robustness and ensures proper error propagation for failed updates. --- .../data/services/remote_update_space_service.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart b/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart index 452a7375..b595e2b9 100644 --- a/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart +++ b/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart @@ -17,19 +17,20 @@ class RemoteUpdateSpaceService implements UpdateSpaceService { Future updateSpace(UpdateSpaceParam param) async { try { final path = await _makeUrl(param); - final response = await _httpService.put( + await _httpService.put( path: path, body: param.space.toJson(), expectedResponseModel: (data) { final response = data as Map; - final space = SpaceDetailsModel.fromJson( - response['data'] as Map, - ); - return space; + final isSuccess = response['success'] as bool; + if (!isSuccess) { + throw APIException(response['error'] as String); + } + return isSuccess; }, ); - return response; + return param.space; } on DioException catch (e) { final message = e.response?.data as Map?; final error = message?['error'] as Map?; From 9e6b14737f5a438f15ac74d20523c73d69a70c3f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Jul 2025 13:07:26 +0300 Subject: [PATCH 05/12] Refactor CreateSpaceButton: Changed from StatelessWidget to StatefulWidget to manage hover state and added tooltip for improved user experience. Enhanced button styling and interaction feedback for better visual cues during space creation. --- .../widgets/create_space_button.dart | 80 ++++++++++++------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart b/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart index b7259d21..90d359e2 100644 --- a/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart +++ b/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class CreateSpaceButton extends StatelessWidget { +class CreateSpaceButton extends StatefulWidget { const CreateSpaceButton({ required this.communityUuid, super.key, @@ -10,38 +10,58 @@ class CreateSpaceButton extends StatelessWidget { final String communityUuid; + @override + State createState() => _CreateSpaceButtonState(); +} + +class _CreateSpaceButtonState extends State { + bool _isHovered = false; + @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () => SpaceDetailsDialogHelper.showCreate( - context, - communityUuid: communityUuid, - ), - child: Container( - height: 60, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues(alpha: 0.5), - spreadRadius: 5, - blurRadius: 7, - offset: const Offset(0, 3), - ), - ], + return Tooltip( + margin: const EdgeInsets.symmetric(vertical: 24), + message: 'Create a new space', + child: GestureDetector( + onTap: () => SpaceDetailsDialogHelper.showCreate( + context, + communityUuid: widget.communityUuid, ), - child: Center( - child: Container( - width: 40, - height: 40, - decoration: const BoxDecoration( - color: ColorsManager.boxColor, - shape: BoxShape.circle, - ), - child: const Icon( - Icons.add, - color: Colors.blue, + child: MouseRegion( + onEnter: (_) => setState(() => _isHovered = true), + onExit: (_) => setState(() => _isHovered = false), + child: AnimatedOpacity( + duration: const Duration(milliseconds: 100), + opacity: _isHovered ? 1.0 : 0.45, + child: Container( + width: 150, + height: 90, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.grey.withValues(alpha: 0.2), + spreadRadius: 3, + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Container( + margin: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.borderColor, width: 2), + color: ColorsManager.boxColor, + shape: BoxShape.circle, + ), + child: const Center( + child: Icon( + Icons.add, + color: Colors.blue, + ), + ), + ), ), ), ), From 9e0ea4ad6f9e916dacb5e5e1e8b1fd10634f41d1 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Jul 2025 13:07:39 +0300 Subject: [PATCH 06/12] Adjust spacer flex in SpaceManagementCommunityStructure widget for improved layout consistency. --- .../widgets/space_management_community_structure.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart b/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart index 4c588ec7..050eac87 100644 --- a/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart +++ b/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart @@ -13,7 +13,7 @@ class SpaceManagementCommunityStructure extends StatelessWidget { final selectionBloc = context.watch().state; final selectedCommunity = selectionBloc.selectedCommunity; final selectedSpace = selectionBloc.selectedSpace; - const spacer = Spacer(flex: 10); + const spacer = Spacer(flex: 6); return Visibility( visible: selectedCommunity!.spaces.isNotEmpty, replacement: Row( From 03c45ed8d0292d44d1a3e74630b39296fc8e5f6e Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Jul 2025 13:07:55 +0300 Subject: [PATCH 07/12] Refactor SpaceCardWidget: Simplified widget structure by removing unnecessary SizedBox. --- .../widgets/space_card_widget.dart | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/space_card_widget.dart b/lib/pages/space_management_v2/main_module/widgets/space_card_widget.dart index e91e577f..54902280 100644 --- a/lib/pages/space_management_v2/main_module/widgets/space_card_widget.dart +++ b/lib/pages/space_management_v2/main_module/widgets/space_card_widget.dart @@ -22,22 +22,20 @@ class _SpaceCardWidgetState extends State { return MouseRegion( onEnter: (_) => setState(() => isHovered = true), onExit: (_) => setState(() => isHovered = false), - child: SizedBox( - child: Stack( - clipBehavior: Clip.none, - alignment: Alignment.center, - children: [ - widget.buildSpaceContainer(), - if (isHovered) - Positioned( - bottom: 0, - child: PlusButtonWidget( - offset: Offset.zero, - onButtonTap: widget.onTap, - ), + child: Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + widget.buildSpaceContainer(), + if (isHovered) + Positioned( + bottom: 0, + child: PlusButtonWidget( + offset: Offset.zero, + onButtonTap: widget.onTap, ), - ], - ), + ), + ], ), ); } From 707cb4791f170563041b994ffe34d4c69b61eb84 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Jul 2025 13:08:43 +0300 Subject: [PATCH 08/12] Added CreateSpaceButton for improved user interaction and updated layout calculations to utilize context extensions for better responsiveness. --- .../widgets/community_structure_canvas.dart | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart index 8ab0c97b..f23405bf 100644 --- a/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart @@ -2,12 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_connection_model.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart'; +import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_card_widget.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_cell.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class CommunityStructureCanvas extends StatefulWidget { const CommunityStructureCanvas({ @@ -31,8 +33,8 @@ class _CommunityStructureCanvasState extends State final double _horizontalSpacing = 150.0; final double _verticalSpacing = 120.0; - late TransformationController _transformationController; - late AnimationController _animationController; + late final TransformationController _transformationController; + late final AnimationController _animationController; @override void initState() { @@ -182,7 +184,8 @@ class _CommunityStructureCanvasState extends State _positions.clear(); final community = widget.community; - _calculateLayout(community.spaces, 0, {}); + final levelXOffset = {}; + _calculateLayout(community.spaces, 0, levelXOffset); final selectedSpace = widget.selectedSpace; final highlightedUuids = {}; @@ -195,6 +198,17 @@ class _CommunityStructureCanvasState extends State final connections = []; _generateWidgets(community.spaces, widgets, connections, highlightedUuids); + final createButtonX = levelXOffset[0] ?? 0.0; + const createButtonY = 0.0; + + widgets.add( + Positioned( + left: createButtonX, + top: createButtonY, + child: CreateSpaceButton(communityUuid: widget.community.uuid), + ), + ); + return [ CustomPaint( painter: SpacesConnectionsArrowPainter( @@ -264,8 +278,8 @@ class _CommunityStructureCanvasState extends State return InteractiveViewer( transformationController: _transformationController, boundaryMargin: EdgeInsets.symmetric( - horizontal: MediaQuery.sizeOf(context).width * 0.3, - vertical: MediaQuery.sizeOf(context).height * 0.3, + horizontal: context.screenWidth * 0.3, + vertical: context.screenHeight * 0.3, ), minScale: 0.5, maxScale: 3.0, @@ -273,8 +287,8 @@ class _CommunityStructureCanvasState extends State child: GestureDetector( onTap: _resetSelectionAndZoom, child: SizedBox( - width: MediaQuery.sizeOf(context).width * 5, - height: MediaQuery.sizeOf(context).height * 5, + width: context.screenWidth * 5, + height: context.screenHeight * 5, child: Stack(children: treeWidgets), ), ), From 2b8d987c69e4b6f6231297429e7261defafa4024 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Jul 2025 16:00:57 +0300 Subject: [PATCH 09/12] Add SpaceReorderDataModel and integrate drag-and-drop functionality in CommunityStructureCanvas for improved space management. --- .../models/space_reorder_data_model.dart | 14 ++ .../widgets/community_structure_canvas.dart | 199 +++++++++++++++--- 2 files changed, 185 insertions(+), 28 deletions(-) create mode 100644 lib/pages/space_management_v2/main_module/models/space_reorder_data_model.dart diff --git a/lib/pages/space_management_v2/main_module/models/space_reorder_data_model.dart b/lib/pages/space_management_v2/main_module/models/space_reorder_data_model.dart new file mode 100644 index 00000000..d05f22c7 --- /dev/null +++ b/lib/pages/space_management_v2/main_module/models/space_reorder_data_model.dart @@ -0,0 +1,14 @@ +import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart'; + +class SpaceReorderDataModel { + const SpaceReorderDataModel({ + required this.space, + this.parent, + this.community, + }); + + final SpaceModel space; + final SpaceModel? parent; + final CommunityModel? community; +} diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart index f23405bf..3cf761ad 100644 --- a/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_connection_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_reorder_data_model.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_card_widget.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_cell.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -35,6 +37,7 @@ class _CommunityStructureCanvasState extends State late final TransformationController _transformationController; late final AnimationController _animationController; + SpaceReorderDataModel? _draggedData; @override void initState() { @@ -99,7 +102,7 @@ class _CommunityStructureCanvasState extends State final position = _positions[space.uuid]; if (position == null) return; - const scale = 1.5; + const scale = 1; final viewSize = context.size; if (viewSize == null) return; @@ -114,16 +117,33 @@ class _CommunityStructureCanvasState extends State _runAnimation(matrix); } + void _onReorder(SpaceReorderDataModel data, int newIndex) { + final newCommunity = widget.community.copyWith(); + final children = data.parent?.children ?? newCommunity.spaces; + final oldIndex = children.indexWhere((s) => s.uuid == data.space.uuid); + if (oldIndex != -1) { + final item = children.removeAt(oldIndex); + if (newIndex > oldIndex) { + children.insert(newIndex - 1, item); + } else { + children.insert(newIndex, item); + } + } + context.read().add( + CommunitiesUpdateCommunity(newCommunity), + ); + } + void _onSpaceTapped(SpaceModel? space) { context.read().add( SelectSpaceEvent(community: widget.community, space: space), ); } - void _resetSelectionAndZoom() { + void _resetSelectionAndZoom([CommunityModel? community]) { context.read().add( SelectSpaceEvent( - community: widget.community, + community: community ?? widget.community, space: null, ), ); @@ -196,7 +216,13 @@ class _CommunityStructureCanvasState extends State final widgets = []; final connections = []; - _generateWidgets(community.spaces, widgets, connections, highlightedUuids); + _generateWidgets( + widget.community.spaces, + widgets, + connections, + highlightedUuids, + community: widget.community, + ); final createButtonX = levelXOffset[0] ?? 0.0; const createButtonY = 0.0; @@ -225,53 +251,170 @@ class _CommunityStructureCanvasState extends State List spaces, List widgets, List connections, - Set highlightedUuids, - ) { - for (final space in spaces) { + Set highlightedUuids, { + CommunityModel? community, + SpaceModel? parent, + }) { + if (spaces.isNotEmpty) { + final firstChildPos = _positions[spaces.first.uuid]!; + final targetPos = Offset( + firstChildPos.dx - (_horizontalSpacing / 4), + firstChildPos.dy, + ); + widgets.add(_buildDropTarget(parent, community, 0, targetPos)); + } + + for (var i = 0; i < spaces.length; i++) { + final space = spaces[i]; final position = _positions[space.uuid]; - if (position == null) continue; + if (position == null) { + continue; + } final isHighlighted = highlightedUuids.contains(space.uuid); final hasNoSelectedSpace = widget.selectedSpace == null; + final spaceCard = SpaceCardWidget( + buildSpaceContainer: () { + return Opacity( + opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5, + child: Tooltip( + message: space.spaceName, + preferBelow: false, + child: SpaceCell( + onTap: () => _onSpaceTapped(space), + icon: space.icon, + name: space.spaceName, + ), + ), + ); + }, + onTap: () => SpaceDetailsDialogHelper.showCreate( + context, + communityUuid: widget.community.uuid, + ), + ); + + final reorderData = SpaceReorderDataModel( + space: space, + parent: parent, + community: community, + ); + widgets.add( Positioned( left: position.dx, top: position.dy, width: _cardWidth, height: _cardHeight, - child: SpaceCardWidget( - buildSpaceContainer: () { - return Opacity( - opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5, - child: Tooltip( - message: space.spaceName, - preferBelow: false, - child: SpaceCell( - onTap: () => _onSpaceTapped(space), - icon: space.icon, - name: space.spaceName, - ), + child: Draggable( + data: reorderData, + feedback: Material( + color: Colors.transparent, + child: Opacity( + opacity: 0.2, + child: SizedBox( + width: _cardWidth, + height: _cardHeight, + child: spaceCard, ), - ); - }, - onTap: () => SpaceDetailsDialogHelper.showCreate( - context, - communityUuid: widget.community.uuid, + ), ), + onDragStarted: () => setState(() => _draggedData = reorderData), + onDragEnd: (_) => setState(() => _draggedData = null), + onDraggableCanceled: (_, __) => setState(() => _draggedData = null), + childWhenDragging: Opacity(opacity: 0.4, child: spaceCard), + child: spaceCard, ), ), ); + final targetPos = Offset( + position.dx + _cardWidth + (_horizontalSpacing / 4) - 20, + position.dy, + ); + widgets.add(_buildDropTarget(parent, community, i + 1, targetPos)); + for (final child in space.children) { - connections.add( - SpaceConnectionModel(from: space.uuid, to: child.uuid), + connections.add(SpaceConnectionModel(from: space.uuid, to: child.uuid)); + } + + if (space.children.isNotEmpty) { + _generateWidgets( + space.children, + widgets, + connections, + highlightedUuids, + parent: space, ); } - _generateWidgets(space.children, widgets, connections, highlightedUuids); } } + Widget _buildDropTarget( + SpaceModel? parent, + CommunityModel? community, + int index, + Offset position, + ) { + return Positioned( + left: position.dx, + top: position.dy, + width: 40, + height: _cardHeight, + child: DragTarget( + builder: (context, candidateData, rejectedData) { + if (_draggedData == null) { + return const SizedBox(); + } + + final isTargetForDragged = (_draggedData?.parent?.uuid == parent?.uuid && + _draggedData?.community == null) || + (_draggedData?.community?.uuid == community?.uuid && + _draggedData?.parent == null); + + if (!isTargetForDragged) { + return const SizedBox(); + } + + return Container( + width: 40, + height: _cardHeight, + decoration: BoxDecoration( + color: context.theme.colorScheme.primary.withValues( + alpha: candidateData.isNotEmpty ? 0.7 : 0.3, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.add, + color: context.theme.colorScheme.onPrimary, + ), + ); + }, + onWillAcceptWithDetails: (data) { + final children = parent?.children ?? community?.spaces ?? []; + final isSameParent = (data.data.parent?.uuid == parent?.uuid && + data.data.community == null) || + (data.data.community?.uuid == community?.uuid && + data.data.parent == null); + + if (!isSameParent) { + return false; + } + + final oldIndex = + children.indexWhere((s) => s.uuid == data.data.space.uuid); + if (oldIndex == index || oldIndex == index - 1) { + return false; + } + return true; + }, + onAcceptWithDetails: (data) => _onReorder(data.data, index), + ), + ); + } + @override Widget build(BuildContext context) { final treeWidgets = _buildTreeWidgets(); From 5cd083a37b007eaa093ed70b2dc8ac458276ee42 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Jul 2025 15:08:49 +0300 Subject: [PATCH 10/12] Refactor Space and Tag Models: Removed unused JSON serialization methods from SpaceDetailsModel, ProductAllocation, and Subspace. Updated Tag model to eliminate unnecessary fields. Enhanced UpdateSpaceParam to streamline JSON conversion for subspaces and product allocations, improving data handling during updates. --- .../domain/models/space_details_model.dart | 26 ---------- .../widgets/space_sub_spaces_dialog.dart | 2 +- .../modules/tags/domain/models/tag.dart | 24 +-------- .../widgets/assign_tags_dialog.dart | 7 ++- .../widgets/product_tag_field.dart | 10 ++-- .../services/remote_update_space_service.dart | 2 +- .../domain/params/update_space_param.dart | 50 +++++++++---------- 7 files changed, 34 insertions(+), 87 deletions(-) diff --git a/lib/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart b/lib/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart index b3e436b1..ec3c9f81 100644 --- a/lib/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart +++ b/lib/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart @@ -40,16 +40,6 @@ class SpaceDetailsModel extends Equatable { ); } - Map toJson() { - return { - 'uuid': uuid, - 'spaceName': spaceName, - 'icon': icon, - 'productAllocations': productAllocations.map((e) => e.toJson()).toList(), - 'subspaces': subspaces.map((e) => e.toJson()).toList(), - }; - } - SpaceDetailsModel copyWith({ String? uuid, String? spaceName, @@ -89,14 +79,6 @@ class ProductAllocation extends Equatable { ); } - Map toJson() { - return { - 'uuid': uuid, - 'product': product.toJson(), - 'tag': tag.toJson(), - }; - } - ProductAllocation copyWith({ String? uuid, Product? product, @@ -134,14 +116,6 @@ class Subspace extends Equatable { ); } - Map toJson() { - return { - 'uuid': uuid, - 'name': name, - 'productAllocations': productAllocations.map((e) => e.toJson()).toList(), - }; - } - Subspace copyWith({ String? uuid, String? name, diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart index 9e81c323..8faac548 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart @@ -37,7 +37,7 @@ class _SpaceSubSpacesDialogState extends State { ..._subspaces, Subspace( name: name, - uuid: const Uuid().v4(), + uuid: '${const Uuid().v4()}-NewTag', productAllocations: const [], ), ]; diff --git a/lib/pages/space_management_v2/modules/tags/domain/models/tag.dart b/lib/pages/space_management_v2/modules/tags/domain/models/tag.dart index 370bdf47..c5bccdbb 100644 --- a/lib/pages/space_management_v2/modules/tags/domain/models/tag.dart +++ b/lib/pages/space_management_v2/modules/tags/domain/models/tag.dart @@ -3,41 +3,19 @@ import 'package:equatable/equatable.dart'; class Tag extends Equatable { final String uuid; final String name; - final String createdAt; - final String updatedAt; const Tag({ required this.uuid, required this.name, - required this.createdAt, - required this.updatedAt, }); - factory Tag.empty() => const Tag( - uuid: '', - name: '', - createdAt: '', - updatedAt: '', - ); - factory Tag.fromJson(Map json) { return Tag( uuid: json['uuid'] as String, name: json['name'] as String, - createdAt: json['createdAt'] as String, - updatedAt: json['updatedAt'] as String, ); } - Map toJson() { - return { - 'uuid': uuid, - 'name': name, - 'createdAt': createdAt, - 'updatedAt': updatedAt, - }; - } - @override - List get props => [uuid, name, createdAt, updatedAt]; + List get props => [uuid, name]; } diff --git a/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_dialog.dart b/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_dialog.dart index 3cab4abe..3f6d42ab 100644 --- a/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_dialog.dart +++ b/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_dialog.dart @@ -214,9 +214,12 @@ class _AssignTagsDialogState extends State { for (final product in newProducts) { _space.productAllocations.add( ProductAllocation( - uuid: const Uuid().v4(), + uuid: '${const Uuid().v4()}-NewProductUuid', product: product, - tag: Tag.empty(), + tag: Tag( + uuid: '${const Uuid().v4()}-NewTag', + name: '', + ), ), ); } diff --git a/lib/pages/space_management_v2/modules/tags/presentation/widgets/product_tag_field.dart b/lib/pages/space_management_v2/modules/tags/presentation/widgets/product_tag_field.dart index 8bbf379d..30282123 100644 --- a/lib/pages/space_management_v2/modules/tags/presentation/widgets/product_tag_field.dart +++ b/lib/pages/space_management_v2/modules/tags/presentation/widgets/product_tag_field.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:uuid/uuid.dart'; class ProductTagField extends StatefulWidget { final List items; @@ -53,13 +54,8 @@ class _ProductTagFieldState extends State { void _submit(String value) { final lowerCaseValue = value.toLowerCase(); final selectedTag = widget.items.firstWhere( - (tag) => tag.name.toLowerCase() == lowerCaseValue, - orElse: () => Tag( - name: value, - uuid: '', - createdAt: '', - updatedAt: '', - ), + (e) => e.name.toLowerCase() == lowerCaseValue, + orElse: () => Tag(uuid: '${const Uuid().v4()}-NewTag', name: value), ); widget.onSelected(selectedTag); _closeDropdown(); diff --git a/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart b/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart index b595e2b9..a70d3b85 100644 --- a/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart +++ b/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart @@ -19,7 +19,7 @@ class RemoteUpdateSpaceService implements UpdateSpaceService { final path = await _makeUrl(param); await _httpService.put( path: path, - body: param.space.toJson(), + body: param.toJson(), expectedResponseModel: (data) { final response = data as Map; final isSuccess = response['success'] as bool; diff --git a/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart b/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart index 884cd581..97fefe03 100644 --- a/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart +++ b/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart @@ -13,33 +13,29 @@ class UpdateSpaceParam { return { 'spaceName': space.spaceName, 'icon': space.icon, - 'subspaces': space.subspaces - .map( - (e) => { - 'subspaceName': e.name, - 'productAllocations': e.productAllocations - .map( - (e) => { - 'name': e.tag.name, - 'productUuid': e.product.uuid, - 'uuid': e.uuid, - }, - ) - .toList(), - 'uuid': e.uuid, - }, - ) - .toList(), - 'productAllocations': space.productAllocations - .map( - (e) => { - 'tagName': e.tag.name, - 'tagUuid': e.tag.uuid, - 'productUuid': e.product.uuid, - }, - ) - .toList(), - 'spaceModelUuid': space.uuid, + 'subspaces': space.subspaces.map((e) => e._toJson()).toList(), + 'productAllocations': + space.productAllocations.map((e) => e._toJson()).toList(), + }; + } +} + +extension _ProductAllocationToJson on ProductAllocation { + Map _toJson() { + final isNewTag = tag.uuid.isEmpty; + return { + if (isNewTag) 'tagName': tag.name else 'tagUuid': tag.uuid, + 'productUuid': product.uuid, + }; + } +} + +extension _SubspaceToJson on Subspace { + Map _toJson() { + final isNewSubspace = uuid.endsWith('-NewTag'); + return { + if (isNewSubspace) 'subspaceName': name else 'uuid': uuid, + 'productAllocations': productAllocations.map((e) => e._toJson()).toList(), }; } } From d87739f1fd10221b448fe3040069ad348af23ebc Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Jul 2025 15:25:41 +0300 Subject: [PATCH 11/12] Refactor JSON Serialization in UpdateSpaceParam: Adjusted the _toJson method for Subspace to ensure 'subspaceName' is always included and 'uuid' is only added when applicable, enhancing clarity and consistency in data representation. --- .../modules/update_space/domain/params/update_space_param.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart b/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart index 97fefe03..5dd9106d 100644 --- a/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart +++ b/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart @@ -34,7 +34,8 @@ extension _SubspaceToJson on Subspace { Map _toJson() { final isNewSubspace = uuid.endsWith('-NewTag'); return { - if (isNewSubspace) 'subspaceName': name else 'uuid': uuid, + if (!isNewSubspace) 'uuid': uuid, + 'subspaceName': name, 'productAllocations': productAllocations.map((e) => e._toJson()).toList(), }; } From 83202204b094cf8c05c1acf05d4430b3ad5e3557 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Jul 2025 15:58:17 +0300 Subject: [PATCH 12/12] Remove BlocProvider for UpdateSpaceBloc in SpaceDetailsDialogHelper to streamline dependency management and improve code clarity. --- .../presentation/helpers/space_details_dialog_helper.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart b/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart index d66d28f4..031e0399 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart @@ -24,11 +24,6 @@ abstract final class SpaceDetailsDialogHelper { RemoteSpaceDetailsService(httpService: HTTPService()), ), ), - BlocProvider( - create: (context) => UpdateSpaceBloc( - RemoteUpdateSpaceService(HTTPService()), - ), - ), ], child: Builder( builder: (context) => SpaceDetailsDialog(