From dcf1df9b4a82f21e9db1bc35a58298fc1de5a685 Mon Sep 17 00:00:00 2001 From: Rafeek Alkhoudare Date: Wed, 21 May 2025 07:25:34 -0500 Subject: [PATCH 01/62] sp1613 delete condition word --- .../flush_presence_sensor/flush_presence_sensor.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_presence_sensor.dart b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_presence_sensor.dart index bf2146ad..35cee5e7 100644 --- a/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_presence_sensor.dart +++ b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_presence_sensor.dart @@ -96,7 +96,7 @@ class _WallPresenceSensorState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const DialogHeader('Presence Sensor Condition'), + const DialogHeader('Presence Sensor'), Expanded(child: _buildMainContent(context, state)), _buildDialogFooter(context, state), ], From fa1eaa570c9158852a394dc5f4a957bf2717580a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Jul 2025 10:01:43 +0300 Subject: [PATCH 02/62] 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 03/62] 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 04/62] 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 05/62] 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 06/62] 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 07/62] 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 08/62] 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 09/62] 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 10/62] 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 11/62] 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 12/62] 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 13/62] 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( From b6664ec1ba7160198f26b54468dc0bda1bed2665 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Fri, 11 Jul 2025 10:53:04 +0300 Subject: [PATCH 14/62] fix Redundant API calls on Routines page when selecting a community from the tree --- .../device_managment_bloc.dart | 14 ++-- .../bloc/routine_bloc/routine_bloc.dart | 81 +++++++++---------- lib/services/devices_mang_api.dart | 17 ++-- lib/utils/constants/api_const.dart | 6 +- 4 files changed, 56 insertions(+), 62 deletions(-) diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart index 98b0c195..eeb5e45c 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart @@ -43,16 +43,15 @@ class DeviceManagementBloc final projectUuid = await ProjectManager.getProjectUUID() ?? ''; if (spaceBloc.state.selectedCommunities.isEmpty) { - devices = - await DevicesManagementApi().fetchDevices('', '', projectUuid); + devices = await DevicesManagementApi().fetchDevices( + projectUuid, + ); } else { for (var community in spaceBloc.state.selectedCommunities) { - List spacesList = + final spacesList = spaceBloc.state.selectedCommunityAndSpaces[community] ?? []; - for (var space in spacesList) { - devices.addAll(await DevicesManagementApi() - .fetchDevices(community, space, projectUuid)); - } + devices.addAll(await DevicesManagementApi() + .fetchDevices(projectUuid, spacesId: spacesList)); } } @@ -270,6 +269,7 @@ class DeviceManagementBloc return 'All'; } } + void _onSearchDevices( SearchDevices event, Emitter emit) { if ((event.community == null || event.community!.isEmpty) && diff --git a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index 3fd07834..971f4f8c 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -170,45 +170,45 @@ class RoutineBloc extends Bloc { } } -Future _onLoadScenes( - LoadScenes event, Emitter emit) async { - emit(state.copyWith(isLoading: true, errorMessage: null)); - List scenes = []; - try { - BuildContext context = NavigationService.navigatorKey.currentContext!; - var createRoutineBloc = context.read(); - final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - if (createRoutineBloc.selectedSpaceId == '' && - createRoutineBloc.selectedCommunityId == '') { - var spaceBloc = context.read(); - for (var communityId in spaceBloc.state.selectedCommunities) { - List spacesList = - spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; - for (var spaceId in spacesList) { - scenes.addAll( - await SceneApi.getScenes(spaceId, communityId, projectUuid)); + Future _onLoadScenes( + LoadScenes event, Emitter emit) async { + emit(state.copyWith(isLoading: true, errorMessage: null)); + List scenes = []; + try { + BuildContext context = NavigationService.navigatorKey.currentContext!; + var createRoutineBloc = context.read(); + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + if (createRoutineBloc.selectedSpaceId == '' && + createRoutineBloc.selectedCommunityId == '') { + var spaceBloc = context.read(); + for (var communityId in spaceBloc.state.selectedCommunities) { + List spacesList = + spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + for (var spaceId in spacesList) { + scenes.addAll( + await SceneApi.getScenes(spaceId, communityId, projectUuid)); + } } + } else { + scenes.addAll(await SceneApi.getScenes( + createRoutineBloc.selectedSpaceId, + createRoutineBloc.selectedCommunityId, + projectUuid)); } - } else { - scenes.addAll(await SceneApi.getScenes( - createRoutineBloc.selectedSpaceId, - createRoutineBloc.selectedCommunityId, - projectUuid)); - } - emit(state.copyWith( - scenes: scenes, - isLoading: false, - )); - } catch (e) { - emit(state.copyWith( + emit(state.copyWith( + scenes: scenes, isLoading: false, - loadScenesErrorMessage: 'Failed to load scenes', - errorMessage: '', - loadAutomationErrorMessage: '', - scenes: scenes)); + )); + } catch (e) { + emit(state.copyWith( + isLoading: false, + loadScenesErrorMessage: 'Failed to load scenes', + errorMessage: '', + loadAutomationErrorMessage: '', + scenes: scenes)); + } } -} Future _onLoadAutomation( LoadAutomation event, Emitter emit) async { @@ -936,16 +936,15 @@ Future _onLoadScenes( for (var communityId in spaceBloc.state.selectedCommunities) { List spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; - for (var spaceId in spacesList) { - devices.addAll(await DevicesManagementApi() - .fetchDevices(communityId, spaceId, projectUuid)); - } + + devices.addAll(await DevicesManagementApi() + .fetchDevices(projectUuid, spacesId: spacesList)); } } else { devices.addAll(await DevicesManagementApi().fetchDevices( - createRoutineBloc.selectedCommunityId, - createRoutineBloc.selectedSpaceId, - projectUuid)); + projectUuid, + spacesId: [createRoutineBloc.selectedSpaceId], + )); } emit(state.copyWith(isLoading: false, devices: devices)); diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 709d6855..8c74dbb1 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -12,20 +12,16 @@ import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; class DevicesManagementApi { - Future> fetchDevices( - String communityId, String spaceId, String projectId) async { + Future> fetchDevices(String projectId, + {List? spacesId}) async { try { final response = await HTTPService().get( - path: communityId.isNotEmpty && spaceId.isNotEmpty - ? ApiEndpoints.getSpaceDevices - .replaceAll('{spaceUuid}', spaceId) - .replaceAll('{communityUuid}', communityId) - .replaceAll('{projectId}', projectId) - : ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId), + path: ApiEndpoints.getSpaceDevices.replaceAll('{projectId}', projectId), + queryParameters: {if (spacesId != null) 'spaces': spacesId}, showServerMessage: true, expectedResponseModel: (json) { - List jsonData = json['data']; - List devicesList = jsonData.map((jsonItem) { + final List jsonData = json['data'] as List; + final List devicesList = jsonData.map((jsonItem) { return AllDevicesModel.fromJson(jsonItem); }).toList(); return devicesList; @@ -416,5 +412,4 @@ class DevicesManagementApi { ); return response; } - } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index eb7b6a3e..c3e8b1d2 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -17,8 +17,7 @@ abstract class ApiEndpoints { ////// Devices Management //////////////// static const String getAllDevices = '/projects/{projectId}/devices'; - static const String getSpaceDevices = - '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/devices'; + static const String getSpaceDevices = '/projects/{projectId}/devices'; static const String getDeviceStatus = '/devices/{uuid}/functions/status'; static const String getBatchStatus = '/devices/batch'; @@ -46,7 +45,8 @@ abstract class ApiEndpoints { // Community Module static const String createCommunity = '/projects/{projectId}/communities'; static const String getCommunityList = '/projects/{projectId}/communities'; - static const String getCommunityListv2 = '/projects/{projectId}/communities/v2'; + static const String getCommunityListv2 = + '/projects/{projectId}/communities/v2'; static const String getCommunityById = '/projects/{projectId}/communities/{communityId}'; static const String updateCommunity = From 2681c837f511227f025293fa2687707c3135e4d3 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Fri, 11 Jul 2025 12:10:53 +0300 Subject: [PATCH 15/62] add company name and replace it with job title --- .../model/edit_user_model.dart | 3 ++ .../model/roles_user_model.dart | 4 ++- .../add_user_dialog/bloc/users_bloc.dart | 12 ++++---- .../add_user_dialog/view/basics_view.dart | 6 ++-- .../users_table/view/users_page.dart | 4 +-- lib/services/user_permission.dart | 30 ++++++++++++------- 6 files changed, 36 insertions(+), 23 deletions(-) diff --git a/lib/pages/roles_and_permission/model/edit_user_model.dart b/lib/pages/roles_and_permission/model/edit_user_model.dart index 81430fa3..6f5564d4 100644 --- a/lib/pages/roles_and_permission/model/edit_user_model.dart +++ b/lib/pages/roles_and_permission/model/edit_user_model.dart @@ -153,6 +153,7 @@ class EditUserModel { final String? jobTitle; // can be empty final String roleType; // e.g. "ADMIN" final List spaces; + final String? companyName; EditUserModel({ required this.uuid, @@ -167,6 +168,7 @@ class EditUserModel { required this.jobTitle, required this.roleType, required this.spaces, + this.companyName, }); /// Create a [UserData] from JSON data @@ -182,6 +184,7 @@ class EditUserModel { invitedBy: json['invitedBy'] as String, phoneNumber: json['phoneNumber'] ?? '', jobTitle: json['jobTitle'] ?? '', + companyName: json['companyName'] as String?, roleType: json['roleType'] as String, spaces: (json['spaces'] as List) .map((e) => UserSpaceModel.fromJson(e as Map)) diff --git a/lib/pages/roles_and_permission/model/roles_user_model.dart b/lib/pages/roles_and_permission/model/roles_user_model.dart index e502370a..ce88e200 100644 --- a/lib/pages/roles_and_permission/model/roles_user_model.dart +++ b/lib/pages/roles_and_permission/model/roles_user_model.dart @@ -12,7 +12,7 @@ class RolesUserModel { final dynamic jobTitle; final dynamic createdDate; final dynamic createdTime; - + final String? companyName; RolesUserModel({ required this.uuid, required this.createdAt, @@ -27,6 +27,7 @@ class RolesUserModel { this.jobTitle, required this.createdDate, required this.createdTime, + this.companyName, }); factory RolesUserModel.fromJson(Map json) { @@ -47,6 +48,7 @@ class RolesUserModel { : json['jobTitle'], createdDate: json['createdDate'], createdTime: json['createdTime'], + companyName: json['companyName'] as String?, ); } } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index 72c4501c..cda61499 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -52,7 +52,7 @@ class UsersBloc extends Bloc { final TextEditingController lastNameController = TextEditingController(); final TextEditingController emailController = TextEditingController(); final TextEditingController phoneController = TextEditingController(); - final TextEditingController jobTitleController = TextEditingController(); + final TextEditingController companyNameController = TextEditingController(); final TextEditingController roleSearchController = TextEditingController(); bool? isCompleteBasics; @@ -352,7 +352,7 @@ class UsersBloc extends Bloc { bool res = await UserPermissionApi().sendInviteUser( email: emailController.text, firstName: firstNameController.text, - jobTitle: jobTitleController.text, + companyName: companyNameController.text, lastName: lastNameController.text, phoneNumber: phoneController.text, roleUuid: roleSelected, @@ -405,7 +405,7 @@ class UsersBloc extends Bloc { bool res = await UserPermissionApi().editInviteUser( userId: event.userId, firstName: firstNameController.text, - jobTitle: jobTitleController.text, + companyName: companyNameController.text, lastName: lastNameController.text, phoneNumber: phoneController.text, roleUuid: roleSelected, @@ -455,7 +455,7 @@ class UsersBloc extends Bloc { Future checkEmail( CheckEmailEvent event, Emitter emit) async { emit(UsersLoadingState()); - String? res = await UserPermissionApi().checkEmail( + String? res = await UserPermissionApi().checkEmail( emailController.text, ); checkEmailValid = res!; @@ -529,7 +529,7 @@ class UsersBloc extends Bloc { lastNameController.text = res.lastName; emailController.text = res.email; phoneController.text = res.phoneNumber ?? ''; - jobTitleController.text = res.jobTitle ?? ''; + companyNameController.text = res.companyName ?? ''; res.roleType; res.spaces.map((space) { selectedIds.add(space.uuid); @@ -645,7 +645,7 @@ class UsersBloc extends Bloc { lastNameController.dispose(); emailController.dispose(); phoneController.dispose(); - jobTitleController.dispose(); + companyNameController.dispose(); roleSearchController.dispose(); return super.close(); } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index 14022cab..250bba3f 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -317,7 +317,7 @@ class BasicsView extends StatelessWidget { child: Row( children: [ Text( - 'Job Title', + 'Company Name', style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w400, fontSize: 13, @@ -328,11 +328,11 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( - controller: _blocRole.jobTitleController, + controller: _blocRole.companyNameController, style: const TextStyle(color: ColorsManager.blackColor), decoration: inputTextFormDeco( - hintText: "Job Title (Optional)") + hintText: "Comapny Name (Optional)") .copyWith( hintStyle: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w400, diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index da159d94..0a7e3714 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -411,7 +411,7 @@ class UsersPage extends StatelessWidget { titles: const [ "Full Name", "Email Address", - "Job Title", + "Company Name", "Role", "Creation Date", "Creation Time", @@ -424,7 +424,7 @@ class UsersPage extends StatelessWidget { return [ Text('${user.firstName} ${user.lastName}'), Text(user.email), - Text(user.jobTitle), + Center(child: Text(user.companyName ?? '-')), Text(user.roleType ?? ''), Text(user.createdDate ?? ''), Text(user.createdTime ?? ''), diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 90a82921..59d8dfcc 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -34,8 +34,9 @@ class UserPermissionApi { path: ApiEndpoints.roleTypes, showServerMessage: true, expectedResponseModel: (json) { - final List fetchedRoles = - (json['data'] as List).map((item) => RoleTypeModel.fromJson(item)).toList(); + final List fetchedRoles = (json['data'] as List) + .map((item) => RoleTypeModel.fromJson(item)) + .toList(); return fetchedRoles; }, ); @@ -47,7 +48,9 @@ class UserPermissionApi { path: ApiEndpoints.permission.replaceAll("roleUuid", roleUuid), showServerMessage: true, expectedResponseModel: (json) { - return (json as List).map((data) => PermissionOption.fromJson(data)).toList(); + return (json as List) + .map((data) => PermissionOption.fromJson(data)) + .toList(); }, ); return response ?? []; @@ -57,7 +60,7 @@ class UserPermissionApi { String? firstName, String? lastName, String? email, - String? jobTitle, + String? companyName, String? phoneNumber, String? roleUuid, List? spaceUuids, @@ -68,7 +71,7 @@ class UserPermissionApi { "firstName": firstName, "lastName": lastName, "email": email, - "jobTitle": jobTitle != '' ? jobTitle : null, + "companyName": companyName != '' ? companyName : null, "phoneNumber": phoneNumber != '' ? phoneNumber : null, "roleUuid": roleUuid, "projectUuid": projectUuid, @@ -140,7 +143,7 @@ class UserPermissionApi { String? firstName, String? userId, String? lastName, - String? jobTitle, + String? companyName, String? phoneNumber, String? roleUuid, List? spaceUuids, @@ -150,8 +153,8 @@ class UserPermissionApi { final body = { "firstName": firstName, "lastName": lastName, - "jobTitle": jobTitle != '' ? jobTitle : " ", - "phoneNumber": phoneNumber != '' ? phoneNumber : " ", + "companyName": companyName != '' ? companyName : ' ', + "phoneNumber": phoneNumber != '' ? phoneNumber : ' ', "roleUuid": roleUuid, "projectUuid": projectUuid, "spaceUuids": spaceUuids, @@ -190,12 +193,17 @@ class UserPermissionApi { } } - Future changeUserStatusById(userUuid, status, String projectUuid) async { + Future changeUserStatusById( + userUuid, status, String projectUuid) async { try { - Map bodya = {"disable": status, "projectUuid": projectUuid}; + Map bodya = { + "disable": status, + "projectUuid": projectUuid + }; final response = await _httpService.put( - path: ApiEndpoints.changeUserStatus.replaceAll("{invitedUserUuid}", userUuid), + path: ApiEndpoints.changeUserStatus + .replaceAll("{invitedUserUuid}", userUuid), body: bodya, expectedResponseModel: (json) { return json['success']; From 7331c8440b567e8413c525ad8a5bbba51a193c5a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 10:27:22 +0300 Subject: [PATCH 16/62] Refactor SpaceManagementPage to use StatefulWidget and initialize CommunitiesBloc in initState. Update CommunityStructureHeader to handle community updates and improve state management in CommunitiesTreeSelectionBloc with new event for community state updates. --- .../views/space_management_page.dart | 30 +++++-- .../widgets/community_structure_header.dart | 36 +++++++++ .../domain/models/space_model.dart | 19 +++++ .../communities_tree_selection_bloc.dart | 79 ++++++++++++++++++- .../communities_tree_selection_event.dart | 9 +++ .../communities_tree_selection_state.dart | 12 +-- .../helpers/space_details_dialog_helper.dart | 24 +++++- 7 files changed, 189 insertions(+), 20 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/views/space_management_page.dart b/lib/pages/space_management_v2/main_module/views/space_management_page.dart index 106b9a3a..40a37891 100644 --- a/lib/pages/space_management_v2/main_module/views/space_management_page.dart +++ b/lib/pages/space_management_v2/main_module/views/space_management_page.dart @@ -16,21 +16,37 @@ import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; -class SpaceManagementPage extends StatelessWidget { +class SpaceManagementPage extends StatefulWidget { const SpaceManagementPage({super.key}); + @override + State createState() => _SpaceManagementPageState(); +} + +class _SpaceManagementPageState extends State { + late final CommunitiesBloc communitiesBloc; + + @override + void initState() { + communitiesBloc = CommunitiesBloc( + communitiesService: DebouncedCommunitiesService( + RemoteCommunitiesService(HTTPService()), + ), + )..add(const LoadCommunities(LoadCommunitiesParam())); + + super.initState(); + } + @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ + BlocProvider.value(value: communitiesBloc), BlocProvider( - create: (context) => CommunitiesBloc( - communitiesService: DebouncedCommunitiesService( - RemoteCommunitiesService(HTTPService()), - ), - )..add(const LoadCommunities(LoadCommunitiesParam())), + create: (context) => CommunitiesTreeSelectionBloc( + communitiesBloc: communitiesBloc, + ), ), - BlocProvider(create: (context) => CommunitiesTreeSelectionBloc()), BlocProvider( create: (context) => SpaceDetailsBloc( UniqueSubspacesDecorator( 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 f27dc8b9..cb6271d1 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 @@ -3,7 +3,10 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.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/domain/models/space_details_model.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'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -11,6 +14,26 @@ import 'package:syncrow_web/utils/constants/assets.dart'; class CommunityStructureHeader extends StatelessWidget { const CommunityStructureHeader({super.key}); + List _updateRecursive( + List spaces, + SpaceDetailsModel updatedSpace, + ) { + return spaces.map((space) { + if (space.uuid == updatedSpace.uuid) { + return space.copyWith( + spaceName: updatedSpace.spaceName, + icon: updatedSpace.icon, + ); + } + if (space.children.isNotEmpty) { + return space.copyWith( + children: _updateRecursive(space.children, updatedSpace), + ); + } + return space; + }).toList(); + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -99,6 +122,19 @@ class CommunityStructureHeader extends StatelessWidget { context, spaceModel: selectedSpace!, communityUuid: selectedCommunity.uuid, + onSuccess: (updatedSpaceDetails) { + final communitiesBloc = context.read(); + final updatedSpaces = _updateRecursive( + selectedCommunity.spaces, + updatedSpaceDetails, + ); + + final community = selectedCommunity.copyWith( + spaces: updatedSpaces, + ); + + communitiesBloc.add(CommunitiesUpdateCommunity(community)); + }, ), selectedSpace: selectedSpace, ), diff --git a/lib/pages/space_management_v2/modules/communities/domain/models/space_model.dart b/lib/pages/space_management_v2/modules/communities/domain/models/space_model.dart index ddcc6a86..bd5a2e50 100644 --- a/lib/pages/space_management_v2/modules/communities/domain/models/space_model.dart +++ b/lib/pages/space_management_v2/modules/communities/domain/models/space_model.dart @@ -46,6 +46,25 @@ class SpaceModel extends Equatable { ); } + SpaceModel copyWith({ + String? uuid, + DateTime? createdAt, + DateTime? updatedAt, + String? spaceName, + String? icon, + List? children, + }) { + return SpaceModel( + uuid: uuid ?? this.uuid, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + spaceName: spaceName ?? this.spaceName, + icon: icon ?? this.icon, + children: children ?? this.children, + parent: parent, + ); + } + @override List get props => [uuid, spaceName, icon, children]; } diff --git a/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart b/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart index bdda04ee..25263d35 100644 --- a/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart +++ b/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart @@ -1,17 +1,39 @@ +import 'dart:async'; + import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.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'; part 'communities_tree_selection_event.dart'; part 'communities_tree_selection_state.dart'; class CommunitiesTreeSelectionBloc extends Bloc { - CommunitiesTreeSelectionBloc() : super(const CommunitiesTreeSelectionState()) { + CommunitiesTreeSelectionBloc({ + required CommunitiesBloc communitiesBloc, + }) : _communitiesBloc = communitiesBloc, + super(const CommunitiesTreeSelectionState()) { on(_onSelectCommunity); on(_onSelectSpace); on(_onClearSelection); + on<_CommunitiesStateUpdated>(_onCommunitiesStateUpdated); + + _communitiesSubscription = _communitiesBloc.stream.listen((communitiesState) { + if (state.selectedCommunity != null) { + add(_CommunitiesStateUpdated(communitiesState)); + } + }); + } + + final CommunitiesBloc _communitiesBloc; + late final StreamSubscription _communitiesSubscription; + + @override + Future close() { + _communitiesSubscription.cancel(); + return super.close(); } void _onSelectCommunity( @@ -44,4 +66,59 @@ class CommunitiesTreeSelectionBloc ) { emit(const CommunitiesTreeSelectionState()); } + + void _onCommunitiesStateUpdated( + _CommunitiesStateUpdated event, + Emitter emit, + ) { + if (state.selectedCommunity == null) return; + + final communities = event.communitiesState.communities; + try { + final updatedCommunity = communities.firstWhere( + (c) => c.uuid == state.selectedCommunity!.uuid, + ); + + var updatedSelectedSpace = state.selectedSpace; + if (state.selectedSpace != null) { + updatedSelectedSpace = _findSpaceInCommunity( + updatedCommunity, + state.selectedSpace!.uuid, + ); + } + emit( + state.copyWith( + selectedCommunity: updatedCommunity, + selectedSpace: updatedSelectedSpace, + clearSelectedSpace: updatedSelectedSpace == null, + ), + ); + } catch (_) { + add(const ClearCommunitiesTreeSelectionEvent()); + } + } + + SpaceModel? _findSpaceInCommunity(CommunityModel community, String spaceUuid) { + try { + return _findSpaceRecursive(community.spaces, spaceUuid); + } catch (_) { + return null; + } + } + + SpaceModel _findSpaceRecursive(List spaces, String spaceUuid) { + for (final space in spaces) { + if (space.uuid == spaceUuid) { + return space; + } + if (space.children.isNotEmpty) { + try { + return _findSpaceRecursive(space.children, spaceUuid); + } catch (_) { + // not found in this branch + } + } + } + throw Exception('Space not found'); + } } diff --git a/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_event.dart b/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_event.dart index 21088632..43a69e05 100644 --- a/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_event.dart +++ b/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_event.dart @@ -29,3 +29,12 @@ final class ClearCommunitiesTreeSelectionEvent extends CommunitiesTreeSelectionEvent { const ClearCommunitiesTreeSelectionEvent(); } + +final class _CommunitiesStateUpdated extends CommunitiesTreeSelectionEvent { + const _CommunitiesStateUpdated(this.communitiesState); + + final CommunitiesState communitiesState; + + @override + List get props => [communitiesState]; +} diff --git a/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_state.dart b/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_state.dart index b14d330b..4c36f778 100644 --- a/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_state.dart +++ b/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_state.dart @@ -12,18 +12,14 @@ final class CommunitiesTreeSelectionState extends Equatable { CommunitiesTreeSelectionState copyWith({ CommunityModel? selectedCommunity, SpaceModel? selectedSpace, - List? expandedCommunities, - List? expandedSpaces, + bool clearSelectedSpace = false, }) { return CommunitiesTreeSelectionState( selectedCommunity: selectedCommunity ?? this.selectedCommunity, - selectedSpace: selectedSpace ?? this.selectedSpace, + selectedSpace: clearSelectedSpace ? null : selectedSpace ?? this.selectedSpace, ); } @override - List get props => [ - selectedCommunity, - selectedSpace, - ]; - } + List get props => [selectedCommunity, selectedSpace]; +} 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 031e0399..c5de7dad 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 @@ -42,6 +42,7 @@ abstract final class SpaceDetailsDialogHelper { BuildContext context, { required SpaceModel spaceModel, required String communityUuid, + required void Function(SpaceDetailsModel updatedSpaceDetails)? onSuccess, }) { showDialog( context: context, @@ -60,7 +61,11 @@ abstract final class SpaceDetailsDialogHelper { ], child: Builder( builder: (context) => BlocListener( - listener: _updateListener, + listener: (context, state) => _updateListener( + context, + state, + onSuccess, + ), child: SpaceDetailsDialog( context: context, title: const SelectableText('Edit Space'), @@ -81,17 +86,28 @@ abstract final class SpaceDetailsDialogHelper { ); } - static void _updateListener(BuildContext context, UpdateSpaceState state) { + static void _updateListener( + BuildContext context, + UpdateSpaceState state, + void Function(SpaceDetailsModel updatedSpaceDetails)? onSuccess, + ) { return switch (state) { UpdateSpaceInitial() => null, UpdateSpaceLoading() => _onLoading(context), - UpdateSpaceSuccess(:final space) => _onUpdateSuccess(context, space), + UpdateSpaceSuccess(:final space) => + _onUpdateSuccess(context, space, onSuccess), UpdateSpaceFailure(:final errorMessage) => _onError(context, errorMessage), }; } - static void _onUpdateSuccess(BuildContext context, SpaceDetailsModel space) { + static void _onUpdateSuccess( + BuildContext context, + SpaceDetailsModel space, + void Function(SpaceDetailsModel updatedSpaceDetails)? onSuccess, + ) { Navigator.of(context).pop(); + Navigator.of(context).pop(); + onSuccess?.call(space); } static void _onLoading(BuildContext context) { From 65d541d59408ed0fd9b167968060520d499061e3 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 14 Jul 2025 10:46:12 +0300 Subject: [PATCH 17/62] Add calendar event management features and UI components and Implement Calendar logic --- .../services/remote_calendar_service.dart | 170 ++++++++ .../domain/models/calendar_event_booking.dart | 134 +++++++ .../services/calendar_system_service.dart | 7 + .../bloc/calendar/events_bloc.dart | 118 +++--- .../bloc/calendar/events_event.dart | 16 +- .../bloc/calendar/events_state.dart | 4 - .../presentation/view/booking_page.dart | 368 ++++++++---------- .../view/widgets/event_tile_widget.dart | 60 +++ .../widgets/hatched_column_background.dart | 91 +++++ .../view/widgets/time_line_widget.dart | 49 +++ .../view/widgets/week_day_header.dart | 39 ++ .../view/widgets/week_navigation.dart | 76 ++++ .../view/widgets/weekly_calendar_page.dart | 138 ++----- lib/utils/color_manager.dart | 2 +- lib/utils/constants/api_const.dart | 1 + 15 files changed, 890 insertions(+), 383 deletions(-) create mode 100644 lib/pages/access_management/booking_system/data/services/remote_calendar_service.dart create mode 100644 lib/pages/access_management/booking_system/domain/models/calendar_event_booking.dart create mode 100644 lib/pages/access_management/booking_system/domain/services/calendar_system_service.dart create mode 100644 lib/pages/access_management/booking_system/presentation/view/widgets/event_tile_widget.dart create mode 100644 lib/pages/access_management/booking_system/presentation/view/widgets/hatched_column_background.dart create mode 100644 lib/pages/access_management/booking_system/presentation/view/widgets/time_line_widget.dart create mode 100644 lib/pages/access_management/booking_system/presentation/view/widgets/week_day_header.dart create mode 100644 lib/pages/access_management/booking_system/presentation/view/widgets/week_navigation.dart diff --git a/lib/pages/access_management/booking_system/data/services/remote_calendar_service.dart b/lib/pages/access_management/booking_system/data/services/remote_calendar_service.dart new file mode 100644 index 00000000..aa3307d3 --- /dev/null +++ b/lib/pages/access_management/booking_system/data/services/remote_calendar_service.dart @@ -0,0 +1,170 @@ +import 'package:dio/dio.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart'; +import 'package:syncrow_web/services/api/api_exception.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; + +class RemoteCalendarService implements CalendarSystemService { + const RemoteCalendarService(this._httpService); + + final HTTPService _httpService; + static const _defaultErrorMessage = 'Failed to load Calendar'; + + @override + Future getCalendarEvents({ + required String spaceId, + }) async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getCalendarEvents, + queryParameters: { + 'spaceId': spaceId, + }, + expectedResponseModel: (json) { + return CalendarEventsResponse.fromJson( + json as Map, + ); + }, + ); + + return CalendarEventsResponse.fromJson(response as Map); + } on DioException catch (e) { + final responseData = e.response?.data; + if (responseData is Map) { + final errorMessage = responseData['error']?['message'] as String? ?? + responseData['message'] as String? ?? + _defaultErrorMessage; + throw APIException(errorMessage); + } + throw APIException(_defaultErrorMessage); + } catch (e) { + throw APIException('$_defaultErrorMessage: ${e.toString()}'); + } + } +} + +class FakeRemoteCalendarService implements CalendarSystemService { + const FakeRemoteCalendarService(this._httpService, {this.useDummy = false}); + + final HTTPService _httpService; + final bool useDummy; + static const _defaultErrorMessage = 'Failed to load Calendar'; + + @override + Future getCalendarEvents({ + required String spaceId, + }) async { + if (useDummy) { + final dummyJson = { + 'statusCode': 200, + 'message': 'Successfully fetched all bookings', + 'data': [ + { + 'uuid': 'd4553fa6-a0c9-4f42-81c9-99a13a57bf80', + 'date': '2025-07-11T10:22:00.626Z', + 'startTime': '09:00:00', + 'endTime': '12:00:00', + 'cost': 10, + 'user': { + 'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e', + 'firstName': 'salsabeel', + 'lastName': 'abuzaid', + 'email': 'test@test.com', + 'companyName': null + }, + 'space': { + 'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e', + 'spaceName': '2(1)' + } + }, + { + 'uuid': 'e9b27af0-b963-4d98-9657-454c4ba78561', + 'date': '2025-07-11T10:22:00.626Z', + 'startTime': '12:00:00', + 'endTime': '13:00:00', + 'cost': 10, + 'user': { + 'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e', + 'firstName': 'salsabeel', + 'lastName': 'abuzaid', + 'email': 'test@test.com', + 'companyName': null + }, + 'space': { + 'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e', + 'spaceName': '2(1)' + } + }, + { + 'uuid': 'e9b27af0-b963-4d98-9657-454c4ba78561', + 'date': '2025-07-13T10:22:00.626Z', + 'startTime': '15:30:00', + 'endTime': '19:00:00', + 'cost': 20, + 'user': { + 'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e', + 'firstName': 'salsabeel', + 'lastName': 'abuzaid', + 'email': 'test@test.com', + 'companyName': null + }, + 'space': { + 'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e', + 'spaceName': '2(1)' + } + } + ], + 'success': true + }; + final response = CalendarEventsResponse.fromJson(dummyJson); + + // Filter events by spaceId + final filteredData = response.data.where((event) { + return event.space.uuid == spaceId; + }).toList(); + print('Filtering events for spaceId: $spaceId'); + print('Found ${filteredData.length} matching events'); + return filteredData.isNotEmpty + ? CalendarEventsResponse( + statusCode: response.statusCode, + message: response.message, + data: filteredData, + success: response.success, + ) + : CalendarEventsResponse( + statusCode: 404, + message: 'No events found for spaceId: $spaceId', + data: [], + success: false, + ); + } + + try { + final response = await _httpService.get( + path: ApiEndpoints.getCalendarEvents, + queryParameters: { + 'spaceId': spaceId, + }, + expectedResponseModel: (json) { + return CalendarEventsResponse.fromJson( + json as Map, + ); + }, + ); + + return CalendarEventsResponse.fromJson(response as Map); + } on DioException catch (e) { + final responseData = e.response?.data; + if (responseData is Map) { + final errorMessage = responseData['error']?['message'] as String? ?? + responseData['message'] as String? ?? + _defaultErrorMessage; + throw APIException(errorMessage); + } + throw APIException(_defaultErrorMessage); + } catch (e) { + throw APIException('$_defaultErrorMessage: ${e.toString()}'); + } + } +} diff --git a/lib/pages/access_management/booking_system/domain/models/calendar_event_booking.dart b/lib/pages/access_management/booking_system/domain/models/calendar_event_booking.dart new file mode 100644 index 00000000..4b8f1ba1 --- /dev/null +++ b/lib/pages/access_management/booking_system/domain/models/calendar_event_booking.dart @@ -0,0 +1,134 @@ +class CalendarEventBooking { + final String uuid; + final DateTime date; + final String startTime; + final String endTime; + final int cost; + final BookingUser user; + final BookingSpace space; + + CalendarEventBooking({ + required this.uuid, + required this.date, + required this.startTime, + required this.endTime, + required this.cost, + required this.user, + required this.space, + }); + + factory CalendarEventBooking.fromJson(Map json) { + return CalendarEventBooking( + uuid: json['uuid'] as String? ?? '', + date: json['date'] != null + ? DateTime.parse(json['date'] as String) + : DateTime.now(), + startTime: json['startTime'] as String? ?? '', + endTime: json['endTime'] as String? ?? '', + cost: _parseInt(json['cost']), + user: json['user'] != null + ? BookingUser.fromJson(json['user'] as Map) + : BookingUser.empty(), + space: json['space'] != null + ? BookingSpace.fromJson(json['space'] as Map) + : BookingSpace.empty(), + ); + } + + static int _parseInt(dynamic value) { + if (value is int) return value; + if (value is String) return int.tryParse(value) ?? 0; + return 0; + } +} + +class BookingUser { + final String uuid; + final String firstName; + final String lastName; + final String email; + final String? companyName; + + BookingUser({ + required this.uuid, + required this.firstName, + required this.lastName, + required this.email, + this.companyName, + }); + + factory BookingUser.fromJson(Map json) { + return BookingUser( + uuid: json['uuid'] as String? ?? '', + firstName: json['firstName'] as String? ?? '', + lastName: json['lastName'] as String? ?? '', + email: json['email'] as String? ?? '', + companyName: json['companyName'] as String?, + ); + } + + factory BookingUser.empty() { + return BookingUser( + uuid: '', + firstName: '', + lastName: '', + email: '', + companyName: null, + ); + } +} + +class BookingSpace { + final String uuid; + final String spaceName; + + BookingSpace({ + required this.uuid, + required this.spaceName, + }); + + factory BookingSpace.fromJson(Map json) { + return BookingSpace( + uuid: json['uuid'] as String? ?? '', + spaceName: json['spaceName'] as String? ?? '', + ); + } + + factory BookingSpace.empty() { + return BookingSpace( + uuid: '', + spaceName: '', + ); + } +} + +class CalendarEventsResponse { + final int statusCode; + final String message; + final List data; + final bool success; + + CalendarEventsResponse({ + required this.statusCode, + required this.message, + required this.data, + required this.success, + }); + + factory CalendarEventsResponse.fromJson(Map json) { + return CalendarEventsResponse( + statusCode: _parseInt(json['statusCode']), + message: json['message'] as String? ?? '', + data: (json['data'] as List? ?? []) + .map((e) => CalendarEventBooking.fromJson(e as Map)) + .toList(), + success: json['success'] as bool? ?? false, + ); + } +} + +int _parseInt(dynamic value) { + if (value is int) return value; + if (value is String) return int.tryParse(value) ?? 0; + return 0; +} diff --git a/lib/pages/access_management/booking_system/domain/services/calendar_system_service.dart b/lib/pages/access_management/booking_system/domain/services/calendar_system_service.dart new file mode 100644 index 00000000..9e178040 --- /dev/null +++ b/lib/pages/access_management/booking_system/domain/services/calendar_system_service.dart @@ -0,0 +1,7 @@ +import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart'; + +abstract class CalendarSystemService { + Future getCalendarEvents({ + required String spaceId, + }); +} diff --git a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart index 431720af..da782d74 100644 --- a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart +++ b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart @@ -2,13 +2,17 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:calendar_view/calendar_view.dart'; import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart'; + part 'events_event.dart'; part 'events_state.dart'; class CalendarEventsBloc extends Bloc { final EventController eventController = EventController(); + final CalendarSystemService calendarService; - CalendarEventsBloc() : super(EventsInitial()) { + CalendarEventsBloc({required this.calendarService}) : super(EventsInitial()) { on(_onLoadEvents); on(_onAddEvent); on(_onStartTimer); @@ -22,53 +26,24 @@ class CalendarEventsBloc extends Bloc { ) async { emit(EventsLoading()); try { - final events = _generateDummyEventsForWeek(event.weekStart); + final response = await calendarService.getCalendarEvents( + spaceId: event.spaceId, + ); + final events = + response.data.map(_toCalendarEventData).toList(); eventController.addAll(events); - emit(EventsLoaded( - events: events, - initialDate: event.weekStart, - weekDays: _getWeekDays(event.weekStart), - )); + emit(EventsLoaded(events: events)); } catch (e) { emit(EventsError('Failed to load events')); } } - List _generateDummyEventsForWeek(DateTime weekStart) { - final events = []; - - for (int i = 0; i < 7; i++) { - final date = weekStart.add(Duration(days: i)); - - events.add(CalendarEventData( - date: date, - startTime: date.copyWith(hour: 9, minute: 0), - endTime: date.copyWith(hour: 10, minute: 30), - title: 'Team Meeting', - description: 'Daily standup', - color: Colors.blue, - )); - events.add(CalendarEventData( - date: date, - startTime: date.copyWith(hour: 14, minute: 0), - endTime: date.copyWith(hour: 15, minute: 0), - title: 'Client Call', - description: 'Project discussion', - color: Colors.green, - )); - } - - return events; - } - void _onAddEvent(AddEvent event, Emitter emit) { eventController.add(event.event); if (state is EventsLoaded) { final loaded = state as EventsLoaded; emit(EventsLoaded( events: [...eventController.events], - initialDate: loaded.initialDate, - weekDays: loaded.weekDays, )); } } @@ -86,47 +61,44 @@ class CalendarEventsBloc extends Bloc { final newWeekDays = _getWeekDays(event.weekDate); emit(EventsLoaded( events: loaded.events, - initialDate: event.weekDate, - weekDays: newWeekDays, )); } } - List _generateDummyEvents() { - final now = DateTime.now(); - return [ - CalendarEventData( - date: now, - startTime: now.copyWith(hour: 8, minute: 00, second: 0), - endTime: now.copyWith(hour: 9, minute: 00, second: 0), - title: 'Team Meeting', - description: 'Weekly team sync', - color: Colors.blue, - ), - CalendarEventData( - date: now, - startTime: now.copyWith(hour: 9, minute: 00, second: 0), - endTime: now.copyWith(hour: 10, minute: 30, second: 0), - title: 'Team Meeting', - description: 'Weekly team sync', - color: Colors.blue, - ), - CalendarEventData( - date: now.add(const Duration(days: 1)), - startTime: now.copyWith(hour: 14, day: now.day + 1), - endTime: now.copyWith(hour: 15, day: now.day + 1), - title: 'Client Call', - description: 'Project discussion', - color: Colors.green, - ), - CalendarEventData( - date: now.add(const Duration(days: 2)), - startTime: now.copyWith(hour: 11, day: now.day + 2), - endTime: now.copyWith(hour: 12, day: now.day + 2), - title: 'Lunch with Team', - color: Colors.orange, - ), - ]; + CalendarEventData _toCalendarEventData(CalendarEventBooking booking) { + final date = booking.date; + + final localDate = date.toLocal(); + + final startParts = booking.startTime.split(':').map(int.parse).toList(); + final endParts = booking.endTime.split(':').map(int.parse).toList(); + + final startTime = DateTime( + localDate.year, + localDate.month, + localDate.day, + startParts[0], + startParts[1], + ); + + final endTime = DateTime( + localDate.year, + localDate.month, + localDate.day, + endParts[0], + endParts[1], + ); + + return CalendarEventData( + date: startTime, + startTime: startTime, + endTime: endTime, + title: + '${booking.space.spaceName} - ${booking.user.firstName} ${booking.user.lastName}', + description: 'Cost: ${booking.cost}', + color: Colors.blue, + event: booking, + ); } List _getWeekDays(DateTime date) { diff --git a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_event.dart b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_event.dart index e23e65de..4f4cafcf 100644 --- a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_event.dart +++ b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_event.dart @@ -6,13 +6,20 @@ abstract class CalendarEventsEvent { } class LoadEvents extends CalendarEventsEvent { + final String spaceId; final DateTime weekStart; - const LoadEvents({required this.weekStart}); + final DateTime weekEnd; + + const LoadEvents({ + required this.spaceId, + required this.weekStart, + required this.weekEnd, + }); } class AddEvent extends CalendarEventsEvent { final CalendarEventData event; - AddEvent(this.event); + const AddEvent(this.event); } class StartTimer extends CalendarEventsEvent {} @@ -23,3 +30,8 @@ class GoToWeek extends CalendarEventsEvent { final DateTime weekDate; GoToWeek(this.weekDate); } + +class CheckWeekHasEvents extends CalendarEventsEvent { + final DateTime weekStart; + const CheckWeekHasEvents(this.weekStart); +} diff --git a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_state.dart b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_state.dart index b7263ec8..bc0c2e31 100644 --- a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_state.dart +++ b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_state.dart @@ -9,13 +9,9 @@ class EventsLoading extends CalendarEventState {} class EventsLoaded extends CalendarEventState { final List events; - final DateTime initialDate; - final List weekDays; EventsLoaded({ required this.events, - required this.initialDate, - required this.weekDays, }); } diff --git a/lib/pages/access_management/booking_system/presentation/view/booking_page.dart b/lib/pages/access_management/booking_system/presentation/view/booking_page.dart index 357cac41..0ff9aaf6 100644 --- a/lib/pages/access_management/booking_system/presentation/view/booking_page.dart +++ b/lib/pages/access_management/booking_system/presentation/view/booking_page.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:intl/intl.dart'; import 'package:calendar_view/calendar_view.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/data/services/remote_calendar_service.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_bloc.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_event.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_state.dart'; @@ -9,7 +10,9 @@ import 'package:syncrow_web/pages/access_management/booking_system/presentation/ import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/booking_sidebar.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/custom_calendar_page.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/week_navigation.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -35,33 +38,20 @@ class _BookingPageState extends State { super.dispose(); } - List _generateDummyEventsForWeek(DateTime weekStart) { - final List events = []; - for (int i = 0; i < 7; i++) { - final date = weekStart.add(Duration(days: i)); - events.add(CalendarEventData( - date: date, - startTime: date.copyWith(hour: 9, minute: 0), - endTime: date.copyWith(hour: 10, minute: 30), - title: 'Team Meeting', - description: 'Daily standup', - color: Colors.blue, - )); - events.add(CalendarEventData( - date: date, - startTime: date.copyWith(hour: 14, minute: 0), - endTime: date.copyWith(hour: 15, minute: 0), - title: 'Client Call', - description: 'Project discussion', - color: Colors.green, - )); - } - return events; - } + void _dispatchLoadEvents(BuildContext context) { + final selectedRoom = + context.read().state.selectedBookableSpace; + final dateState = context.read().state; - void _loadEventsForWeek(DateTime weekStart) { - _eventController.removeWhere((_) => true); - _eventController.addAll(_generateDummyEventsForWeek(weekStart)); + if (selectedRoom != null) { + context.read().add( + LoadEvents( + spaceId: selectedRoom.uuid, + weekStart: dateState.weekStart, + weekEnd: dateState.weekStart.add(const Duration(days: 6)), + ), + ); + } } @override @@ -70,197 +60,181 @@ class _BookingPageState extends State { providers: [ BlocProvider(create: (_) => SelectedBookableSpaceBloc()), BlocProvider(create: (_) => DateSelectionBloc()), + BlocProvider( + create: (_) => CalendarEventsBloc( + calendarService: + FakeRemoteCalendarService(HTTPService(), useDummy: true), + ), + ), ], - child: BlocListener( - listenWhen: (previous, current) => - previous.weekStart != current.weekStart, - listener: (context, state) { - _loadEventsForWeek(state.weekStart); - }, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Container( - decoration: BoxDecoration( - color: ColorsManager.whiteColors, - boxShadow: [ - BoxShadow( - color: ColorsManager.blackColor.withOpacity(0.1), - offset: const Offset(3, 0), - blurRadius: 6, - spreadRadius: 0, - ), - ], - ), - child: Column( - children: [ - Expanded( - flex: 2, - child: BlocBuilder( - builder: (context, state) { - return BookingSidebar( - onRoomSelected: (selectedRoom) { - context - .read() - .add(SelectBookableSpace(selectedRoom)); - }, - ); - }, + child: Builder( + builder: (context) => + BlocListener( + listenWhen: (prev, curr) => curr is EventsLoaded, + listener: (context, state) { + if (state is EventsLoaded) { + _eventController.removeWhere((_) => true); + _eventController.addAll(state.events); + } + }, + child: BlocListener( + listener: (context, state) => _dispatchLoadEvents(context), + child: BlocListener( + listener: (context, state) => _dispatchLoadEvents(context), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + boxShadow: [ + BoxShadow( + color: ColorsManager.blackColor.withOpacity(0.1), + offset: const Offset(3, 0), + blurRadius: 6, + spreadRadius: 0, + ), + ], + ), + child: Column( + children: [ + Expanded( + flex: 2, + child: BlocBuilder( + builder: (context, state) { + return BookingSidebar( + onRoomSelected: (selectedRoom) { + context + .read() + .add(SelectBookableSpace(selectedRoom)); + }, + ); + }, + ), + ), + Expanded( + child: BlocBuilder( + builder: (context, dateState) { + return CustomCalendarPage( + selectedDate: dateState.selectedDate, + onDateChanged: (day, month, year) { + final newDate = DateTime(year, month, day); + context + .read() + .add(SelectDate(newDate)); + context.read().add( + SelectDateFromSidebarCalendar(newDate)); + }, + ); + }, + ), + ), + ], ), ), - Expanded( - child: BlocBuilder( - builder: (context, dateState) { - return CustomCalendarPage( - selectedDate: dateState.selectedDate, - onDateChanged: (day, month, year) { - final newDate = DateTime(year, month, day); - context - .read() - .add(SelectDate(newDate)); - context - .read() - .add(SelectDateFromSidebarCalendar(newDate)); - }, - ); - }, - ), - ), - ], - ), - ), - ), - Expanded( - flex: 4, - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - SvgTextButton( - svgAsset: Assets.homeIcon, - label: 'Manage Bookable Spaces', - onPressed: () {}, - ), - const SizedBox(width: 20), - SvgTextButton( - svgAsset: Assets.groupIcon, - label: 'Manage Users', - onPressed: () {}, - ), - ], - ), - BlocBuilder( - builder: (context, state) { - final weekStart = state.weekStart; - final weekEnd = - weekStart.add(const Duration(days: 6)); - return Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, vertical: 5), - decoration: BoxDecoration( - color: ColorsManager.circleRolesBackground, - borderRadius: BorderRadius.circular(10), - boxShadow: const [ - BoxShadow( - color: ColorsManager.lightGrayColor, - blurRadius: 4, - offset: Offset(0, 1), + ), + Expanded( + flex: 4, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + SvgTextButton( + svgAsset: Assets.homeIcon, + label: 'Manage Bookable Spaces', + onPressed: () {}, + ), + const SizedBox(width: 20), + SvgTextButton( + svgAsset: Assets.groupIcon, + label: 'Manage Users', + onPressed: () {}, ), ], ), - child: Row( - children: [ - IconButton( - iconSize: 15, - icon: const Icon(Icons.arrow_back_ios, - color: ColorsManager.lightGrayColor), - onPressed: () { + BlocBuilder( + builder: (context, state) { + final weekStart = state.weekStart; + final weekEnd = + weekStart.add(const Duration(days: 6)); + return WeekNavigation( + weekStart: weekStart, + weekEnd: weekEnd, + onPreviousWeek: () { context .read() .add(PreviousWeek()); }, - ), - const SizedBox(width: 10), - Text( - _getMonthYearText(weekStart, weekEnd), - style: const TextStyle( - color: ColorsManager.lightGrayColor, - fontSize: 14, - fontWeight: FontWeight.w400, - ), - ), - const SizedBox(width: 10), - IconButton( - iconSize: 15, - icon: const Icon(Icons.arrow_forward_ios, - color: ColorsManager.lightGrayColor), - onPressed: () { + onNextWeek: () { context .read() .add(NextWeek()); }, - ), - ], + ); + }, ), - ); - }, - ), - ], - ), - Expanded( - child: BlocBuilder( - builder: (context, roomState) { - final selectedRoom = roomState.selectedBookableSpace; - return BlocBuilder( - builder: (context, dateState) { - return WeeklyCalendarPage( - startTime: - selectedRoom?.bookableConfig.startTime, - endTime: selectedRoom?.bookableConfig.endTime, - weekStart: dateState.weekStart, - selectedDate: dateState.selectedDate, - eventController: _eventController, - selectedDateFromSideBarCalender: context - .watch() - .state - .selectedDateFromSideBarCalender, - ); - }, - ); - }, + ], + ), + Expanded( + child: BlocBuilder( + builder: (context, roomState) { + final selectedRoom = + roomState.selectedBookableSpace; + return BlocBuilder( + builder: (context, dateState) { + return BlocListener( + listenWhen: (prev, curr) => + curr is EventsLoaded, + listener: (context, state) { + if (state is EventsLoaded) { + _eventController + .removeWhere((_) => true); + _eventController.addAll(state.events); + } + }, + child: WeeklyCalendarPage( + startTime: selectedRoom + ?.bookableConfig.startTime, + endTime: selectedRoom + ?.bookableConfig.endTime, + weekStart: dateState.weekStart, + selectedDate: dateState.selectedDate, + eventController: _eventController, + selectedDateFromSideBarCalender: context + .watch() + .state + .selectedDateFromSideBarCalender, + ), + ); + }, + ); + }, + ), + ), + ], ), ), - ], - ), + ), + ], ), ), - ], + ), ), ), ); } - - String _getMonthYearText(DateTime start, DateTime end) { - final startMonth = DateFormat('MMM').format(start); - final endMonth = DateFormat('MMM').format(end); - final year = start.year == end.year - ? start.year.toString() - : '${start.year}-${end.year}'; - - if (start.month == end.month) { - return '$startMonth $year'; - } else { - return '$startMonth - $endMonth $year'; - } - } } diff --git a/lib/pages/access_management/booking_system/presentation/view/widgets/event_tile_widget.dart b/lib/pages/access_management/booking_system/presentation/view/widgets/event_tile_widget.dart new file mode 100644 index 00000000..6c0f9cb2 --- /dev/null +++ b/lib/pages/access_management/booking_system/presentation/view/widgets/event_tile_widget.dart @@ -0,0 +1,60 @@ +import 'package:calendar_view/calendar_view.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class EventTileWidget extends StatelessWidget { + final List> events; + + const EventTileWidget({ + super.key, + required this.events, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 2, horizontal: 2), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: events.map((event) { + final bool isEventEnded = + event.endTime != null && event.endTime!.isBefore(DateTime.now()); + return Expanded( + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: isEventEnded + ? ColorsManager.lightGrayBorderColor + : ColorsManager.blue1.withOpacity(0.25), + borderRadius: BorderRadius.circular(6), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + DateFormat('h:mm a').format(event.startTime!), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12, + color: Colors.black87, + ), + ), + const SizedBox(height: 2), + Text( + event.title, + style: const TextStyle( + fontSize: 12, + color: ColorsManager.blackColor, + ), + ), + ], + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/lib/pages/access_management/booking_system/presentation/view/widgets/hatched_column_background.dart b/lib/pages/access_management/booking_system/presentation/view/widgets/hatched_column_background.dart new file mode 100644 index 00000000..da74d07f --- /dev/null +++ b/lib/pages/access_management/booking_system/presentation/view/widgets/hatched_column_background.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'dart:math' as math; + +class HatchedColumnBackground extends StatelessWidget { + final Color backgroundColor; + final Color lineColor; + final double opacity; + final double stripeSpacing; + final BorderRadius? borderRadius; + + const HatchedColumnBackground({ + super.key, + required this.backgroundColor, + required this.lineColor, + this.opacity = 0.15, + this.stripeSpacing = 12, + this.borderRadius, + }); + + @override + Widget build(BuildContext context) { + return CustomPaint( + painter: _HatchedBackgroundPainter( + backgroundColor: backgroundColor, + opacity: opacity, + lineColor: lineColor, + stripeSpacing: stripeSpacing, + borderRadius: borderRadius, + ), + size: Size.infinite, + ); + } +} + +class _HatchedBackgroundPainter extends CustomPainter { + final Color backgroundColor; + final double opacity; + final Color lineColor; + final double stripeSpacing; + final BorderRadius? borderRadius; + + _HatchedBackgroundPainter({ + required this.backgroundColor, + required this.opacity, + required this.lineColor, + required this.stripeSpacing, + this.borderRadius, + }); + + @override + void paint(Canvas canvas, Size size) { + final rect = Rect.fromLTWH(0, 0, size.width, size.height); + final RRect rrect = borderRadius?.toRRect(rect) ?? + RRect.fromRectAndRadius(rect, Radius.zero); + final backgroundPaint = Paint() + ..color = backgroundColor.withOpacity(0.02) + ..style = PaintingStyle.fill; + canvas.drawRRect(rrect, backgroundPaint); + canvas.save(); + canvas.clipRRect(rrect); + final linePaint = Paint() + ..color = lineColor + ..strokeWidth = 0.5 + ..style = PaintingStyle.stroke; + final maxExtent = + math.sqrt(size.width * size.width + size.height * size.height); + + canvas.translate(0, size.height); + canvas.rotate(-math.pi / 4); + double y = -maxExtent; + while (y < maxExtent) { + canvas.drawLine( + Offset(-maxExtent, y), + Offset(maxExtent, y), + linePaint, + ); + y += stripeSpacing; + } + + canvas.restore(); + } + + @override + bool shouldRepaint(covariant _HatchedBackgroundPainter oldDelegate) { + return backgroundColor != oldDelegate.backgroundColor || + opacity != oldDelegate.opacity || + lineColor != oldDelegate.lineColor || + stripeSpacing != oldDelegate.stripeSpacing || + borderRadius != oldDelegate.borderRadius; + } +} diff --git a/lib/pages/access_management/booking_system/presentation/view/widgets/time_line_widget.dart b/lib/pages/access_management/booking_system/presentation/view/widgets/time_line_widget.dart new file mode 100644 index 00000000..eada3b97 --- /dev/null +++ b/lib/pages/access_management/booking_system/presentation/view/widgets/time_line_widget.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class TimeLineWidget extends StatelessWidget { + final DateTime date; + + const TimeLineWidget({Key? key, required this.date}) : super(key: key); + + @override + Widget build(BuildContext context) { + int hour = + date.hour == 0 ? 12 : (date.hour > 12 ? date.hour - 12 : date.hour); + String period = date.hour >= 12 ? 'PM' : 'AM'; + return Container( + height: 60, + alignment: Alignment.center, + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: '$hour', + style: const TextStyle( + fontWeight: FontWeight.w700, + fontSize: 24, + color: ColorsManager.blackColor, + ), + ), + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(left: 2, top: 6), + child: Text( + period, + style: const TextStyle( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.blackColor, + letterSpacing: 1, + ), + ), + ), + alignment: PlaceholderAlignment.baseline, + baseline: TextBaseline.alphabetic, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/access_management/booking_system/presentation/view/widgets/week_day_header.dart b/lib/pages/access_management/booking_system/presentation/view/widgets/week_day_header.dart new file mode 100644 index 00000000..57e35c6d --- /dev/null +++ b/lib/pages/access_management/booking_system/presentation/view/widgets/week_day_header.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class WeekDayHeader extends StatelessWidget { + final DateTime date; + final bool isSelectedDay; + + const WeekDayHeader({ + Key? key, + required this.date, + required this.isSelectedDay, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text( + DateFormat('EEE').format(date).toUpperCase(), + style: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 14, + color: isSelectedDay ? Colors.blue : Colors.black, + ), + ), + Text( + DateFormat('d').format(date), + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 20, + color: + isSelectedDay ? ColorsManager.blue1 : ColorsManager.blackColor, + ), + ), + ], + ); + } +} diff --git a/lib/pages/access_management/booking_system/presentation/view/widgets/week_navigation.dart b/lib/pages/access_management/booking_system/presentation/view/widgets/week_navigation.dart new file mode 100644 index 00000000..bdc65b8e --- /dev/null +++ b/lib/pages/access_management/booking_system/presentation/view/widgets/week_navigation.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class WeekNavigation extends StatelessWidget { + final DateTime weekStart; + final DateTime weekEnd; + final VoidCallback onPreviousWeek; + final VoidCallback onNextWeek; + + const WeekNavigation({ + Key? key, + required this.weekStart, + required this.weekEnd, + required this.onPreviousWeek, + required this.onNextWeek, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: ColorsManager.circleRolesBackground, + borderRadius: BorderRadius.circular(10), + boxShadow: const [ + BoxShadow( + color: ColorsManager.lightGrayColor, + blurRadius: 4, + offset: Offset(0, 1), + ), + ], + ), + child: Row( + children: [ + IconButton( + iconSize: 15, + icon: const Icon(Icons.arrow_back_ios, + color: ColorsManager.lightGrayColor), + onPressed: onPreviousWeek, + ), + const SizedBox(width: 10), + Text( + _getMonthYearText(weekStart, weekEnd), + style: const TextStyle( + color: ColorsManager.lightGrayColor, + fontSize: 14, + fontWeight: FontWeight.w400, + ), + ), + const SizedBox(width: 10), + IconButton( + iconSize: 15, + icon: const Icon(Icons.arrow_forward_ios, + color: ColorsManager.lightGrayColor), + onPressed: onNextWeek, + ), + ], + ), + ); + } + + String _getMonthYearText(DateTime start, DateTime end) { + final startMonth = DateFormat('MMM').format(start); + final endMonth = DateFormat('MMM').format(end); + final year = start.year == end.year + ? start.year.toString() + : '${start.year}-${end.year}'; + + if (start.month == end.month) { + return '$startMonth $year'; + } else { + return '$startMonth - $endMonth $year'; + } + } +} diff --git a/lib/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart b/lib/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart index 5c38e2fc..0dd343a7 100644 --- a/lib/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart +++ b/lib/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:calendar_view/calendar_view.dart'; -import 'package:intl/intl.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/event_tile_widget.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/hatched_column_background.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/time_line_widget.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/week_day_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class WeeklyCalendarPage extends StatelessWidget { @@ -60,18 +63,39 @@ class WeeklyCalendarPage extends StatelessWidget { const double timeLineWidth = 80; const int totalDays = 7; - + final DateTime highlightStart = DateTime(2025, 7, 10); + final DateTime highlightEnd = DateTime(2025, 7, 19); return LayoutBuilder( builder: (context, constraints) { final double calendarWidth = constraints.maxWidth; final double dayColumnWidth = (calendarWidth - timeLineWidth) / totalDays - 0.1; + bool isInRange(DateTime date, DateTime start, DateTime end) { + return !date.isBefore(start) && !date.isAfter(end); + } return Padding( padding: const EdgeInsets.only(left: 25.0, right: 25.0, top: 25), child: Stack( children: [ WeekView( + weekDetectorBuilder: ({ + required date, + required height, + required heightPerMinute, + required minuteSlotSize, + required width, + }) { + return isInRange(date, highlightStart, highlightEnd) + ? HatchedColumnBackground( + backgroundColor: ColorsManager.grey800, + lineColor: ColorsManager.textGray, + opacity: 0.3, + stripeSpacing: 12, + borderRadius: BorderRadius.circular(8), + ) + : const SizedBox(); + }, pageViewPhysics: const NeverScrollableScrollPhysics(), key: ValueKey(weekStart), controller: eventController, @@ -88,70 +112,13 @@ class WeeklyCalendarPage extends StatelessWidget { height: 0, ), weekDayBuilder: (date) { - final index = weekDays.indexWhere((d) => isSameDay(d, date)); - final isSelectedDay = index == selectedDayIndex; - return Column( - children: [ - Text( - DateFormat('EEE').format(date).toUpperCase(), - style: TextStyle( - fontWeight: FontWeight.w400, - fontSize: 14, - color: isSelectedDay ? Colors.blue : Colors.black, - ), - ), - Text( - DateFormat('d').format(date), - style: TextStyle( - fontWeight: FontWeight.w700, - fontSize: 20, - color: isSelectedDay - ? ColorsManager.blue1 - : ColorsManager.blackColor, - ), - ), - ], + return WeekDayHeader( + date: date, + isSelectedDay: isSameDay(date, selectedDate), ); }, timeLineBuilder: (date) { - int hour = date.hour == 0 - ? 12 - : (date.hour > 12 ? date.hour - 12 : date.hour); - String period = date.hour >= 12 ? 'PM' : 'AM'; - return Container( - height: 60, - alignment: Alignment.center, - child: RichText( - text: TextSpan( - children: [ - TextSpan( - text: '$hour', - style: const TextStyle( - fontWeight: FontWeight.w700, - fontSize: 24, - color: ColorsManager.blackColor, - ), - ), - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(left: 2, top: 6), - child: Text( - period, - style: const TextStyle( - fontWeight: FontWeight.w400, - fontSize: 12, - color: ColorsManager.blackColor, - letterSpacing: 1, - ), - ), - ), - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic, - ), - ], - ), - ), - ); + return TimeLineWidget(date: date); }, timeLineWidth: timeLineWidth, weekPageHeaderBuilder: (start, end) => Container(), @@ -174,49 +141,8 @@ class WeeklyCalendarPage extends StatelessWidget { ), ), eventTileBuilder: (date, events, boundary, start, end) { - return Container( - margin: - const EdgeInsets.symmetric(vertical: 2, horizontal: 2), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: events.map((event) { - final bool isEventEnded = event.endTime != null && - event.endTime!.isBefore(DateTime.now()); - return Expanded( - child: Container( - width: double.infinity, - padding: const EdgeInsets.all(6), - decoration: BoxDecoration( - color: isEventEnded - ? ColorsManager.lightGrayBorderColor - : ColorsManager.blue1.withOpacity(0.25), - borderRadius: BorderRadius.circular(6), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - DateFormat('h:mm a').format(event.startTime!), - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 12, - color: Colors.black87, - ), - ), - const SizedBox(height: 2), - Text( - event.title, - style: const TextStyle( - fontSize: 12, - color: ColorsManager.blackColor, - ), - ), - ], - ), - ), - ); - }).toList(), - ), + return EventTileWidget( + events: events, ); }, ), diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index a36d1193..55bfef1d 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -69,7 +69,6 @@ abstract class ColorsManager { static const Color invitedOrange = Color(0xFFFFE193); static const Color invitedOrangeText = Color(0xFFFFBF00); static const Color lightGrayBorderColor = Color(0xB2D5D5D5); - //background: #F8F8F8; static const Color vividBlue = Color(0xFF023DFE); static const Color semiTransparentRed = Color(0x99FF0000); static const Color grey700 = Color(0xFF2D3748); @@ -85,4 +84,5 @@ abstract class ColorsManager { static const Color minBlueDot = Color(0xFF023DFE); static const Color grey25 = Color(0xFFF9F9F9); static const Color grey50 = Color(0xFF718096); + static const Color grey800 = Color(0xffF8F8F8); } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index f908db85..8797f0cd 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -141,4 +141,5 @@ abstract class ApiEndpoints { static const String saveSchedule = '/schedule/{deviceUuid}'; static const String getBookableSpaces = '/bookable-spaces'; + static const String getCalendarEvents = '/api'; } From e0980b324c778815825fd7a9790b173085bef733 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 10:54:07 +0300 Subject: [PATCH 18/62] Add DeleteSpaceParam and DeleteSpaceService for space deletion functionality --- .../delete_space/domain/params/delete_space_param.dart | 9 +++++++++ .../domain/services/delete_space_service.dart | 5 +++++ 2 files changed, 14 insertions(+) create mode 100644 lib/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart create mode 100644 lib/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart diff --git a/lib/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart b/lib/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart new file mode 100644 index 00000000..d6781876 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart @@ -0,0 +1,9 @@ +class DeleteSpaceParam { + const DeleteSpaceParam({ + required this.spaceUuid, + required this.communityUuid, + }); + + final String spaceUuid; + final String communityUuid; +} diff --git a/lib/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart b/lib/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart new file mode 100644 index 00000000..a537645c --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart @@ -0,0 +1,5 @@ +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart'; + +abstract interface class DeleteSpaceService { + Future delete(DeleteSpaceParam param); +} From 3cd01253109e25219155b261223006210dfae82a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 10:54:13 +0300 Subject: [PATCH 19/62] Refactor space deletion: Introduce DeleteSpaceParam and DeleteSpaceService for enhanced space management functionality --- .../data/remote_delete_space_service.dart | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart diff --git a/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart b/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart new file mode 100644 index 00000000..48d65934 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart @@ -0,0 +1,63 @@ +import 'package:dio/dio.dart'; +import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart'; +import 'package:syncrow_web/services/api/api_exception.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; + +final class RemoteDeleteSpaceService implements DeleteSpaceService { + RemoteDeleteSpaceService({ + required this.httpService, + }); + + final HTTPService httpService; + + @override + Future delete(DeleteSpaceParam param) async { + try { + await httpService.delete( + path: await _makeUrl(param), + expectedResponseModel: (json) { + final response = json as Map; + final data = response['data'] as Map?; + final hasSuccessfullyDeletedSpace = data?['success'] as bool? ?? false; + + if (!hasSuccessfullyDeletedSpace) { + throw APIException('Failed to delete space'); + } + + return hasSuccessfullyDeletedSpace; + }, + ); + } on DioException catch (e) { + final message = e.response?.data as Map?; + throw APIException(_getErrorMessageFromBody(message)); + } catch (e) { + final formattedErrorMessage = ['Failed to delete space', '$e'].join(': '); + throw APIException(formattedErrorMessage); + } + } + + String _getErrorMessageFromBody(Map? body) { + if (body == null) return 'Failed to delete space'; + final error = body['error'] as Map?; + final errorMessage = error?['message'] as String? ?? ''; + return errorMessage; + } + + Future _makeUrl(DeleteSpaceParam param) async { + final projectUuid = await ProjectManager.getProjectUUID(); + if (projectUuid == null) { + throw APIException('Project UUID is not set'); + } + + if (param.communityUuid.isEmpty) { + throw APIException('Community UUID is not set'); + } + + if (param.spaceUuid.isEmpty) { + throw APIException('Space UUID is not set'); + } + return '/projects/$projectUuid/communities/${param.communityUuid}/spaces/${param.spaceUuid}'; + } +} From 5663e2084eb752b17229fa1701d56c0f26518d7d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 10:57:34 +0300 Subject: [PATCH 20/62] Created `DeleteSpaceBloc`. --- .../presentation/bloc/delete_space_bloc.dart | 31 +++++++++++++++++++ .../presentation/bloc/delete_space_event.dart | 17 ++++++++++ .../presentation/bloc/delete_space_state.dart | 30 ++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart create mode 100644 lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_event.dart create mode 100644 lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_state.dart diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart new file mode 100644 index 00000000..f515fc09 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart @@ -0,0 +1,31 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart'; +import 'package:syncrow_web/services/api/api_exception.dart'; + +part 'delete_space_event.dart'; +part 'delete_space_state.dart'; + +class DeleteSpaceBloc extends Bloc { + DeleteSpaceBloc(this._deleteSpaceService) : super(DeleteSpaceInitial()) { + on(_onDeleteSpace); + } + + final DeleteSpaceService _deleteSpaceService; + + Future _onDeleteSpace( + DeleteSpace event, + Emitter emit + ) async { + emit(DeleteSpaceLoading()); + try { + await _deleteSpaceService.delete(event.param); + emit(const DeleteSpaceSuccess('Space deleted successfully')); + } on APIException catch (e) { + emit(DeleteSpaceFailure(e.message)); + } catch (e) { + emit(const DeleteSpaceFailure('Failed to delete space')); + } + } +} diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_event.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_event.dart new file mode 100644 index 00000000..c80346e8 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_event.dart @@ -0,0 +1,17 @@ +part of 'delete_space_bloc.dart'; + +sealed class DeleteSpaceEvent extends Equatable { + const DeleteSpaceEvent(); + + @override + List get props => []; +} + +final class DeleteSpace extends DeleteSpaceEvent { + const DeleteSpace(this.param); + + final DeleteSpaceParam param; + + @override + List get props => [param]; +} diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_state.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_state.dart new file mode 100644 index 00000000..96b6d5b7 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_state.dart @@ -0,0 +1,30 @@ +part of 'delete_space_bloc.dart'; + +sealed class DeleteSpaceState extends Equatable { + const DeleteSpaceState(); + + @override + List get props => []; +} + +final class DeleteSpaceInitial extends DeleteSpaceState {} + +final class DeleteSpaceLoading extends DeleteSpaceState {} + +final class DeleteSpaceSuccess extends DeleteSpaceState { + const DeleteSpaceSuccess(this.successMessage); + + final String successMessage; + + @override + List get props => [successMessage]; +} + +final class DeleteSpaceFailure extends DeleteSpaceState { + const DeleteSpaceFailure(this.errorMessage); + + final String errorMessage; + + @override + List get props => [errorMessage]; +} From c112cde63490aa5ca9a90d03a1444859270d3b46 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 11:04:29 +0300 Subject: [PATCH 21/62] Uses Inkwell instead of Gesture Detector for canvas widgets. --- .../main_module/widgets/create_space_button.dart | 2 +- .../space_management_v2/main_module/widgets/space_cell.dart | 2 +- 2 files changed, 2 insertions(+), 2 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 90d359e2..e6dfbb15 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 @@ -22,7 +22,7 @@ class _CreateSpaceButtonState extends State { return Tooltip( margin: const EdgeInsets.symmetric(vertical: 24), message: 'Create a new space', - child: GestureDetector( + child: InkWell( onTap: () => SpaceDetailsDialogHelper.showCreate( context, communityUuid: widget.communityUuid, diff --git a/lib/pages/space_management_v2/main_module/widgets/space_cell.dart b/lib/pages/space_management_v2/main_module/widgets/space_cell.dart index bcde6560..80b18526 100644 --- a/lib/pages/space_management_v2/main_module/widgets/space_cell.dart +++ b/lib/pages/space_management_v2/main_module/widgets/space_cell.dart @@ -17,7 +17,7 @@ class SpaceCell extends StatelessWidget { @override Widget build(BuildContext context) { - return GestureDetector( + return InkWell( onTap: onTap, child: Container( width: 150, From cf1b34ee0ac4e5d1d552f9c135bbd282d16a4d34 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 12:17:07 +0300 Subject: [PATCH 22/62] Add x_delete icon asset. --- assets/icons/x_delete.svg | 5 +++++ lib/utils/constants/assets.dart | 1 + 2 files changed, 6 insertions(+) create mode 100644 assets/icons/x_delete.svg diff --git a/assets/icons/x_delete.svg b/assets/icons/x_delete.svg new file mode 100644 index 00000000..637f2e72 --- /dev/null +++ b/assets/icons/x_delete.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index f92975f3..b7ad15b5 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -517,4 +517,5 @@ class Assets { static const String emptyRangeOfAqi = 'assets/icons/empty_range_of_aqi.svg'; static const String homeIcon = 'assets/icons/home_icon.svg'; static const String groupIcon = 'assets/icons/group_icon.svg'; + static const String xDelete = 'assets/icons/x_delete.svg'; } From 035c03c6b2b0239844178b55e6b7420fa6ab5ae4 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 14:17:01 +0300 Subject: [PATCH 23/62] Fix error handling in DeleteSpaceBloc: update failure message to include exception details --- .../delete_space/presentation/bloc/delete_space_bloc.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart index f515fc09..6334bb33 100644 --- a/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart @@ -16,7 +16,7 @@ class DeleteSpaceBloc extends Bloc { Future _onDeleteSpace( DeleteSpace event, - Emitter emit + Emitter emit, ) async { emit(DeleteSpaceLoading()); try { @@ -25,7 +25,7 @@ class DeleteSpaceBloc extends Bloc { } on APIException catch (e) { emit(DeleteSpaceFailure(e.message)); } catch (e) { - emit(const DeleteSpaceFailure('Failed to delete space')); + emit(DeleteSpaceFailure(e.toString())); } } } From 086f3cedf824f77af40eef6f828984cdcb6fd622 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 14:30:17 +0300 Subject: [PATCH 24/62] Refactor RemoteDeleteSpaceService: Simplify success response handling and improve error message formatting --- .../delete_space/data/remote_delete_space_service.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart b/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart index 48d65934..74724644 100644 --- a/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart +++ b/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart @@ -19,8 +19,7 @@ final class RemoteDeleteSpaceService implements DeleteSpaceService { path: await _makeUrl(param), expectedResponseModel: (json) { final response = json as Map; - final data = response['data'] as Map?; - final hasSuccessfullyDeletedSpace = data?['success'] as bool? ?? false; + final hasSuccessfullyDeletedSpace = response['success'] as bool? ?? false; if (!hasSuccessfullyDeletedSpace) { throw APIException('Failed to delete space'); @@ -33,8 +32,7 @@ final class RemoteDeleteSpaceService implements DeleteSpaceService { final message = e.response?.data as Map?; throw APIException(_getErrorMessageFromBody(message)); } catch (e) { - final formattedErrorMessage = ['Failed to delete space', '$e'].join(': '); - throw APIException(formattedErrorMessage); + throw APIException(e.toString()); } } From f4b5c6fb52917d4fabf1ee7d3483c2c72ea09e42 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 14:44:08 +0300 Subject: [PATCH 25/62] Implement delete space functionality in CommunityStructureHeader: Integrate DeleteSpaceDialog for space deletion confirmation and update routing for space management page. --- .../widgets/community_structure_header.dart | 15 ++- .../widgets/delete_space_dialog.dart | 118 ++++++++++++++++++ .../widgets/delete_space_dialog_form.dart | 106 ++++++++++++++++ 3 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart create mode 100644 lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.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..b8380bb7 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 @@ -4,6 +4,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.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/delete_space/presentation/widgets/delete_space_dialog.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'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -91,7 +92,19 @@ class CommunityStructureHeader extends StatelessWidget { ), const SizedBox(width: 8), CommunityStructureHeaderActionButtons( - onDelete: (space) {}, + onDelete: (space) => showDialog( + context: context, + barrierDismissible: false, + builder: (_) => DeleteSpaceDialog( + space: space, + community: selectedCommunity, + onSuccess: () { + context.read().add( + SelectCommunityEvent(community: selectedCommunity), + ); + }, + ), + ), onDuplicate: (space) {}, onEdit: (space) { SpaceDetailsDialogHelper.showEdit( diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart new file mode 100644 index 00000000..c8356e32 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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/delete_space/data/remote_delete_space_service.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class DeleteSpaceDialog extends StatelessWidget { + const DeleteSpaceDialog({ + required this.space, + required this.community, + required this.onSuccess, + super.key, + }); + + final SpaceModel space; + final CommunityModel community; + final void Function() onSuccess; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => DeleteSpaceBloc( + RemoteDeleteSpaceService(httpService: HTTPService()), + ), + child: Builder( + builder: (context) => Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Container( + padding: const EdgeInsetsDirectional.all(32), + constraints: BoxConstraints( + maxWidth: context.screenWidth * 0.2, + ), + child: BlocConsumer( + listener: (context, state) { + if (state case DeleteSpaceSuccess()) onSuccess(); + }, + builder: (context, state) => switch (state) { + DeleteSpaceInitial() => DeleteSpaceDialogForm( + space: space, + communityUuid: community.uuid, + ), + DeleteSpaceLoading() => const DeleteSpaceLoadingWidget(), + DeleteSpaceSuccess(:final successMessage) => DeleteSpaceStatusWidget( + message: successMessage, + icon: const Icon( + Icons.check_circle, + size: 92, + color: ColorsManager.goodGreen, + ), + ), + DeleteSpaceFailure() => DeleteSpaceStatusWidget( + message: state.errorMessage, + icon: const Icon( + Icons.error, + size: 92, + color: ColorsManager.red, + ), + ), + }, + ), + ), + ), + ), + ); + } +} + +class DeleteSpaceLoadingWidget extends StatelessWidget { + const DeleteSpaceLoadingWidget({super.key}); + + @override + Widget build(BuildContext context) { + return const SizedBox.square( + dimension: 32, + child: Center(child: CircularProgressIndicator()), + ); + } +} + +class DeleteSpaceStatusWidget extends StatelessWidget { + const DeleteSpaceStatusWidget({ + required this.message, + required this.icon, + super.key, + }); + + final String message; + final Widget icon; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + spacing: 16, + children: [ + icon, + SelectableText( + message, + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.blackColor, + fontSize: 22, + ), + textAlign: TextAlign.center, + ), + FilledButton( + onPressed: Navigator.of(context).pop, + child: const Text('Close'), + ), + ], + ); + } +} diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.dart new file mode 100644 index 00000000..055b67b8 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.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/delete_space/domain/params/delete_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class DeleteSpaceDialogForm extends StatelessWidget { + const DeleteSpaceDialogForm({ + required this.space, + required this.communityUuid, + super.key, + }); + + final SpaceModel space; + final String communityUuid; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset(Assets.xDelete, width: 36, height: 36), + const SizedBox(height: 16), + SelectableText( + 'Delete Space', + textAlign: TextAlign.center, + style: context.textTheme.titleLarge?.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 24, + ), + ), + const SizedBox(height: 8), + SelectableText( + 'Are you sure you want to delete this space? This action is irreversible', + textAlign: TextAlign.center, + style: context.textTheme.bodyLarge?.copyWith( + color: ColorsManager.lightGreyColor, + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ), + const SizedBox(height: 24), + Row( + children: [ + Expanded( + child: FilledButton( + style: _buildButtonStyle( + context, + color: ColorsManager.grey25, + textColor: ColorsManager.blackColor, + ), + onPressed: Navigator.of(context).pop, + child: const Text('Cancel'), + ), + ), + const SizedBox(width: 16), + Expanded( + child: FilledButton( + style: _buildButtonStyle( + context, + color: ColorsManager.semiTransparentRed, + textColor: ColorsManager.whiteColors, + ), + onPressed: () { + context.read().add( + DeleteSpace( + DeleteSpaceParam( + spaceUuid: space.uuid, + communityUuid: communityUuid, + ), + ), + ); + }, + child: const Text('Delete'), + ), + ), + ], + ), + ], + ); + } + + ButtonStyle _buildButtonStyle( + BuildContext context, { + required Color color, + required Color textColor, + }) { + return FilledButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12), + backgroundColor: color, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + foregroundColor: textColor, + textStyle: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ); + } +} From beb33e37fa10b72cc546a3343db9f8cd3190dafe Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 14:59:34 +0300 Subject: [PATCH 26/62] Add SpacesRecursiveHelper for recursive space updates and deletions; refactor CommunityStructureHeader to use CommunityStructureHeaderActionButtonsComposer for improved action handling. --- .../helpers/spaces_recursive_helper.dart | 41 +++++++++++ .../widgets/community_structure_header.dart | 73 ++----------------- ...ucture_header_action_buttons_composer.dart | 69 ++++++++++++++++++ 3 files changed, 118 insertions(+), 65 deletions(-) create mode 100644 lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart create mode 100644 lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons_composer.dart diff --git a/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart b/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart new file mode 100644 index 00000000..e751e8d8 --- /dev/null +++ b/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart @@ -0,0 +1,41 @@ +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/domain/models/space_details_model.dart'; + +abstract final class SpacesRecursiveHelper { + static List recusrivelyUpdate( + List spaces, + SpaceDetailsModel updatedSpace, + ) { + return spaces.map((space) { + if (space.uuid == updatedSpace.uuid) { + return space.copyWith( + spaceName: updatedSpace.spaceName, + icon: updatedSpace.icon, + ); + } + if (space.children.isNotEmpty) { + return space.copyWith( + children: recusrivelyUpdate(space.children, updatedSpace), + ); + } + return space; + }).toList(); + } + + static List recusrivelyDelete( + List spaces, + String spaceUuid, + ) { + final s = spaces.map((space) { + if (space.uuid == spaceUuid) return null; + if (space.children.isNotEmpty) { + return space.copyWith( + children: recusrivelyDelete(space.children, spaceUuid), + ); + } + return space; + }).toList(); + + return s.whereType().toList(); + } +} 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 de148ecd..432b3ce4 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 @@ -2,42 +2,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart'; -import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.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/main_module/widgets/community_structure_header_action_buttons_composer.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/delete_space/presentation/widgets/delete_space_dialog.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/helpers/space_details_dialog_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class CommunityStructureHeader extends StatelessWidget { const CommunityStructureHeader({super.key}); - List _updateRecursive( - List spaces, - SpaceDetailsModel updatedSpace, - ) { - return spaces.map((space) { - if (space.uuid == updatedSpace.uuid) { - return space.copyWith( - spaceName: updatedSpace.spaceName, - icon: updatedSpace.icon, - ); - } - if (space.children.isNotEmpty) { - return space.copyWith( - children: _updateRecursive(space.children, updatedSpace), - ); - } - return space; - }).toList(); - } - @override Widget build(BuildContext context) { - final theme = Theme.of(context); final screenWidth = MediaQuery.of(context).size.width; return Container( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), @@ -58,7 +33,7 @@ class CommunityStructureHeader extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: _buildCommunityInfo(context, theme, screenWidth), + child: _buildCommunityInfo(context, screenWidth), ), const SizedBox(width: 16), ], @@ -68,8 +43,7 @@ class CommunityStructureHeader extends StatelessWidget { ); } - Widget _buildCommunityInfo( - BuildContext context, ThemeData theme, double screenWidth) { + Widget _buildCommunityInfo(BuildContext context, double screenWidth) { final selectedCommunity = context.watch().state.selectedCommunity; final selectedSpace = @@ -79,7 +53,7 @@ class CommunityStructureHeader extends StatelessWidget { children: [ Text( 'Community Structure', - style: theme.textTheme.headlineLarge?.copyWith( + style: context.textTheme.headlineLarge?.copyWith( color: ColorsManager.blackColor, ), ), @@ -92,7 +66,7 @@ class CommunityStructureHeader extends StatelessWidget { Flexible( child: SelectableText( selectedCommunity.name, - style: theme.textTheme.bodyLarge?.copyWith( + style: context.textTheme.bodyLarge?.copyWith( color: ColorsManager.blackColor, ), maxLines: 1, @@ -116,39 +90,8 @@ class CommunityStructureHeader extends StatelessWidget { ), ), const SizedBox(width: 8), - CommunityStructureHeaderActionButtons( - onDelete: (space) => showDialog( - context: context, - barrierDismissible: false, - builder: (_) => DeleteSpaceDialog( - space: space, - community: selectedCommunity, - onSuccess: () { - context.read().add( - SelectCommunityEvent(community: selectedCommunity), - ); - }, - ), - ), - onDuplicate: (space) {}, - onEdit: (space) => SpaceDetailsDialogHelper.showEdit( - context, - spaceModel: selectedSpace!, - communityUuid: selectedCommunity.uuid, - onSuccess: (updatedSpaceDetails) { - final communitiesBloc = context.read(); - final updatedSpaces = _updateRecursive( - selectedCommunity.spaces, - updatedSpaceDetails, - ); - - final community = selectedCommunity.copyWith( - spaces: updatedSpaces, - ); - - communitiesBloc.add(CommunitiesUpdateCommunity(community)); - }, - ), + CommunityStructureHeaderActionButtonsComposer( + selectedCommunity: selectedCommunity, selectedSpace: selectedSpace, ), ], diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons_composer.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons_composer.dart new file mode 100644 index 00000000..d7403588 --- /dev/null +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons_composer.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart'; +import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.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/delete_space/presentation/widgets/delete_space_dialog.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart'; + +class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget { + const CommunityStructureHeaderActionButtonsComposer({ + required this.selectedCommunity, + required this.selectedSpace, + super.key, + }); + final CommunityModel selectedCommunity; + final SpaceModel? selectedSpace; + + @override + Widget build(BuildContext context) { + return CommunityStructureHeaderActionButtons( + onDelete: (space) => showDialog( + context: context, + barrierDismissible: false, + builder: (_) => DeleteSpaceDialog( + space: space, + community: selectedCommunity, + onSuccess: () { + final updatedSpaces = SpacesRecursiveHelper.recusrivelyDelete( + selectedCommunity.spaces, + space.uuid, + ); + final community = selectedCommunity.copyWith( + spaces: updatedSpaces, + ); + context.read().add( + CommunitiesUpdateCommunity(community), + ); + context.read().add( + SelectCommunityEvent(community: selectedCommunity), + ); + }, + ), + ), + onDuplicate: (space) {}, + onEdit: (space) => SpaceDetailsDialogHelper.showEdit( + context, + spaceModel: selectedSpace!, + communityUuid: selectedCommunity.uuid, + onSuccess: (updatedSpaceDetails) { + final communitiesBloc = context.read(); + final updatedSpaces = SpacesRecursiveHelper.recusrivelyUpdate( + selectedCommunity.spaces, + updatedSpaceDetails, + ); + + final community = selectedCommunity.copyWith( + spaces: updatedSpaces, + ); + + communitiesBloc.add(CommunitiesUpdateCommunity(community)); + }, + ), + selectedSpace: selectedSpace, + ); + } +} From 81679266209c8a325f97e92d34d3916c12160a99 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 14 Jul 2025 15:14:56 +0300 Subject: [PATCH 27/62] Enhance UI components: update color management, adjust button styles, and improve text formatting for better readability --- .../schedule_widgets/count_down_button.dart | 10 ++- .../count_down_inching_view.dart | 4 +- .../schedule_widgets/schedual_view.dart | 10 +-- .../schedule_widgets/schedule_header.dart | 6 +- .../schedule_managment_ui.dart | 2 +- .../schedule_mode_buttons.dart | 6 +- .../schedule_mode_selector.dart | 3 +- .../helper/add_schedule_dialog_helper.dart | 63 ++++++++----------- lib/utils/color_manager.dart | 1 + 9 files changed, 55 insertions(+), 50 deletions(-) diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/count_down_button.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/count_down_button.dart index b28a6a23..8fa1e290 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/count_down_button.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/count_down_button.dart @@ -29,7 +29,9 @@ class CountdownModeButtons extends StatelessWidget { children: [ Expanded( child: DefaultButton( + elevation: 2.5, height: 40, + borderRadius: 8, onPressed: () => Navigator.pop(context), backgroundColor: ColorsManager.boxColor, child: Text('Cancel', style: context.textTheme.bodyMedium), @@ -39,6 +41,8 @@ class CountdownModeButtons extends StatelessWidget { Expanded( child: isActive ? DefaultButton( + elevation: 2.5, + borderRadius: 8, height: 40, onPressed: () { context.read().add( @@ -49,10 +53,12 @@ class CountdownModeButtons extends StatelessWidget { ), ); }, - backgroundColor: Colors.red, + backgroundColor: ColorsManager.red100, child: const Text('Stop'), ) : DefaultButton( + elevation: 2.5, + borderRadius: 8, height: 40, onPressed: () { context.read().add( @@ -63,7 +69,7 @@ class CountdownModeButtons extends StatelessWidget { countDownCode: countDownCode), ); }, - backgroundColor: ColorsManager.primaryColor, + backgroundColor: ColorsManager.primaryColorWithOpacity, child: const Text('Save'), ), ), diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/count_down_inching_view.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/count_down_inching_view.dart index e64b7cf7..c6a35bb6 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/count_down_inching_view.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/count_down_inching_view.dart @@ -226,6 +226,7 @@ class _CountdownInchingViewState extends State { index.toString().padLeft(2, '0'), style: TextStyle( fontSize: 24, + fontWeight: FontWeight.w400, color: isActive ? ColorsManager.grayColor : Colors.black, ), ), @@ -240,7 +241,8 @@ class _CountdownInchingViewState extends State { label, style: const TextStyle( color: ColorsManager.grayColor, - fontSize: 18, + fontSize: 24, + fontWeight: FontWeight.w400, ), ), ], diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart index b654698d..d5194f35 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart @@ -31,12 +31,11 @@ class BuildScheduleView extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (_) => ScheduleBloc( - deviceId: deviceUuid, - ) + create: (_) => ScheduleBloc(deviceId: deviceUuid,) ..add(ScheduleGetEvent(category: category)) ..add(ScheduleFetchStatusEvent( - deviceId: deviceUuid, countdownCode: countdownCode ?? '')), + deviceId: deviceUuid, + countdownCode: countdownCode ?? '')), child: Dialog( backgroundColor: Colors.white, insetPadding: const EdgeInsets.all(20), @@ -77,7 +76,8 @@ class BuildScheduleView extends StatelessWidget { category: category, time: '', function: Status( - code: code.toString(), value: null), + code: code.toString(), + value: true), days: [], ), isEdit: false, diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_header.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_header.dart index 87afe430..06f785eb 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_header.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_header.dart @@ -13,9 +13,9 @@ class ScheduleHeader extends StatelessWidget { Text( 'Scheduling', style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 22, - color: ColorsManager.dialogBlueTitle, + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.w700, + fontSize: 30, ), ), Container( diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_managment_ui.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_managment_ui.dart index 1a89c1ee..39899fe5 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_managment_ui.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_managment_ui.dart @@ -27,7 +27,7 @@ class ScheduleManagementUI extends StatelessWidget { width: 170, height: 40, child: DefaultButton( - borderColor: ColorsManager.boxColor, + borderColor: ColorsManager.grayColor.withOpacity(0.5), padding: 2, backgroundColor: ColorsManager.graysColor, borderRadius: 15, diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart index f1307d5f..f1df1f20 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart @@ -19,6 +19,8 @@ class ScheduleModeButtons extends StatelessWidget { children: [ Expanded( child: DefaultButton( + elevation: 2.5, + borderRadius: 8, height: 40, onPressed: () { Navigator.pop(context); @@ -33,9 +35,11 @@ class ScheduleModeButtons extends StatelessWidget { const SizedBox(width: 20), Expanded( child: DefaultButton( + elevation: 2.5, + borderRadius: 8, height: 40, onPressed: onSave, - backgroundColor: ColorsManager.primaryColor, + backgroundColor: ColorsManager.primaryColorWithOpacity, child: const Text('Save'), ), ), diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_selector.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_selector.dart index 200d8c66..3b2f6502 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_selector.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_selector.dart @@ -35,12 +35,12 @@ class ScheduleModeSelector extends StatelessWidget { ), const SizedBox(height: 4), Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildRadioTile( context, 'Countdown', ScheduleModes.countdown, currentMode), _buildRadioTile( context, 'Schedule', ScheduleModes.schedule, currentMode), + const Spacer(flex: 1), // _buildRadioTile( // context, 'Circulate', ScheduleModes.circulate, currentMode), // _buildRadioTile( @@ -65,6 +65,7 @@ class ScheduleModeSelector extends StatelessWidget { style: context.textTheme.bodySmall!.copyWith( fontSize: 13, color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, ), ), leading: Radio( diff --git a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart index 0a65595e..e5695b9e 100644 --- a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart +++ b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; class ScheduleDialogHelper { static const List allDays = [ @@ -56,8 +58,9 @@ class ScheduleDialogHelper { Text( isEdit ? 'Edit Schedule' : 'Add Schedule', style: Theme.of(context).textTheme.titleLarge!.copyWith( - color: Colors.blue, - fontWeight: FontWeight.bold, + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.w700, + fontSize: 30, ), ), const SizedBox(), @@ -69,9 +72,9 @@ class ScheduleDialogHelper { height: 40, child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: Colors.grey[200], + backgroundColor: ColorsManager.boxColor, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15), + borderRadius: BorderRadius.circular(8), ), ), onPressed: () async { @@ -110,39 +113,27 @@ class ScheduleDialogHelper { ], ), actions: [ - SizedBox( - width: 100, - child: OutlinedButton( - onPressed: () { - Navigator.pop(ctx, null); - }, - child: const Text('Cancel'), - ), + ScheduleModeButtons( + onSave: () { + dynamic temp; + if (deviceType == 'CUR_2') { + temp = functionOn! ? 'open' : 'close'; + } else { + temp = functionOn; + } + final entry = ScheduleEntry( + category: schedule?.category ?? 'switch_1', + time: _formatTimeOfDayToISO(selectedTime), + function: Status( + code: code ?? 'switch_1', + value: temp, + ), + days: _convertSelectedDaysToStrings(selectedDays), + scheduleId: schedule.scheduleId, + ); + Navigator.pop(ctx, entry); + }, ), - SizedBox( - width: 100, - child: ElevatedButton( - onPressed: () { - dynamic temp; - if (deviceType == 'CUR_2') { - temp = functionOn! ? 'open' : 'close'; - } else { - temp = functionOn; - } - final entry = ScheduleEntry( - category: schedule?.category ?? 'switch_1', - time: _formatTimeOfDayToISO(selectedTime), - function: Status( - code: code ?? 'switch_1', - value: temp, - ), - days: _convertSelectedDaysToStrings(selectedDays), - scheduleId: schedule.scheduleId, - ); - Navigator.pop(ctx, entry); - }, - child: const Text('Save'), - )), ], ); }, diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index a36d1193..f2123046 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -85,4 +85,5 @@ abstract class ColorsManager { static const Color minBlueDot = Color(0xFF023DFE); static const Color grey25 = Color(0xFFF9F9F9); static const Color grey50 = Color(0xFF718096); + static const Color red100 = Color(0xFFFE0202); } From ab6a6851f2b732e38b71e6441f50ea308b15b62b Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 15:23:01 +0300 Subject: [PATCH 28/62] Update control points in SpacesConnectionsArrowPainter for smoother arrow rendering --- .../painters/spaces_connections_arrow_painter.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart b/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart index e9fa0a15..5200cfff 100644 --- a/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart +++ b/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart @@ -37,8 +37,8 @@ class SpacesConnectionsArrowPainter extends CustomPainter { final path = Path()..moveTo(startPoint.dx, startPoint.dy); - final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 20); - final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 60); + final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 100); + final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 100); path.cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx, controlPoint2.dy, endPoint.dx, endPoint.dy); From 52186417058a846e265242e823c5d1c1d63d8942 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 16:05:44 +0300 Subject: [PATCH 29/62] Refactor didUpdateWidget in CommunityStructureCanvas to ensure proper widget lifecycle management --- .../main_module/widgets/community_structure_canvas.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3cf761ad..67ccf380 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 @@ -51,7 +51,6 @@ class _CommunityStructureCanvasState extends State @override void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) { - super.didUpdateWidget(oldWidget); if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { @@ -59,6 +58,7 @@ class _CommunityStructureCanvasState extends State } }); } + super.didUpdateWidget(oldWidget); } @override From de5d8df01c184b36866d18e27e0df651687eb330 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 16:19:57 +0300 Subject: [PATCH 30/62] Update SpaceCell widget shadow properties for improved visual appearance --- .../space_management_v2/main_module/widgets/space_cell.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/space_cell.dart b/lib/pages/space_management_v2/main_module/widgets/space_cell.dart index 80b18526..1f7bd808 100644 --- a/lib/pages/space_management_v2/main_module/widgets/space_cell.dart +++ b/lib/pages/space_management_v2/main_module/widgets/space_cell.dart @@ -74,9 +74,8 @@ class SpaceCell extends StatelessWidget { borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( - color: ColorsManager.lightGrayColor.withValues(alpha: 0.5), - spreadRadius: 2, - blurRadius: 5, + color: ColorsManager.lightGrayColor.withValues(alpha: 0.25), + blurRadius: 7, offset: const Offset(0, 3), ), ], From 466f5b89c7ac621f216d1d9ded017b9a97550af9 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 16:56:51 +0300 Subject: [PATCH 31/62] Enhanced SpacesConnectionsArrowPainter and CommunityStructureCanvas to support dynamic card widths; enhance SpaceCell widget layout and shadow properties for improved UI consistency. --- .../spaces_connections_arrow_painter.dart | 30 +++++--- .../widgets/community_structure_canvas.dart | 68 ++++++++++++++----- .../main_module/widgets/space_cell.dart | 23 +++---- 3 files changed, 83 insertions(+), 38 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart b/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart index 5200cfff..ed797c74 100644 --- a/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart +++ b/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart @@ -5,13 +5,14 @@ import 'package:syncrow_web/utils/color_manager.dart'; class SpacesConnectionsArrowPainter extends CustomPainter { final List connections; final Map positions; - final double cardWidth = 150.0; + final Map cardWidths; final double cardHeight = 90.0; final Set highlightedUuids; SpacesConnectionsArrowPainter({ required this.connections, required this.positions, + required this.cardWidths, required this.highlightedUuids, }); @@ -29,19 +30,30 @@ class SpacesConnectionsArrowPainter extends CustomPainter { final from = positions[connection.from]; final to = positions[connection.to]; + final fromWidth = cardWidths[connection.from] ?? 150.0; + final toWidth = cardWidths[connection.to] ?? 150.0; if (from != null && to != null) { final startPoint = - Offset(from.dx + cardWidth / 2, from.dy + cardHeight - 10); - final endPoint = Offset(to.dx + cardWidth / 2, to.dy); + Offset(from.dx + fromWidth / 2, from.dy + cardHeight - 10); + final endPoint = Offset(to.dx + toWidth / 2, to.dy); final path = Path()..moveTo(startPoint.dx, startPoint.dy); - final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 100); - final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 100); - - path.cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx, - controlPoint2.dy, endPoint.dx, endPoint.dy); + if ((startPoint.dx - endPoint.dx).abs() < 1.0) { + path.lineTo(endPoint.dx, endPoint.dy); + } else { + final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 100); + final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 100); + path.cubicTo( + controlPoint1.dx, + controlPoint1.dy, + controlPoint2.dx, + controlPoint2.dy, + endPoint.dx, + endPoint.dy, + ); + } canvas.drawPath(path, paint); @@ -51,7 +63,7 @@ class SpacesConnectionsArrowPainter extends CustomPainter { : ColorsManager.blackColor.withValues(alpha: 0.5) ..style = PaintingStyle.fill ..blendMode = BlendMode.srcIn; - canvas.drawCircle(endPoint, 4, circlePaint); + canvas.drawCircle(endPoint, 6, circlePaint); } } } 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 67ccf380..65c0b95f 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 @@ -30,10 +30,11 @@ class CommunityStructureCanvas extends StatefulWidget { class _CommunityStructureCanvasState extends State with SingleTickerProviderStateMixin { final Map _positions = {}; - final double _cardWidth = 150.0; + final Map _cardWidths = {}; final double _cardHeight = 90.0; final double _horizontalSpacing = 150.0; final double _verticalSpacing = 120.0; + static const double _minCardWidth = 150.0; late final TransformationController _transformationController; late final AnimationController _animationController; @@ -51,6 +52,7 @@ class _CommunityStructureCanvasState extends State @override void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) { + super.didUpdateWidget(oldWidget); if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { @@ -58,7 +60,6 @@ class _CommunityStructureCanvasState extends State } }); } - super.didUpdateWidget(oldWidget); } @override @@ -68,6 +69,34 @@ class _CommunityStructureCanvasState extends State super.dispose(); } + double _calculateCardWidth(String text) { + final style = context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.bold, + ); + final textPainter = TextPainter( + text: TextSpan(text: text, style: style), + maxLines: 1, + textDirection: TextDirection.ltr, + )..layout(); + + const iconWidth = 40.0; + const horizontalPadding = 10.0; + const contentPadding = 10.0; + final calculatedWidth = + iconWidth + horizontalPadding + textPainter.width + contentPadding; + + return calculatedWidth.clamp(_minCardWidth, double.infinity); + } + + void _calculateAllCardWidths(List spaces) { + for (final space in spaces) { + _cardWidths[space.uuid] = _calculateCardWidth(space.spaceName); + if (space.children.isNotEmpty) { + _calculateAllCardWidths(space.children); + } + } + } + Set _getAllDescendantUuids(SpaceModel space) { final uuids = {}; for (final child in space.children) { @@ -102,11 +131,12 @@ class _CommunityStructureCanvasState extends State final position = _positions[space.uuid]; if (position == null) return; + final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth; const scale = 1; final viewSize = context.size; if (viewSize == null) return; - final x = -position.dx * scale + (viewSize.width / 2) - (_cardWidth * scale / 2); + final x = -position.dx * scale + (viewSize.width / 2) - (cardWidth * scale / 2); final y = -position.dy * scale + (viewSize.height / 2) - (_cardHeight * scale / 2); @@ -155,13 +185,16 @@ class _CommunityStructureCanvasState extends State Map levelXOffset, ) { for (final space in spaces) { + final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth; double childSubtreeWidth = 0; if (space.children.isNotEmpty) { _calculateLayout(space.children, depth + 1, levelXOffset); final firstChildPos = _positions[space.children.first.uuid]; final lastChildPos = _positions[space.children.last.uuid]; if (firstChildPos != null && lastChildPos != null) { - childSubtreeWidth = (lastChildPos.dx + _cardWidth) - firstChildPos.dx; + final lastChildWidth = + _cardWidths[space.children.last.uuid] ?? _minCardWidth; + childSubtreeWidth = (lastChildPos.dx + lastChildWidth) - firstChildPos.dx; } } @@ -170,7 +203,7 @@ class _CommunityStructureCanvasState extends State if (space.children.isNotEmpty) { final firstChildPos = _positions[space.children.first.uuid]!; - x = firstChildPos.dx + (childSubtreeWidth - _cardWidth) / 2; + x = firstChildPos.dx + (childSubtreeWidth - cardWidth) / 2; } else { x = currentX; } @@ -187,7 +220,7 @@ class _CommunityStructureCanvasState extends State final y = depth * (_verticalSpacing + _cardHeight); _positions[space.uuid] = Offset(x, y); - levelXOffset[depth] = x + _cardWidth + _horizontalSpacing; + levelXOffset[depth] = x + cardWidth + _horizontalSpacing; } } @@ -202,8 +235,11 @@ class _CommunityStructureCanvasState extends State List _buildTreeWidgets() { _positions.clear(); + _cardWidths.clear(); final community = widget.community; + _calculateAllCardWidths(community.spaces); + final levelXOffset = {}; _calculateLayout(community.spaces, 0, levelXOffset); @@ -240,6 +276,7 @@ class _CommunityStructureCanvasState extends State painter: SpacesConnectionsArrowPainter( connections: connections, positions: _positions, + cardWidths: _cardWidths, highlightedUuids: highlightedUuids, ), child: Stack(alignment: AlignmentDirectional.center, children: widgets), @@ -271,6 +308,7 @@ class _CommunityStructureCanvasState extends State continue; } + final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth; final isHighlighted = highlightedUuids.contains(space.uuid); final hasNoSelectedSpace = widget.selectedSpace == null; @@ -278,14 +316,10 @@ class _CommunityStructureCanvasState extends State 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: SpaceCell( + onTap: () => _onSpaceTapped(space), + icon: space.icon, + name: space.spaceName, ), ); }, @@ -305,7 +339,7 @@ class _CommunityStructureCanvasState extends State Positioned( left: position.dx, top: position.dy, - width: _cardWidth, + width: cardWidth, height: _cardHeight, child: Draggable( data: reorderData, @@ -314,7 +348,7 @@ class _CommunityStructureCanvasState extends State child: Opacity( opacity: 0.2, child: SizedBox( - width: _cardWidth, + width: cardWidth, height: _cardHeight, child: spaceCard, ), @@ -330,7 +364,7 @@ class _CommunityStructureCanvasState extends State ); final targetPos = Offset( - position.dx + _cardWidth + (_horizontalSpacing / 4) - 20, + position.dx + cardWidth + (_horizontalSpacing / 4) - 20, position.dy, ); widgets.add(_buildDropTarget(parent, community, i + 1, targetPos)); diff --git a/lib/pages/space_management_v2/main_module/widgets/space_cell.dart b/lib/pages/space_management_v2/main_module/widgets/space_cell.dart index 1f7bd808..3eb6d5df 100644 --- a/lib/pages/space_management_v2/main_module/widgets/space_cell.dart +++ b/lib/pages/space_management_v2/main_module/widgets/space_cell.dart @@ -20,21 +20,19 @@ class SpaceCell extends StatelessWidget { return InkWell( onTap: onTap, child: Container( - width: 150, + padding: const EdgeInsetsDirectional.only(end: 10), height: 70, decoration: _containerDecoration(), child: Row( + spacing: 10, + mainAxisSize: MainAxisSize.min, children: [ _buildIconContainer(), - const SizedBox(width: 10), - Expanded( - child: Text( - name, - style: context.textTheme.bodyLarge?.copyWith( - fontWeight: FontWeight.bold, - color: ColorsManager.blackColor, - ), - overflow: TextOverflow.ellipsis, + Text( + name, + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.bold, + color: ColorsManager.blackColor, ), ), ], @@ -74,8 +72,9 @@ class SpaceCell extends StatelessWidget { borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( - color: ColorsManager.lightGrayColor.withValues(alpha: 0.25), - blurRadius: 7, + color: ColorsManager.lightGrayColor.withValues(alpha: 0.5), + spreadRadius: 2, + blurRadius: 5, offset: const Offset(0, 3), ), ], From 338d4f5737d5f6fdba39d117b2f3d025279458cf Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Tue, 15 Jul 2025 08:19:43 +0300 Subject: [PATCH 32/62] fix typo --- .../users_page/add_user_dialog/view/basics_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index 250bba3f..7128ef2c 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -332,7 +332,7 @@ class BasicsView extends StatelessWidget { style: const TextStyle(color: ColorsManager.blackColor), decoration: inputTextFormDeco( - hintText: "Comapny Name (Optional)") + hintText: 'Company Name (Optional)') .copyWith( hintStyle: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w400, From 428c81effffe37b70a6c3bc171db9b0911a0ea5c Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 15 Jul 2025 10:05:07 +0300 Subject: [PATCH 33/62] Adjust table scroll behavior and modify height for improved layout --- lib/pages/common/custom_table.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index 93f8998e..6c02e889 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -132,6 +132,8 @@ class _DynamicTableState extends State { child: SingleChildScrollView( controller: _horizontalScrollController, scrollDirection: Axis.horizontal, + physics: + widget.isEmpty ? const NeverScrollableScrollPhysics() : null, child: SizedBox( width: _totalTableWidth, child: Column( @@ -164,7 +166,6 @@ class _DynamicTableState extends State { ], ), ), - Expanded( child: widget.isEmpty ? _buildEmptyState() @@ -265,7 +266,7 @@ class _DynamicTableState extends State { ), ], ), - SizedBox(height: widget.size.height * 0.5), + SizedBox(height: widget.size.height * 0.2), ], ), ); From b2231949506223f69333ff702c57422d067bc6a3 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 10:07:12 +0300 Subject: [PATCH 34/62] Add loading and status widgets for delete space dialog; refactor dialog to utilize new components for improved user feedback during space deletion process. --- .../widgets/delete_space_dialog.dart | 53 ++----------------- .../widgets/delete_space_loading_widget.dart | 13 +++++ .../widgets/delete_space_status_widget.dart | 38 +++++++++++++ 3 files changed, 55 insertions(+), 49 deletions(-) create mode 100644 lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_loading_widget.dart create mode 100644 lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_status_widget.dart diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart index c8356e32..b8734eee 100644 --- a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart @@ -5,6 +5,8 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_loading_widget.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_status_widget.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -45,8 +47,8 @@ class DeleteSpaceDialog extends StatelessWidget { communityUuid: community.uuid, ), DeleteSpaceLoading() => const DeleteSpaceLoadingWidget(), - DeleteSpaceSuccess(:final successMessage) => DeleteSpaceStatusWidget( - message: successMessage, + DeleteSpaceSuccess() => DeleteSpaceStatusWidget( + message: state.successMessage, icon: const Icon( Icons.check_circle, size: 92, @@ -69,50 +71,3 @@ class DeleteSpaceDialog extends StatelessWidget { ); } } - -class DeleteSpaceLoadingWidget extends StatelessWidget { - const DeleteSpaceLoadingWidget({super.key}); - - @override - Widget build(BuildContext context) { - return const SizedBox.square( - dimension: 32, - child: Center(child: CircularProgressIndicator()), - ); - } -} - -class DeleteSpaceStatusWidget extends StatelessWidget { - const DeleteSpaceStatusWidget({ - required this.message, - required this.icon, - super.key, - }); - - final String message; - final Widget icon; - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - spacing: 16, - children: [ - icon, - SelectableText( - message, - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.blackColor, - fontSize: 22, - ), - textAlign: TextAlign.center, - ), - FilledButton( - onPressed: Navigator.of(context).pop, - child: const Text('Close'), - ), - ], - ); - } -} diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_loading_widget.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_loading_widget.dart new file mode 100644 index 00000000..b658b3b3 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_loading_widget.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class DeleteSpaceLoadingWidget extends StatelessWidget { + const DeleteSpaceLoadingWidget({super.key}); + + @override + Widget build(BuildContext context) { + return const SizedBox.square( + dimension: 32, + child: Center(child: CircularProgressIndicator()), + ); + } +} diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_status_widget.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_status_widget.dart new file mode 100644 index 00000000..d597a451 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_status_widget.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class DeleteSpaceStatusWidget extends StatelessWidget { + const DeleteSpaceStatusWidget({ + required this.message, + required this.icon, + super.key, + }); + + final String message; + final Widget icon; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + spacing: 16, + children: [ + icon, + SelectableText( + message, + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.blackColor, + fontSize: 22, + ), + textAlign: TextAlign.center, + ), + FilledButton( + onPressed: Navigator.of(context).pop, + child: const Text('Close'), + ), + ], + ); + } +} From acefe7b35510d9db30fc5c6c9f1aa5899beef61e Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 11:09:04 +0300 Subject: [PATCH 35/62] Refactor RemoteDeleteSpaceService to use a private HTTPService instance and update URL construction with ApiEndpoints for improved maintainability. Update DeleteSpaceDialog to reflect changes in service initialization. --- .../data/remote_delete_space_service.dart | 15 +++++++++------ .../presentation/widgets/delete_space_dialog.dart | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart b/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart index 74724644..5320f625 100644 --- a/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart +++ b/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart @@ -4,18 +4,17 @@ import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domai import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart'; import 'package:syncrow_web/services/api/api_exception.dart'; import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; final class RemoteDeleteSpaceService implements DeleteSpaceService { - RemoteDeleteSpaceService({ - required this.httpService, - }); + const RemoteDeleteSpaceService(this._httpService); - final HTTPService httpService; + final HTTPService _httpService; @override Future delete(DeleteSpaceParam param) async { try { - await httpService.delete( + await _httpService.delete( path: await _makeUrl(param), expectedResponseModel: (json) { final response = json as Map; @@ -56,6 +55,10 @@ final class RemoteDeleteSpaceService implements DeleteSpaceService { if (param.spaceUuid.isEmpty) { throw APIException('Space UUID is not set'); } - return '/projects/$projectUuid/communities/${param.communityUuid}/spaces/${param.spaceUuid}'; + + return ApiEndpoints.deleteSpace + .replaceAll('{projectId}', projectUuid) + .replaceAll('{communityId}', param.communityUuid) + .replaceAll('{spaceId}', param.spaceUuid); } } diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart index b8734eee..f2ddf24a 100644 --- a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart @@ -27,7 +27,7 @@ class DeleteSpaceDialog extends StatelessWidget { Widget build(BuildContext context) { return BlocProvider( create: (context) => DeleteSpaceBloc( - RemoteDeleteSpaceService(httpService: HTTPService()), + RemoteDeleteSpaceService(HTTPService()), ), child: Builder( builder: (context) => Dialog( From fe4063ef8f287b780e6fdaf3c6d0d587167f052a Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Tue, 15 Jul 2025 11:20:13 +0300 Subject: [PATCH 36/62] changed the title from Routine to Workflow Automation --- .../all_devices/view/device_managment_page.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index 8210fb2f..17650f36 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -24,12 +24,12 @@ class DeviceManagementPage extends StatefulWidget with HelperResponsiveLayout { } class _DeviceManagementPageState extends State { - -@override + @override void initState() { context.read().add(InitialEvent()); super.initState(); } + @override Widget build(BuildContext context) { return MultiBlocProvider( @@ -90,7 +90,7 @@ class _DeviceManagementPageState extends State { const TriggerSwitchTabsEvent(isRoutineTab: true)); }, child: Text( - 'Routines', + 'Workflow Automation', style: context.textTheme.titleMedium?.copyWith( color: state.routineTab ? ColorsManager.whiteColors From fa930571dc45cfa97ad3493fd911732d186f3581 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 12:27:45 +0300 Subject: [PATCH 37/62] Ensure proper handling of null selectedSpace in CommunityStructureCanvas during widget updates to prevent unnecessary processing. --- .../main_module/widgets/community_structure_canvas.dart | 1 + 1 file changed, 1 insertion(+) 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 65c0b95f..02a49d8c 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 @@ -53,6 +53,7 @@ class _CommunityStructureCanvasState extends State @override void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) { super.didUpdateWidget(oldWidget); + if (widget.selectedSpace == null) return; if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { From f832c5d884ea950225a57a8910aa00f80cc85ce1 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 12:30:24 +0300 Subject: [PATCH 38/62] Refactor SpaceManagementCommunityStructure to improve widget structure and visibility handling. Introduce separate methods for building the canvas and empty state, enhancing readability and maintainability. --- .../space_management_community_structure.dart | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 deletions(-) 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 050eac87..b325c1ec 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 @@ -3,6 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.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'; class SpaceManagementCommunityStructure extends StatelessWidget { @@ -13,28 +15,44 @@ class SpaceManagementCommunityStructure extends StatelessWidget { final selectionBloc = context.watch().state; final selectedCommunity = selectionBloc.selectedCommunity; final selectedSpace = selectionBloc.selectedSpace; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CommunityStructureHeader(), + Visibility( + visible: selectedCommunity!.spaces.isNotEmpty, + replacement: _buildEmptyWidget(selectedCommunity), + child: _buildCanvas(selectedCommunity, selectedSpace), + ), + ], + ); + } + + Widget _buildCanvas( + CommunityModel selectedCommunity, + SpaceModel? selectedSpace, + ) { + return Expanded( + child: CommunityStructureCanvas( + community: selectedCommunity, + selectedSpace: selectedSpace, + ), + ); + } + + Widget _buildEmptyWidget(CommunityModel selectedCommunity) { const spacer = Spacer(flex: 6); - return Visibility( - visible: selectedCommunity!.spaces.isNotEmpty, - replacement: Row( + + return Expanded( + child: Row( children: [ spacer, Expanded( - child: CreateSpaceButton(communityUuid: selectedCommunity.uuid), - ), - spacer - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const CommunityStructureHeader(), - Expanded( - child: CommunityStructureCanvas( - community: selectedCommunity, - selectedSpace: selectedSpace, + child: CreateSpaceButton( + communityUuid: selectedCommunity.uuid, ), ), + spacer, ], ), ); From df39fca050099f000d6d977c431cab242ceacc84 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 12:49:37 +0300 Subject: [PATCH 39/62] Refactor CommunityStructureHeaderActionButtons to simplify null handling for selectedSpace and improve widget structure. Ensure buttons are always displayed when selectedSpace is not null, enhancing readability and maintainability. --- ...unity_structure_header_action_buttons.dart | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart index a965c866..edeb4d8e 100644 --- a/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart @@ -19,27 +19,27 @@ class CommunityStructureHeaderActionButtons extends StatelessWidget { @override Widget build(BuildContext context) { + if (selectedSpace == null) return const SizedBox.shrink(); + return Wrap( alignment: WrapAlignment.end, spacing: 10, children: [ - if (selectedSpace != null) ...[ - CommunityStructureHeaderButton( - label: 'Edit', - svgAsset: Assets.editSpace, - onPressed: () => onEdit(selectedSpace!), - ), - CommunityStructureHeaderButton( - label: 'Duplicate', - svgAsset: Assets.duplicate, - onPressed: () => onDuplicate(selectedSpace!), - ), - CommunityStructureHeaderButton( - label: 'Delete', - svgAsset: Assets.spaceDelete, - onPressed: () => onDelete(selectedSpace!), - ), - ], + CommunityStructureHeaderButton( + label: 'Edit', + svgAsset: Assets.editSpace, + onPressed: () => onEdit(selectedSpace!), + ), + CommunityStructureHeaderButton( + label: 'Duplicate', + svgAsset: Assets.duplicate, + onPressed: () => onDuplicate(selectedSpace!), + ), + CommunityStructureHeaderButton( + label: 'Delete', + svgAsset: Assets.spaceDelete, + onPressed: () => onDelete(selectedSpace!), + ), ], ); } From 903c5dd29b28b9143901f280fdc6c1610672e860 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 12:55:26 +0300 Subject: [PATCH 40/62] Refactor SpacesRecursiveHelper to improve variable naming and enhance readability. Update mapping logic to clarify the distinction between updated and non-null spaces, ensuring better maintainability of the recursive space handling. --- .../main_module/helpers/spaces_recursive_helper.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart b/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart index e751e8d8..b725bf31 100644 --- a/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart +++ b/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart @@ -7,13 +7,15 @@ abstract final class SpacesRecursiveHelper { SpaceDetailsModel updatedSpace, ) { return spaces.map((space) { - if (space.uuid == updatedSpace.uuid) { + final isUpdatedSpace = space.uuid == updatedSpace.uuid; + if (isUpdatedSpace) { return space.copyWith( spaceName: updatedSpace.spaceName, icon: updatedSpace.icon, ); } - if (space.children.isNotEmpty) { + final hasChildren = space.children.isNotEmpty; + if (hasChildren) { return space.copyWith( children: recusrivelyUpdate(space.children, updatedSpace), ); @@ -26,7 +28,7 @@ abstract final class SpacesRecursiveHelper { List spaces, String spaceUuid, ) { - final s = spaces.map((space) { + final updatedSpaces = spaces.map((space) { if (space.uuid == spaceUuid) return null; if (space.children.isNotEmpty) { return space.copyWith( @@ -35,7 +37,7 @@ abstract final class SpacesRecursiveHelper { } return space; }).toList(); - - return s.whereType().toList(); + final nonNullSpaces = updatedSpaces.whereType().toList(); + return nonNullSpaces; } } From c60078c96ab7aac6d4fedc8c576b96aff0c1a515 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 13:04:37 +0300 Subject: [PATCH 41/62] Refactor CommunityStructureCanvas to improve widget structure by rearranging the InteractiveViewer and GestureDetector hierarchy. This change enhances readability and maintainability while ensuring proper interaction handling. --- .../widgets/community_structure_canvas.dart | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 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 02a49d8c..0fa5630b 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 @@ -453,17 +453,17 @@ class _CommunityStructureCanvasState extends State @override Widget build(BuildContext context) { final treeWidgets = _buildTreeWidgets(); - return InteractiveViewer( - transformationController: _transformationController, - boundaryMargin: EdgeInsets.symmetric( - horizontal: context.screenWidth * 0.3, - vertical: context.screenHeight * 0.3, - ), - minScale: 0.5, - maxScale: 3.0, - constrained: false, - child: GestureDetector( - onTap: _resetSelectionAndZoom, + return GestureDetector( + onTap: _resetSelectionAndZoom, + child: InteractiveViewer( + transformationController: _transformationController, + boundaryMargin: EdgeInsets.symmetric( + horizontal: context.screenWidth * 0.3, + vertical: context.screenHeight * 0.3, + ), + minScale: 0.5, + maxScale: 3.0, + constrained: false, child: SizedBox( width: context.screenWidth * 5, height: context.screenHeight * 5, From e74065250796fbdb69b8f562da355b771b091dc9 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 13:11:22 +0300 Subject: [PATCH 42/62] Refactor PlusButtonWidget and SpaceCardWidget to improve widget structure and interaction handling. Replace GestureDetector with IconButton for better usability and update positioning logic for the PlusButtonWidget, enhancing maintainability and readability. --- .../widgets/plus_button_widget.dart | 27 +++++++------------ .../widgets/space_card_widget.dart | 5 ++-- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/plus_button_widget.dart b/lib/pages/space_management_v2/main_module/widgets/plus_button_widget.dart index 68169861..236b73c9 100644 --- a/lib/pages/space_management_v2/main_module/widgets/plus_button_widget.dart +++ b/lib/pages/space_management_v2/main_module/widgets/plus_button_widget.dart @@ -2,31 +2,22 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class PlusButtonWidget extends StatelessWidget { - final Offset offset; - final void Function() onButtonTap; + final void Function() onTap; const PlusButtonWidget({ + required this.onTap, super.key, - required this.offset, - required this.onButtonTap, }); @override Widget build(BuildContext context) { - return GestureDetector( - onTap: onButtonTap, - child: Container( - width: 30, - height: 30, - decoration: const BoxDecoration( - color: ColorsManager.spaceColor, - shape: BoxShape.circle, - ), - child: const Icon( - Icons.add, - color: ColorsManager.whiteColors, - size: 20, - ), + return IconButton.filled( + onPressed: onTap, + style: IconButton.styleFrom(backgroundColor: ColorsManager.spaceColor), + icon: const Icon( + Icons.add, + color: ColorsManager.whiteColors, + size: 20, ), ); } 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 54902280..da79b817 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 @@ -29,10 +29,9 @@ class _SpaceCardWidgetState extends State { widget.buildSpaceContainer(), if (isHovered) Positioned( - bottom: 0, + bottom: -5, child: PlusButtonWidget( - offset: Offset.zero, - onButtonTap: widget.onTap, + onTap: widget.onTap, ), ), ], From 5a3cf93748d7143414c711649b906bdba6fb3094 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 14:37:16 +0300 Subject: [PATCH 43/62] Improved UniqueSubspacesDecorator implementation to improve handling of duplicate subspace names. --- .../data/services/unique_subspaces_decorator.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart b/lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart index 8309c545..df0ab727 100644 --- a/lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart +++ b/lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart @@ -12,13 +12,17 @@ class UniqueSubspacesDecorator implements SpaceDetailsService { final response = await _decoratee.getSpaceDetails(param); final uniqueSubspaces = {}; + final duplicateNames = {}; for (final subspace in response.subspaces) { final normalizedName = subspace.name.trim().toLowerCase(); - if (!uniqueSubspaces.containsKey(normalizedName)) { + if (uniqueSubspaces.containsKey(normalizedName)) { + duplicateNames.add(normalizedName); + } else { uniqueSubspaces[normalizedName] = subspace; } } + duplicateNames.forEach(uniqueSubspaces.remove); return response.copyWith( subspaces: uniqueSubspaces.values.toList(), From f539b0ac8d559ab2e18758a50e58828e188fbd26 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 14:38:44 +0300 Subject: [PATCH 44/62] Rename `UniqueSubspacesDecorator` to `UniqueSpaceDetailsSpacesDecoratorService` --- .../main_module/views/space_management_page.dart | 4 ++-- ...art => unique_space_details_spaces_decorator_service.dart} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename lib/pages/space_management_v2/modules/space_details/data/services/{unique_subspaces_decorator.dart => unique_space_details_spaces_decorator_service.dart} (88%) diff --git a/lib/pages/space_management_v2/main_module/views/space_management_page.dart b/lib/pages/space_management_v2/main_module/views/space_management_page.dart index 40a37891..55e47de1 100644 --- a/lib/pages/space_management_v2/main_module/views/space_management_page.dart +++ b/lib/pages/space_management_v2/main_module/views/space_management_page.dart @@ -10,7 +10,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen import 'package:syncrow_web/pages/space_management_v2/modules/products/data/services/remote_products_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.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/data/services/unique_subspaces_decorator.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; @@ -49,7 +49,7 @@ class _SpaceManagementPageState extends State { ), BlocProvider( create: (context) => SpaceDetailsBloc( - UniqueSubspacesDecorator( + UniqueSpaceDetailsSpacesDecoratorService( RemoteSpaceDetailsService(httpService: HTTPService()), ), ), diff --git a/lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart b/lib/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart similarity index 88% rename from lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart rename to lib/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart index df0ab727..72cf8b89 100644 --- a/lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart +++ b/lib/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart @@ -2,10 +2,10 @@ import 'package:syncrow_web/pages/space_management_v2/modules/space_details/doma 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/domain/services/space_details_service.dart'; -class UniqueSubspacesDecorator implements SpaceDetailsService { +class UniqueSpaceDetailsSpacesDecoratorService implements SpaceDetailsService { final SpaceDetailsService _decoratee; - const UniqueSubspacesDecorator(this._decoratee); + const UniqueSpaceDetailsSpacesDecoratorService(this._decoratee); @override Future getSpaceDetails(LoadSpaceDetailsParam param) async { From d65f9ceea9a230c2abe0ea374e7e5ef56c87122f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 14:53:04 +0300 Subject: [PATCH 45/62] Enhance AddDeviceTypeWidget to support initial product counts and update selection logic. Modify AssignTagsDialog to pass initial products from space allocations, improving user experience and maintainability. --- .../widgets/add_device_type_widget.dart | 42 +++++++++++++++++-- .../widgets/assign_tags_dialog.dart | 9 +++- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/lib/pages/space_management_v2/modules/tags/presentation/widgets/add_device_type_widget.dart b/lib/pages/space_management_v2/modules/tags/presentation/widgets/add_device_type_widget.dart index 4c9990ae..2c100b15 100644 --- a/lib/pages/space_management_v2/modules/tags/presentation/widgets/add_device_type_widget.dart +++ b/lib/pages/space_management_v2/modules/tags/presentation/widgets/add_device_type_widget.dart @@ -10,7 +10,12 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class AddDeviceTypeWidget extends StatefulWidget { - const AddDeviceTypeWidget({super.key}); + const AddDeviceTypeWidget({ + super.key, + this.initialProducts = const [], + }); + + final List initialProducts; @override State createState() => _AddDeviceTypeWidgetState(); @@ -18,6 +23,16 @@ class AddDeviceTypeWidget extends StatefulWidget { class _AddDeviceTypeWidgetState extends State { final Map _selectedProducts = {}; + final Map _initialProductCounts = {}; + + @override + void initState() { + super.initState(); + for (final product in widget.initialProducts) { + _initialProductCounts[product] = (_initialProductCounts[product] ?? 0) + 1; + } + _selectedProducts.addAll(_initialProductCounts); + } void _onIncrement(Product product) { setState(() { @@ -27,8 +42,12 @@ class _AddDeviceTypeWidgetState extends State { void _onDecrement(Product product) { setState(() { - if ((_selectedProducts[product] ?? 0) > 0) { - _selectedProducts[product] = _selectedProducts[product]! - 1; + final initialCount = _initialProductCounts[product] ?? 0; + final currentCount = _selectedProducts[product] ?? 0; + if (currentCount > initialCount) { + _selectedProducts[product] = currentCount - 1; + } else if (currentCount > 0 && initialCount == 0) { + _selectedProducts[product] = currentCount - 1; if (_selectedProducts[product] == 0) { _selectedProducts.remove(product); } @@ -63,7 +82,22 @@ class _AddDeviceTypeWidgetState extends State { actions: [ SpaceDetailsActionButtons( onSave: () { - final result = _selectedProducts.entries + final resultMap = {}; + resultMap.addAll(_selectedProducts); + + for (final entry in _initialProductCounts.entries) { + final product = entry.key; + final initialCount = entry.value; + final currentCount = resultMap[product] ?? 0; + + if (currentCount > initialCount) { + resultMap[product] = currentCount - initialCount; + } else { + resultMap.remove(product); + } + } + + final result = resultMap.entries .expand((entry) => List.generate(entry.value, (_) => entry.key)) .toList(); Navigator.of(context).pop(result); 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 3f6d42ab..7a1002dc 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 @@ -205,7 +205,14 @@ class _AssignTagsDialogState extends State { onCancel: () async { final newProducts = await showDialog>( context: context, - builder: (context) => const AddDeviceTypeWidget(), + builder: (context) => AddDeviceTypeWidget( + initialProducts: [ + ..._space.productAllocations.map((e) => e.product), + ..._space.subspaces + .expand((s) => s.productAllocations) + .map((e) => e.product), + ], + ), ); if (newProducts == null || newProducts.isEmpty) return; From 308eb65d469141eda5f302bc5a36e798b014a09a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 09:56:55 +0300 Subject: [PATCH 46/62] Update error handling in RemoteUpdateSpaceService to retrieve error messages from the 'message' field instead of 'error', enhancing clarity in error reporting. --- .../update_space/data/services/remote_update_space_service.dart | 2 +- 1 file changed, 1 insertion(+), 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 a70d3b85..2585b1e7 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 @@ -34,7 +34,7 @@ class RemoteUpdateSpaceService implements UpdateSpaceService { } on DioException catch (e) { final message = e.response?.data as Map?; final error = message?['error'] as Map?; - final errorMessage = error?['error'] as String? ?? ''; + final errorMessage = error?['message'] as String? ?? ''; final formattedErrorMessage = [ _defaultErrorMessage, errorMessage, From 39c5fd1bcaa32f1dd870414bf0e32a59f4cbc124 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 09:57:29 +0300 Subject: [PATCH 47/62] Refactor SpaceDetailsModel to integrate Subspace and ProductAllocation models, enhancing data structure and serialization. Update related widgets to utilize Subspace instead of SpaceDetailsModel for improved clarity and maintainability. --- .../services/remote_create_space_service.dart | 63 +++++++++++++ .../domain/params/create_space_param.dart | 17 ++++ .../domain/services/create_space_service.dart | 6 ++ ...pace_details_spaces_decorator_service.dart | 1 + .../domain/models/product_allocation.dart | 47 ++++++++++ .../domain/models/space_details_model.dart | 91 ++++--------------- .../space_details/domain/models/subspace.dart | 48 ++++++++++ .../widgets/space_sub_spaces_box.dart | 2 +- .../widgets/space_sub_spaces_dialog.dart | 2 +- .../widgets/sub_spaces_input.dart | 2 +- .../presentation/widgets/subspace_chip.dart | 2 +- .../widgets/subspace_name_display_widget.dart | 2 +- .../widgets/assign_tags_dialog.dart | 1 + .../widgets/assign_tags_table.dart | 3 +- .../domain/params/update_space_param.dart | 31 +------ .../space_details_model_bloc.dart | 2 + 16 files changed, 210 insertions(+), 110 deletions(-) create mode 100644 lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart create mode 100644 lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart create mode 100644 lib/pages/space_management_v2/modules/create_space/domain/services/create_space_service.dart create mode 100644 lib/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart create mode 100644 lib/pages/space_management_v2/modules/space_details/domain/models/subspace.dart diff --git a/lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart b/lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart new file mode 100644 index 00000000..f8dba7dc --- /dev/null +++ b/lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart @@ -0,0 +1,63 @@ +import 'package:dio/dio.dart'; +import 'package:syncrow_web/pages/common/bloc/project_manager.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/create_space/domain/params/create_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/create_space/domain/services/create_space_service.dart'; +import 'package:syncrow_web/services/api/api_exception.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; + +final class RemoteCreateSpaceService implements CreateSpaceService { + const RemoteCreateSpaceService(this._httpService); + + final HTTPService _httpService; + + static const _defaultErrorMessage = 'Failed to create space'; + + @override + Future createSpace(CreateSpaceParam param) async { + try { + final path = await _makeUrl(param); + final response = await _httpService.post( + path: path, + body: param.toJson(), + expectedResponseModel: (data) { + final response = data as Map; + final isSuccess = response['success'] as bool; + if (!isSuccess) { + throw APIException(response['error'] as String); + } + + return SpaceModel.fromJson(response['data'] as Map); + }, + ); + + return response; + } on DioException catch (e) { + final message = e.response?.data as Map?; + final error = message?['error'] as Map?; + final errorMessage = error?['error'] as String? ?? ''; + final formattedErrorMessage = [ + _defaultErrorMessage, + errorMessage, + ].join(': '); + throw APIException(formattedErrorMessage); + } catch (e) { + final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': '); + throw APIException(formattedErrorMessage); + } + } + + Future _makeUrl(CreateSpaceParam param) async { + final projectUuid = await ProjectManager.getProjectUUID(); + if (projectUuid == null || projectUuid.isEmpty) { + throw APIException('Project UUID is not set'); + } + + final communityUuid = param.communityUuid; + if (communityUuid.isEmpty) { + throw APIException('Community UUID is not set'); + } + + return '/projects/$projectUuid/communities/$communityUuid'; + } +} diff --git a/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart b/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart new file mode 100644 index 00000000..75c6d1d5 --- /dev/null +++ b/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart @@ -0,0 +1,17 @@ +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; + +class CreateSpaceParam { + final String communityUuid; + final SpaceDetailsModel space; + final String? parentUuid; + + const CreateSpaceParam({ + required this.communityUuid, + required this.space, + required this.parentUuid, + }); + + Map toJson() { + return {'spaceModelUuid': parentUuid, ...space.toJson()}; + } +} diff --git a/lib/pages/space_management_v2/modules/create_space/domain/services/create_space_service.dart b/lib/pages/space_management_v2/modules/create_space/domain/services/create_space_service.dart new file mode 100644 index 00000000..553b87e7 --- /dev/null +++ b/lib/pages/space_management_v2/modules/create_space/domain/services/create_space_service.dart @@ -0,0 +1,6 @@ +import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart'; + +abstract interface class CreateSpaceService { + Future createSpace(CreateSpaceParam param); +} diff --git a/lib/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart b/lib/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart index 72cf8b89..da7fd4eb 100644 --- a/lib/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart +++ b/lib/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart @@ -1,4 +1,5 @@ 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/models/subspace.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/domain/services/space_details_service.dart'; diff --git a/lib/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart b/lib/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart new file mode 100644 index 00000000..250dad5c --- /dev/null +++ b/lib/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart @@ -0,0 +1,47 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/models/product.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart'; +import 'package:uuid/uuid.dart'; + +class ProductAllocation extends Equatable { + final String uuid; + final Product product; + final Tag tag; + + const ProductAllocation({ + required this.uuid, + required this.product, + required this.tag, + }); + + factory ProductAllocation.fromJson(Map json) { + return ProductAllocation( + uuid: json['uuid'] as String? ?? const Uuid().v4(), + product: Product.fromJson(json['product'] as Map), + tag: Tag.fromJson(json['tag'] as Map), + ); + } + + ProductAllocation copyWith({ + String? uuid, + Product? product, + Tag? tag, + }) { + return ProductAllocation( + uuid: uuid ?? this.uuid, + product: product ?? this.product, + tag: tag ?? this.tag, + ); + } + + Map toJson() { + final isNewTag = tag.uuid.isEmpty; + return { + if (isNewTag) 'tagName': tag.name else 'tagUuid': tag.uuid, + 'productUuid': product.uuid, + }; + } + + @override + List get props => [uuid, product, tag]; +} 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 ec3c9f81..bd8ff714 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 @@ -1,8 +1,7 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/models/product.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; -import 'package:uuid/uuid.dart'; class SpaceDetailsModel extends Equatable { final String uuid; @@ -26,6 +25,7 @@ class SpaceDetailsModel extends Equatable { productAllocations: [], subspaces: [], ); + factory SpaceDetailsModel.fromJson(Map json) { return SpaceDetailsModel( uuid: json['uuid'] as String, @@ -56,78 +56,21 @@ class SpaceDetailsModel extends Equatable { ); } - @override - List get props => [uuid, spaceName, icon, productAllocations, subspaces]; -} - -class ProductAllocation extends Equatable { - final String uuid; - final Product product; - final Tag tag; - - const ProductAllocation({ - required this.uuid, - required this.product, - required this.tag, - }); - - factory ProductAllocation.fromJson(Map json) { - return ProductAllocation( - uuid: json['uuid'] as String? ?? const Uuid().v4(), - product: Product.fromJson(json['product'] as Map), - tag: Tag.fromJson(json['tag'] as Map), - ); - } - - ProductAllocation copyWith({ - String? uuid, - Product? product, - Tag? tag, - }) { - return ProductAllocation( - uuid: uuid ?? this.uuid, - product: product ?? this.product, - tag: tag ?? this.tag, - ); + Map toJson() { + return { + 'spaceName': spaceName, + 'icon': icon, + 'subspaces': subspaces.map((e) => e.toJson()).toList(), + 'productAllocations': productAllocations.map((e) => e.toJson()).toList(), + }; } @override - List get props => [uuid, product, tag]; -} - -class Subspace extends Equatable { - final String uuid; - final String name; - final List productAllocations; - - const Subspace({ - required this.uuid, - required this.name, - required this.productAllocations, - }); - - factory Subspace.fromJson(Map json) { - return Subspace( - uuid: json['uuid'] as String, - name: json['subspaceName'] as String, - productAllocations: (json['productAllocations'] as List) - .map((e) => ProductAllocation.fromJson(e as Map)) - .toList(), - ); - } - - Subspace copyWith({ - String? uuid, - String? name, - List? productAllocations, - }) { - return Subspace( - uuid: uuid ?? this.uuid, - name: name ?? this.name, - productAllocations: productAllocations ?? this.productAllocations, - ); - } - - @override - List get props => [uuid, name, productAllocations]; + List get props => [ + uuid, + spaceName, + icon, + productAllocations, + subspaces, + ]; } diff --git a/lib/pages/space_management_v2/modules/space_details/domain/models/subspace.dart b/lib/pages/space_management_v2/modules/space_details/domain/models/subspace.dart new file mode 100644 index 00000000..4a962eb3 --- /dev/null +++ b/lib/pages/space_management_v2/modules/space_details/domain/models/subspace.dart @@ -0,0 +1,48 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart'; + +class Subspace extends Equatable { + final String uuid; + final String name; + final List productAllocations; + + const Subspace({ + required this.uuid, + required this.name, + required this.productAllocations, + }); + + factory Subspace.fromJson(Map json) { + return Subspace( + uuid: json['uuid'] as String, + name: json['subspaceName'] as String, + productAllocations: (json['productAllocations'] as List) + .map((e) => ProductAllocation.fromJson(e as Map)) + .toList(), + ); + } + + Map toJson() { + final isNewSubspace = uuid.endsWith('-NewTag'); + return { + if (!isNewSubspace) 'uuid': uuid, + 'subspaceName': name, + 'productAllocations': productAllocations.map((e) => e.toJson()).toList(), + }; + } + + Subspace copyWith({ + String? uuid, + String? name, + List? productAllocations, + }) { + return Subspace( + uuid: uuid ?? this.uuid, + name: name ?? this.name, + productAllocations: productAllocations ?? this.productAllocations, + ); + } + + @override + List get props => [uuid, name, productAllocations]; +} diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_box.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_box.dart index 68bf68bd..719988c6 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_box.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_box.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/common/edit_chip.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/models/subspace.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_name_display_widget.dart'; 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 8faac548..587c9ea7 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 @@ -1,5 +1,5 @@ import 'package:flutter/material.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/models/subspace.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_action_buttons.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/sub_spaces_input.dart'; import 'package:uuid/uuid.dart'; diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/sub_spaces_input.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/sub_spaces_input.dart index 854b79bc..591f741c 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/sub_spaces_input.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/sub_spaces_input.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.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/models/subspace.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_chip.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_chip.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_chip.dart index a80ddd15..6bc9f6d1 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_chip.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_chip.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.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/models/subspace.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_name_display_widget.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_name_display_widget.dart index bf13ffd3..e72bffde 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_name_display_widget.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_name_display_widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_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/models/subspace.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; 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 7a1002dc..d14a3923 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 @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/models/product.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/product_allocation.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/widgets/space_details_action_buttons.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart'; diff --git a/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_table.dart b/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_table.dart index 6e7e2097..1711e019 100644 --- a/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_table.dart +++ b/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_table.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/common/dialog_dropdown.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/models/product_allocation.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/data/services/remote_tags_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/bloc/tags_bloc.dart'; 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 5dd9106d..25d54caa 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 @@ -9,34 +9,5 @@ class UpdateSpaceParam { final SpaceDetailsModel space; final String communityUuid; - Map toJson() { - return { - 'spaceName': space.spaceName, - 'icon': space.icon, - '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) 'uuid': uuid, - 'subspaceName': name, - 'productAllocations': productAllocations.map((e) => e._toJson()).toList(), - }; - } + Map toJson() => space.toJson(); } diff --git a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart index 15a22fda..4c11e694 100644 --- a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart +++ b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart @@ -1,6 +1,8 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/product_allocation.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/models/subspace.dart'; part 'space_details_model_event.dart'; From 9a203b2fd9de08c531c045611df941f935d6ae4e Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 10:01:51 +0300 Subject: [PATCH 48/62] Add CreateSpaceBloc, CreateSpaceEvent, and CreateSpaceState for managing space creation logic. Implement event handling and state management to enhance user experience during space creation. --- .../presentation/bloc/create_space_bloc.dart | 34 +++++++++++++++++++ .../presentation/bloc/create_space_event.dart | 17 ++++++++++ .../presentation/bloc/create_space_state.dart | 31 +++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_bloc.dart create mode 100644 lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_event.dart create mode 100644 lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_state.dart diff --git a/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_bloc.dart b/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_bloc.dart new file mode 100644 index 00000000..46a8abb8 --- /dev/null +++ b/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_bloc.dart @@ -0,0 +1,34 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.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/create_space/domain/params/create_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/create_space/domain/services/create_space_service.dart'; +import 'package:syncrow_web/services/api/api_exception.dart'; + +part 'create_space_event.dart'; +part 'create_space_state.dart'; + +class CreateSpaceBloc extends Bloc { + CreateSpaceBloc( + this._createSpaceService, + ) : super(const CreateSpaceInitial()) { + on(_onCreateSpace); + } + + final CreateSpaceService _createSpaceService; + + Future _onCreateSpace( + CreateSpace event, + Emitter emit, + ) async { + emit(const CreateSpaceLoading()); + try { + final result = await _createSpaceService.createSpace(event.param); + emit(CreateSpaceSuccess(result)); + } on APIException catch (e) { + emit(CreateSpaceFailure(e.message)); + } catch (e) { + emit(CreateSpaceFailure(e.toString())); + } + } +} diff --git a/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_event.dart b/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_event.dart new file mode 100644 index 00000000..09ef8698 --- /dev/null +++ b/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_event.dart @@ -0,0 +1,17 @@ +part of 'create_space_bloc.dart'; + +sealed class CreateSpaceEvent extends Equatable { + const CreateSpaceEvent(); + + @override + List get props => []; +} + +final class CreateSpace extends CreateSpaceEvent { + const CreateSpace(this.param); + + final CreateSpaceParam param; + + @override + List get props => [param]; +} diff --git a/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_state.dart b/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_state.dart new file mode 100644 index 00000000..c5b035bb --- /dev/null +++ b/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_state.dart @@ -0,0 +1,31 @@ +part of 'create_space_bloc.dart'; + +sealed class CreateSpaceState extends Equatable { + const CreateSpaceState(); + + @override + List get props => []; +} + +final class CreateSpaceInitial extends CreateSpaceState { + const CreateSpaceInitial(); +} + +final class CreateSpaceLoading extends CreateSpaceState { + const CreateSpaceLoading(); +} + +final class CreateSpaceSuccess extends CreateSpaceState { + const CreateSpaceSuccess(this.space); + + final SpaceModel space; + + @override + List get props => [space]; +} + +final class CreateSpaceFailure extends CreateSpaceState { + const CreateSpaceFailure(this.errorMessage); + + final String errorMessage; +} From 4ef4858ceeca7b11e54437daeed0e83bbfac7036 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 10:29:43 +0300 Subject: [PATCH 49/62] Added x, and y to the `toJson` method of `CreateSpaceParam`, since the API still needs them as required properties. Revert the once the BE removes those properties. --- .../create_space/domain/params/create_space_param.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart b/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart index 75c6d1d5..931588d2 100644 --- a/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart +++ b/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart @@ -12,6 +12,11 @@ class CreateSpaceParam { }); Map toJson() { - return {'spaceModelUuid': parentUuid, ...space.toJson()}; + return { + 'spaceModelUuid': parentUuid, + ...space.toJson(), + 'x': 0, + 'y': 0, + }; } } From 8e303af0d73fd908ea6bb2d20a685b9696de1b1d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 11:01:26 +0300 Subject: [PATCH 50/62] Update RemoteCreateSpaceService to correct API endpoint for space creation, changing the return path to include '/spaces' for accurate resource targeting. --- .../create_space/data/services/remote_create_space_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart b/lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart index f8dba7dc..768f6438 100644 --- a/lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart +++ b/lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart @@ -58,6 +58,6 @@ final class RemoteCreateSpaceService implements CreateSpaceService { throw APIException('Community UUID is not set'); } - return '/projects/$projectUuid/communities/$communityUuid'; + return '/projects/$projectUuid/communities/$communityUuid/spaces'; } } From 3e634dc7a2970c4b77f653c07de3425c35feaa84 Mon Sep 17 00:00:00 2001 From: Rafeek-Khoudare Date: Wed, 16 Jul 2025 11:02:32 +0300 Subject: [PATCH 51/62] fix communities filtiring issue --- .../device_managment_bloc.dart | 18 +++++++++++------- .../bloc/routine_bloc/routine_bloc.dart | 8 ++++++-- lib/services/devices_mang_api.dart | 8 ++++++-- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart index d8cd04df..f79528f8 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart @@ -53,8 +53,9 @@ class DeviceManagementBloc for (var community in spaceBloc.state.selectedCommunities) { final spacesList = spaceBloc.state.selectedCommunityAndSpaces[community] ?? []; - devices.addAll(await DevicesManagementApi() - .fetchDevices(projectUuid, spacesId: spacesList)); + devices.addAll(await DevicesManagementApi().fetchDevices(projectUuid, + spacesId: spacesList, + communities: spaceBloc.state.selectedCommunities)); } } @@ -158,7 +159,8 @@ class DeviceManagementBloc add(FilterDevices(_getFilterFromIndex(_selectedIndex))); } - void _onSelectDevice(SelectDevice event, Emitter emit) { + void _onSelectDevice( + SelectDevice event, Emitter emit) { final selectedUuid = event.selectedDevice.uuid; if (_selectedDevices.any((device) => device.uuid == selectedUuid)) { @@ -254,7 +256,8 @@ class DeviceManagementBloc _onlineCount = _devices.where((device) => device.online == true).length; _offlineCount = _devices.where((device) => device.online == false).length; _lowBatteryCount = _devices - .where((device) => device.batteryLevel != null && device.batteryLevel! < 20) + .where((device) => + device.batteryLevel != null && device.batteryLevel! < 20) .length; } @@ -271,7 +274,8 @@ class DeviceManagementBloc } } - void _onSearchDevices(SearchDevices event, Emitter emit) { + void _onSearchDevices( + SearchDevices event, Emitter emit) { if ((event.community == null || event.community!.isEmpty) && (event.unitName == null || event.unitName!.isEmpty) && (event.deviceNameOrProductName == null || @@ -435,8 +439,8 @@ class DeviceManagementBloc final selectedDevices = loaded.selectedDevice?.map((device) { if (device.uuid == event.deviceId) { return device.copyWith( - subspace: - device.subspace?.copyWith(subspaceName: event.newSubSpaceName)); + subspace: device.subspace + ?.copyWith(subspaceName: event.newSubSpaceName)); } return device; }).toList(); diff --git a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index 971f4f8c..a26a2715 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -937,13 +937,17 @@ class RoutineBloc extends Bloc { List spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; - devices.addAll(await DevicesManagementApi() - .fetchDevices(projectUuid, spacesId: spacesList)); + devices.addAll(await DevicesManagementApi().fetchDevices( + projectUuid, + spacesId: spacesList, + communities: spaceBloc.state.selectedCommunities, + )); } } else { devices.addAll(await DevicesManagementApi().fetchDevices( projectUuid, spacesId: [createRoutineBloc.selectedSpaceId], + communities: spaceBloc.state.selectedCommunities, )); } diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 8c74dbb1..684165e2 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -13,11 +13,15 @@ import 'package:syncrow_web/utils/constants/api_const.dart'; class DevicesManagementApi { Future> fetchDevices(String projectId, - {List? spacesId}) async { + {List? spacesId, List? communities}) async { try { final response = await HTTPService().get( path: ApiEndpoints.getSpaceDevices.replaceAll('{projectId}', projectId), - queryParameters: {if (spacesId != null) 'spaces': spacesId}, + queryParameters: { + if (spacesId != null && spacesId.isNotEmpty) 'spaces': spacesId, + if (communities != null && communities.isNotEmpty) + 'communities': communities, + }, showServerMessage: true, expectedResponseModel: (json) { final List jsonData = json['data'] as List; From 62f67f5a5f8250ff94ad4697fd9ce0c11eb0e08d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 11:16:41 +0300 Subject: [PATCH 52/62] Refactor CreateSpaceButton and CommunityStructureCanvas to utilize CommunityModel directly, enhancing data handling during space creation. Implement success callback for space creation to update community state in CommunitiesBloc, improving user experience and maintainability. --- .../widgets/community_structure_canvas.dart | 15 +++++- .../widgets/create_space_button.dart | 17 ++++-- .../space_management_community_structure.dart | 49 ++++++++++------- .../helpers/space_details_dialog_helper.dart | 54 ++++++++++++++++--- 4 files changed, 107 insertions(+), 28 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 0fa5630b..63f8e8ec 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 @@ -268,7 +268,7 @@ class _CommunityStructureCanvasState extends State Positioned( left: createButtonX, top: createButtonY, - child: CreateSpaceButton(communityUuid: widget.community.uuid), + child: CreateSpaceButton(community: widget.community), ), ); @@ -327,6 +327,19 @@ class _CommunityStructureCanvasState extends State onTap: () => SpaceDetailsDialogHelper.showCreate( context, communityUuid: widget.community.uuid, + parentUuid: space.uuid, + onSuccess: (updatedSpaceModel) { + // TODO(FarisArmoush): insert the space under the parent, which is the space that was tapped + final newCommunity = widget.community.copyWith(); + final parentIndex = + newCommunity.spaces.indexWhere((s) => s.uuid == space.uuid); + if (parentIndex != -1) { + newCommunity.spaces.insert(parentIndex + 1, updatedSpaceModel); + } + context.read().add( + CommunitiesUpdateCommunity(newCommunity), + ); + }, ), ); 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 e6dfbb15..610962e5 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 @@ -1,14 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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/presentation/bloc/communities_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/color_manager.dart'; class CreateSpaceButton extends StatefulWidget { const CreateSpaceButton({ - required this.communityUuid, + required this.community, super.key, }); - final String communityUuid; + final CommunityModel community; @override State createState() => _CreateSpaceButtonState(); @@ -25,7 +28,15 @@ class _CreateSpaceButtonState extends State { child: InkWell( onTap: () => SpaceDetailsDialogHelper.showCreate( context, - communityUuid: widget.communityUuid, + communityUuid: widget.community.uuid, + onSuccess: (updatedSpaceModel) { + final newCommunity = widget.community.copyWith( + spaces: [updatedSpaceModel, ...widget.community.spaces], + ); + context.read().add( + CommunitiesUpdateCommunity(newCommunity), + ); + }, ), child: MouseRegion( onEnter: (_) => setState(() => _isHovered = true), 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 b325c1ec..11478fbe 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 @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/commun import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.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'; class SpaceManagementCommunityStructure extends StatelessWidget { @@ -12,19 +13,35 @@ class SpaceManagementCommunityStructure extends StatelessWidget { @override Widget build(BuildContext context) { - final selectionBloc = context.watch().state; - final selectedCommunity = selectionBloc.selectedCommunity; - final selectedSpace = selectionBloc.selectedSpace; - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - const CommunityStructureHeader(), - Visibility( - visible: selectedCommunity!.spaces.isNotEmpty, - replacement: _buildEmptyWidget(selectedCommunity), - child: _buildCanvas(selectedCommunity, selectedSpace), - ), - ], + return BlocBuilder( + builder: (context, state) { + final selectedCommunity = state.selectedCommunity; + final selectedSpace = state.selectedSpace; + + if (selectedCommunity == null) { + return const SizedBox.shrink(); + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CommunityStructureHeader(), + BlocBuilder( + builder: (context, state) { + final community = state.communities.firstWhere( + (element) => element.uuid == selectedCommunity.uuid, + orElse: () => selectedCommunity, + ); + return Visibility( + visible: community.spaces.isNotEmpty, + replacement: _buildEmptyWidget(community), + child: _buildCanvas(community, selectedSpace), + ); + }, + ), + ], + ); + }, ); } @@ -47,11 +64,7 @@ class SpaceManagementCommunityStructure extends StatelessWidget { child: Row( children: [ spacer, - Expanded( - child: CreateSpaceButton( - communityUuid: selectedCommunity.uuid, - ), - ), + Expanded(child: CreateSpaceButton(community: selectedCommunity)), spacer, ], ), 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 c5de7dad..45cb0e89 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 @@ -1,6 +1,9 @@ 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/create_space/data/services/remote_create_space_service.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_bloc.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'; @@ -14,6 +17,8 @@ abstract final class SpaceDetailsDialogHelper { static void showCreate( BuildContext context, { required String communityUuid, + required void Function(SpaceModel updatedSpaceModel)? onSuccess, + String? parentUuid, }) { showDialog( context: context, @@ -24,14 +29,41 @@ abstract final class SpaceDetailsDialogHelper { RemoteSpaceDetailsService(httpService: HTTPService()), ), ), + BlocProvider( + create: (context) => CreateSpaceBloc( + RemoteCreateSpaceService(HTTPService()), + ), + ), ], child: Builder( - builder: (context) => SpaceDetailsDialog( - context: context, - title: const SelectableText('Create Space'), - spaceModel: SpaceModel.empty(), - onSave: (space) {}, - communityUuid: communityUuid, + builder: (context) => BlocListener( + listener: (context, state) => switch (state) { + CreateSpaceInitial() => null, + CreateSpaceLoading() => _onLoading(context), + CreateSpaceSuccess() => _onCreateSuccess( + context, + state.space, + onSuccess, + ), + CreateSpaceFailure() => _onError(context, state.errorMessage), + }, + child: SpaceDetailsDialog( + context: context, + title: const SelectableText('Create Space'), + spaceModel: SpaceModel.empty(), + onSave: (space) { + context.read().add( + CreateSpace( + CreateSpaceParam( + communityUuid: communityUuid, + space: space, + parentUuid: parentUuid, + ), + ), + ); + }, + communityUuid: communityUuid, + ), ), ), ), @@ -135,4 +167,14 @@ abstract final class SpaceDetailsDialogHelper { ), ); } + + static void _onCreateSuccess( + BuildContext context, + SpaceModel space, + void Function(SpaceModel updatedSpaceModel)? onSuccess, + ) { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + onSuccess?.call(space); + } } From 6ec972a5207f993c919ef7884854a3560e548364 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 11:18:24 +0300 Subject: [PATCH 53/62] Integrate CommunitiesTreeSelectionBloc into CreateSpaceButton to handle space selection upon successful space creation. Update community state in CommunitiesBloc to reflect new space addition, enhancing user experience and maintainability. --- .../main_module/widgets/create_space_button.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 610962e5..4032c2ab 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,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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/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/color_manager.dart'; @@ -31,11 +32,17 @@ class _CreateSpaceButtonState extends State { communityUuid: widget.community.uuid, onSuccess: (updatedSpaceModel) { final newCommunity = widget.community.copyWith( - spaces: [updatedSpaceModel, ...widget.community.spaces], + spaces: [...widget.community.spaces, updatedSpaceModel], ); context.read().add( CommunitiesUpdateCommunity(newCommunity), ); + context.read().add( + SelectSpaceEvent( + space: updatedSpaceModel, + community: newCommunity, + ), + ); }, ), child: MouseRegion( From f03c28f7fdc1bcc11eba3aabea3b05bc8a06da3c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 11:47:27 +0300 Subject: [PATCH 54/62] Add recursive insertion method in SpacesRecursiveHelper for managing space hierarchy. Update CommunityStructureCanvas to utilize this method for inserting new spaces under the correct parent, enhancing community state management during space creation. --- .../helpers/spaces_recursive_helper.dart | 20 +++++++++++++++++++ .../widgets/community_structure_canvas.dart | 17 ++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart b/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart index b725bf31..4322c4e8 100644 --- a/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart +++ b/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart @@ -40,4 +40,24 @@ abstract final class SpacesRecursiveHelper { final nonNullSpaces = updatedSpaces.whereType().toList(); return nonNullSpaces; } + + static List recusrivelyInsert( + List spaces, + SpaceModel newSpace, + String parentUuid, + ) { + return spaces.map((space) { + if (space.uuid == parentUuid) { + return space.copyWith( + children: [...space.children, newSpace], + ); + } + if (space.children.isNotEmpty) { + return space.copyWith( + children: recusrivelyInsert(space.children, newSpace, parentUuid), + ); + } + return space; + }).toList(); + } } 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 63f8e8ec..03efab7d 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,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.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'; @@ -329,15 +330,15 @@ class _CommunityStructureCanvasState extends State communityUuid: widget.community.uuid, parentUuid: space.uuid, onSuccess: (updatedSpaceModel) { - // TODO(FarisArmoush): insert the space under the parent, which is the space that was tapped - final newCommunity = widget.community.copyWith(); - final parentIndex = - newCommunity.spaces.indexWhere((s) => s.uuid == space.uuid); - if (parentIndex != -1) { - newCommunity.spaces.insert(parentIndex + 1, updatedSpaceModel); - } + final updatedSpaces = SpacesRecursiveHelper.recusrivelyInsert( + spaces, + updatedSpaceModel, + space.uuid, + ); context.read().add( - CommunitiesUpdateCommunity(newCommunity), + CommunitiesUpdateCommunity( + widget.community.copyWith(spaces: updatedSpaces), + ), ); }, ), From fc70669f1d2032c5923f69d9d00007e9ca78e131 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 15:27:31 +0300 Subject: [PATCH 55/62] sends correct parentUuid key in create space. --- .../modules/create_space/domain/params/create_space_param.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart b/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart index 931588d2..90a82a6b 100644 --- a/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart +++ b/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart @@ -13,7 +13,7 @@ class CreateSpaceParam { Map toJson() { return { - 'spaceModelUuid': parentUuid, + 'parentUuid': parentUuid, ...space.toJson(), 'x': 0, 'y': 0, From c6729f476f808581823dced0235e595c86a9fe89 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 16 Jul 2025 15:36:49 +0300 Subject: [PATCH 56/62] Enhance booking system: update API endpoints, improve event loading with caching, and refine UI components --- .../services/remote_calendar_service.dart | 144 +-------- .../services/calendar_system_service.dart | 2 + .../bloc/calendar/events_bloc.dart | 73 ++++- .../bloc/calendar/events_state.dart | 8 +- .../presentation/view/booking_page.dart | 8 +- .../view/widgets/booking_sidebar.dart | 7 +- .../view/widgets/event_tile_widget.dart | 98 +++++-- .../view/widgets/room_list_item.dart | 4 + .../view/widgets/weekly_calendar_page.dart | 276 +++++++++--------- lib/utils/constants/api_const.dart | 3 +- 10 files changed, 307 insertions(+), 316 deletions(-) diff --git a/lib/pages/access_management/booking_system/data/services/remote_calendar_service.dart b/lib/pages/access_management/booking_system/data/services/remote_calendar_service.dart index aa3307d3..15fb1fd9 100644 --- a/lib/pages/access_management/booking_system/data/services/remote_calendar_service.dart +++ b/lib/pages/access_management/booking_system/data/services/remote_calendar_service.dart @@ -14,146 +14,20 @@ class RemoteCalendarService implements CalendarSystemService { @override Future getCalendarEvents({ required String spaceId, + required String month, + required String year, }) async { + try { - final response = await _httpService.get( - path: ApiEndpoints.getCalendarEvents, - queryParameters: { - 'spaceId': spaceId, - }, + return await _httpService.get( + path: ApiEndpoints.getBookings + .replaceAll('{mm}', month) + .replaceAll('{yyyy}', year) + .replaceAll('{space}', spaceId), expectedResponseModel: (json) { - return CalendarEventsResponse.fromJson( - json as Map, - ); + return CalendarEventsResponse.fromJson(json as Map); }, ); - - return CalendarEventsResponse.fromJson(response as Map); - } on DioException catch (e) { - final responseData = e.response?.data; - if (responseData is Map) { - final errorMessage = responseData['error']?['message'] as String? ?? - responseData['message'] as String? ?? - _defaultErrorMessage; - throw APIException(errorMessage); - } - throw APIException(_defaultErrorMessage); - } catch (e) { - throw APIException('$_defaultErrorMessage: ${e.toString()}'); - } - } -} - -class FakeRemoteCalendarService implements CalendarSystemService { - const FakeRemoteCalendarService(this._httpService, {this.useDummy = false}); - - final HTTPService _httpService; - final bool useDummy; - static const _defaultErrorMessage = 'Failed to load Calendar'; - - @override - Future getCalendarEvents({ - required String spaceId, - }) async { - if (useDummy) { - final dummyJson = { - 'statusCode': 200, - 'message': 'Successfully fetched all bookings', - 'data': [ - { - 'uuid': 'd4553fa6-a0c9-4f42-81c9-99a13a57bf80', - 'date': '2025-07-11T10:22:00.626Z', - 'startTime': '09:00:00', - 'endTime': '12:00:00', - 'cost': 10, - 'user': { - 'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e', - 'firstName': 'salsabeel', - 'lastName': 'abuzaid', - 'email': 'test@test.com', - 'companyName': null - }, - 'space': { - 'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e', - 'spaceName': '2(1)' - } - }, - { - 'uuid': 'e9b27af0-b963-4d98-9657-454c4ba78561', - 'date': '2025-07-11T10:22:00.626Z', - 'startTime': '12:00:00', - 'endTime': '13:00:00', - 'cost': 10, - 'user': { - 'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e', - 'firstName': 'salsabeel', - 'lastName': 'abuzaid', - 'email': 'test@test.com', - 'companyName': null - }, - 'space': { - 'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e', - 'spaceName': '2(1)' - } - }, - { - 'uuid': 'e9b27af0-b963-4d98-9657-454c4ba78561', - 'date': '2025-07-13T10:22:00.626Z', - 'startTime': '15:30:00', - 'endTime': '19:00:00', - 'cost': 20, - 'user': { - 'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e', - 'firstName': 'salsabeel', - 'lastName': 'abuzaid', - 'email': 'test@test.com', - 'companyName': null - }, - 'space': { - 'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e', - 'spaceName': '2(1)' - } - } - ], - 'success': true - }; - final response = CalendarEventsResponse.fromJson(dummyJson); - - // Filter events by spaceId - final filteredData = response.data.where((event) { - return event.space.uuid == spaceId; - }).toList(); - print('Filtering events for spaceId: $spaceId'); - print('Found ${filteredData.length} matching events'); - return filteredData.isNotEmpty - ? CalendarEventsResponse( - statusCode: response.statusCode, - message: response.message, - data: filteredData, - success: response.success, - ) - : CalendarEventsResponse( - statusCode: 404, - message: 'No events found for spaceId: $spaceId', - data: [], - success: false, - ); - } - - try { - final response = await _httpService.get( - path: ApiEndpoints.getCalendarEvents, - queryParameters: { - 'spaceId': spaceId, - }, - expectedResponseModel: (json) { - return CalendarEventsResponse.fromJson( - json as Map, - ); - }, - ); - - return CalendarEventsResponse.fromJson(response as Map); } on DioException catch (e) { final responseData = e.response?.data; if (responseData is Map) { diff --git a/lib/pages/access_management/booking_system/domain/services/calendar_system_service.dart b/lib/pages/access_management/booking_system/domain/services/calendar_system_service.dart index 9e178040..d999ef1a 100644 --- a/lib/pages/access_management/booking_system/domain/services/calendar_system_service.dart +++ b/lib/pages/access_management/booking_system/domain/services/calendar_system_service.dart @@ -3,5 +3,7 @@ import 'package:syncrow_web/pages/access_management/booking_system/domain/models abstract class CalendarSystemService { Future getCalendarEvents({ required String spaceId, + required String month, + required String year, }); } diff --git a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart index da782d74..1985348e 100644 --- a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart +++ b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart @@ -4,35 +4,70 @@ import 'package:calendar_view/calendar_view.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart'; - part 'events_event.dart'; part 'events_state.dart'; class CalendarEventsBloc extends Bloc { final EventController eventController = EventController(); final CalendarSystemService calendarService; + final Map> _eventsCache = {}; + + String? _lastSpaceId; + int? _lastMonth; + int? _lastYear; CalendarEventsBloc({required this.calendarService}) : super(EventsInitial()) { on(_onLoadEvents); on(_onAddEvent); - on(_onStartTimer); on(_onDisposeResources); on(_onGoToWeek); } - Future _onLoadEvents( LoadEvents event, Emitter emit, ) async { + final month = event.weekEnd.month; + final year = event.weekEnd.year; + final cacheKey = + '${event.spaceId}-$year-${month.toString().padLeft(2, '0')}'; + + if (_eventsCache.containsKey(cacheKey)) { + final cachedEvents = _eventsCache[cacheKey]!; + eventController.addAll(cachedEvents); + emit(EventsLoaded( + events: cachedEvents, + spaceId: event.spaceId, + month: month, + year: year, + )); + return; + } + if (_lastSpaceId == event.spaceId && + _lastMonth == month && + _lastYear == year) { + return; + } + emit(EventsLoading()); try { final response = await calendarService.getCalendarEvents( + month: month.toString().padLeft(2, '0'), + year: year.toString(), spaceId: event.spaceId, ); - final events = - response.data.map(_toCalendarEventData).toList(); + + final events = response.data.map(_toCalendarEventData).toList(); + _eventsCache[cacheKey] = events; eventController.addAll(events); - emit(EventsLoaded(events: events)); + _lastSpaceId = event.spaceId; + _lastMonth = month; + _lastYear = year; + emit(EventsLoaded( + events: events, + spaceId: event.spaceId, + month: month, + year: year, + )); } catch (e) { emit(EventsError('Failed to load events')); } @@ -40,16 +75,28 @@ class CalendarEventsBloc extends Bloc { void _onAddEvent(AddEvent event, Emitter emit) { eventController.add(event.event); + if (state is EventsLoaded) { final loaded = state as EventsLoaded; + final cacheKey = + '${loaded.spaceId}-${loaded.year}-${loaded.month.toString().padLeft(2, '0')}'; + + if (_eventsCache.containsKey(cacheKey)) { + final cachedEvents = + List.from(_eventsCache[cacheKey]!); + cachedEvents.add(event.event); + _eventsCache[cacheKey] = cachedEvents; + } + emit(EventsLoaded( events: [...eventController.events], + spaceId: loaded.spaceId, + month: loaded.month, + year: loaded.year, )); } } - void _onStartTimer(StartTimer event, Emitter emit) {} - void _onDisposeResources( DisposeResources event, Emitter emit) { eventController.dispose(); @@ -61,6 +108,9 @@ class CalendarEventsBloc extends Bloc { final newWeekDays = _getWeekDays(event.weekDate); emit(EventsLoaded( events: loaded.events, + spaceId: loaded.spaceId, + month: loaded.month, + year: loaded.year, )); } } @@ -90,14 +140,13 @@ class CalendarEventsBloc extends Bloc { ); return CalendarEventData( - date: startTime, + date: startTime, startTime: startTime, endTime: endTime, - title: - '${booking.space.spaceName} - ${booking.user.firstName} ${booking.user.lastName}', + title: '${booking.user.firstName} ${booking.user.lastName}', description: 'Cost: ${booking.cost}', color: Colors.blue, - event: booking, + event: booking, ); } diff --git a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_state.dart b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_state.dart index bc0c2e31..b98fd2fb 100644 --- a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_state.dart +++ b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_state.dart @@ -7,11 +7,17 @@ class EventsInitial extends CalendarEventState {} class EventsLoading extends CalendarEventState {} -class EventsLoaded extends CalendarEventState { +final class EventsLoaded extends CalendarEventState { final List events; + final String spaceId; + final int month; + final int year; EventsLoaded({ required this.events, + required this.spaceId, + required this.month, + required this.year, }); } diff --git a/lib/pages/access_management/booking_system/presentation/view/booking_page.dart b/lib/pages/access_management/booking_system/presentation/view/booking_page.dart index 0ff9aaf6..8d04f932 100644 --- a/lib/pages/access_management/booking_system/presentation/view/booking_page.dart +++ b/lib/pages/access_management/booking_system/presentation/view/booking_page.dart @@ -62,8 +62,9 @@ class _BookingPageState extends State { BlocProvider(create: (_) => DateSelectionBloc()), BlocProvider( create: (_) => CalendarEventsBloc( - calendarService: - FakeRemoteCalendarService(HTTPService(), useDummy: true), + calendarService: RemoteCalendarService( + HTTPService(), + ), ), ), ], @@ -138,7 +139,7 @@ class _BookingPageState extends State { ), ), Expanded( - flex: 4, + flex: 5, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( @@ -187,6 +188,7 @@ class _BookingPageState extends State { ], ), Expanded( + flex: 5, child: BlocBuilder( builder: (context, roomState) { diff --git a/lib/pages/access_management/booking_system/presentation/view/widgets/booking_sidebar.dart b/lib/pages/access_management/booking_system/presentation/view/widgets/booking_sidebar.dart index e3d84924..666df3bb 100644 --- a/lib/pages/access_management/booking_system/presentation/view/widgets/booking_sidebar.dart +++ b/lib/pages/access_management/booking_system/presentation/view/widgets/booking_sidebar.dart @@ -72,11 +72,7 @@ class __SidebarContentState extends State<_SidebarContent> { @override Widget build(BuildContext context) { return BlocConsumer( - listener: (context, state) { - if (state.currentPage == 1 && searchController.text.isNotEmpty) { - searchController.clear(); - } - }, + listener: (context, state) {}, builder: (context, state) { return Column( children: [ @@ -147,6 +143,7 @@ class __SidebarContentState extends State<_SidebarContent> { IconButton( icon: const Icon(Icons.close), onPressed: () { + searchController.clear(); context.read().add(ResetSearch()); }, ), diff --git a/lib/pages/access_management/booking_system/presentation/view/widgets/event_tile_widget.dart b/lib/pages/access_management/booking_system/presentation/view/widgets/event_tile_widget.dart index 6c0f9cb2..b7e942d6 100644 --- a/lib/pages/access_management/booking_system/presentation/view/widgets/event_tile_widget.dart +++ b/lib/pages/access_management/booking_system/presentation/view/widgets/event_tile_widget.dart @@ -1,16 +1,15 @@ import 'package:calendar_view/calendar_view.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class EventTileWidget extends StatelessWidget { final List> events; - const EventTileWidget({ super.key, required this.events, }); - @override Widget build(BuildContext context) { return Container( @@ -18,39 +17,86 @@ class EventTileWidget extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: events.map((event) { - final bool isEventEnded = + final booking = event.event is CalendarEventBooking + ? event.event! as CalendarEventBooking + : null; + + final companyName = booking?.user.companyName ?? 'Unknown Company'; + final startTime = DateFormat('hh:mm a').format(event.startTime!); + final endTime = DateFormat('hh:mm a').format(event.endTime!); + final isEventEnded = event.endTime != null && event.endTime!.isBefore(DateTime.now()); + + final duration = event.endTime!.difference(event.startTime!); + final bool isLongEnough = duration.inMinutes >= 31; return Expanded( child: Container( width: double.infinity, - padding: const EdgeInsets.all(6), + padding: EdgeInsets.all(5), decoration: BoxDecoration( color: isEventEnded - ? ColorsManager.lightGrayBorderColor - : ColorsManager.blue1.withOpacity(0.25), + ? ColorsManager.grayColor.withOpacity(0.1) + : ColorsManager.blue1.withOpacity(0.1), borderRadius: BorderRadius.circular(6), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - DateFormat('h:mm a').format(event.startTime!), - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 12, - color: Colors.black87, - ), + border: const Border( + left: BorderSide( + color: ColorsManager.grayColor, + width: 4, ), - const SizedBox(height: 2), - Text( - event.title, - style: const TextStyle( - fontSize: 12, - color: ColorsManager.blackColor, - ), - ), - ], + ), ), + child: isLongEnough + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '$startTime - $endTime', + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12, + color: isEventEnded + ? ColorsManager.grayColor.withOpacity(0.9) + : ColorsManager.blackColor, + fontWeight: FontWeight.w400, + ), + ), + const SizedBox(height: 2), + Text( + event.title, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14, + color: isEventEnded + ? ColorsManager.grayColor + : ColorsManager.blackColor, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 2), + Text( + companyName, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14, + color: isEventEnded + ? ColorsManager.grayColor.withOpacity(0.9) + : ColorsManager.blackColor, + fontWeight: FontWeight.w400, + ), + ), + ], + ) + : Text( + event.title, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14, + color: isEventEnded + ? ColorsManager.grayColor + : ColorsManager.blackColor, + fontWeight: FontWeight.bold, + ), + ), ), ); }).toList(), diff --git a/lib/pages/access_management/booking_system/presentation/view/widgets/room_list_item.dart b/lib/pages/access_management/booking_system/presentation/view/widgets/room_list_item.dart index 4a4b608d..83eda16b 100644 --- a/lib/pages/access_management/booking_system/presentation/view/widgets/room_list_item.dart +++ b/lib/pages/access_management/booking_system/presentation/view/widgets/room_list_item.dart @@ -24,17 +24,21 @@ class RoomListItem extends StatelessWidget { activeColor: ColorsManager.primaryColor, title: Text( room.spaceName, + maxLines: 2, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: ColorsManager.lightGrayColor, fontWeight: FontWeight.w700, + overflow: TextOverflow.ellipsis, fontSize: 12), ), subtitle: Text( room.virtualLocation, + maxLines: 2, style: Theme.of(context).textTheme.bodySmall?.copyWith( fontSize: 10, fontWeight: FontWeight.w400, color: ColorsManager.textGray, + overflow: TextOverflow.ellipsis, ), ), ); diff --git a/lib/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart b/lib/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart index 0dd343a7..03972632 100644 --- a/lib/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart +++ b/lib/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:calendar_view/calendar_view.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/event_tile_widget.dart'; -import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/hatched_column_background.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/time_line_widget.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/week_day_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -23,6 +22,12 @@ class WeeklyCalendarPage extends StatelessWidget { this.endTime, this.selectedDateFromSideBarCalender, }); + static const double timeLineWidth = 65; + static const int totalDays = 7; + static const double dayColumnWidth = 220; // or any width you want + + final double calendarContentWidth = + timeLineWidth + (totalDays * dayColumnWidth); @override Widget build(BuildContext context) { @@ -52,154 +57,159 @@ class WeeklyCalendarPage extends StatelessWidget { ); } - final weekDays = _getWeekDays(weekStart); + - final selectedDayIndex = - weekDays.indexWhere((d) => isSameDay(d, selectedDate)); - final selectedSidebarIndex = selectedDateFromSideBarCalender == null - ? -1 - : weekDays - .indexWhere((d) => isSameDay(d, selectedDateFromSideBarCalender!)); + const double timeLineWidth = 65; - const double timeLineWidth = 80; - const int totalDays = 7; - final DateTime highlightStart = DateTime(2025, 7, 10); - final DateTime highlightEnd = DateTime(2025, 7, 19); return LayoutBuilder( builder: (context, constraints) { - final double calendarWidth = constraints.maxWidth; - final double dayColumnWidth = - (calendarWidth - timeLineWidth) / totalDays - 0.1; + bool isInRange(DateTime date, DateTime start, DateTime end) { - return !date.isBefore(start) && !date.isAfter(end); + !date.isBefore(start) && !date.isAfter(end); + // remove this line and Check if the date is within the range + return false; } - return Padding( - padding: const EdgeInsets.only(left: 25.0, right: 25.0, top: 25), - child: Stack( - children: [ - WeekView( - weekDetectorBuilder: ({ - required date, - required height, - required heightPerMinute, - required minuteSlotSize, - required width, - }) { - return isInRange(date, highlightStart, highlightEnd) - ? HatchedColumnBackground( - backgroundColor: ColorsManager.grey800, - lineColor: ColorsManager.textGray, - opacity: 0.3, - stripeSpacing: 12, - borderRadius: BorderRadius.circular(8), - ) - : const SizedBox(); - }, - pageViewPhysics: const NeverScrollableScrollPhysics(), - key: ValueKey(weekStart), - controller: eventController, - initialDay: weekStart, - startHour: startHour - 1, - endHour: endHour, - heightPerMinute: 1.1, - showLiveTimeLineInAllDays: false, - showVerticalLines: true, - emulateVerticalOffsetBy: -80, - startDay: WeekDays.monday, - liveTimeIndicatorSettings: const LiveTimeIndicatorSettings( - showBullet: false, - height: 0, - ), - weekDayBuilder: (date) { - return WeekDayHeader( - date: date, - isSelectedDay: isSameDay(date, selectedDate), - ); - }, - timeLineBuilder: (date) { - return TimeLineWidget(date: date); - }, - timeLineWidth: timeLineWidth, - weekPageHeaderBuilder: (start, end) => Container(), - weekTitleHeight: 60, - weekNumberBuilder: (firstDayOfWeek) => Padding( - padding: const EdgeInsets.only(right: 15, bottom: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Text( - firstDayOfWeek.timeZoneName.replaceAll(':00', ''), - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - fontSize: 12, - color: ColorsManager.blackColor, - fontWeight: FontWeight.w400, - ), + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: calendarContentWidth, + child: Padding( + padding: + const EdgeInsets.only(left: 25.0, right: 25.0, top: 25), + child: Stack( + children: [ + Container( + child: WeekView( + minuteSlotSize: MinuteSlotSize.minutes15, + weekDetectorBuilder: ({ + required date, + required height, + required heightPerMinute, + required minuteSlotSize, + required width, + }) { + final isSelected = isSameDay(date, selectedDate); + final isSidebarSelected = + selectedDateFromSideBarCalender != null && + isSameDay( + date, selectedDateFromSideBarCalender!); + if (isSidebarSelected && !isSelected) { + return Container( + height: height, + width: width, + decoration: BoxDecoration( + color: Colors.orange.withOpacity(0.13), + borderRadius: BorderRadius.circular(8), + ), + ); + } else if (isSelected) { + return Container( + height: height, + width: width, + decoration: BoxDecoration( + color: + ColorsManager.spaceColor.withOpacity(0.07), + borderRadius: BorderRadius.circular(8), + ), + ); + } + return const SizedBox.shrink(); + }, + + // weekDetectorBuilder: ({ + // required date, + // required height, + // required heightPerMinute, + // required minuteSlotSize, + // required width, + // }) { + // return isInRange(date, highlightStart, highlightEnd) + // ? HatchedColumnBackground( + // backgroundColor: ColorsManager.grey800, + // lineColor: ColorsManager.textGray, + // opacity: 0.3, + // stripeSpacing: 12, + // borderRadius: BorderRadius.circular(8), + // ) + // : const SizedBox(); + // }, + pageViewPhysics: const NeverScrollableScrollPhysics(), + key: ValueKey(weekStart), + controller: eventController, + initialDay: weekStart, + startHour: startHour - 1, + endHour: endHour, + heightPerMinute: 1.7, + showLiveTimeLineInAllDays: false, + showVerticalLines: true, + emulateVerticalOffsetBy: -80, + startDay: WeekDays.monday, + liveTimeIndicatorSettings: + const LiveTimeIndicatorSettings( + showBullet: false, + height: 0, + ), + weekDayBuilder: (date) { + return WeekDayHeader( + date: date, + isSelectedDay: isSameDay(date, selectedDate), + ); + }, + timeLineBuilder: (date) { + return TimeLineWidget(date: date); + }, + timeLineWidth: timeLineWidth, + weekPageHeaderBuilder: (start, end) => Container(), + weekTitleHeight: 60, + weekNumberBuilder: (firstDayOfWeek) => Padding( + padding: const EdgeInsets.only(right: 15, bottom: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + firstDayOfWeek.timeZoneName + .replaceAll(':00', ''), + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + fontSize: 12, + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + eventTileBuilder: (date, events, boundary, start, end) { + return EventTileWidget( + events: events, + ); + }, ), - ], - ), - ), - eventTileBuilder: (date, events, boundary, start, end) { - return EventTileWidget( - events: events, - ); - }, - ), - if (selectedDayIndex >= 0) - Positioned( - left: (timeLineWidth + 3) + - (dayColumnWidth - 8) * (selectedDayIndex - 0.01), - top: 0, - bottom: 0, - width: dayColumnWidth, - child: IgnorePointer( - child: Container( - margin: const EdgeInsets.symmetric( - vertical: 0, horizontal: 4), - color: ColorsManager.spaceColor.withOpacity(0.07), ), - ), - ), - if (selectedSidebarIndex >= 0 && - selectedSidebarIndex != selectedDayIndex) - Positioned( - left: (timeLineWidth + 3) + - (dayColumnWidth - 8) * (selectedSidebarIndex - 0.01), - top: 0, - bottom: 0, - width: dayColumnWidth, - child: IgnorePointer( - child: Container( - margin: const EdgeInsets.symmetric( - vertical: 0, horizontal: 4), - color: Colors.orange.withOpacity(0.14), + Positioned( + right: 0, + top: 50, + bottom: 0, + child: IgnorePointer( + child: Container( + width: 1, + color: Theme.of(context).scaffoldBackgroundColor, + ), + ), ), - ), - ), - Positioned( - right: 0, - top: 50, - bottom: 0, - child: IgnorePointer( - child: Container( - width: 1, - color: Theme.of(context).scaffoldBackgroundColor, - ), + ], ), ), - ], - ), - ); + )); }, ); } - List _getWeekDays(DateTime date) { - final int weekday = date.weekday; - final DateTime monday = date.subtract(Duration(days: weekday - 1)); - return List.generate(7, (i) => monday.add(Duration(days: i))); - } + } bool isSameDay(DateTime d1, DateTime d2) { diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index b3c8a168..e99f4796 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -140,5 +140,6 @@ abstract class ApiEndpoints { static const String saveSchedule = '/schedule/{deviceUuid}'; static const String getBookableSpaces = '/bookable-spaces'; - static const String getCalendarEvents = '/api'; + static const String getBookings = + '/bookings?month={mm}%2F{yyyy}&space={space}'; } From 8522c0bbc35bf123941faea4f6a5450927f6207f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 16:26:15 +0300 Subject: [PATCH 57/62] Made `SpacesRecursiveHelper` private, to avoid creating unnecessary instances, since all its methods are static. --- .../main_module/helpers/spaces_recursive_helper.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart b/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart index 4322c4e8..c470ffc1 100644 --- a/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart +++ b/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart @@ -2,6 +2,8 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; abstract final class SpacesRecursiveHelper { + const SpacesRecursiveHelper._(); + static List recusrivelyUpdate( List spaces, SpaceDetailsModel updatedSpace, From 7b5b40a03cda611236defe5a4ad67ebae5ba2bab Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 16:30:38 +0300 Subject: [PATCH 58/62] Refactor `recursivelyInsert` method in `SpacesRecursiveHelper` to use named parameters. Update `CommunityStructureCanvas` to reflect these changes, ensuring correct space insertion under the specified parent. --- .../helpers/spaces_recursive_helper.dart | 22 ++++++++++++------- .../widgets/community_structure_canvas.dart | 8 +++---- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart b/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart index c470ffc1..de5ce34f 100644 --- a/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart +++ b/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart @@ -43,20 +43,26 @@ abstract final class SpacesRecursiveHelper { return nonNullSpaces; } - static List recusrivelyInsert( - List spaces, - SpaceModel newSpace, - String parentUuid, - ) { + static List recursivelyInsert({ + required List spaces, + required String parentUuid, + required SpaceModel newSpace, + }) { return spaces.map((space) { - if (space.uuid == parentUuid) { + final isParentSpace = space.uuid == parentUuid; + if (isParentSpace) { return space.copyWith( children: [...space.children, newSpace], ); } - if (space.children.isNotEmpty) { + final hasChildren = space.children.isNotEmpty; + if (hasChildren) { return space.copyWith( - children: recusrivelyInsert(space.children, newSpace, parentUuid), + children: recursivelyInsert( + spaces: space.children, + parentUuid: parentUuid, + newSpace: newSpace, + ), ); } return space; 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 03efab7d..692ffc0a 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 @@ -330,10 +330,10 @@ class _CommunityStructureCanvasState extends State communityUuid: widget.community.uuid, parentUuid: space.uuid, onSuccess: (updatedSpaceModel) { - final updatedSpaces = SpacesRecursiveHelper.recusrivelyInsert( - spaces, - updatedSpaceModel, - space.uuid, + final updatedSpaces = SpacesRecursiveHelper.recursivelyInsert( + spaces: widget.community.spaces, + parentUuid: space.uuid, + newSpace: updatedSpaceModel, ); context.read().add( CommunitiesUpdateCommunity( From c9b8fbb0c269210ce78c344511e92702e5b86048 Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 17 Jul 2025 11:20:32 +0300 Subject: [PATCH 59/62] Refactor calendar event loading: replace parameters with LoadEventsParam class and implement memory caching for improved performance --- .../services/remote_calendar_service.dart | 9 ++-- .../domain/LoadEventsParam.dart | 28 ++++++++++++ .../services/calendar_system_service.dart | 5 +-- .../bloc/calendar/events_bloc.dart | 45 ++++++++----------- .../bloc/calendar/events_event.dart | 12 ++--- .../model/memory_bookable_space_service.dart | 35 +++++++++++++++ .../presentation/view/booking_page.dart | 11 +++-- .../view/widgets/weekly_calendar_page.dart | 2 +- 8 files changed, 100 insertions(+), 47 deletions(-) create mode 100644 lib/pages/access_management/booking_system/domain/LoadEventsParam.dart create mode 100644 lib/pages/access_management/booking_system/presentation/model/memory_bookable_space_service.dart diff --git a/lib/pages/access_management/booking_system/data/services/remote_calendar_service.dart b/lib/pages/access_management/booking_system/data/services/remote_calendar_service.dart index 15fb1fd9..55a5b0b8 100644 --- a/lib/pages/access_management/booking_system/data/services/remote_calendar_service.dart +++ b/lib/pages/access_management/booking_system/data/services/remote_calendar_service.dart @@ -1,4 +1,5 @@ import 'package:dio/dio.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart'; import 'package:syncrow_web/services/api/api_exception.dart'; @@ -13,17 +14,17 @@ class RemoteCalendarService implements CalendarSystemService { @override Future getCalendarEvents({ - required String spaceId, - required String month, - required String year, + required LoadEventsParam params, }) async { + final month = params.startDate.month.toString().padLeft(2, '0'); + final year = params.startDate.year.toString(); try { return await _httpService.get( path: ApiEndpoints.getBookings .replaceAll('{mm}', month) .replaceAll('{yyyy}', year) - .replaceAll('{space}', spaceId), + .replaceAll('{space}', params.id), expectedResponseModel: (json) { return CalendarEventsResponse.fromJson(json as Map); }, diff --git a/lib/pages/access_management/booking_system/domain/LoadEventsParam.dart b/lib/pages/access_management/booking_system/domain/LoadEventsParam.dart new file mode 100644 index 00000000..f5088b6e --- /dev/null +++ b/lib/pages/access_management/booking_system/domain/LoadEventsParam.dart @@ -0,0 +1,28 @@ +import 'package:equatable/equatable.dart'; + +class LoadEventsParam extends Equatable { + final DateTime startDate; + final DateTime endDate; + final String id; + + const LoadEventsParam({ + required this.startDate, + required this.endDate, + required this.id, + }); + + @override + List get props => [startDate, endDate, id]; + + LoadEventsParam copyWith({ + DateTime? startDate, + DateTime? endDate, + String? id, + }) { + return LoadEventsParam( + startDate: startDate ?? this.startDate, + endDate: endDate ?? this.endDate, + id: id ?? this.id, + ); + } +} diff --git a/lib/pages/access_management/booking_system/domain/services/calendar_system_service.dart b/lib/pages/access_management/booking_system/domain/services/calendar_system_service.dart index d999ef1a..3522054c 100644 --- a/lib/pages/access_management/booking_system/domain/services/calendar_system_service.dart +++ b/lib/pages/access_management/booking_system/domain/services/calendar_system_service.dart @@ -1,9 +1,8 @@ +import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart'; abstract class CalendarSystemService { Future getCalendarEvents({ - required String spaceId, - required String month, - required String year, + required LoadEventsParam params, }); } diff --git a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart index 1985348e..3945e5f5 100644 --- a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart +++ b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart @@ -2,8 +2,10 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:calendar_view/calendar_view.dart'; import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/presentation/model/memory_bookable_space_service.dart'; part 'events_event.dart'; part 'events_state.dart'; @@ -11,12 +13,14 @@ class CalendarEventsBloc extends Bloc { final EventController eventController = EventController(); final CalendarSystemService calendarService; final Map> _eventsCache = {}; + final MemoryBookableSpaceService memoryService; - String? _lastSpaceId; - int? _lastMonth; - int? _lastYear; - CalendarEventsBloc({required this.calendarService}) : super(EventsInitial()) { + + CalendarEventsBloc({ + required this.calendarService, + required this.memoryService, + }) : super(EventsInitial()) { on(_onLoadEvents); on(_onAddEvent); on(_onDisposeResources); @@ -26,45 +30,32 @@ class CalendarEventsBloc extends Bloc { LoadEvents event, Emitter emit, ) async { - final month = event.weekEnd.month; - final year = event.weekEnd.year; - final cacheKey = - '${event.spaceId}-$year-${month.toString().padLeft(2, '0')}'; - - if (_eventsCache.containsKey(cacheKey)) { - final cachedEvents = _eventsCache[cacheKey]!; + final param = event.param; + final month = param.endDate.month; + final year = param.endDate.year; + final spaceId = param.id; + final cachedEvents = memoryService.getEvents(spaceId, year, month); + if (cachedEvents != null) { eventController.addAll(cachedEvents); emit(EventsLoaded( events: cachedEvents, - spaceId: event.spaceId, + spaceId: spaceId, month: month, year: year, )); return; } - if (_lastSpaceId == event.spaceId && - _lastMonth == month && - _lastYear == year) { - return; - } emit(EventsLoading()); try { - final response = await calendarService.getCalendarEvents( - month: month.toString().padLeft(2, '0'), - year: year.toString(), - spaceId: event.spaceId, - ); + final response = await calendarService.getCalendarEvents(params: param); final events = response.data.map(_toCalendarEventData).toList(); - _eventsCache[cacheKey] = events; + memoryService.setEvents(spaceId, year, month, events); eventController.addAll(events); - _lastSpaceId = event.spaceId; - _lastMonth = month; - _lastYear = year; emit(EventsLoaded( events: events, - spaceId: event.spaceId, + spaceId: spaceId, month: month, year: year, )); diff --git a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_event.dart b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_event.dart index 4f4cafcf..6a368e17 100644 --- a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_event.dart +++ b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_event.dart @@ -6,17 +6,11 @@ abstract class CalendarEventsEvent { } class LoadEvents extends CalendarEventsEvent { - final String spaceId; - final DateTime weekStart; - final DateTime weekEnd; - - const LoadEvents({ - required this.spaceId, - required this.weekStart, - required this.weekEnd, - }); + final LoadEventsParam param; + const LoadEvents(this.param); } + class AddEvent extends CalendarEventsEvent { final CalendarEventData event; const AddEvent(this.event); diff --git a/lib/pages/access_management/booking_system/presentation/model/memory_bookable_space_service.dart b/lib/pages/access_management/booking_system/presentation/model/memory_bookable_space_service.dart new file mode 100644 index 00000000..c9017d90 --- /dev/null +++ b/lib/pages/access_management/booking_system/presentation/model/memory_bookable_space_service.dart @@ -0,0 +1,35 @@ +import 'package:calendar_view/calendar_view.dart'; + +class MemoryBookableSpaceService { + final Map> _eventsCache = {}; + + List? getEvents(String spaceId, int year, int month) { + final key = _generateKey(spaceId, year, month); + return _eventsCache[key]; + } + + void setEvents( + String spaceId, + int year, + int month, + List events, + ) { + final key = _generateKey(spaceId, year, month); + _eventsCache[key] = events; + } + + void addEvent(String spaceId, int year, int month, CalendarEventData event) { + final key = _generateKey(spaceId, year, month); + final events = _eventsCache[key] ?? []; + events.add(event); + _eventsCache[key] = events; + } + + void clear() { + _eventsCache.clear(); + } + + String _generateKey(String spaceId, int year, int month) { + return '$spaceId-$year-${month.toString().padLeft(2, '0')}'; + } +} \ No newline at end of file diff --git a/lib/pages/access_management/booking_system/presentation/view/booking_page.dart b/lib/pages/access_management/booking_system/presentation/view/booking_page.dart index 8d04f932..1030462d 100644 --- a/lib/pages/access_management/booking_system/presentation/view/booking_page.dart +++ b/lib/pages/access_management/booking_system/presentation/view/booking_page.dart @@ -2,11 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:calendar_view/calendar_view.dart'; import 'package:syncrow_web/pages/access_management/booking_system/data/services/remote_calendar_service.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_bloc.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_event.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_state.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/selected_bookable_space_bloc/selected_bookable_space_bloc.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/presentation/model/memory_bookable_space_service.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/booking_sidebar.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/custom_calendar_page.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart'; @@ -46,9 +48,11 @@ class _BookingPageState extends State { if (selectedRoom != null) { context.read().add( LoadEvents( - spaceId: selectedRoom.uuid, - weekStart: dateState.weekStart, - weekEnd: dateState.weekStart.add(const Duration(days: 6)), + LoadEventsParam( + startDate: dateState.weekStart, + endDate: dateState.weekStart.add(const Duration(days: 6)), + id: selectedRoom.uuid, + ), ), ); } @@ -62,6 +66,7 @@ class _BookingPageState extends State { BlocProvider(create: (_) => DateSelectionBloc()), BlocProvider( create: (_) => CalendarEventsBloc( + memoryService: MemoryBookableSpaceService(), calendarService: RemoteCalendarService( HTTPService(), ), diff --git a/lib/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart b/lib/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart index 03972632..2bfd5429 100644 --- a/lib/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart +++ b/lib/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart @@ -24,7 +24,7 @@ class WeeklyCalendarPage extends StatelessWidget { }); static const double timeLineWidth = 65; static const int totalDays = 7; - static const double dayColumnWidth = 220; // or any width you want + static const double dayColumnWidth = 220; final double calendarContentWidth = timeLineWidth + (totalDays * dayColumnWidth); From fe2f4a872ba6535d02f037d95888df17bc115e3e Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 17 Jul 2025 12:38:58 +0300 Subject: [PATCH 60/62] Refactor memory event handling: replace MemoryBookableSpaceService with a new implementation and integrate caching logic in CalendarEventsBloc --- .../memory_bookable_space_service.dart | 63 +++++++++++++++++++ .../domain/LoadEventsParam.dart | 6 ++ .../bloc/calendar/events_bloc.dart | 28 +-------- .../model/memory_bookable_space_service.dart | 35 ----------- .../presentation/view/booking_page.dart | 17 ++--- 5 files changed, 79 insertions(+), 70 deletions(-) create mode 100644 lib/pages/access_management/booking_system/data/services/memory_bookable_space_service.dart delete mode 100644 lib/pages/access_management/booking_system/presentation/model/memory_bookable_space_service.dart diff --git a/lib/pages/access_management/booking_system/data/services/memory_bookable_space_service.dart b/lib/pages/access_management/booking_system/data/services/memory_bookable_space_service.dart new file mode 100644 index 00000000..7aec2398 --- /dev/null +++ b/lib/pages/access_management/booking_system/data/services/memory_bookable_space_service.dart @@ -0,0 +1,63 @@ +import 'package:syncrow_web/pages/access_management/booking_system/data/services/remote_calendar_service.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart'; + +class MemoryBookableSpaceService implements CalendarSystemService { + final Map _eventsCache = {}; + + @override + Future getCalendarEvents({ + required LoadEventsParam params, + }) async { + final key = params.generateKey(); + + return _eventsCache[key]!; + } + + void setEvents( + LoadEventsParam param, + CalendarEventsResponse events, + ) { + final key = param.generateKey(); + _eventsCache[key] = events; + } + + void addEvent(LoadEventsParam param, CalendarEventsResponse event) { + final key = param.generateKey(); + + _eventsCache[key] = event; + } + + void clear() { + _eventsCache.clear(); + } +} + +class MemoryCalendarServiceWithRemoteFallback implements CalendarSystemService { + final MemoryBookableSpaceService memoryService; + final RemoteCalendarService remoteService; + + MemoryCalendarServiceWithRemoteFallback({ + required this.memoryService, + required this.remoteService, + }); + + @override + Future getCalendarEvents({ + required LoadEventsParam params, + }) async { + final key = params.generateKey(); + final doesExistInMemory = memoryService._eventsCache.containsKey(key); + + if (doesExistInMemory) { + return memoryService.getCalendarEvents(params: params); + } else { + final remoteResult = + await remoteService.getCalendarEvents(params: params); + memoryService.setEvents(params, remoteResult); + + return remoteResult; + } + } +} diff --git a/lib/pages/access_management/booking_system/domain/LoadEventsParam.dart b/lib/pages/access_management/booking_system/domain/LoadEventsParam.dart index f5088b6e..542dd5dc 100644 --- a/lib/pages/access_management/booking_system/domain/LoadEventsParam.dart +++ b/lib/pages/access_management/booking_system/domain/LoadEventsParam.dart @@ -26,3 +26,9 @@ class LoadEventsParam extends Equatable { ); } } + +extension KeyGenerator on LoadEventsParam { + String generateKey() { + return '$id-${startDate.year}-${startDate.month.toString().padLeft(2, '0')}'; + } +} \ No newline at end of file diff --git a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart index 3945e5f5..b42947bd 100644 --- a/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart +++ b/lib/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart @@ -5,21 +5,16 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart'; -import 'package:syncrow_web/pages/access_management/booking_system/presentation/model/memory_bookable_space_service.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/data/services/memory_bookable_space_service.dart'; part 'events_event.dart'; part 'events_state.dart'; class CalendarEventsBloc extends Bloc { final EventController eventController = EventController(); final CalendarSystemService calendarService; - final Map> _eventsCache = {}; - final MemoryBookableSpaceService memoryService; - - CalendarEventsBloc({ required this.calendarService, - required this.memoryService, }) : super(EventsInitial()) { on(_onLoadEvents); on(_onAddEvent); @@ -34,24 +29,12 @@ class CalendarEventsBloc extends Bloc { final month = param.endDate.month; final year = param.endDate.year; final spaceId = param.id; - final cachedEvents = memoryService.getEvents(spaceId, year, month); - if (cachedEvents != null) { - eventController.addAll(cachedEvents); - emit(EventsLoaded( - events: cachedEvents, - spaceId: spaceId, - month: month, - year: year, - )); - return; - } emit(EventsLoading()); try { final response = await calendarService.getCalendarEvents(params: param); final events = response.data.map(_toCalendarEventData).toList(); - memoryService.setEvents(spaceId, year, month, events); eventController.addAll(events); emit(EventsLoaded( events: events, @@ -69,15 +52,6 @@ class CalendarEventsBloc extends Bloc { if (state is EventsLoaded) { final loaded = state as EventsLoaded; - final cacheKey = - '${loaded.spaceId}-${loaded.year}-${loaded.month.toString().padLeft(2, '0')}'; - - if (_eventsCache.containsKey(cacheKey)) { - final cachedEvents = - List.from(_eventsCache[cacheKey]!); - cachedEvents.add(event.event); - _eventsCache[cacheKey] = cachedEvents; - } emit(EventsLoaded( events: [...eventController.events], diff --git a/lib/pages/access_management/booking_system/presentation/model/memory_bookable_space_service.dart b/lib/pages/access_management/booking_system/presentation/model/memory_bookable_space_service.dart deleted file mode 100644 index c9017d90..00000000 --- a/lib/pages/access_management/booking_system/presentation/model/memory_bookable_space_service.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:calendar_view/calendar_view.dart'; - -class MemoryBookableSpaceService { - final Map> _eventsCache = {}; - - List? getEvents(String spaceId, int year, int month) { - final key = _generateKey(spaceId, year, month); - return _eventsCache[key]; - } - - void setEvents( - String spaceId, - int year, - int month, - List events, - ) { - final key = _generateKey(spaceId, year, month); - _eventsCache[key] = events; - } - - void addEvent(String spaceId, int year, int month, CalendarEventData event) { - final key = _generateKey(spaceId, year, month); - final events = _eventsCache[key] ?? []; - events.add(event); - _eventsCache[key] = events; - } - - void clear() { - _eventsCache.clear(); - } - - String _generateKey(String spaceId, int year, int month) { - return '$spaceId-$year-${month.toString().padLeft(2, '0')}'; - } -} \ No newline at end of file diff --git a/lib/pages/access_management/booking_system/presentation/view/booking_page.dart b/lib/pages/access_management/booking_system/presentation/view/booking_page.dart index 1030462d..b1e1ebfe 100644 --- a/lib/pages/access_management/booking_system/presentation/view/booking_page.dart +++ b/lib/pages/access_management/booking_system/presentation/view/booking_page.dart @@ -8,7 +8,7 @@ import 'package:syncrow_web/pages/access_management/booking_system/presentation/ import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_event.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_state.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/selected_bookable_space_bloc/selected_bookable_space_bloc.dart'; -import 'package:syncrow_web/pages/access_management/booking_system/presentation/model/memory_bookable_space_service.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/data/services/memory_bookable_space_service.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/booking_sidebar.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/custom_calendar_page.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart'; @@ -65,13 +65,14 @@ class _BookingPageState extends State { BlocProvider(create: (_) => SelectedBookableSpaceBloc()), BlocProvider(create: (_) => DateSelectionBloc()), BlocProvider( - create: (_) => CalendarEventsBloc( - memoryService: MemoryBookableSpaceService(), - calendarService: RemoteCalendarService( - HTTPService(), - ), - ), - ), + create: (_) => CalendarEventsBloc( + calendarService: MemoryCalendarServiceWithRemoteFallback( + remoteService: RemoteCalendarService( + HTTPService(), + ), + memoryService: MemoryBookableSpaceService(), + ), + )), ], child: Builder( builder: (context) => From 7876af9756f7da82266d6bf1b36731a6fdbf5b2a Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 17 Jul 2025 12:46:07 +0300 Subject: [PATCH 61/62] Refactor memory service implementation: rename MemoryBookableSpaceService to MemoryCalendarService for clarity and consistency --- .../data/services/memory_bookable_space_service.dart | 4 ++-- .../booking_system/presentation/view/booking_page.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/access_management/booking_system/data/services/memory_bookable_space_service.dart b/lib/pages/access_management/booking_system/data/services/memory_bookable_space_service.dart index 7aec2398..034480ec 100644 --- a/lib/pages/access_management/booking_system/data/services/memory_bookable_space_service.dart +++ b/lib/pages/access_management/booking_system/data/services/memory_bookable_space_service.dart @@ -3,7 +3,7 @@ import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEv import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart'; -class MemoryBookableSpaceService implements CalendarSystemService { +class MemoryCalendarService implements CalendarSystemService { final Map _eventsCache = {}; @override @@ -35,7 +35,7 @@ class MemoryBookableSpaceService implements CalendarSystemService { } class MemoryCalendarServiceWithRemoteFallback implements CalendarSystemService { - final MemoryBookableSpaceService memoryService; + final MemoryCalendarService memoryService; final RemoteCalendarService remoteService; MemoryCalendarServiceWithRemoteFallback({ diff --git a/lib/pages/access_management/booking_system/presentation/view/booking_page.dart b/lib/pages/access_management/booking_system/presentation/view/booking_page.dart index b1e1ebfe..aac5c5b7 100644 --- a/lib/pages/access_management/booking_system/presentation/view/booking_page.dart +++ b/lib/pages/access_management/booking_system/presentation/view/booking_page.dart @@ -70,7 +70,7 @@ class _BookingPageState extends State { remoteService: RemoteCalendarService( HTTPService(), ), - memoryService: MemoryBookableSpaceService(), + memoryService: MemoryCalendarService(), ), )), ], From 06f00da02c67e366c126af502dc54d99be5fb414 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Jul 2025 13:00:38 +0300 Subject: [PATCH 62/62] Update shadow properties in CommunityStructureHeader for improved visual aesthetics. Adjusted blur radius and offset to enhance the header's appearance in the space management interface. --- .../main_module/widgets/community_structure_header.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 432b3ce4..2e1a350e 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 @@ -20,9 +20,9 @@ class CommunityStructureHeader extends StatelessWidget { color: ColorsManager.whiteColors, boxShadow: [ BoxShadow( - color: ColorsManager.shadowBlackColor, - blurRadius: 8, - offset: const Offset(0, 4), + color: ColorsManager.shadowBlackColor.withValues(alpha: 0.1), + blurRadius: 20, + offset: const Offset(0, 1), ), ], ),