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(); }