From 318e1d9af7571658fd4a0e0cfa894557186b571d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 3 Jul 2025 13:09:43 +0300 Subject: [PATCH] Implement Space Management Header and Action Buttons; integrate SpaceDetailsBloc for improved space management functionality. Add CommunityStructureHeader, CommunityStructureHeaderActionButtons, and CommunityStructureHeaderButton widgets to enhance UI and user interactions. Update SpaceManagementCommunityStructure to include the new header and refactor space details service for better endpoint handling. --- .../views/space_management_page.dart | 7 ++ .../widgets/community_structure_header.dart | 116 ++++++++++++++++++ ...unity_structure_header_action_buttons.dart | 46 +++++++ .../community_structure_header_button.dart | 61 +++++++++ .../space_management_community_structure.dart | 15 ++- .../remote_space_details_service.dart | 17 ++- .../params/load_space_details_param.dart | 2 + .../helpers/space_details_dialog_helper.dart | 32 +++-- .../widgets/space_details_dialog.dart | 14 ++- 9 files changed, 295 insertions(+), 15 deletions(-) create mode 100644 lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart create mode 100644 lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart create mode 100644 lib/pages/space_management_v2/main_module/widgets/community_structure_header_button.dart 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 957be65a..05768035 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 @@ -7,6 +7,8 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/data/s import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.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/data/services/remote_space_details_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'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; @@ -26,6 +28,11 @@ class SpaceManagementPage extends StatelessWidget { )..add(const LoadCommunities(LoadCommunitiesParam())), ), BlocProvider(create: (context) => CommunitiesTreeSelectionBloc()), + BlocProvider( + create: (context) => SpaceDetailsBloc( + RemoteSpaceDetailsService(httpService: HTTPService()), + ), + ), ], child: WebScaffold( appBarTitle: Text( 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 new file mode 100644 index 00000000..b457c413 --- /dev/null +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart @@ -0,0 +1,116 @@ +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/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/create_community/presentation/create_community_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'; + +class CommunityStructureHeader extends StatelessWidget { + const CommunityStructureHeader({super.key}); + + @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), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + boxShadow: [ + BoxShadow( + color: ColorsManager.shadowBlackColor, + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: _buildCommunityInfo(context, theme, screenWidth), + ), + const SizedBox(width: 16), + ], + ), + ], + ), + ); + } + + void _showCreateCommunityDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) => CreateCommunityDialog( + title: const Text('Edit Community'), + onCreateCommunity: (community) { + // TODO(FarisArmoush): Implement + }, + ), + ); + } + + Widget _buildCommunityInfo( + BuildContext context, ThemeData theme, double screenWidth) { + final selectedCommunity = + context.watch().state.selectedCommunity; + final selectedSpace = + context.watch().state.selectedSpace; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Community Structure', + style: theme.textTheme.headlineLarge + ?.copyWith(color: ColorsManager.blackColor), + ), + if (selectedCommunity != null) + Row( + children: [ + Expanded( + child: Row( + children: [ + Flexible( + child: SelectableText( + selectedCommunity.name, + style: theme.textTheme.bodyLarge + ?.copyWith(color: ColorsManager.blackColor), + maxLines: 1, + ), + ), + const SizedBox(width: 2), + GestureDetector( + onTap: () => _showCreateCommunityDialog(context), + child: SvgPicture.asset( + Assets.iconEdit, + width: 16, + height: 16, + ), + ), + ], + ), + ), + const SizedBox(width: 8), + CommunityStructureHeaderActionButtons( + onDelete: (space) {}, + onDuplicate: (space) {}, + onEdit: (space) { + SpaceDetailsDialogHelper.showEdit( + context, + spaceModel: selectedSpace!, + ); + }, + selectedSpace: selectedSpace, + ), + ], + ), + ], + ); + } +} 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 new file mode 100644 index 00000000..a965c866 --- /dev/null +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_button.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class CommunityStructureHeaderActionButtons extends StatelessWidget { + const CommunityStructureHeaderActionButtons({ + super.key, + required this.onDelete, + required this.selectedSpace, + required this.onDuplicate, + required this.onEdit, + }); + + final void Function(SpaceModel space) onDelete; + final void Function(SpaceModel space) onDuplicate; + final void Function(SpaceModel space) onEdit; + final SpaceModel? selectedSpace; + + @override + Widget build(BuildContext context) { + 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!), + ), + ], + ], + ); + } +} diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_header_button.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_header_button.dart new file mode 100644 index 00000000..4c0285e3 --- /dev/null +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_header_button.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class CommunityStructureHeaderButton extends StatelessWidget { + const CommunityStructureHeaderButton({ + super.key, + required this.label, + required this.onPressed, + this.svgAsset, + }); + + final String label; + final VoidCallback onPressed; + final String? svgAsset; + + @override + Widget build(BuildContext context) { + const double buttonHeight = 40; + return ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 130, + minHeight: buttonHeight, + ), + child: DefaultButton( + onPressed: onPressed, + borderWidth: 2, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: ColorsManager.blackColor, + borderRadius: 12.0, + padding: 2.0, + height: buttonHeight, + elevation: 0, + borderColor: ColorsManager.lightGrayColor, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (svgAsset != null) + SvgPicture.asset( + svgAsset!, + width: 20, + height: 20, + ), + const SizedBox(width: 10), + Flexible( + child: Text( + label, + style: context.textTheme.bodySmall + ?.copyWith(color: ColorsManager.blackColor, fontSize: 14), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + ], + ), + ), + ); + } +} 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 99d0668a..e1f1fc00 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 @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; 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/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart'; @@ -18,9 +19,17 @@ class SpaceManagementCommunityStructure extends StatelessWidget { replacement: const Row( children: [spacer, Expanded(child: CreateSpaceButton()), spacer], ), - child: CommunityStructureCanvas( - community: selectedCommunity, - selectedSpace: selectedSpace, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CommunityStructureHeader(), + Expanded( + child: CommunityStructureCanvas( + community: selectedCommunity, + selectedSpace: selectedSpace, + ), + ), + ], ), ); } diff --git a/lib/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart b/lib/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart index 17514e85..8b40cbfb 100644 --- a/lib/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart +++ b/lib/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart @@ -1,4 +1,5 @@ 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/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'; @@ -18,9 +19,12 @@ class RemoteSpaceDetailsService implements SpaceDetailsService { Future getSpaceDetails(LoadSpaceDetailsParam param) async { try { final response = await _httpService.get( - path: 'endpoint', + path: await _makeEndpoint(param), expectedResponseModel: (data) { - return SpaceDetailsModel.fromJson(data as Map); + final response = data as Map; + return SpaceDetailsModel.fromJson( + response['data'] as Map, + ); }, ); return response; @@ -37,4 +41,13 @@ class RemoteSpaceDetailsService implements SpaceDetailsService { throw APIException(formattedErrorMessage); } } + + Future _makeEndpoint(LoadSpaceDetailsParam 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.spaceUuid}'; + } } diff --git a/lib/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart b/lib/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart index c4c6c565..7242e62e 100644 --- a/lib/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart +++ b/lib/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart @@ -1,7 +1,9 @@ class LoadSpaceDetailsParam { const LoadSpaceDetailsParam({ required this.spaceUuid, + required this.communityUuid, }); final String spaceUuid; + final String communityUuid; } 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 723a5bc1..c8835716 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,15 +1,25 @@ 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/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/services/api/http_service.dart'; abstract final class SpaceDetailsDialogHelper { static void showCreate(BuildContext context) { showDialog( context: context, - builder: (_) => SpaceDetailsDialog( - title: const Text('Create Space'), - spaceModel: SpaceModel.empty(), - onSave: (space) => print(space), + builder: (_) => BlocProvider( + create: (context) => SpaceDetailsBloc( + RemoteSpaceDetailsService(httpService: HTTPService()), + ), + child: SpaceDetailsDialog( + context: context, + title: const Text('Create Space'), + spaceModel: SpaceModel.empty(), + onSave: print, + ), ), ); } @@ -20,10 +30,16 @@ abstract final class SpaceDetailsDialogHelper { }) { showDialog( context: context, - builder: (_) => SpaceDetailsDialog( - title: const Text('Edit Space'), - spaceModel: spaceModel, - onSave: (space) {}, + builder: (_) => BlocProvider( + create: (context) => SpaceDetailsBloc( + RemoteSpaceDetailsService(httpService: HTTPService()), + ), + child: SpaceDetailsDialog( + context: context, + title: const Text('Edit Space'), + spaceModel: spaceModel, + onSave: (space) {}, + ), ), ); } 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 85e9f009..abbd9aae 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,6 +1,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/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'; @@ -13,12 +14,14 @@ class SpaceDetailsDialog extends StatefulWidget { required this.title, required this.spaceModel, required this.onSave, + required this.context, super.key, }); final Widget title; final SpaceModel spaceModel; final void Function(SpaceDetailsModel space) onSave; + final BuildContext context; @override State createState() => _SpaceDetailsDialogState(); @@ -30,8 +33,15 @@ class _SpaceDetailsDialogState extends State { final isCreateMode = widget.spaceModel.uuid.isEmpty; if (!isCreateMode) { - final param = LoadSpaceDetailsParam(spaceUuid: widget.spaceModel.uuid); - context.read().add(LoadSpaceDetails(param)); + final param = LoadSpaceDetailsParam( + spaceUuid: widget.spaceModel.uuid, + communityUuid: widget.context + .read() + .state + .selectedCommunity! + .uuid, + ); + widget.context.read().add(LoadSpaceDetails(param)); } super.initState(); }