diff --git a/assets/icons/delete_space_link_icon.svg b/assets/icons/delete_space_link_icon.svg
new file mode 100644
index 00000000..a55d2e04
--- /dev/null
+++ b/assets/icons/delete_space_link_icon.svg
@@ -0,0 +1,21 @@
+
diff --git a/assets/icons/space_link_icon.svg b/assets/icons/space_link_icon.svg
new file mode 100644
index 00000000..f10c57ad
--- /dev/null
+++ b/assets/icons/space_link_icon.svg
@@ -0,0 +1,25 @@
+
diff --git a/assets/icons/success_icon.svg b/assets/icons/success_icon.svg
new file mode 100644
index 00000000..6f5dbf9e
--- /dev/null
+++ b/assets/icons/success_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart
index 2863a862..cbe15ecd 100644
--- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart
+++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart
@@ -20,7 +20,6 @@ class TreeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final _blocRole = BlocProvider.of(context);
- debugPrint('TreeView constructed with userId = $userId');
return BlocProvider(
create: (_) => UsersBloc(),
// ..add(const LoadCommunityAndSpacesEvent()),
diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart
index 5d52a4d3..9e5f9725 100644
--- a/lib/pages/space_tree/view/space_tree_view.dart
+++ b/lib/pages/space_tree/view/space_tree_view.dart
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/common/widgets/search_bar.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
@@ -8,11 +9,14 @@ import 'package:syncrow_web/pages/space_tree/view/custom_expansion.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.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';
import 'package:syncrow_web/utils/style.dart';
class SpaceTreeView extends StatefulWidget {
+ final bool? isSide;
final Function onSelect;
- const SpaceTreeView({required this.onSelect, super.key});
+ const SpaceTreeView({required this.onSelect, this.isSide, super.key});
@override
State createState() => _SpaceTreeViewState();
@@ -29,21 +33,77 @@ class _SpaceTreeViewState extends State {
@override
Widget build(BuildContext context) {
- return BlocBuilder(builder: (context, state) {
- List list = state.isSearching ? state.filteredCommunity : state.communityList;
+ return BlocBuilder(
+ builder: (context, state) {
+ List list =
+ state.isSearching ? state.filteredCommunity : state.communityList;
return Container(
height: MediaQuery.sizeOf(context).height,
- decoration: subSectionContainerDecoration,
+ decoration:
+ widget.isSide == true ? subSectionContainerDecoration : null,
child: state is SpaceTreeLoadingState
? const Center(child: CircularProgressIndicator())
: Column(
children: [
- CustomSearchBar(
- searchQuery: state.searchQuery,
- onSearchChanged: (query) {
- context.read().add(SearchQueryEvent(query));
- },
- ),
+ widget.isSide == true
+ ? Container(
+ decoration: const BoxDecoration(
+ color: ColorsManager.circleRolesBackground,
+ borderRadius: BorderRadius.only(
+ topRight: Radius.circular(20),
+ topLeft: Radius.circular(20)),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Row(
+ children: [
+ Expanded(
+ child: Container(
+ decoration: BoxDecoration(
+ borderRadius: const BorderRadius.all(
+ Radius.circular(20)),
+ border: Border.all(
+ color: ColorsManager.grayBorder)),
+ child: TextFormField(
+ style:
+ const TextStyle(color: Colors.black),
+ onChanged: (value) {
+ context
+ .read()
+ .add(SearchQueryEvent(value));
+ },
+ decoration: textBoxDecoration(radios: 20)!
+ .copyWith(
+ fillColor: Colors.white,
+ suffixIcon: Padding(
+ padding:
+ const EdgeInsets.only(right: 16),
+ child: SvgPicture.asset(
+ Assets.textFieldSearch,
+ width: 24,
+ height: 24,
+ ),
+ ),
+ hintStyle: context.textTheme.bodyMedium
+ ?.copyWith(
+ fontWeight: FontWeight.w400,
+ fontSize: 12,
+ color: ColorsManager.textGray),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ )
+ : CustomSearchBar(
+ onSearchChanged: (query) {
+ context
+ .read()
+ .add(SearchQueryEvent(query));
+ },
+ ),
const SizedBox(height: 16),
Expanded(
child: ListView(
@@ -57,14 +117,18 @@ class _SpaceTreeViewState extends State {
? Center(
child: Text(
'No results found',
- style: Theme.of(context).textTheme.bodySmall!.copyWith(
+ style: Theme.of(context)
+ .textTheme
+ .bodySmall!
+ .copyWith(
color: ColorsManager.lightGrayColor,
fontWeight: FontWeight.w400,
),
),
)
: Scrollbar(
- scrollbarOrientation: ScrollbarOrientation.left,
+ scrollbarOrientation:
+ ScrollbarOrientation.left,
thumbVisibility: true,
controller: _scrollController,
child: Padding(
@@ -74,30 +138,39 @@ class _SpaceTreeViewState extends State {
shrinkWrap: true,
children: list
.map(
- (community) => CustomExpansionTileSpaceTree(
+ (community) =>
+ CustomExpansionTileSpaceTree(
title: community.name,
- isSelected: state.selectedCommunities
+ isSelected: state
+ .selectedCommunities
.contains(community.uuid),
- isSoldCheck: state.selectedCommunities
+ isSoldCheck: state
+ .selectedCommunities
.contains(community.uuid),
onExpansionChanged: () {
context
.read()
- .add(OnCommunityExpanded(community.uuid));
+ .add(OnCommunityExpanded(
+ community.uuid));
},
- isExpanded: state.expandedCommunities
+ isExpanded: state
+ .expandedCommunities
.contains(community.uuid),
onItemSelected: () {
- context.read().add(
- OnCommunitySelected(
- community.uuid, community.spaces));
+ context
+ .read()
+ .add(OnCommunitySelected(
+ community.uuid,
+ community.spaces));
widget.onSelect();
},
- children: community.spaces.map((space) {
+ children:
+ community.spaces.map((space) {
return CustomExpansionTileSpaceTree(
title: space.name,
- isExpanded:
- state.expandedSpaces.contains(space.uuid),
+ isExpanded: state
+ .expandedSpaces
+ .contains(space.uuid),
onItemSelected: () {
context.read().add(
OnSpaceSelected(community, space.uuid ?? '',
@@ -105,14 +178,20 @@ class _SpaceTreeViewState extends State {
widget.onSelect();
},
onExpansionChanged: () {
- context.read().add(
- OnSpaceExpanded(
- community.uuid, space.uuid ?? ''));
+ context
+ .read()
+ .add(OnSpaceExpanded(
+ community.uuid,
+ space.uuid ?? ''));
},
- isSelected:
- state.selectedSpaces.contains(space.uuid) ||
- state.soldCheck.contains(space.uuid),
- isSoldCheck: state.soldCheck.contains(space.uuid),
+ isSelected: state
+ .selectedSpaces
+ .contains(
+ space.uuid) ||
+ state.soldCheck
+ .contains(space.uuid),
+ isSoldCheck: state.soldCheck
+ .contains(space.uuid),
children: _buildNestedSpaces(
context, state, space, community),
);
@@ -200,8 +279,8 @@ class _SpaceTreeViewState extends State {
BuildContext context, SpaceTreeState state, SpaceModel space, CommunityModel community) {
return space.children.map((child) {
return CustomExpansionTileSpaceTree(
- isSelected:
- state.selectedSpaces.contains(child.uuid) || state.soldCheck.contains(child.uuid),
+ isSelected: state.selectedSpaces.contains(child.uuid) ||
+ state.soldCheck.contains(child.uuid),
isSoldCheck: state.soldCheck.contains(child.uuid),
title: child.name,
isExpanded: state.expandedSpaces.contains(child.uuid),
diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart
deleted file mode 100644
index aa9a446d..00000000
--- a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart
+++ /dev/null
@@ -1,12 +0,0 @@
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart';
-import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart';
-
-
-class SpaceModelBloc extends Bloc {
- SpaceModelBloc() : super(SpaceModelInitial()) {
- on((event, emit) {
- emit(SpaceModelSelectedState(event.selectedIndex));
- });
- }
-}
\ No newline at end of file
diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart
deleted file mode 100644
index 8bff0202..00000000
--- a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart
+++ /dev/null
@@ -1,7 +0,0 @@
-abstract class SpaceModelEvent {}
-
-class SpaceModelSelectedEvent extends SpaceModelEvent {
- final int selectedIndex;
-
- SpaceModelSelectedEvent(this.selectedIndex);
-}
diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart
deleted file mode 100644
index cc745e4d..00000000
--- a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart
+++ /dev/null
@@ -1,9 +0,0 @@
-abstract class SpaceModelState {}
-
-class SpaceModelInitial extends SpaceModelState {}
-
-class SpaceModelSelectedState extends SpaceModelState {
- final int selectedIndex;
-
- SpaceModelSelectedState(this.selectedIndex);
-}
diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart
new file mode 100644
index 00000000..c789c2a9
--- /dev/null
+++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart
@@ -0,0 +1,104 @@
+import 'package:dio/dio.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
+import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
+import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart';
+import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart';
+import 'package:syncrow_web/services/space_model_mang_api.dart';
+import 'package:syncrow_web/utils/navigation_service.dart';
+
+class LinkSpaceToModelBloc
+ extends Bloc {
+ LinkSpaceToModelBloc() : super(SpaceModelInitial()) {
+ on(_getSpaceIds);
+ on(_handleLinkSpaceModel);
+ on(_validateLinkSpaceModel);
+ on((event, emit) {
+ emit(SpaceModelSelectedState(event.selectedIndex));
+ });
+ }
+
+ List spacesListIds = [];
+ bool hasSelectedSpaces = false;
+ String validate = '';
+
+ Future _getSpaceIds(LinkSpaceModelSelectedIdsEvent event,
+ Emitter emit) async {
+ try {
+ BuildContext context = NavigationService.navigatorKey.currentContext!;
+ var spaceBloc = context.read();
+ for (var communityId in spaceBloc.state.selectedCommunities) {
+ List spacesList =
+ spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
+ spacesListIds = spacesList;
+ }
+ hasSelectedSpaces =
+ spaceBloc.state.selectedCommunities.any((communityId) {
+ List spacesList =
+ spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
+ return spacesList.isNotEmpty;
+ });
+ if (hasSelectedSpaces) {
+ debugPrint("At least one space is selected.");
+ } else {
+ debugPrint("No spaces selected.");
+ }
+ } catch (e) {
+ debugPrint("Error in _getSpaceIds: $e");
+ }
+ }
+
+ Future _handleLinkSpaceModel(
+ LinkSpaceModelEvent event,
+ Emitter emit,
+ ) async {
+ emit(SpaceModelLoading());
+ try {
+ final projectUuid = await ProjectManager.getProjectUUID() ?? '';
+ await SpaceModelManagementApi().linkSpaceModel(
+ spaceModelUuid: event.selectedSpaceMode!,
+ projectId: projectUuid,
+ spaceUuids: spacesListIds,
+ isOverWrite: event.isOverWrite);
+ emit(SpaceModelLinkSuccess());
+ } on DioException catch (e) {
+ final errorMessage = _parseDioError(e);
+ emit(SpaceModelOperationFailure(errorMessage));
+ } catch (e) {
+ emit(SpaceModelOperationFailure('Unexpected error: $e'));
+ }
+ }
+
+ Future _validateLinkSpaceModel(
+ ValidateSpaceModelEvent event,
+ Emitter emit,
+ ) async {
+ emit(SpaceModelLoading());
+ try {
+ final projectUuid = await ProjectManager.getProjectUUID() ?? '';
+ await SpaceModelManagementApi().validateSpaceModel(
+ projectUuid,
+ spacesListIds,
+ );
+ emit(SpaceValidationSuccess());
+ } on DioException catch (e) {
+ final errorMessage = _parseDioError(e);
+ if (errorMessage ==
+ 'Selected spaces already have linked space model / sub-spaces and devices') {
+ emit(const AlreadyHaveLinkedState());
+ } else {
+ emit(SpaceModelOperationFailure(errorMessage));
+ }
+ } catch (e) {
+ emit(SpaceModelOperationFailure('Unexpected error: $e'));
+ }
+ }
+
+ String _parseDioError(DioException e) {
+ if (e.response?.data is Map) {
+ return e.response!.data['error']['message'] ?? 'Unknown error occurred';
+ }
+ return e.message ?? 'Network request failed';
+ }
+}
diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart
new file mode 100644
index 00000000..694358db
--- /dev/null
+++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart
@@ -0,0 +1,22 @@
+import 'package:flutter/material.dart';
+
+abstract class LinkSpaceToModelEvent {}
+
+class LinkSpaceModelSelectedEvent extends LinkSpaceToModelEvent {
+ final int selectedIndex;
+
+ LinkSpaceModelSelectedEvent(this.selectedIndex);
+}
+
+class LinkSpaceModelSelectedIdsEvent extends LinkSpaceToModelEvent {}
+
+class LinkSpaceModelEvent extends LinkSpaceToModelEvent {
+ final String? selectedSpaceMode;
+ final bool isOverWrite;
+ LinkSpaceModelEvent({this.selectedSpaceMode, this.isOverWrite = false});
+}
+
+class ValidateSpaceModelEvent extends LinkSpaceToModelEvent {
+ BuildContext? context;
+ ValidateSpaceModelEvent({this.context});
+}
diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart
new file mode 100644
index 00000000..047567a9
--- /dev/null
+++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart
@@ -0,0 +1,35 @@
+abstract class LinkSpaceToModelState {
+ const LinkSpaceToModelState();
+}
+
+class SpaceModelInitial extends LinkSpaceToModelState {}
+
+class SpaceModelLoading extends LinkSpaceToModelState {}
+
+class SpaceModelSelectedState extends LinkSpaceToModelState {
+ final int selectedIndex;
+ const SpaceModelSelectedState(this.selectedIndex);
+}
+
+class SpaceModelSelectionUpdated extends LinkSpaceToModelState {
+ final bool hasSelectedSpaces;
+ const SpaceModelSelectionUpdated(this.hasSelectedSpaces);
+}
+
+class SpaceValidationSuccess extends LinkSpaceToModelState {}
+
+class SpaceModelLinkSuccess extends LinkSpaceToModelState {}
+
+class ValidationError extends LinkSpaceToModelState {
+ final String message;
+ const ValidationError(this.message);
+}
+
+class SpaceModelOperationFailure extends LinkSpaceToModelState {
+ final String message;
+ const SpaceModelOperationFailure(this.message);
+}
+
+class AlreadyHaveLinkedState extends LinkSpaceToModelState {
+ const AlreadyHaveLinkedState();
+}
diff --git a/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart b/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart
index 69023857..bbd0de36 100644
--- a/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart
+++ b/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart
@@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
-import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart';
-import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart';
-import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart';
+import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart';
+import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart';
+import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
@@ -24,13 +24,13 @@ class LinkSpaceModelDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
- create: (context) => SpaceModelBloc()
+ create: (context) => LinkSpaceToModelBloc()
..add(
- SpaceModelSelectedEvent(initialSelectedIndex ?? -1),
+ LinkSpaceModelSelectedEvent(initialSelectedIndex ?? -1),
),
child: Builder(
builder: (context) {
- final bloc = context.read();
+ final bloc = context.read();
return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
title: const Text('Link a space model'),
@@ -39,7 +39,7 @@ class LinkSpaceModelDialog extends StatelessWidget {
color: ColorsManager.textFieldGreyColor,
width: MediaQuery.of(context).size.width * 0.7,
height: MediaQuery.of(context).size.height * 0.6,
- child: BlocBuilder(
+ child: BlocBuilder(
builder: (context, state) {
int selectedIndex = -1;
if (state is SpaceModelSelectedState) {
@@ -59,7 +59,7 @@ class LinkSpaceModelDialog extends StatelessWidget {
final isSelected = selectedIndex == index;
return GestureDetector(
onTap: () {
- bloc.add(SpaceModelSelectedEvent(index));
+ bloc.add(LinkSpaceModelSelectedEvent(index));
},
child: Container(
margin: const EdgeInsets.all(10.0),
@@ -93,7 +93,7 @@ class LinkSpaceModelDialog extends StatelessWidget {
label: 'Cancel',
),
const SizedBox(width: 10),
- BlocBuilder(
+ BlocBuilder(
builder: (context, state) {
final isEnabled = state is SpaceModelSelectedState &&
state.selectedIndex >= 0;
diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart
index 22378f1f..3323fe6e 100644
--- a/lib/pages/spaces_management/space_model/models/space_template_model.dart
+++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart
@@ -11,6 +11,7 @@ class SpaceTemplateModel extends Equatable {
List? subspaceModels;
final List? tags;
String internalId;
+ String? createdAt;
@override
List