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/assets/images/delete_space_link_icon.svg b/assets/images/delete_space_link_icon.svg new file mode 100644 index 00000000..a55d2e04 --- /dev/null +++ b/assets/images/delete_space_link_icon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/space_link_icon.svg b/assets/images/space_link_icon.svg new file mode 100644 index 00000000..f10c57ad --- /dev/null +++ b/assets/images/space_link_icon.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/success_icon.svg b/assets/images/success_icon.svg new file mode 100644 index 00000000..6f5dbf9e --- /dev/null +++ b/assets/images/success_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/common/dialog_textfield_dropdown.dart b/lib/common/tag_dialog_textfield_dropdown.dart similarity index 75% rename from lib/common/dialog_textfield_dropdown.dart rename to lib/common/tag_dialog_textfield_dropdown.dart index ac88e5dc..219e03ce 100644 --- a/lib/common/dialog_textfield_dropdown.dart +++ b/lib/common/tag_dialog_textfield_dropdown.dart @@ -1,35 +1,38 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class DialogTextfieldDropdown extends StatefulWidget { - final List items; - final ValueChanged onSelected; - final String? initialValue; +class TagDialogTextfieldDropdown extends StatefulWidget { + final List items; + final ValueChanged onSelected; + final Tag? initialValue; + final String product; - const DialogTextfieldDropdown({ + const TagDialogTextfieldDropdown({ Key? key, required this.items, required this.onSelected, this.initialValue, + required this.product, }) : super(key: key); @override - _DialogTextfieldDropdownState createState() => - _DialogTextfieldDropdownState(); + _DialogTextfieldDropdownState createState() => _DialogTextfieldDropdownState(); } -class _DialogTextfieldDropdownState extends State { +class _DialogTextfieldDropdownState extends State { bool _isOpen = false; OverlayEntry? _overlayEntry; final TextEditingController _controller = TextEditingController(); final FocusNode _focusNode = FocusNode(); - List _filteredItems = []; + List _filteredItems = []; @override void initState() { super.initState(); - _controller.text = widget.initialValue ?? ''; - _filteredItems = List.from(widget.items); + _controller.text = widget.initialValue?.tag ?? ''; + + _filterItems(); _focusNode.addListener(() { if (!_focusNode.hasFocus) { @@ -38,6 +41,12 @@ class _DialogTextfieldDropdownState extends State { }); } + void _filterItems() { + setState(() { + _filteredItems = widget.items.where((tag) => tag.product?.uuid == widget.product).toList(); + }); + } + void _toggleDropdown() { if (_isOpen) { _closeDropdown(); @@ -87,7 +96,7 @@ class _DialogTextfieldDropdownState extends State { shrinkWrap: true, itemCount: _filteredItems.length, itemBuilder: (context, index) { - final item = _filteredItems[index]; + final tag = _filteredItems[index]; return Container( decoration: const BoxDecoration( @@ -99,19 +108,16 @@ class _DialogTextfieldDropdownState extends State { ), ), child: ListTile( - title: Text(item, + title: Text(tag.tag ?? '', style: Theme.of(context) .textTheme .bodyMedium - ?.copyWith( - color: ColorsManager - .textPrimaryColor)), + ?.copyWith(color: ColorsManager.textPrimaryColor)), onTap: () { - _controller.text = item; - widget.onSelected(item); + _controller.text = tag.tag ?? ''; + widget.onSelected(tag); setState(() { - _filteredItems - .remove(item); // Remove selected item + _filteredItems.remove(tag); }); _closeDropdown(); }, @@ -150,11 +156,14 @@ class _DialogTextfieldDropdownState extends State { controller: _controller, focusNode: _focusNode, onFieldSubmitted: (value) { - widget.onSelected(value); + final selectedTag = _filteredItems.firstWhere((tag) => tag.tag == value, + orElse: () => Tag(tag: value)); + widget.onSelected(selectedTag); _closeDropdown(); }, onTapOutside: (event) { - widget.onSelected(_controller.text); + widget.onSelected(_filteredItems.firstWhere((tag) => tag.tag == _controller.text, + orElse: () => Tag(tag: _controller.text))); _closeDropdown(); }, style: Theme.of(context).textTheme.bodyMedium, diff --git a/lib/main.dart b/lib/main.dart index 20b0311e..f2f640e4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:go_router/go_router.dart'; -import 'package:syncrow_web/firebase_options_dev.dart'; import 'package:syncrow_web/firebase_options_prod.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index 752b3255..d7c7a9dd 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_event.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; -import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart'; import 'package:syncrow_web/pages/common/custom_table.dart'; diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index b22dae7b..9e0ac2f9 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -15,7 +15,6 @@ import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/services/auth_api.dart'; import 'package:syncrow_web/utils/constants/strings_manager.dart'; import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart'; -import 'package:syncrow_web/utils/navigation_service.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; class AuthBloc extends Bloc { 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 e52debb0..7ed3a377 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 @@ -5,9 +5,6 @@ import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; -import 'package:syncrow_web/utils/constants/strings_manager.dart'; -import 'package:syncrow_web/utils/constants/temp_const.dart'; -import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart'; part 'device_managment_event.dart'; part 'device_managment_state.dart'; 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 81a21046..45af9751 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 @@ -1,6 +1,5 @@ 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/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; 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/add_device_type/views/add_device_type_widget.dart b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart index 1930963b..ede6afb9 100644 --- a/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart +++ b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart @@ -24,6 +24,8 @@ class AddDeviceTypeWidget extends StatelessWidget { final String spaceName; final bool isCreate; final Function(List, List?)? onSave; + final List projectTags; + const AddDeviceTypeWidget( {super.key, @@ -35,7 +37,8 @@ class AddDeviceTypeWidget extends StatelessWidget { this.allTags, this.spaceTags, this.onSave, - required this.spaceName}); + required this.spaceName, + required this.projectTags}); @override Widget build(BuildContext context) { @@ -134,7 +137,8 @@ class AddDeviceTypeWidget extends StatelessWidget { spaceName: spaceName, initialTags: initialTags, title: dialogTitle, - onSave: onSave), + onSave: onSave, + projectTags: projectTags), ); } }, diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 49fa5d72..47fa5508 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -19,8 +19,7 @@ import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart' as custom_action; -class SpaceManagementBloc - extends Bloc { +class SpaceManagementBloc extends Bloc { final CommunitySpaceManagementApi _api; final ProductApi _productApi; final SpaceModelManagementApi _spaceModelApi; @@ -28,6 +27,7 @@ class SpaceManagementBloc List? _cachedProducts; List? _cachedSpaceModels; final SpaceTreeBloc _spaceTreeBloc; + List? _cachedTags; SpaceManagementBloc( this._api, @@ -54,40 +54,38 @@ class SpaceManagementBloc UpdateSpaceModelCache event, Emitter emit) async { if (_cachedSpaceModels != null) { _cachedSpaceModels = _cachedSpaceModels!.map((model) { - return model.uuid == event.updatedModel.uuid - ? event.updatedModel - : model; + return model.uuid == event.updatedModel.uuid ? event.updatedModel : model; }).toList(); } else { _cachedSpaceModels = await fetchSpaceModels(); } + await fetchTags(); + emit(SpaceModelLoaded( - communities: state is SpaceManagementLoaded - ? (state as SpaceManagementLoaded).communities - : [], - products: _cachedProducts ?? [], - spaceModels: List.from(_cachedSpaceModels ?? []), - )); + communities: + state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [], + products: _cachedProducts ?? [], + spaceModels: List.from(_cachedSpaceModels ?? []), + allTags: _cachedTags ?? [])); } - void _deleteSpaceModelFromCache(DeleteSpaceModelFromCache event, - Emitter emit) async { + void _deleteSpaceModelFromCache( + DeleteSpaceModelFromCache event, Emitter emit) async { if (_cachedSpaceModels != null) { - _cachedSpaceModels = _cachedSpaceModels! - .where((model) => model.uuid != event.deletedUuid) - .toList(); + _cachedSpaceModels = + _cachedSpaceModels!.where((model) => model.uuid != event.deletedUuid).toList(); } else { _cachedSpaceModels = await fetchSpaceModels(); } + await fetchTags(); emit(SpaceModelLoaded( - communities: state is SpaceManagementLoaded - ? (state as SpaceManagementLoaded).communities - : [], - products: _cachedProducts ?? [], - spaceModels: List.from(_cachedSpaceModels ?? []), - )); + communities: + state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [], + products: _cachedProducts ?? [], + spaceModels: List.from(_cachedSpaceModels ?? []), + allTags: _cachedTags ?? [])); } void updateCachedSpaceModels(List updatedModels) { @@ -112,8 +110,8 @@ class SpaceManagementBloc int page = 1; while (hasNext) { - final spaceModels = await _spaceModelApi.listSpaceModels( - page: page, projectId: projectUuid); + final spaceModels = + await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid); if (spaceModels.isNotEmpty) { allSpaceModels.addAll(spaceModels); page++; @@ -130,6 +128,20 @@ class SpaceManagementBloc } } + Future> fetchTags() async { + try { + if (_cachedTags != null) { + return _cachedTags!; + } + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + final allTags = await _spaceModelApi.listTags(projectId: projectUuid); + _cachedTags = allTags; + return allTags; + } catch (e) { + return []; + } + } + void _onUpdateCommunity( UpdateCommunityEvent event, Emitter emit, @@ -137,14 +149,13 @@ class SpaceManagementBloc final previousState = state; try { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + await fetchTags(); emit(SpaceManagementLoading()); - final success = await _api.updateCommunity( - event.communityUuid, event.name, projectUuid); + final success = await _api.updateCommunity(event.communityUuid, event.name, projectUuid); if (success) { if (previousState is SpaceManagementLoaded) { - final updatedCommunities = - List.from(previousState.communities); + final updatedCommunities = List.from(previousState.communities); for (var community in updatedCommunities) { if (community.uuid == event.communityUuid) { community.name = event.name; @@ -157,11 +168,11 @@ class SpaceManagementBloc var prevSpaceModels = await fetchSpaceModels(); emit(SpaceManagementLoaded( - communities: updatedCommunities, - products: previousState.products, - selectedCommunity: previousState.selectedCommunity, - spaceModels: prevSpaceModels, - )); + communities: updatedCommunities, + products: previousState.products, + selectedCommunity: previousState.selectedCommunity, + spaceModels: prevSpaceModels, + allTags: _cachedTags ?? [])); } } else { emit(const SpaceManagementError('Failed to update the community.')); @@ -189,8 +200,7 @@ class SpaceManagementBloc } } - Future> _fetchSpacesForCommunity( - String communityUuid) async { + Future> _fetchSpacesForCommunity(String communityUuid) async { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; return await _api.getSpaceHierarchy(communityUuid, projectUuid); @@ -201,7 +211,7 @@ class SpaceManagementBloc Emitter emit, ) async { try { - final previousState = state; + await fetchTags(); if (event.communities.isEmpty) { emit(const SpaceManagementError('No communities provided.')); @@ -211,33 +221,33 @@ class SpaceManagementBloc var prevSpaceModels = await fetchSpaceModels(); emit(BlankState( - communities: event.communities, - products: _cachedProducts ?? [], - spaceModels: prevSpaceModels, - )); + communities: event.communities, + products: _cachedProducts ?? [], + spaceModels: prevSpaceModels, + allTags: _cachedTags ?? [])); } catch (error) { emit(SpaceManagementError('Error loading communities: $error')); } } - Future _onBlankState( - BlankStateEvent event, Emitter emit) async { + Future _onBlankState(BlankStateEvent event, Emitter emit) async { try { final previousState = state; final projectUuid = await ProjectManager.getProjectUUID() ?? ''; var spaceBloc = event.context.read(); List communities = await _waitForCommunityList(spaceBloc); + await fetchSpaceModels(); + await fetchTags(); var prevSpaceModels = await fetchSpaceModels(); - if (previousState is SpaceManagementLoaded || - previousState is BlankState) { + if (previousState is SpaceManagementLoaded || previousState is BlankState) { final prevCommunities = (previousState as dynamic).communities ?? []; emit(BlankState( - communities: List.from(prevCommunities), - products: _cachedProducts ?? [], - spaceModels: prevSpaceModels, - )); + communities: List.from(prevCommunities), + products: _cachedProducts ?? [], + spaceModels: prevSpaceModels, + allTags: _cachedTags ?? [])); return; } @@ -246,8 +256,7 @@ class SpaceManagementBloc List updatedCommunities = await Future.wait( communities.map((community) async { - List spaces = - await _fetchSpacesForCommunity(community.uuid); + List spaces = await _fetchSpacesForCommunity(community.uuid); return CommunityModel( uuid: community.uuid, createdAt: community.createdAt, @@ -267,6 +276,7 @@ class SpaceManagementBloc spaceModels: prevSpaceModels, communities: communities, products: _cachedProducts ?? [], + allTags: _cachedTags ?? [], )); } catch (error) { emit(SpaceManagementError('Error loading communities: $error')); @@ -279,21 +289,20 @@ class SpaceManagementBloc ) async { var spaceBloc = event.context.read(); _onloadProducts(); - + await fetchTags(); // Wait until `communityList` is loaded List communities = await _waitForCommunityList(spaceBloc); // Fetch space models after communities are available final prevSpaceModels = await fetchSpaceModels(); emit(SpaceManagementLoaded( - communities: communities, - products: _cachedProducts ?? [], - spaceModels: prevSpaceModels, - )); + communities: communities, + products: _cachedProducts ?? [], + spaceModels: prevSpaceModels, + allTags: _cachedTags ?? [])); } - Future> _waitForCommunityList( - SpaceTreeBloc spaceBloc) async { + Future> _waitForCommunityList(SpaceTreeBloc spaceBloc) async { // Check if communityList is already populated if (spaceBloc.state.communityList.isNotEmpty) { return spaceBloc.state.communityList; @@ -320,8 +329,7 @@ class SpaceManagementBloc emit(SpaceManagementLoading()); final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - final success = - await _api.deleteCommunity(event.communityUuid, projectUuid); + final success = await _api.deleteCommunity(event.communityUuid, projectUuid); if (success) { // add(LoadCommunityAndSpacesEvent()); } else { @@ -342,14 +350,13 @@ class SpaceManagementBloc try { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - - CommunityModel? newCommunity = await _api.createCommunity( - event.name, event.description, projectUuid); + await fetchTags(); + CommunityModel? newCommunity = + await _api.createCommunity(event.name, event.description, projectUuid); var prevSpaceModels = await fetchSpaceModels(); if (newCommunity != null) { - if (previousState is SpaceManagementLoaded || - previousState is BlankState) { + if (previousState is SpaceManagementLoaded || previousState is BlankState) { final prevCommunities = List.from( (previousState as dynamic).communities, ); @@ -357,11 +364,13 @@ class SpaceManagementBloc _spaceTreeBloc.add(OnCommunityAdded(newCommunity)); emit(SpaceManagementLoaded( - spaceModels: prevSpaceModels, - communities: updatedCommunities, - products: _cachedProducts ?? [], - selectedCommunity: newCommunity, - selectedSpace: null)); + spaceModels: prevSpaceModels, + communities: updatedCommunities, + products: _cachedProducts ?? [], + selectedCommunity: newCommunity, + selectedSpace: null, + allTags: _cachedTags ?? [], + )); } } else { emit(const SpaceManagementError('Error creating community')); @@ -401,11 +410,12 @@ class SpaceManagementBloc required Emitter emit, CommunityModel? selectedCommunity, SpaceModel? selectedSpace, - }) { + }) async { final previousState = state; emit(SpaceManagementLoading()); try { + await fetchTags(); if (previousState is SpaceManagementLoaded || previousState is BlankState || previousState is SpaceModelLoaded) { @@ -421,7 +431,8 @@ class SpaceManagementBloc products: _cachedProducts ?? [], selectedCommunity: selectedCommunity, selectedSpace: selectedSpace, - spaceModels: spaceModels)); + spaceModels: spaceModels, + allTags: _cachedTags ?? [])); } } catch (e) { emit(SpaceManagementError('Error updating state: $e')); @@ -436,8 +447,7 @@ class SpaceManagementBloc emit(SpaceManagementLoading()); try { - final updatedSpaces = - await saveSpacesHierarchically(event.spaces, event.communityUuid); + final updatedSpaces = await saveSpacesHierarchically(event.spaces, event.communityUuid); final allSpaces = await _fetchSpacesForCommunity(event.communityUuid); @@ -467,20 +477,21 @@ class SpaceManagementBloc Emitter emit, ) async { var prevSpaceModels = await fetchSpaceModels(); - + await fetchTags(); final communities = List.from(previousState.communities); for (var community in communities) { if (community.uuid == communityUuid) { community.spaces = allSpaces; - _spaceTreeBloc.add(OnCommunityUpdated(community)); + _spaceTreeBloc.add(InitialEvent()); emit(SpaceManagementLoaded( communities: communities, products: _cachedProducts ?? [], selectedCommunity: community, selectedSpace: null, - spaceModels: prevSpaceModels)); + spaceModels: prevSpaceModels, + allTags: _cachedTags ?? [])); return; } } @@ -511,8 +522,7 @@ class SpaceManagementBloc if (space.uuid != null && space.uuid!.isNotEmpty) { List tagUpdates = []; - final prevSpace = - await _api.getSpace(communityUuid, space.uuid!, projectUuid); + final prevSpace = await _api.getSpace(communityUuid, space.uuid!, projectUuid); final List subspaceUpdates = []; final List? prevSubspaces = prevSpace?.subspaces; final List? newSubspaces = space.subspaces; @@ -522,19 +532,17 @@ class SpaceManagementBloc if (prevSubspaces != null || newSubspaces != null) { if (prevSubspaces != null && newSubspaces != null) { for (var prevSubspace in prevSubspaces) { - final existsInNew = newSubspaces - .any((subspace) => subspace.uuid == prevSubspace.uuid); + final existsInNew = + newSubspaces.any((subspace) => subspace.uuid == prevSubspace.uuid); if (!existsInNew) { subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: custom_action.Action.delete, - uuid: prevSubspace.uuid)); + action: custom_action.Action.delete, uuid: prevSubspace.uuid)); } } } else if (prevSubspaces != null && newSubspaces == null) { for (var prevSubspace in prevSubspaces) { subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: custom_action.Action.delete, - uuid: prevSubspace.uuid)); + action: custom_action.Action.delete, uuid: prevSubspace.uuid)); } } @@ -548,7 +556,7 @@ class SpaceManagementBloc for (var tag in newSubspace.tags!) { tagUpdates.add(TagModelUpdate( action: custom_action.Action.add, - uuid: tag.uuid == '' ? null : tag.uuid, + newTagUuid: tag.uuid == '' ? null : tag.uuid, tag: tag.tag, productUuid: tag.product?.uuid)); } @@ -562,9 +570,7 @@ class SpaceManagementBloc } if (prevSubspaces != null && newSubspaces != null) { - final newSubspaceMap = { - for (var subspace in newSubspaces) subspace.uuid: subspace - }; + final newSubspaceMap = {for (var subspace in newSubspaces) subspace.uuid: subspace}; for (var prevSubspace in prevSubspaces) { final newSubspace = newSubspaceMap[prevSubspace.uuid]; @@ -601,10 +607,8 @@ class SpaceManagementBloc : []; final createSubspaceBodyModels = space.subspaces?.map((subspace) { - final tagBodyModels = subspace.tags - ?.map((tag) => tag.toCreateTagBodyModel()) - .toList() ?? - []; + final tagBodyModels = + subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? []; return CreateSubspaceModel() ..subspaceName = subspace.subspaceName ..tags = tagBodyModels; @@ -657,12 +661,11 @@ class SpaceManagementBloc return result.toList(); // Convert back to a list } - void _onLoadSpaceModel( - SpaceModelLoadEvent event, Emitter emit) async { + void _onLoadSpaceModel(SpaceModelLoadEvent event, Emitter emit) async { emit(SpaceManagementLoading()); try { - var prevState = state; + await fetchTags(); final projectUuid = await ProjectManager.getProjectUUID() ?? ''; var spaceBloc = event.context.read(); List communities = spaceBloc.state.communityList; @@ -674,8 +677,7 @@ class SpaceManagementBloc List updatedCommunities = await Future.wait( communities.map((community) async { - List spaces = - await _fetchSpacesForCommunity(community.uuid); + List spaces = await _fetchSpacesForCommunity(community.uuid); return CommunityModel( uuid: community.uuid, createdAt: community.createdAt, @@ -692,10 +694,10 @@ class SpaceManagementBloc } emit(SpaceModelLoaded( - communities: communities, - products: _cachedProducts ?? [], - spaceModels: prevSpaceModels, - )); + communities: communities, + products: _cachedProducts ?? [], + spaceModels: prevSpaceModels, + allTags: _cachedTags ?? [])); } catch (e) { emit(SpaceManagementError('Error loading communities and spaces: $e')); } @@ -713,7 +715,7 @@ class SpaceManagementBloc tagUpdates.add(TagModelUpdate( action: custom_action.Action.add, tag: newTag.tag, - uuid: newTag.uuid, + newTagUuid: newTag.uuid, productUuid: newTag.product?.uuid, )); } @@ -724,17 +726,14 @@ class SpaceManagementBloc // Case 1: Tags deleted if (prevTags != null && newTags != null) { for (var prevTag in prevTags) { - final existsInNew = - newTags.any((newTag) => newTag.uuid == prevTag.uuid); + final existsInNew = newTags.any((newTag) => newTag.uuid == prevTag.uuid); if (!existsInNew) { - tagUpdates.add(TagModelUpdate( - action: custom_action.Action.delete, uuid: prevTag.uuid)); + tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid)); } } } else if (prevTags != null && newTags == null) { for (var prevTag in prevTags) { - tagUpdates.add(TagModelUpdate( - action: custom_action.Action.delete, uuid: prevTag.uuid)); + tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid)); } } @@ -749,7 +748,7 @@ class SpaceManagementBloc tagUpdates.add(TagModelUpdate( action: custom_action.Action.add, tag: newTag.tag, - uuid: newTag.uuid == '' ? null : newTag.uuid, + newTagUuid: newTag.uuid == '' ? null : newTag.uuid, productUuid: newTag.product?.uuid)); processedTags.add(newTag.tag); } @@ -766,6 +765,7 @@ class SpaceManagementBloc tagUpdates.add(TagModelUpdate( action: custom_action.Action.update, uuid: newTag.uuid, + newTagUuid: newTag.uuid, tag: newTag.tag, )); } else {} diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart index 674fce4c..3efa7c00 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart @@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; abstract class SpaceManagementState extends Equatable { @@ -21,22 +22,25 @@ class SpaceManagementLoaded extends SpaceManagementState { CommunityModel? selectedCommunity; SpaceModel? selectedSpace; List? spaceModels; + List allTags; SpaceManagementLoaded( {required this.communities, required this.products, this.selectedCommunity, this.selectedSpace, - this.spaceModels}); + this.spaceModels, + required this.allTags}); } class BlankState extends SpaceManagementState { final List communities; final List products; List? spaceModels; + final List allTags; BlankState( - {required this.communities, required this.products, this.spaceModels}); + {required this.communities, required this.products, this.spaceModels, required this.allTags}); } class SpaceCreationSuccess extends SpaceManagementState { @@ -61,14 +65,14 @@ class SpaceModelLoaded extends SpaceManagementState { List spaceModels; final List products; final List communities; + final List allTags; - SpaceModelLoaded({ - required this.communities, - required this.products, - required this.spaceModels, - }); + SpaceModelLoaded( + {required this.communities, + required this.products, + required this.spaceModels, + required this.allTags}); @override - List get props => [communities, products, spaceModels]; + List get props => [communities, products, spaceModels, allTags]; } - diff --git a/lib/pages/spaces_management/all_spaces/model/tag.dart b/lib/pages/spaces_management/all_spaces/model/tag.dart index 34bd08bb..8959986c 100644 --- a/lib/pages/spaces_management/all_spaces/model/tag.dart +++ b/lib/pages/spaces_management/all_spaces/model/tag.dart @@ -25,22 +25,21 @@ class Tag extends BaseTag { return Tag( uuid: json['uuid'] ?? '', internalId: internalId, - tag: json['tag'] ?? '', - product: json['product'] != null - ? ProductModel.fromMap(json['product']) - : null, + tag: json['name'] ?? '', + product: json['product'] != null ? ProductModel.fromMap(json['product']) : null, ); } @override Tag copyWith({ + String? uuid, String? tag, ProductModel? product, String? location, String? internalId, }) { return Tag( - uuid: uuid, + uuid: uuid ?? this.uuid, tag: tag ?? this.tag, product: product ?? this.product, location: location ?? this.location, @@ -60,7 +59,7 @@ class Tag extends BaseTag { extension TagModelExtensions on Tag { TagBodyModel toTagBodyModel() { return TagBodyModel() - ..uuid = uuid ?? '' + ..uuid = uuid ..tag = tag ?? '' ..productUuid = product?.uuid; } diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index 0a847837..00b2cf44 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; @@ -62,9 +60,9 @@ class SpaceManagementPageState extends State { selectedSpace: null, products: state.products, shouldNavigateToSpaceModelPage: false, + projectTags: state.allTags, ); } else if (state is SpaceManagementLoaded) { - return LoadedSpaceView( communities: state.communities, selectedCommunity: state.selectedCommunity, @@ -72,6 +70,7 @@ class SpaceManagementPageState extends State { products: state.products, spaceModels: state.spaceModels, shouldNavigateToSpaceModelPage: false, + projectTags: state.allTags, ); } else if (state is SpaceModelLoaded) { return LoadedSpaceView( @@ -79,6 +78,7 @@ class SpaceManagementPageState extends State { products: state.products, spaceModels: state.spaceModels, shouldNavigateToSpaceModelPage: true, + projectTags: state.allTags, ); } else if (state is SpaceManagementError) { return Center(child: Text('Error: ${state.errorMessage}')); diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index f82bce56..12d76452 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -36,16 +36,17 @@ class CommunityStructureArea extends StatefulWidget { final List communities; final List spaces; final List? spaceModels; + final List projectTags; - CommunityStructureArea({ - this.selectedCommunity, - this.selectedSpace, - required this.communities, - this.products, - required this.spaces, - this.onSpaceSelected, - this.spaceModels, - }); + CommunityStructureArea( + {this.selectedCommunity, + this.selectedSpace, + required this.communities, + this.products, + required this.spaces, + this.onSpaceSelected, + this.spaceModels, + required this.projectTags}); @override _CommunityStructureAreaState createState() => _CommunityStructureAreaState(); @@ -64,8 +65,7 @@ class _CommunityStructureAreaState extends State { void initState() { super.initState(); spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; - connections = - widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; + connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; _adjustCanvasSizeForSpaces(); _nameController = TextEditingController( text: widget.selectedCommunity?.name ?? '', @@ -92,14 +92,12 @@ class _CommunityStructureAreaState extends State { if (oldWidget.spaces != widget.spaces) { setState(() { spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; - connections = - widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; + connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; _adjustCanvasSizeForSpaces(); }); } - if (widget.selectedSpace != oldWidget.selectedSpace && - widget.selectedSpace != null) { + if (widget.selectedSpace != oldWidget.selectedSpace && widget.selectedSpace != null) { WidgetsBinding.instance.addPostFrameCallback((_) { _moveToSpace(widget.selectedSpace!); }); @@ -182,8 +180,7 @@ class _CommunityStructureAreaState extends State { connection, widget.selectedSpace) ? 1.0 : 0.3, // Adjust opacity - child: CustomPaint( - painter: CurvedLinePainter([connection])), + child: CustomPaint(painter: CurvedLinePainter([connection])), ), for (var entry in spaces.asMap().entries) if (entry.value.status != SpaceStatus.deleted && @@ -193,15 +190,12 @@ class _CommunityStructureAreaState extends State { top: entry.value.position.dy, child: SpaceCardWidget( index: entry.key, - onButtonTap: (int index, Offset newPosition, - String direction) { - _showCreateSpaceDialog( - screenSize, - position: - spaces[index].position + newPosition, - parentIndex: index, - direction: direction, - ); + onButtonTap: (int index, Offset newPosition, String direction) { + _showCreateSpaceDialog(screenSize, + position: spaces[index].position + newPosition, + parentIndex: index, + direction: direction, + projectTags: widget.projectTags); }, position: entry.value.position, isHovered: entry.value.isHovered, @@ -211,9 +205,8 @@ class _CommunityStructureAreaState extends State { _updateNodePosition(entry.value, newPosition); }, buildSpaceContainer: (int index) { - final bool isHighlighted = - SpaceHelper.isHighlightedSpace( - spaces[index], widget.selectedSpace); + final bool isHighlighted = SpaceHelper.isHighlightedSpace( + spaces[index], widget.selectedSpace); return Opacity( opacity: isHighlighted ? 1.0 : 0.3, @@ -238,7 +231,8 @@ class _CommunityStructureAreaState extends State { onTap: () { _showCreateSpaceDialog(screenSize, canvasHeight: canvasHeight, - canvasWidth: canvasWidth); + canvasWidth: canvasWidth, + projectTags: widget.projectTags); }, ), ), @@ -292,26 +286,22 @@ class _CommunityStructureAreaState extends State { int? parentIndex, String? direction, double? canvasWidth, - double? canvasHeight}) { + double? canvasHeight, + required List projectTags}) { showDialog( context: context, builder: (BuildContext context) { return CreateSpaceDialog( products: widget.products, spaceModels: widget.spaceModels, - allTags: - TagHelper.getAllTagValues(widget.communities, widget.spaceModels), + allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels), parentSpace: parentIndex != null ? spaces[parentIndex] : null, - onCreateSpace: (String name, - String icon, - List selectedProducts, - SpaceTemplateModel? spaceModel, - List? subspaces, - List? tags) { + projectTags: projectTags, + onCreateSpace: (String name, String icon, List selectedProducts, + SpaceTemplateModel? spaceModel, List? subspaces, List? tags) { setState(() { // Set the first space in the center or use passed position - Offset centerPosition = - position ?? ConnectionHelper.getCenterPosition(screenSize); + Offset centerPosition = position ?? ConnectionHelper.getCenterPosition(screenSize); SpaceModel newSpace = SpaceModel( name: name, icon: icon, @@ -356,21 +346,17 @@ class _CommunityStructureAreaState extends State { spaceModels: widget.spaceModels, name: widget.selectedSpace!.name, icon: widget.selectedSpace!.icon, - parentSpace: SpaceHelper.findSpaceByInternalId( - widget.selectedSpace?.parent?.internalId, spaces), + projectTags: widget.projectTags, + parentSpace: + SpaceHelper.findSpaceByInternalId(widget.selectedSpace?.parent?.internalId, spaces), editSpace: widget.selectedSpace, currentSpaceModel: widget.selectedSpace?.spaceModel, tags: widget.selectedSpace?.tags, subspaces: widget.selectedSpace?.subspaces, isEdit: true, - allTags: TagHelper.getAllTagValues( - widget.communities, widget.spaceModels), - onCreateSpace: (String name, - String icon, - List selectedProducts, - SpaceTemplateModel? spaceModel, - List? subspaces, - List? tags) { + allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels), + onCreateSpace: (String name, String icon, List selectedProducts, + SpaceTemplateModel? spaceModel, List? subspaces, List? tags) { setState(() { // Update the space's properties widget.selectedSpace!.name = name; @@ -380,8 +366,7 @@ class _CommunityStructureAreaState extends State { widget.selectedSpace!.tags = tags; if (widget.selectedSpace!.status != SpaceStatus.newSpace) { - widget.selectedSpace!.status = - SpaceStatus.modified; // Mark as modified + widget.selectedSpace!.status = SpaceStatus.modified; // Mark as modified } for (var space in spaces) { @@ -411,8 +396,7 @@ class _CommunityStructureAreaState extends State { List result = []; void flatten(SpaceModel space) { - if (space.status == SpaceStatus.deleted || - space.status == SpaceStatus.parentDeleted) { + if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) { return; } result.add(space); @@ -527,16 +511,13 @@ class _CommunityStructureAreaState extends State { void _selectSpace(BuildContext context, SpaceModel space) { context.read().add( - SelectSpaceEvent( - selectedCommunity: widget.selectedCommunity, - selectedSpace: space), + SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: space), ); } void _deselectSpace(BuildContext context) { context.read().add( - SelectSpaceEvent( - selectedCommunity: widget.selectedCommunity, selectedSpace: null), + SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: null), ); } @@ -625,19 +606,16 @@ class _CommunityStructureAreaState extends State { const double horizontalGap = 200.0; const double verticalGap = 100.0; - SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition, - SpaceModel? duplicatedParent) { - Offset newPosition = - Offset(parentPosition.dx + horizontalGap, original.position.dy); + SpaceModel duplicateRecursive( + SpaceModel original, Offset parentPosition, SpaceModel? duplicatedParent) { + Offset newPosition = Offset(parentPosition.dx + horizontalGap, original.position.dy); while (spaces.any((s) => - (s.position - newPosition).distance < horizontalGap && - s.status != SpaceStatus.deleted)) { + (s.position - newPosition).distance < horizontalGap && s.status != SpaceStatus.deleted)) { newPosition += Offset(horizontalGap, 0); } - final duplicatedName = - SpaceHelper.generateUniqueSpaceName(original.name, spaces); + final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces); final List? duplicatedSubspaces; final List? duplicatedTags; @@ -681,8 +659,7 @@ class _CommunityStructureAreaState extends State { if (original.parent != null && duplicatedParent == null) { final originalParent = original.parent!; - final duplicatedParent = - originalToDuplicate[originalParent] ?? originalParent; + final duplicatedParent = originalToDuplicate[originalParent] ?? originalParent; final parentConnection = Connection( startSpace: duplicatedParent, @@ -698,8 +675,7 @@ class _CommunityStructureAreaState extends State { final childrenWithDownDirection = original.children .where((child) => - child.incomingConnection?.direction == "down" && - child.status != SpaceStatus.deleted) + child.incomingConnection?.direction == "down" && child.status != SpaceStatus.deleted) .toList(); Offset childStartPosition = childrenWithDownDirection.length == 1 @@ -707,8 +683,7 @@ class _CommunityStructureAreaState extends State { : newPosition + Offset(0, verticalGap); for (final child in original.children) { - final isDownDirection = - child.incomingConnection?.direction == "down" ?? false; + final isDownDirection = child.incomingConnection?.direction == "down" ?? false; if (isDownDirection && childrenWithDownDirection.length == 1) { childStartPosition = duplicated.position + Offset(0, verticalGap); @@ -716,8 +691,7 @@ class _CommunityStructureAreaState extends State { childStartPosition = duplicated.position + Offset(horizontalGap, 0); } - final duplicatedChild = - duplicateRecursive(child, childStartPosition, duplicated); + final duplicatedChild = duplicateRecursive(child, childStartPosition, duplicated); duplicated.children.add(duplicatedChild); childStartPosition += Offset(0, verticalGap); } @@ -728,8 +702,7 @@ class _CommunityStructureAreaState extends State { if (space.parent == null) { duplicateRecursive(space, space.position, null); } else { - final duplicatedParent = - originalToDuplicate[space.parent!] ?? space.parent!; + final duplicatedParent = originalToDuplicate[space.parent!] ?? space.parent!; duplicateRecursive(space, space.position, duplicatedParent); } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index f3df1637..1c14a1f1 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -42,6 +42,7 @@ class CreateSpaceDialog extends StatefulWidget { final List? tags; final List? allTags; final SpaceTemplateModel? currentSpaceModel; + final List projectTags; const CreateSpaceDialog( {super.key, @@ -57,7 +58,8 @@ class CreateSpaceDialog extends StatefulWidget { this.spaceModels, this.subspaces, this.tags, - this.currentSpaceModel}); + this.currentSpaceModel, + required this.projectTags}); @override CreateSpaceDialogState createState() => CreateSpaceDialogState(); @@ -80,10 +82,8 @@ class CreateSpaceDialogState extends State { super.initState(); selectedIcon = widget.icon ?? Assets.location; nameController = TextEditingController(text: widget.name ?? ''); - selectedProducts = - widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; - isOkButtonEnabled = - enteredName.isNotEmpty || nameController.text.isNotEmpty; + selectedProducts = widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; + isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty; if (widget.currentSpaceModel != null) { subspaces = []; tags = []; @@ -96,15 +96,13 @@ class CreateSpaceDialogState extends State { @override Widget build(BuildContext context) { - bool isSpaceModelDisabled = (tags != null && tags!.isNotEmpty || - subspaces != null && subspaces!.isNotEmpty); + bool isSpaceModelDisabled = + (tags != null && tags!.isNotEmpty || subspaces != null && subspaces!.isNotEmpty); bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null); final screenWidth = MediaQuery.of(context).size.width; return AlertDialog( - title: widget.isEdit - ? const Text('Edit Space') - : const Text('Create New Space'), + title: widget.isEdit ? const Text('Edit Space') : const Text('Create New Space'), backgroundColor: ColorsManager.whiteColors, content: SizedBox( width: screenWidth * 0.5, @@ -178,7 +176,8 @@ class CreateSpaceDialogState extends State { isNameFieldInvalid = value.isEmpty; if (!isNameFieldInvalid) { - if (SpaceHelper.isNameConflict(value, widget.parentSpace, widget.editSpace)) { + if (SpaceHelper.isNameConflict( + value, widget.parentSpace, widget.editSpace)) { isNameFieldExist = true; isOkButtonEnabled = false; } else { @@ -245,9 +244,7 @@ class CreateSpaceDialogState extends State { padding: EdgeInsets.zero, ), onPressed: () { - isSpaceModelDisabled - ? null - : _showLinkSpaceModelDialog(context); + isSpaceModelDisabled ? null : _showLinkSpaceModelDialog(context); }, child: ButtonContentWidget( svgAssets: Assets.link, @@ -257,8 +254,7 @@ class CreateSpaceDialogState extends State { ) : Container( width: screenWidth * 0.25, - padding: const EdgeInsets.symmetric( - vertical: 10.0, horizontal: 16.0), + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), decoration: BoxDecoration( color: ColorsManager.boxColor, borderRadius: BorderRadius.circular(10), @@ -273,8 +269,7 @@ class CreateSpaceDialogState extends State { style: Theme.of(context) .textTheme .bodyMedium! - .copyWith( - color: ColorsManager.spaceColor), + .copyWith(color: ColorsManager.spaceColor), ), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( @@ -343,8 +338,8 @@ class CreateSpaceDialogState extends State { onPressed: () async { isTagsAndSubspaceModelDisabled ? null - : _showSubSpaceDialog(context, enteredName, - [], false, widget.products, subspaces); + : _showSubSpaceDialog( + context, enteredName, [], false, widget.products, subspaces); }, child: ButtonContentWidget( icon: Icons.add, @@ -371,22 +366,16 @@ class CreateSpaceDialogState extends State { if (subspaces != null) ...subspaces!.map((subspace) { return Column( - crossAxisAlignment: - CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ SubspaceNameDisplayWidget( text: subspace.subspaceName, validateName: (updatedName) { - bool nameExists = - subspaces!.any((s) { - bool isSameId = s.internalId == - subspace.internalId; - bool isSameName = s.subspaceName - .trim() - .toLowerCase() == - updatedName - .trim() - .toLowerCase(); + bool nameExists = subspaces!.any((s) { + bool isSameId = s.internalId == subspace.internalId; + bool isSameName = + s.subspaceName.trim().toLowerCase() == + updatedName.trim().toLowerCase(); return !isSameId && isSameName; }); @@ -395,8 +384,7 @@ class CreateSpaceDialogState extends State { }, onNameChanged: (updatedName) { setState(() { - subspace.subspaceName = - updatedName; + subspace.subspaceName = updatedName; }); }, ), @@ -405,8 +393,8 @@ class CreateSpaceDialogState extends State { }), EditChip( onTap: () async { - _showSubSpaceDialog(context, enteredName, - [], true, widget.products, subspaces); + _showSubSpaceDialog(context, enteredName, [], true, + widget.products, subspaces); }, ) ], @@ -415,9 +403,7 @@ class CreateSpaceDialogState extends State { ), const SizedBox(height: 10), (tags?.isNotEmpty == true || - subspaces?.any((subspace) => - subspace.tags?.isNotEmpty == true) == - true) + subspaces?.any((subspace) => subspace.tags?.isNotEmpty == true) == true) ? SizedBox( width: screenWidth * 0.25, child: Container( @@ -437,16 +423,14 @@ class CreateSpaceDialogState extends State { // Combine tags from spaceModel and subspaces ...TagHelper.groupTags([ ...?tags, - ...?subspaces?.expand( - (subspace) => subspace.tags ?? []) + ...?subspaces?.expand((subspace) => subspace.tags ?? []) ]).entries.map( (entry) => Chip( avatar: SizedBox( width: 24, height: 24, child: SvgPicture.asset( - entry.key.icon ?? - 'assets/icons/gateway.svg', + entry.key.icon ?? 'assets/icons/gateway.svg', fit: BoxFit.contain, ), ), @@ -455,15 +439,11 @@ class CreateSpaceDialogState extends State { style: Theme.of(context) .textTheme .bodySmall - ?.copyWith( - color: ColorsManager - .spaceColor), + ?.copyWith(color: ColorsManager.spaceColor), ), - backgroundColor: - ColorsManager.whiteColors, + backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16), + borderRadius: BorderRadius.circular(16), side: const BorderSide( color: ColorsManager.spaceColor, ), @@ -472,23 +452,21 @@ class CreateSpaceDialogState extends State { ), EditChip(onTap: () async { - final result = await showDialog( + await showDialog( context: context, builder: (context) => AssignTagDialog( products: widget.products, subspaces: subspaces, allTags: widget.allTags, - addedProducts: TagHelper - .createInitialSelectedProductsForTags( + addedProducts: + TagHelper.createInitialSelectedProductsForTags( tags ?? [], subspaces), title: 'Edit Device', - initialTags: - TagHelper.generateInitialForTags( - spaceTags: tags, - subspaces: subspaces), + initialTags: TagHelper.generateInitialForTags( + spaceTags: tags, subspaces: subspaces), spaceName: widget.name ?? '', - onSave: - (updatedTags, updatedSubspaces) { + projectTags: widget.projectTags, + onSave: (updatedTags, updatedSubspaces) { setState(() { tags = updatedTags; subspaces = updatedSubspaces; @@ -547,25 +525,17 @@ class CreateSpaceDialogState extends State { }); return; } else { - String newName = enteredName.isNotEmpty - ? enteredName - : (widget.name ?? ''); + String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? ''); if (newName.isNotEmpty) { - widget.onCreateSpace( - newName, - selectedIcon, - selectedProducts, - selectedSpaceModel, - subspaces, - tags); + widget.onCreateSpace(newName, selectedIcon, selectedProducts, + selectedSpaceModel, subspaces, tags); Navigator.of(context).pop(); } } }, borderRadius: 10, - backgroundColor: isOkButtonEnabled - ? ColorsManager.secondaryColor - : ColorsManager.grayColor, + backgroundColor: + isOkButtonEnabled ? ColorsManager.secondaryColor : ColorsManager.grayColor, foregroundColor: ColorsManager.whiteColors, child: const Text('OK'), ), @@ -592,7 +562,6 @@ class CreateSpaceDialogState extends State { ); } - void _showLinkSpaceModelDialog(BuildContext context) { showDialog( context: context, @@ -613,13 +582,8 @@ class CreateSpaceDialogState extends State { ); } - void _showSubSpaceDialog( - BuildContext context, - String name, - final List? spaceTags, - bool isEdit, - List? products, - final List? existingSubSpaces) { + void _showSubSpaceDialog(BuildContext context, String name, final List? spaceTags, + bool isEdit, List? products, final List? existingSubSpaces) { showDialog( context: context, builder: (BuildContext context) { @@ -634,12 +598,10 @@ class CreateSpaceDialogState extends State { final List tagsToAppendToSpace = []; if (slectedSubspaces != null) { - final updatedIds = - slectedSubspaces.map((s) => s.internalId).toSet(); + final updatedIds = slectedSubspaces.map((s) => s.internalId).toSet(); if (existingSubSpaces != null) { - final deletedSubspaces = existingSubSpaces - .where((s) => !updatedIds.contains(s.internalId)) - .toList(); + final deletedSubspaces = + existingSubSpaces.where((s) => !updatedIds.contains(s.internalId)).toList(); for (var s in deletedSubspaces) { if (s.tags != null) { tagsToAppendToSpace.addAll(s.tags!); @@ -659,20 +621,20 @@ class CreateSpaceDialogState extends State { ); } - void _showTagCreateDialog(BuildContext context, String name, bool isEdit, - List? products) { + void _showTagCreateDialog( + BuildContext context, String name, bool isEdit, List? products) { isEdit ? showDialog( context: context, builder: (BuildContext context) { return AssignTagDialog( title: 'Edit Device', - addedProducts: TagHelper.createInitialSelectedProductsForTags( - tags, subspaces), + addedProducts: TagHelper.createInitialSelectedProductsForTags(tags, subspaces), spaceName: name, products: products, subspaces: subspaces, allTags: widget.allTags, + projectTags: widget.projectTags, onSave: (selectedSpaceTags, selectedSubspaces) { setState(() { tags = selectedSpaceTags; @@ -682,8 +644,7 @@ class CreateSpaceDialogState extends State { if (subspaces != null) { for (final subspace in subspaces!) { for (final selectedSubspace in selectedSubspaces) { - if (subspace.subspaceName == - selectedSubspace.subspaceName) { + if (subspace.subspaceName == selectedSubspace.subspaceName) { subspace.tags = selectedSubspace.tags; } } @@ -705,9 +666,9 @@ class CreateSpaceDialogState extends State { spaceTags: tags, isCreate: true, allTags: widget.allTags, + projectTags: widget.projectTags, initialSelectedProducts: - TagHelper.createInitialSelectedProductsForTags( - tags, subspaces), + TagHelper.createInitialSelectedProductsForTags(tags, subspaces), onSave: (selectedSpaceTags, selectedSubspaces) { setState(() { tags = selectedSpaceTags; @@ -717,8 +678,7 @@ class CreateSpaceDialogState extends State { if (subspaces != null) { for (final subspace in subspaces!) { for (final selectedSubspace in selectedSubspaces) { - if (subspace.subspaceName == - selectedSubspace.subspaceName) { + if (subspace.subspaceName == selectedSubspace.subspaceName) { subspace.tags = selectedSubspace.tags; } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart index 2961116f..bcaec59f 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/gradient_canvas_border_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart'; @@ -20,16 +21,17 @@ class LoadedSpaceView extends StatefulWidget { final List? products; final List? spaceModels; final bool shouldNavigateToSpaceModelPage; + final List projectTags; - const LoadedSpaceView({ - super.key, - required this.communities, - this.selectedCommunity, - this.selectedSpace, - this.products, - this.spaceModels, - required this.shouldNavigateToSpaceModelPage, - }); + const LoadedSpaceView( + {super.key, + required this.communities, + this.selectedCommunity, + this.selectedSpace, + this.products, + this.spaceModels, + required this.shouldNavigateToSpaceModelPage, + required this.projectTags}); @override _LoadedSpaceViewState createState() => _LoadedSpaceViewState(); @@ -93,6 +95,7 @@ class _LoadedSpaceViewState extends State { child: SpaceModelPage( products: widget.products, onSpaceModelsUpdated: _onSpaceModelsUpdated, + projectTags: widget.projectTags, ), ), ), @@ -113,6 +116,7 @@ class _LoadedSpaceViewState extends State { products: widget.products, communities: widget.communities, spaceModels: _spaceModels, + projectTags: widget.projectTags, ), ], ), diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart index a06e6977..74161b6f 100644 --- a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart @@ -4,17 +4,16 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_e import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; class AssignTagBloc extends Bloc { - final List allTags; + final List projectTags; - AssignTagBloc(this.allTags) : super(AssignTagInitial()) { + AssignTagBloc(this.projectTags) : super(AssignTagInitial()) { on((event, emit) { final initialTags = event.initialTags ?? []; final existingTagCounts = {}; for (var tag in initialTags) { if (tag.product != null) { - existingTagCounts[tag.product!.uuid] = - (existingTagCounts[tag.product!.uuid] ?? 0) + 1; + existingTagCounts[tag.product!.uuid] = (existingTagCounts[tag.product!.uuid] ?? 0) + 1; } } @@ -23,17 +22,14 @@ class AssignTagBloc extends Bloc { for (var selectedProduct in event.addedProducts) { final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; - if (selectedProduct.count == 0 || - selectedProduct.count <= existingCount) { - tags.addAll(initialTags - .where((tag) => tag.product?.uuid == selectedProduct.productId)); + if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) { + tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId)); continue; } final missingCount = selectedProduct.count - existingCount; - tags.addAll(initialTags - .where((tag) => tag.product?.uuid == selectedProduct.productId)); + tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId)); if (missingCount > 0) { tags.addAll(List.generate( @@ -47,11 +43,11 @@ class AssignTagBloc extends Bloc { } } - final updatedTags = _calculateAvailableTags(allTags, tags); + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagLoaded( tags: tags, - updatedTags: updatedTags, + updatedTags: updatedTags, isSaveEnabled: _validateTags(tags), errorMessage: '', )); @@ -62,9 +58,16 @@ class AssignTagBloc extends Bloc { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); - tags[event.index] = tags[event.index].copyWith(tag: event.tag); + if (event.index < 0 || event.index >= tags.length) return; - final updatedTags = _calculateAvailableTags(allTags, tags); + tags[event.index] = tags[event.index].copyWith( + tag: event.tag.tag, + uuid: event.tag.uuid, + product: event.tag.product, + internalId: event.tag.internalId, + location: event.tag.location, + ); + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagLoaded( tags: tags, @@ -82,10 +85,9 @@ class AssignTagBloc extends Bloc { final tags = List.from(currentState.tags); // Update the location - tags[event.index] = - tags[event.index].copyWith(location: event.location); + tags[event.index] = tags[event.index].copyWith(location: event.location); - final updatedTags = _calculateAvailableTags(allTags, tags); + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagLoaded( tags: tags, @@ -104,7 +106,7 @@ class AssignTagBloc extends Bloc { emit(AssignTagLoaded( tags: tags, - updatedTags: _calculateAvailableTags(allTags, tags), + updatedTags: _calculateAvailableTags(projectTags, tags), isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); @@ -115,11 +117,10 @@ class AssignTagBloc extends Bloc { final currentState = state; if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { - final tags = List.from(currentState.tags) - ..remove(event.tagToDelete); + final tags = List.from(currentState.tags)..remove(event.tagToDelete); // Recalculate available tags - final updatedTags = _calculateAvailableTags(allTags, tags); + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagLoaded( tags: tags, @@ -140,10 +141,8 @@ class AssignTagBloc extends Bloc { // Get validation error for duplicate tags String? _getValidationError(List tags) { - final nonEmptyTags = tags - .map((tag) => tag.tag?.trim() ?? '') - .where((tag) => tag.isNotEmpty) - .toList(); + final nonEmptyTags = + tags.map((tag) => tag.tag?.trim() ?? '').where((tag) => tag.isNotEmpty).toList(); final duplicateTags = nonEmptyTags .fold>({}, (map, tag) { @@ -162,14 +161,16 @@ class AssignTagBloc extends Bloc { return null; } - List _calculateAvailableTags(List allTags, List tags) { - final selectedTags = tags + List _calculateAvailableTags(List allTags, List selectedTags) { + final selectedTagSet = selectedTags .where((tag) => (tag.tag?.trim().isNotEmpty ?? false)) .map((tag) => tag.tag!.trim()) .toSet(); - final availableTags = - allTags.where((tag) => !selectedTags.contains(tag.trim())).toList(); + final availableTags = allTags + .where((tag) => tag.tag != null && !selectedTagSet.contains(tag.tag!.trim())) + .toList(); + return availableTags; } } diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart index 9116b094..7d81ffdb 100644 --- a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart @@ -24,7 +24,7 @@ class InitializeTags extends AssignTagEvent { class UpdateTagEvent extends AssignTagEvent { final int index; - final String tag; + final Tag tag; const UpdateTagEvent({required this.index, required this.tag}); diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart index 6a2dae4b..53700a33 100644 --- a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart @@ -5,7 +5,7 @@ abstract class AssignTagState extends Equatable { const AssignTagState(); @override - List get props => []; + List get props => []; } class AssignTagInitial extends AssignTagState {} @@ -14,7 +14,7 @@ class AssignTagLoading extends AssignTagState {} class AssignTagLoaded extends AssignTagState { final List tags; - final List updatedTags; + final List updatedTags; final bool isSaveEnabled; final String? errorMessage; @@ -27,8 +27,7 @@ class AssignTagLoaded extends AssignTagState { }); @override - List get props => - [tags, updatedTags, isSaveEnabled, errorMessage ?? '']; + List get props => [tags, updatedTags, isSaveEnabled, errorMessage ?? '']; } class AssignTagError extends AssignTagState { @@ -37,5 +36,5 @@ class AssignTagError extends AssignTagState { const AssignTagError(this.errorMessage); @override - List get props => [errorMessage]; + List get props => [errorMessage]; } diff --git a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart index 3b794b61..fd1454e5 100644 --- a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart +++ b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/common/dialog_dropdown.dart'; -import 'package:syncrow_web/common/dialog_textfield_dropdown.dart'; +import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.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/add_device_type/views/add_device_type_widget.dart'; @@ -26,6 +26,7 @@ class AssignTagDialog extends StatelessWidget { final String spaceName; final String title; final Function(List, List?)? onSave; + final List projectTags; const AssignTagDialog( {Key? key, @@ -37,18 +38,17 @@ class AssignTagDialog extends StatelessWidget { this.allTags, required this.spaceName, required this.title, - this.onSave}) + this.onSave, + required this.projectTags}) : super(key: key); @override Widget build(BuildContext context) { - final List locations = (subspaces ?? []) - .map((subspace) => subspace.subspaceName) - .toList() - ..add('Main Space'); + final List locations = + (subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space'); return BlocProvider( - create: (_) => AssignTagBloc(allTags ?? []) + create: (_) => AssignTagBloc(projectTags) ..add(InitializeTags( initialTags: initialTags, addedProducts: addedProducts, @@ -70,8 +70,7 @@ class AssignTagDialog extends StatelessWidget { ClipRRect( borderRadius: BorderRadius.circular(20), child: DataTable( - headingRowColor: WidgetStateProperty.all( - ColorsManager.dataHeaderGrey), + headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey), key: ValueKey(state.tags.length), border: TableBorder.all( color: ColorsManager.dataHeaderGrey, @@ -80,22 +79,15 @@ class AssignTagDialog extends StatelessWidget { ), columns: [ DataColumn( - label: Text('#', - style: - Theme.of(context).textTheme.bodyMedium)), + label: Text('#', style: Theme.of(context).textTheme.bodyMedium)), DataColumn( - label: Text('Device', - style: - Theme.of(context).textTheme.bodyMedium)), + label: Text('Device', style: Theme.of(context).textTheme.bodyMedium)), DataColumn( numeric: false, - label: Text('Tag', - style: - Theme.of(context).textTheme.bodyMedium)), + label: Text('Tag', style: Theme.of(context).textTheme.bodyMedium)), DataColumn( - label: Text('Location', - style: - Theme.of(context).textTheme.bodyMedium)), + label: + Text('Location', style: Theme.of(context).textTheme.bodyMedium)), ], rows: state.tags.isEmpty ? [ @@ -103,12 +95,8 @@ class AssignTagDialog extends StatelessWidget { DataCell( Center( child: Text('No Data Available', - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith( - color: ColorsManager - .lightGrayColor, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: ColorsManager.lightGrayColor, )), ), ), @@ -126,8 +114,7 @@ class AssignTagDialog extends StatelessWidget { DataCell(Text((index + 1).toString())), DataCell( Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( @@ -141,31 +128,25 @@ class AssignTagDialog extends StatelessWidget { decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( - color: ColorsManager - .lightGrayColor, + color: ColorsManager.lightGrayColor, width: 1.0, ), ), child: IconButton( icon: const Icon( Icons.close, - color: ColorsManager - .lightGreyColor, + color: ColorsManager.lightGreyColor, size: 16, ), onPressed: () { - context - .read() - .add(DeleteTag( - tagToDelete: tag, - tags: state.tags)); + context.read().add( + DeleteTag(tagToDelete: tag, tags: state.tags)); controllers.removeAt(index); }, tooltip: 'Delete Tag', padding: EdgeInsets.zero, - constraints: - const BoxConstraints(), + constraints: const BoxConstraints(), ), ), ], @@ -173,23 +154,20 @@ class AssignTagDialog extends StatelessWidget { ), DataCell( Container( - alignment: Alignment - .centerLeft, // Align cell content to the left + alignment: + Alignment.centerLeft, // Align cell content to the left child: SizedBox( - width: double - .infinity, // Ensure full width for dropdown - child: DialogTextfieldDropdown( - key: ValueKey( - 'dropdown_${Uuid().v4()}_${index}'), + width: double.infinity, + child: TagDialogTextfieldDropdown( + key: ValueKey('dropdown_${const Uuid().v4()}_$index'), items: state.updatedTags, - initialValue: tag.tag, + product: tag.product?.uuid ?? 'Unknown', + initialValue: tag, onSelected: (value) { - controller.text = value; - context - .read() - .add(UpdateTagEvent( + controller.text = value.tag ?? ''; + context.read().add(UpdateTagEvent( index: index, - tag: value.trim(), + tag: value, )); }, ), @@ -201,12 +179,9 @@ class AssignTagDialog extends StatelessWidget { width: double.infinity, child: DialogDropdown( items: locations, - selectedValue: - tag.location ?? 'Main Space', + selectedValue: tag.location ?? 'Main Space', onSelected: (value) { - context - .read() - .add(UpdateLocation( + context.read().add(UpdateLocation( index: index, location: value, )); @@ -238,13 +213,11 @@ class AssignTagDialog extends StatelessWidget { label: 'Add New Device', onPressed: () async { final updatedTags = List.from(state.tags); - final result = - TagHelper.processTags(updatedTags, subspaces); + final result = TagHelper.processTags(updatedTags, subspaces); - final processedTags = - result['updatedTags'] as List; - final processedSubspaces = List.from( - result['subspaces'] as List); + final processedTags = result['updatedTags'] as List; + final processedSubspaces = + List.from(result['subspaces'] as List); Navigator.of(context).pop(); @@ -253,8 +226,9 @@ class AssignTagDialog extends StatelessWidget { builder: (context) => AddDeviceTypeWidget( products: products, subspaces: processedSubspaces, - initialSelectedProducts: TagHelper - .createInitialSelectedProductsForTags( + projectTags: projectTags, + initialSelectedProducts: + TagHelper.createInitialSelectedProductsForTags( processedTags, processedSubspaces), spaceName: spaceName, spaceTags: processedTags, @@ -278,14 +252,11 @@ class AssignTagDialog extends StatelessWidget { onPressed: state.isSaveEnabled ? () async { final updatedTags = List.from(state.tags); - final result = TagHelper.processTags( - updatedTags, subspaces); + final result = TagHelper.processTags(updatedTags, subspaces); - final processedTags = - result['updatedTags'] as List; + final processedTags = result['updatedTags'] as List; final processedSubspaces = - List.from( - result['subspaces'] as List); + List.from(result['subspaces'] as List); onSave?.call(processedTags, processedSubspaces); Navigator.of(context).pop(); } diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index d0e37f6a..7df82b5e 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -1,45 +1,40 @@ import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; -class AssignTagModelBloc - extends Bloc { - final List allTags; +class AssignTagModelBloc extends Bloc { + final List projectTags; - AssignTagModelBloc(this.allTags) : super(AssignTagModelInitial()) { + AssignTagModelBloc(this.projectTags) : super(AssignTagModelInitial()) { on((event, emit) { - final initialTags = event.initialTags ?? []; + final initialTags = event.initialTags; final existingTagCounts = {}; for (var tag in initialTags) { if (tag.product != null) { - existingTagCounts[tag.product!.uuid] = - (existingTagCounts[tag.product!.uuid] ?? 0) + 1; + existingTagCounts[tag.product!.uuid] = (existingTagCounts[tag.product!.uuid] ?? 0) + 1; } } - final tags = []; + final tags = []; for (var selectedProduct in event.addedProducts) { final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; - if (selectedProduct.count == 0 || - selectedProduct.count <= existingCount) { - tags.addAll(initialTags - .where((tag) => tag.product?.uuid == selectedProduct.productId)); + if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) { + tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId)); continue; } final missingCount = selectedProduct.count - existingCount; - tags.addAll(initialTags - .where((tag) => tag.product?.uuid == selectedProduct.productId)); + tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId)); if (missingCount > 0) { tags.addAll(List.generate( missingCount, - (index) => TagModel( + (index) => Tag( tag: '', product: selectedProduct.product, location: 'Main Space', @@ -48,7 +43,7 @@ class AssignTagModelBloc } } - final updatedTags = _calculateAvailableTags(allTags, tags); + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagModelLoaded( tags: tags, @@ -59,11 +54,20 @@ class AssignTagModelBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagModelLoaded && - currentState.tags.isNotEmpty) { - final tags = List.from(currentState.tags); - tags[event.index] = tags[event.index].copyWith(tag: event.tag); - final updatedTags = _calculateAvailableTags(allTags, tags); + if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); + + if (event.index < 0 || event.index >= tags.length) return; + + tags[event.index] = tags[event.index].copyWith( + tag: event.tag.tag, + uuid: event.tag.uuid, + product: event.tag.product, + internalId: event.tag.internalId, + location: event.tag.location, + ); + + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagModelLoaded( tags: tags, @@ -77,15 +81,12 @@ class AssignTagModelBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagModelLoaded && - currentState.tags.isNotEmpty) { - final tags = List.from(currentState.tags); - + if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); // Use copyWith for immutability - tags[event.index] = - tags[event.index].copyWith(location: event.location); + tags[event.index] = tags[event.index].copyWith(location: event.location); - final updatedTags = _calculateAvailableTags(allTags, tags); + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagModelLoaded( tags: tags, @@ -99,13 +100,12 @@ class AssignTagModelBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagModelLoaded && - currentState.tags.isNotEmpty) { - final tags = List.from(currentState.tags); + if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); emit(AssignTagModelLoaded( tags: tags, - updatedTags: _calculateAvailableTags(allTags, tags), + updatedTags: _calculateAvailableTags(projectTags, tags), isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); @@ -115,12 +115,10 @@ class AssignTagModelBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagModelLoaded && - currentState.tags.isNotEmpty) { - final tags = List.from(currentState.tags) - ..remove(event.tagToDelete); + if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags)..remove(event.tagToDelete); - final updatedTags = _calculateAvailableTags(allTags, tags); + final updatedTags = _calculateAvailableTags(projectTags, tags); emit(AssignTagModelLoaded( tags: tags, @@ -128,24 +126,22 @@ class AssignTagModelBloc isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); - } + } }); } - bool _validateTags(List tags) { + bool _validateTags(List tags) { final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); final isValid = uniqueTags.length == tags.length && !hasEmptyTag; return isValid; } - String? _getValidationError(List tags) { + String? _getValidationError(List tags) { // Check for duplicate tags - final nonEmptyTags = tags - .map((tag) => tag.tag?.trim() ?? '') - .where((tag) => tag.isNotEmpty) - .toList(); + final nonEmptyTags = + tags.map((tag) => tag.tag?.trim() ?? '').where((tag) => tag.isNotEmpty).toList(); final duplicateTags = nonEmptyTags .fold>({}, (map, tag) { @@ -164,15 +160,16 @@ class AssignTagModelBloc return null; } - List _calculateAvailableTags( - List allTags, List tags) { - final selectedTags = tags + List _calculateAvailableTags(List allTags, List selectedTags) { + final selectedTagSet = selectedTags .where((tag) => (tag.tag?.trim().isNotEmpty ?? false)) .map((tag) => tag.tag!.trim()) .toSet(); - final availableTags = - allTags.where((tag) => !selectedTags.contains(tag.trim())).toList(); + final availableTags = allTags + .where((tag) => tag.tag != null && !selectedTagSet.contains(tag.tag!.trim())) + .toList(); + return availableTags; } } diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart index 38642d96..cb878bde 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; abstract class AssignTagModelEvent extends Equatable { @@ -10,7 +10,7 @@ abstract class AssignTagModelEvent extends Equatable { } class InitializeTagModels extends AssignTagModelEvent { - final List initialTags; + final List initialTags; final List addedProducts; const InitializeTagModels({ @@ -24,7 +24,7 @@ class InitializeTagModels extends AssignTagModelEvent { class UpdateTag extends AssignTagModelEvent { final int index; - final String tag; + final Tag tag; const UpdateTag({required this.index, required this.tag}); @@ -45,8 +45,8 @@ class UpdateLocation extends AssignTagModelEvent { class ValidateTagModels extends AssignTagModelEvent {} class DeleteTagModel extends AssignTagModelEvent { - final TagModel tagToDelete; - final List tags; + final Tag tagToDelete; + final List tags; const DeleteTagModel({required this.tagToDelete, required this.tags}); diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart index 167a6ac2..08168e6d 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; abstract class AssignTagModelState extends Equatable { const AssignTagModelState(); @@ -13,11 +13,11 @@ class AssignTagModelInitial extends AssignTagModelState {} class AssignTagModelLoading extends AssignTagModelState {} class AssignTagModelLoaded extends AssignTagModelState { - final List tags; + final List tags; final bool isSaveEnabled; final String? errorMessage; - final List updatedTags; + final List updatedTags; const AssignTagModelLoaded({ required this.tags, diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index d13766d4..85be3bf3 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/common/dialog_dropdown.dart'; -import 'package:syncrow_web/common/dialog_textfield_dropdown.dart'; +import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.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/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_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/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -23,8 +23,8 @@ class AssignTagModelsDialog extends StatelessWidget { final List? subspaces; final SpaceTemplateModel? spaceModel; - final List initialTags; - final ValueChanged>? onTagsAssigned; + final List initialTags; + final ValueChanged>? onTagsAssigned; final List addedProducts; final List? allTags; final String spaceName; @@ -32,6 +32,7 @@ class AssignTagModelsDialog extends StatelessWidget { final BuildContext? pageContext; final List? otherSpaceModels; final List? allSpaceModels; + final List projectTags; const AssignTagModelsDialog( {Key? key, @@ -46,18 +47,17 @@ class AssignTagModelsDialog extends StatelessWidget { this.pageContext, this.otherSpaceModels, this.spaceModel, - this.allSpaceModels}) + this.allSpaceModels, + required this.projectTags}) : super(key: key); @override Widget build(BuildContext context) { - final List locations = (subspaces ?? []) - .map((subspace) => subspace.subspaceName) - .toList() - ..add('Main Space'); + final List locations = + (subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space'); return BlocProvider( - create: (_) => AssignTagModelBloc(allTags ?? []) + create: (_) => AssignTagModelBloc(projectTags) ..add(InitializeTagModels( initialTags: initialTags, addedProducts: addedProducts, @@ -81,8 +81,7 @@ class AssignTagModelsDialog extends StatelessWidget { ClipRRect( borderRadius: BorderRadius.circular(20), child: DataTable( - headingRowColor: WidgetStateProperty.all( - ColorsManager.dataHeaderGrey), + headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey), key: ValueKey(state.tags.length), border: TableBorder.all( color: ColorsManager.dataHeaderGrey, @@ -91,26 +90,17 @@ class AssignTagModelsDialog extends StatelessWidget { ), columns: [ DataColumn( - label: Text('#', - style: Theme.of(context) - .textTheme - .bodyMedium)), + label: Text('#', style: Theme.of(context).textTheme.bodyMedium)), DataColumn( label: Text('Device', - style: Theme.of(context) - .textTheme - .bodyMedium)), + style: Theme.of(context).textTheme.bodyMedium)), DataColumn( numeric: false, - label: Text('Tag', - style: Theme.of(context) - .textTheme - .bodyMedium)), + label: + Text('Tag', style: Theme.of(context).textTheme.bodyMedium)), DataColumn( label: Text('Location', - style: Theme.of(context) - .textTheme - .bodyMedium)), + style: Theme.of(context).textTheme.bodyMedium)), ], rows: state.tags.isEmpty ? [ @@ -118,13 +108,10 @@ class AssignTagModelsDialog extends StatelessWidget { DataCell( Center( child: Text('No Devices Available', - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith( - color: ColorsManager - .lightGrayColor, - )), + style: + Theme.of(context).textTheme.bodyMedium?.copyWith( + color: ColorsManager.lightGrayColor, + )), ), ), const DataCell(SizedBox()), @@ -141,8 +128,7 @@ class AssignTagModelsDialog extends StatelessWidget { DataCell(Text((index + 1).toString())), DataCell( Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( @@ -156,31 +142,25 @@ class AssignTagModelsDialog extends StatelessWidget { decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( - color: ColorsManager - .lightGrayColor, + color: ColorsManager.lightGrayColor, width: 1.0, ), ), child: IconButton( icon: const Icon( Icons.close, - color: ColorsManager - .lightGreyColor, + color: ColorsManager.lightGreyColor, size: 16, ), onPressed: () { - context - .read< - AssignTagModelBloc>() - .add(DeleteTagModel( - tagToDelete: tag, - tags: state.tags)); + context.read().add( + DeleteTagModel( + tagToDelete: tag, tags: state.tags)); controllers.removeAt(index); }, tooltip: 'Delete Tag', padding: EdgeInsets.zero, - constraints: - const BoxConstraints(), + constraints: const BoxConstraints(), ), ), ], @@ -191,19 +171,16 @@ class AssignTagModelsDialog extends StatelessWidget { alignment: Alignment .centerLeft, // Align cell content to the left child: SizedBox( - width: double - .infinity, // Ensure full width for dropdown - child: DialogTextfieldDropdown( - key: ValueKey( - 'dropdown_${Uuid().v4()}_${index}'), + width: double.infinity, + child: TagDialogTextfieldDropdown( + key: ValueKey( + 'dropdown_${const Uuid().v4()}_$index'), + product: tag.product?.uuid ?? 'Unknown', items: state.updatedTags, - initialValue: tag.tag, + initialValue: tag, onSelected: (value) { - controller.text = value; - context - .read< - AssignTagModelBloc>() - .add(UpdateTag( + controller.text = value.tag ?? ''; + context.read().add(UpdateTag( index: index, tag: value, )); @@ -217,12 +194,10 @@ class AssignTagModelsDialog extends StatelessWidget { width: double.infinity, child: DialogDropdown( items: locations, - selectedValue: tag.location ?? - 'Main Space', + selectedValue: tag.location ?? 'Main Space', onSelected: (value) { context - .read< - AssignTagModelBloc>() + .read() .add(UpdateLocation( index: index, location: value, @@ -254,17 +229,13 @@ class AssignTagModelsDialog extends StatelessWidget { builder: (buttonContext) => CancelButton( label: 'Add New Device', onPressed: () async { - final updatedTags = - List.from(state.tags); + final updatedTags = List.from(state.tags); final result = - TagHelper.updateSubspaceTagModels( - updatedTags, subspaces); + TagHelper.updateSubspaceTagModels(updatedTags, subspaces); - final processedTags = - result['updatedTags'] as List; - final processedSubspaces = - List.from( - result['subspaces'] as List); + final processedTags = result['updatedTags'] as List; + final processedSubspaces = List.from( + result['subspaces'] as List); if (context.mounted) { Navigator.of(context).pop(); @@ -272,28 +243,25 @@ class AssignTagModelsDialog extends StatelessWidget { await showDialog( barrierDismissible: false, context: context, - builder: (dialogContext) => - AddDeviceTypeModelWidget( - products: products, - subspaces: processedSubspaces, - isCreate: false, - initialSelectedProducts: TagHelper - .createInitialSelectedProducts( - processedTags, - processedSubspaces), - allTags: allTags, - spaceName: spaceName, - otherSpaceModels: otherSpaceModels, - spaceTagModels: processedTags, - pageContext: pageContext, - spaceModel: SpaceTemplateModel( - modelName: spaceName, - tags: updatedTags, - uuid: spaceModel?.uuid, - internalId: - spaceModel?.internalId, - subspaceModels: - processedSubspaces)), + builder: (dialogContext) => AddDeviceTypeModelWidget( + products: products, + subspaces: processedSubspaces, + isCreate: false, + initialSelectedProducts: + TagHelper.createInitialSelectedProducts( + processedTags, processedSubspaces), + allTags: allTags, + spaceName: spaceName, + otherSpaceModels: otherSpaceModels, + spaceTagModels: processedTags, + pageContext: pageContext, + projectTags: projectTags, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + tags: updatedTags, + uuid: spaceModel?.uuid, + internalId: spaceModel?.internalId, + subspaceModels: processedSubspaces)), ); } }, @@ -310,22 +278,16 @@ class AssignTagModelsDialog extends StatelessWidget { : ColorsManager.whiteColorsWithOpacity, onPressed: state.isSaveEnabled ? () async { - final updatedTags = - List.from(state.tags); + final updatedTags = List.from(state.tags); final result = - TagHelper.updateSubspaceTagModels( - updatedTags, subspaces); + TagHelper.updateSubspaceTagModels(updatedTags, subspaces); - final processedTags = - result['updatedTags'] as List; - final processedSubspaces = - List.from( - result['subspaces'] - as List); + final processedTags = result['updatedTags'] as List; + final processedSubspaces = List.from( + result['subspaces'] as List); - Navigator.of(context) - .popUntil((route) => route.isFirst); + Navigator.of(context).popUntil((route) => route.isFirst); await showDialog( context: context, @@ -334,16 +296,15 @@ class AssignTagModelsDialog extends StatelessWidget { products: products, allSpaceModels: allSpaceModels, allTags: allTags, + projectTags: projectTags, pageContext: pageContext, otherSpaceModels: otherSpaceModels, spaceModel: SpaceTemplateModel( modelName: spaceName, tags: processedTags, uuid: spaceModel?.uuid, - internalId: - spaceModel?.internalId, - subspaceModels: - processedSubspaces), + internalId: spaceModel?.internalId, + subspaceModels: processedSubspaces), ); }, ); diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index 3e3353a3..e1dc3cc3 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -7,7 +7,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_mo import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; class TagHelper { static Map updateTags({ @@ -131,9 +130,9 @@ class TagHelper { } static List getAvailableTagModels( - List allTags, List currentTags, TagModel currentTag) { + List allTags, List currentTags, Tag currentTag) { List availableTagsForTagModel = - TagHelper.getAvailableTags( + TagHelper.getAvailableTags( allTags: allTags, currentTags: currentTags, currentTag: currentTag, @@ -142,11 +141,11 @@ class TagHelper { return availableTagsForTagModel; } - static List generateInitialTags({ - List? spaceTagModels, + static List generateInitialTags({ + List? spaceTagModels, List? subspaces, }) { - final List initialTags = []; + final List initialTags = []; if (spaceTagModels != null) { initialTags.addAll(spaceTagModels); @@ -212,7 +211,7 @@ class TagHelper { } static List createInitialSelectedProducts( - List? tags, List? subspaces) { + List? tags, List? subspaces) { final Map productCounts = {}; if (tags != null) { @@ -282,7 +281,7 @@ class TagHelper { } static int? checkTagExistInSubspaceModels( - TagModel tag, List? subspaces) { + Tag tag, List? subspaces) { if (subspaces == null) return null; for (int i = 0; i < subspaces.length; i++) { @@ -298,8 +297,8 @@ class TagHelper { } static Map updateSubspaceTagModels( - List updatedTags, List? subspaces) { - final result = TagHelper.updateTags( + List updatedTags, List? subspaces) { + final result = TagHelper.updateTags( updatedTags: updatedTags, subspaces: subspaces, getInternalId: (tag) => tag.internalId, @@ -311,7 +310,7 @@ class TagHelper { checkTagExistInSubspace: checkTagExistInSubspaceModels, ); - final processedTags = result['updatedTags'] as List; + final processedTags = result['updatedTags'] as List; final processedSubspaces = List.from(result['subspaces'] as List); 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/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 8237c172..5428c343 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -1,11 +1,11 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; @@ -94,14 +94,9 @@ class CreateSpaceModelBloc orElse: () => subspace, ); - // Update the subspace's tags - final eventTagIds = matchingEventSubspace.tags - ?.map((e) => e.internalId) - .toSet() ?? - {}; final updatedTags = [ - ...?subspace.tags?.map((tag) { + ...?subspace.tags?.map((tag) { final matchingTag = matchingEventSubspace.tags?.firstWhere( (e) => e.internalId == tag.internalId, @@ -112,14 +107,14 @@ class CreateSpaceModelBloc ? tag.copyWith(tag: matchingTag?.tag) : tag; }) ?? - [], + [], ...?matchingEventSubspace.tags?.where( (e) => subspace.tags ?.every((t) => t.internalId != e.internalId) ?? true, ) ?? - [], + [], ]; return subspace.copyWith( subspaceName: matchingEventSubspace.subspaceName, @@ -244,7 +239,7 @@ class CreateSpaceModelBloc } if (newSubspaces != null) { - for (var newSubspace in newSubspaces!) { + for (var newSubspace in newSubspaces) { // Tag without UUID if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) { final List tagUpdates = []; @@ -253,7 +248,7 @@ class CreateSpaceModelBloc for (var tag in newSubspace.tags!) { tagUpdates.add(TagModelUpdate( action: Action.add, - uuid: tag.uuid == '' ? null : tag.uuid, + newTagUuid: tag.uuid == '' ? null : tag.uuid, tag: tag.tag, productUuid: tag.product?.uuid)); } @@ -268,7 +263,7 @@ class CreateSpaceModelBloc if (prevSubspaces != null && newSubspaces != null) { final newSubspaceMap = { - for (var subspace in newSubspaces!) subspace.uuid: subspace + for (var subspace in newSubspaces) subspace.uuid: subspace }; for (var prevSubspace in prevSubspaces) { @@ -309,8 +304,8 @@ class CreateSpaceModelBloc } List processTagUpdates( - List? prevTags, - List? newTags, + List? prevTags, + List? newTags, ) { final List tagUpdates = []; final processedTags = {}; @@ -320,7 +315,7 @@ class CreateSpaceModelBloc tagUpdates.add(TagModelUpdate( action: Action.add, tag: newTag.tag, - uuid: newTag.uuid, + newTagUuid: newTag.uuid, productUuid: newTag.product?.uuid, )); } @@ -332,7 +327,7 @@ class CreateSpaceModelBloc if (prevTags != null && newTags != null) { for (var prevTag in prevTags) { final existsInNew = - newTags!.any((newTag) => newTag.uuid == prevTag.uuid); + newTags.any((newTag) => newTag.uuid == prevTag.uuid); if (!existsInNew) { tagUpdates .add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid)); @@ -349,14 +344,14 @@ class CreateSpaceModelBloc if (newTags != null) { final prevTagUuids = prevTags?.map((t) => t.uuid).toSet() ?? {}; - for (var newTag in newTags!) { + for (var newTag in newTags) { // Tag without UUID if ((newTag.uuid == null || !prevTagUuids.contains(newTag.uuid)) && !processedTags.contains(newTag.tag)) { tagUpdates.add(TagModelUpdate( action: Action.add, tag: newTag.tag, - uuid: newTag.uuid == '' ? null : newTag.uuid, + newTagUuid: newTag.uuid == '' ? null : newTag.uuid, productUuid: newTag.product?.uuid)); processedTags.add(newTag.tag); } @@ -365,14 +360,15 @@ class CreateSpaceModelBloc // Case 3: Tags updated if (prevTags != null && newTags != null) { - final newTagMap = {for (var tag in newTags!) tag.uuid: tag}; + final newTagMap = {for (var tag in newTags) tag.uuid: tag}; - for (var prevTag in prevTags!) { + for (var prevTag in prevTags) { final newTag = newTagMap[prevTag.uuid]; if (newTag != null) { tagUpdates.add(TagModelUpdate( action: Action.update, - uuid: newTag.uuid, + uuid: prevTag.uuid, + newTagUuid: newTag.uuid, tag: newTag.tag, )); } else {} diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index d0cd245c..0d2a3a4f 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -1,7 +1,7 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; abstract class CreateSpaceModelEvent extends Equatable { const CreateSpaceModelEvent(); @@ -49,7 +49,7 @@ class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { } class AddTagsToSpaceTemplate extends CreateSpaceModelEvent { - final List tags; + final List tags; AddTagsToSpaceTemplate(this.tags); } diff --git a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart index ad0770d5..161bbf3d 100644 --- a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart +++ b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart @@ -6,7 +6,7 @@ class TagBodyModel { Map toJson() { return { 'uuid': uuid, - 'tag': tag, + 'name': tag, 'productUuid': productUuid, }; } 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..82c3cfb1 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 @@ -1,6 +1,6 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; import 'package:uuid/uuid.dart'; @@ -9,8 +9,9 @@ class SpaceTemplateModel extends Equatable { String? uuid; String modelName; List? subspaceModels; - final List? tags; + final List? tags; String internalId; + String? createdAt; @override List get props => [modelName, subspaceModels, tags]; @@ -21,6 +22,7 @@ class SpaceTemplateModel extends Equatable { required this.modelName, this.subspaceModels, this.tags, + this.createdAt, }) : internalId = internalId ?? const Uuid().v4(); factory SpaceTemplateModel.fromJson(Map json) { @@ -28,6 +30,7 @@ class SpaceTemplateModel extends Equatable { return SpaceTemplateModel( uuid: json['uuid'] ?? '', + createdAt: json['createdAt'] ?? '', internalId: internalId, modelName: json['modelName'] ?? '', subspaceModels: (json['subspaceModels'] as List?) @@ -38,7 +41,7 @@ class SpaceTemplateModel extends Equatable { [], tags: (json['tags'] as List?) ?.where((item) => item is Map) // Validate type - .map((item) => TagModel.fromJson(item as Map)) + .map((item) => Tag.fromJson(item as Map)) .toList() ?? [], ); @@ -47,7 +50,7 @@ class SpaceTemplateModel extends Equatable { String? uuid, String? modelName, List? subspaceModels, - List? tags, + List? tags, String? internalId, }) { return SpaceTemplateModel( diff --git a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart index 9c69b4c8..130a477e 100644 --- a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart @@ -1,11 +1,11 @@ -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:uuid/uuid.dart'; class SubspaceTemplateModel { final String? uuid; String subspaceName; final bool disabled; - List? tags; + List? tags; String internalId; SubspaceTemplateModel({ @@ -25,7 +25,7 @@ class SubspaceTemplateModel { internalId: internalId, disabled: json['disabled'] ?? false, tags: (json['tags'] as List?) - ?.map((item) => TagModel.fromJson(item)) + ?.map((item) => Tag.fromJson(item)) .toList() ?? [], ); @@ -44,7 +44,7 @@ class SubspaceTemplateModel { String? uuid, String? subspaceName, bool? disabled, - List? tags, + List? tags, String? internalId, }) { return SubspaceTemplateModel( diff --git a/lib/pages/spaces_management/space_model/models/tag_body_model.dart b/lib/pages/spaces_management/space_model/models/tag_body_model.dart index d66e2884..49947831 100644 --- a/lib/pages/spaces_management/space_model/models/tag_body_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_body_model.dart @@ -4,7 +4,7 @@ class CreateTagBodyModel { Map toJson() { return { - 'tag': tag, + 'name': tag, 'productUuid': productUuid, }; } diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart deleted file mode 100644 index 20bd50e2..00000000 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; -import 'package:uuid/uuid.dart'; - -class TagModel extends BaseTag { - TagModel({ - String? uuid, - required String? tag, - ProductModel? product, - String? internalId, - String? location, - }) : super( - uuid: uuid, - tag: tag, - product: product, - internalId: internalId, - location: location, - ); - factory TagModel.fromJson(Map json) { - final String internalId = json['internalId'] ?? const Uuid().v4(); - - return TagModel( - uuid: json['uuid'] , - internalId: internalId, - tag: json['tag'] ?? '', - product: json['product'] != null - ? ProductModel.fromMap(json['product']) - : null, - ); - } - - @override - TagModel copyWith( - {String? tag, - ProductModel? product, - String? uuid, - String? location, - String? internalId}) { - return TagModel( - tag: tag ?? this.tag, - product: product ?? this.product, - location: location ?? this.location, - internalId: internalId ?? this.internalId, - uuid:uuid?? this.uuid - ); - } - - Map toJson() { - return { - 'uuid': uuid, - 'tag': tag, - 'product': product?.toMap(), - }; - } -} - -extension TagModelExtensions on TagModel { - TagBodyModel toTagBodyModel() { - return TagBodyModel() - ..uuid = uuid - ..tag = tag ?? '' - ..productUuid = product?.uuid; - } -} diff --git a/lib/pages/spaces_management/space_model/models/tag_update_model.dart b/lib/pages/spaces_management/space_model/models/tag_update_model.dart index c7190dc8..c2177058 100644 --- a/lib/pages/spaces_management/space_model/models/tag_update_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_update_model.dart @@ -5,12 +5,14 @@ class TagModelUpdate { final String? uuid; final String? tag; final String? productUuid; + final String? newTagUuid; TagModelUpdate({ required this.action, this.uuid, this.tag, this.productUuid, + this.newTagUuid, }); factory TagModelUpdate.fromJson(Map json) { @@ -26,9 +28,10 @@ class TagModelUpdate { Map toJson() { return { 'action': action.value, - 'uuid': uuid, // Nullable field - 'tag': tag, + 'tagUuid': uuid, + 'name': tag, 'productUuid': productUuid, + 'newTagUuid': newTagUuid }; } } diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 35611868..e1aa7a92 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; @@ -12,8 +13,10 @@ import 'package:syncrow_web/utils/color_manager.dart'; class SpaceModelPage extends StatelessWidget { final List? products; final Function(List)? onSpaceModelsUpdated; + final List projectTags; - const SpaceModelPage({Key? key, this.products, this.onSpaceModelsUpdated}) + const SpaceModelPage( + {Key? key, this.products, this.onSpaceModelsUpdated, required this.projectTags}) : super(key: key); @override @@ -60,6 +63,7 @@ class SpaceModelPage extends StatelessWidget { allTags: allTagValues, pageContext: context, otherSpaceModels: allSpaceModelNames, + projectTags: projectTags, ); }, ); @@ -69,8 +73,7 @@ class SpaceModelPage extends StatelessWidget { } // Render existing space model final model = spaceModels[index]; - final otherModel = - List.from(allSpaceModelNames); + final otherModel = List.from(allSpaceModelNames); otherModel.remove(model.modelName); return GestureDetector( onTap: () { @@ -84,6 +87,7 @@ class SpaceModelPage extends StatelessWidget { otherSpaceModels: otherModel, pageContext: context, allSpaceModels: spaceModels, + projectTags: projectTags, ); }, ); @@ -107,10 +111,8 @@ class SpaceModelPage extends StatelessWidget { return Center( child: Text( 'Error: ${state.message}', - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: ColorsManager.warningRed), + style: + Theme.of(context).textTheme.bodySmall?.copyWith(color: ColorsManager.warningRed), ), ); } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/confirm_merge_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/confirm_merge_dialog.dart new file mode 100644 index 00000000..2a39d67b --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/confirm_merge_dialog.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class ConfirmMergeDialog extends StatelessWidget { + const ConfirmMergeDialog({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: Center( + child: Text( + 'Merge', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 30), + )), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Are you sure you want to merge?', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 18), + ), + const SizedBox(height: 25), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.boxColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Cancel", + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 16), + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.secondaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Ok", + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 16, + color: ColorsManager.whiteColors, + ), + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/confirm_overwrite_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/confirm_overwrite_dialog.dart new file mode 100644 index 00000000..0497b570 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/confirm_overwrite_dialog.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class ConfirmOverwriteDialog extends StatelessWidget { + const ConfirmOverwriteDialog({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: Center( + child: Text( + 'Overwrite', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 30), + )), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Are you sure you want to overwrite?', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 18), + ), + Center( + child: Text( + 'Selected spaces already have linked space \nmodel / sub-spaces and devices', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 14, + color: ColorsManager.grayColor), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 25), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.boxColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Cancel", + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 16), + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return const LinkingSuccessful(); + }, + ); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.secondaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Ok", + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 16, + color: ColorsManager.whiteColors, + ), + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 69e619b7..98251382 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; @@ -25,6 +26,7 @@ class CreateSpaceModelDialog extends StatelessWidget { final BuildContext? pageContext; final List? otherSpaceModels; final List? allSpaceModels; + final List projectTags; const CreateSpaceModelDialog( {Key? key, @@ -33,7 +35,8 @@ class CreateSpaceModelDialog extends StatelessWidget { this.spaceModel, this.pageContext, this.otherSpaceModels, - this.allSpaceModels}) + this.allSpaceModels, + required this.projectTags}) : super(key: key); @override @@ -68,8 +71,7 @@ class CreateSpaceModelDialog extends StatelessWidget { spaceNameController.addListener(() { bloc.add(UpdateSpaceTemplateName( - name: spaceNameController.text, - allModels: otherSpaceModels ?? [])); + name: spaceNameController.text, allModels: otherSpaceModels ?? [])); }); return bloc; @@ -87,9 +89,7 @@ class CreateSpaceModelDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - spaceModel?.uuid == null - ? 'Create New Space Model' - : 'Edit Space Model', + spaceModel?.uuid == null ? 'Create New Space Model' : 'Edit Space Model', style: Theme.of(context) .textTheme .headlineLarge @@ -101,10 +101,8 @@ class CreateSpaceModelDialog extends StatelessWidget { child: TextField( controller: spaceNameController, onChanged: (value) { - context.read().add( - UpdateSpaceTemplateName( - name: value, - allModels: otherSpaceModels ?? [])); + context.read().add(UpdateSpaceTemplateName( + name: value, allModels: otherSpaceModels ?? [])); }, style: Theme.of(context) .textTheme @@ -157,6 +155,7 @@ class CreateSpaceModelDialog extends StatelessWidget { pageContext: pageContext, otherSpaceModels: otherSpaceModels, allSpaceModels: allSpaceModels, + projectTags: projectTags, ), const SizedBox(height: 20), SizedBox( @@ -179,84 +178,55 @@ class CreateSpaceModelDialog extends StatelessWidget { !isNameValid) ? null : () { - final updatedSpaceTemplate = - updatedSpaceModel.copyWith( - modelName: - spaceNameController.text.trim(), + final updatedSpaceTemplate = updatedSpaceModel.copyWith( + modelName: spaceNameController.text.trim(), ); if (updatedSpaceModel.uuid == null) { - context - .read() - .add( + context.read().add( CreateSpaceTemplate( - spaceTemplate: - updatedSpaceTemplate, + spaceTemplate: updatedSpaceTemplate, onCreate: (newModel) { if (pageContext != null) { + pageContext!.read().add( + CreateSpaceModel(newSpaceModel: newModel)); pageContext! - .read() - .add(CreateSpaceModel( - newSpaceModel: - newModel)); - pageContext! - .read< - SpaceManagementBloc>() - .add( - UpdateSpaceModelCache( - newModel)); + .read() + .add(UpdateSpaceModelCache(newModel)); } - Navigator.of(context) - .pop(); // Close the dialog + Navigator.of(context).pop(); // Close the dialog }, ), ); } else { if (pageContext != null) { - final currentState = pageContext! - .read() - .state; - if (currentState - is SpaceModelLoaded) { - final spaceModels = - List.from( - currentState.spaceModels); + final currentState = + pageContext!.read().state; + if (currentState is SpaceModelLoaded) { + final spaceModels = List.from( + currentState.spaceModels); - final SpaceTemplateModel? - currentSpaceModel = spaceModels - .cast() - .firstWhere( - (sm) => - sm?.uuid == - updatedSpaceModel - .uuid, + final SpaceTemplateModel? currentSpaceModel = + spaceModels.cast().firstWhere( + (sm) => sm?.uuid == updatedSpaceModel.uuid, orElse: () => null, ); if (currentSpaceModel != null) { context .read() .add(ModifySpaceTemplate( - spaceTemplate: - currentSpaceModel, - updatedSpaceTemplate: - updatedSpaceTemplate, + spaceTemplate: currentSpaceModel, + updatedSpaceTemplate: updatedSpaceTemplate, onUpdate: (newModel) { - if (pageContext != - null) { - pageContext! - .read< - SpaceModelBloc>() - .add(UpdateSpaceModel( + if (pageContext != null) { + pageContext!.read().add( + UpdateSpaceModel( spaceModelUuid: - newModel.uuid ?? - '')); + newModel.uuid ?? '')); pageContext! - .read< - SpaceManagementBloc>() - .add(UpdateSpaceModelCache( - newModel)); + .read() + .add(UpdateSpaceModelCache(newModel)); } - Navigator.of(context) - .pop(); + Navigator.of(context).pop(); })); } } @@ -265,11 +235,11 @@ class CreateSpaceModelDialog extends StatelessWidget { }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: ((state.errorMessage != null && - state.errorMessage != '') || - !isNameValid) - ? ColorsManager.whiteColorsWithOpacity - : ColorsManager.whiteColors, + foregroundColor: + ((state.errorMessage != null && state.errorMessage != '') || + !isNameValid) + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, child: const Text('OK'), ), ), diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/custom_loading_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/custom_loading_dialog.dart new file mode 100644 index 00000000..e0260887 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/custom_loading_dialog.dart @@ -0,0 +1,75 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart'; + + +class CustomLoadingIndicator extends StatefulWidget { + @override + _CustomLoadingIndicatorState createState() => _CustomLoadingIndicatorState(); +} + +class _CustomLoadingIndicatorState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(seconds: 1), + )..repeat(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 50, + height: 50, + child: AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return Transform.rotate( + angle: _controller.value * 2 * pi, + child: CustomPaint( + painter: LoadingPainter(), + ), + ); + }, + ), + ); + } +} + +class LoadingPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final Paint paint = Paint() + ..strokeWidth = 5 + ..strokeCap = StrokeCap.round + ..style = PaintingStyle.stroke; + + final double radius = size.width / 2; + final Offset center = Offset(size.width / 2, size.height / 2); + + for (int i = 0; i < 12; i++) { + final double angle = (i * 30) * (pi / 180); + final double startX = center.dx + radius * cos(angle); + final double startY = center.dy + radius * sin(angle); + final double endX = center.dx + (radius - 8) * cos(angle); + final double endY = center.dy + (radius - 8) * sin(angle); + + paint.color = Colors.blue.withOpacity(i / 12); // Gradient effect + canvas.drawLine(Offset(startX, startY), Offset(endX, endY), paint); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/link_space_model_spaces_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/link_space_model_spaces_dialog.dart new file mode 100644 index 00000000..8c6ef3e9 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/link_space_model_spaces_dialog.dart @@ -0,0 +1,221 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/view/space_tree_view.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/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class LinkSpaceModelSpacesDialog extends StatefulWidget { + final SpaceTemplateModel spaceModel; + + const LinkSpaceModelSpacesDialog({super.key, required this.spaceModel}); + + @override + State createState() => + _LinkSpaceModelSpacesDialogState(); +} + +class _LinkSpaceModelSpacesDialogState + extends State { + @override + void initState() { + context.read().add(LinkSpaceModelSelectedIdsEvent()); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + backgroundColor: Colors.white, + content: SizedBox( + width: MediaQuery.of(context).size.width * 0.4, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildDialogContent(), + _buildActionButtons(), + ], + ), + ), + ); + } + + Widget _buildDialogContent() { + return Expanded( + child: Padding( + padding: const EdgeInsets.all(15.0), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.4, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(15.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Center( + child: Text( + "Link Space Model to Spaces", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.blueAccent, + ), + ), + ), + const Divider(), + const SizedBox(height: 16), + _buildDetailRow( + "Space model name:", widget.spaceModel.modelName), + _buildDetailRow("Creation date and time:", + widget.spaceModel.createdAt.toString()), + _buildDetailRow("Created by:", "Admin"), + const SizedBox(height: 12), + const Text( + "Link to:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const Text( + "Please select all the spaces where you would like to link the Routine.", + style: TextStyle(fontSize: 12, color: Colors.grey), + ), + const SizedBox(height: 8), + Expanded( + child: SizedBox( + child: Column( + children: [ + Expanded( + flex: 7, + child: Container( + color: ColorsManager.whiteColors, + child: SpaceTreeView( + isSide: true, + onSelect: () { + context + .read() + .add( + LinkSpaceModelSelectedIdsEvent()); + }))) + ], + ), + ), + ), + const SizedBox( + height: 20, + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildActionButtons() { + return Row( + children: [ + Expanded( + child: Container( + height: 50, + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.grayColor, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.grayColor, + width: 1, + ), + ), + ), + child: _buildButton("Cancel", Colors.grey, () { + Navigator.of(context).pop(); + }), + ), + ), + Expanded( + child: Container( + height: 50, + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.grayColor, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.grayColor, + width: 1.0, + ), + ), + ), + child: _buildButton( + "Confirm", + ColorsManager.onSecondaryColor, + () { + final spaceModelBloc = context.read(); + if (!spaceModelBloc.hasSelectedSpaces) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Please select at least one space")), + ); + return; + } else { + spaceModelBloc.add(ValidateSpaceModelEvent(context: context)); + } + }, + ), + ), + ), + ], + ); + } +} + +Widget _buildDetailRow(String label, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + Expanded( + child: Text( + label, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + value, + style: const TextStyle( + fontWeight: FontWeight.bold, color: Colors.black), + ), + ), + ], + ), + ); +} + +Widget _buildButton(String text, Color color, VoidCallback onPressed) { + return InkWell( + onTap: onPressed, + child: Center( + child: Text( + text, + style: + TextStyle(color: color, fontWeight: FontWeight.w400, fontSize: 14), + ), + ), + ); +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/linking_attention_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/linking_attention_dialog.dart new file mode 100644 index 00000000..15d92029 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/linking_attention_dialog.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/confirm_merge_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/confirm_overwrite_dialog.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class LinkingAttentionDialog extends StatelessWidget { + const LinkingAttentionDialog({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: Center( + child: Text( + 'Linking Attention', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 30), + )), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Do you want to merge or overwrite?', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 18), + ), + const SizedBox(height: 8), + Text( + 'Selected spaces already have commissioned Devices', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 14), + ), + const SizedBox(height: 25), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // Cancel Button + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return const ConfirmOverwriteDialog(); + }, + ); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.boxColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Overwrite", + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 16), + ), + ), + ), + const SizedBox(width: 10), + + // OK Button + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return const ConfirmMergeDialog(); + }, + ); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: ColorsManager.boxColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: Text( + "Merge", + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 16), + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart b/lib/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart new file mode 100644 index 00000000..6a228fc1 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class LinkingSuccessful extends StatelessWidget { + const LinkingSuccessful({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: Center( + child: SvgPicture.asset( + Assets.successIcon, + )), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Linking successful', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w400, fontSize: 18), + ), + const SizedBox(height: 25), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/overwrite_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/overwrite_dialog.dart new file mode 100644 index 00000000..9f57a4b1 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/overwrite_dialog.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.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/space_model/models/space_template_model.dart'; + +void showOverwriteDialog( + BuildContext context, LinkSpaceToModelBloc bloc, SpaceTemplateModel model) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return SizedBox( + child: Dialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + elevation: 10, + backgroundColor: Colors.white, + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.3, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + "Overwrite", + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + const SizedBox(height: 15), + const Text( + "Are you sure you want to overwrite?", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + textAlign: TextAlign.center, + ), + const SizedBox(height: 5), + Text( + "Selected spaces already have linked space model / sub-spaces and devices", + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 25), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: Colors.grey[200], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 0, + ), + child: const Text( + "Cancel", + style: TextStyle( + fontSize: 16, + color: Colors.black, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: ElevatedButton( + onPressed: () { + bloc.add(LinkSpaceModelEvent( + isOverWrite: true, + selectedSpaceMode: model.uuid)); + Navigator.of(context).pop(); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: Colors.blue, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + ), + child: const Text( + "OK", + style: TextStyle( + fontSize: 16, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + }, + ); +} diff --git a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart index f446695e..e9be3829 100644 --- a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart @@ -1,13 +1,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; - import 'package:flutter_svg/svg.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/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart'; - import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.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/dialog/custom_loading_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/link_space_model_spaces_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/overwrite_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/delete_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart'; @@ -69,48 +74,174 @@ class SpaceModelCardWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: Text( - model.modelName, - style: - Theme.of(context).textTheme.headlineMedium?.copyWith( - color: Colors.black, - fontWeight: FontWeight.bold, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - if (!topActionsDisabled) - GestureDetector( - onTap: () => _showDeleteDialog(context), - child: Container( - width: 36, // Adjust size as needed - height: 36, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - spreadRadius: 2, - blurRadius: 5, - offset: const Offset(0, 2), - ), - ], + Text( + model.modelName, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, ), - child: Center( - child: SvgPicture.asset( - Assets.deleteSpaceModel, // Your actual SVG path - width: 20, - height: 20, - colorFilter: const ColorFilter.mode( - Colors.grey, BlendMode.srcIn), - ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + Row( + children: [ + InkWell( + onTap: () { + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return BlocProvider( + create: (_) => LinkSpaceToModelBloc(), + child: BlocListener( + listener: (context, state) { + final _bloc = + BlocProvider.of( + context); + if (state is SpaceModelLoading) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(20)), + elevation: 10, + backgroundColor: Colors.white, + child: Padding( + padding: + const EdgeInsets.symmetric( + vertical: 30, + horizontal: 50), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CustomLoadingIndicator(), + const SizedBox(height: 20), + const Text( + "Linking in progress", + style: TextStyle( + fontSize: 16, + fontWeight: + FontWeight.w500, + color: Colors.black87, + ), + ), + ], + ), + ), + ); + }, + ); + } else if (state + is AlreadyHaveLinkedState) { + Navigator.of(dialogContext).pop(); + showOverwriteDialog( + context, _bloc, model); + } else if (state + is SpaceValidationSuccess) { + _bloc.add(LinkSpaceModelEvent( + isOverWrite: false, + selectedSpaceMode: model.uuid)); + + Future.delayed(const Duration(seconds: 1), + () { + Navigator.of(dialogContext).pop(); + Navigator.of(dialogContext).pop(); + Navigator.of(dialogContext).pop(); + }); + + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return const LinkingSuccessful(); + }, + ).then((v) { + Future.delayed( + const Duration(seconds: 2), () { + Navigator.of(dialogContext).pop(); + }); + }); + } else if (state is SpaceModelLinkSuccess) { + Navigator.of(dialogContext).pop(); + Navigator.of(dialogContext).pop(); + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return const LinkingSuccessful(); + }, + ); + } + }, + child: LinkSpaceModelSpacesDialog( + spaceModel: model, + ), + ), + ); + }, + ); + }, + child: SvgPicture.asset( + Assets.spaceLinkIcon, + fit: BoxFit.contain, ), ), - ), + if (!topActionsDisabled) + InkWell( + onTap: () { + _showDeleteDialog(context); + }, + child: SvgPicture.asset( + Assets.deleteSpaceLinkIcon, + fit: BoxFit.contain, + ), + ), + ], + ), + // Expanded( + // child: Text( + // model.modelName, + // style: + // Theme.of(context).textTheme.headlineMedium?.copyWith( + // color: Colors.black, + // fontWeight: FontWeight.bold, + // ), + // maxLines: 1, + // overflow: TextOverflow.ellipsis, + // ), + // ), + // if (!topActionsDisabled) + // GestureDetector( + // onTap: () => _showDeleteDialog(context), + // child: Container( + // width: 36, // Adjust size as needed + // height: 36, + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // color: Colors.white, + // boxShadow: [ + // BoxShadow( + // color: Colors.black.withOpacity(0.1), + // spreadRadius: 2, + // blurRadius: 5, + // offset: const Offset(0, 2), + // ), + // ], + // ), + // child: Center( + // child: SvgPicture.asset( + // Assets.deleteSpaceModel, // Your actual SVG path + // width: 20, + // height: 20, + // colorFilter: const ColorFilter.mode( + // Colors.grey, BlendMode.srcIn), + // ), + // ), + // ), + // ), ], ), if (!showOnlyName) ...[ diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index d8e27bec..4ebd65df 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/common/edit_chip.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart'; @@ -10,9 +10,9 @@ import 'package:syncrow_web/utils/color_manager.dart'; class SubspaceModelCreate extends StatefulWidget { final List subspaces; final void Function( - List newSubspaces, List? tags)? + List newSubspaces, List? tags)? onSpaceModelUpdate; - final List tags; + final List tags; const SubspaceModelCreate({ Key? key, @@ -28,7 +28,7 @@ class SubspaceModelCreate extends StatefulWidget { class _SubspaceModelCreateState extends State { late List _subspaces; String? errorSubspaceId; - late List _tags; + late List _tags; @override void initState() { @@ -117,7 +117,7 @@ class _SubspaceModelCreateState extends State { .where((s) => !updatedIds.contains(s.internalId)) .toList(); - final List tagsToAppendToSpace = []; + final List tagsToAppendToSpace = []; for (var s in deletedSubspaces) { if (s.tags != null) { diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index a07f9b29..fc6a8c88 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/common/edit_chip.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; @@ -20,6 +21,7 @@ class TagChipDisplay extends StatelessWidget { final BuildContext? pageContext; final List? otherSpaceModels; final List? allSpaceModels; + final List projectTags; const TagChipDisplay(BuildContext context, {Key? key, @@ -31,14 +33,14 @@ class TagChipDisplay extends StatelessWidget { required this.spaceNameController, this.pageContext, this.otherSpaceModels, - this.allSpaceModels}) + this.allSpaceModels, + required this.projectTags}) : super(key: key); @override Widget build(BuildContext context) { return (spaceModel?.tags?.isNotEmpty == true || - spaceModel?.subspaceModels - ?.any((subspace) => subspace.tags?.isNotEmpty == true) == + spaceModel?.subspaceModels?.any((subspace) => subspace.tags?.isNotEmpty == true) == true) ? SizedBox( width: screenWidth * 0.25, @@ -59,8 +61,7 @@ class TagChipDisplay extends StatelessWidget { // Combine tags from spaceModel and subspaces ...TagHelper.groupTags([ ...?spaceModel?.tags, - ...?spaceModel?.subspaceModels - ?.expand((subspace) => subspace.tags ?? []) + ...?spaceModel?.subspaceModels?.expand((subspace) => subspace.tags ?? []) ]).entries.map( (entry) => Chip( avatar: SizedBox( @@ -76,9 +77,7 @@ class TagChipDisplay extends StatelessWidget { style: Theme.of(context) .textTheme .bodySmall! - .copyWith( - color: - ColorsManager.spaceColor), + .copyWith(color: ColorsManager.spaceColor), ), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( @@ -105,13 +104,12 @@ class TagChipDisplay extends StatelessWidget { spaceModel: spaceModel, otherSpaceModels: otherSpaceModels, initialTags: TagHelper.generateInitialTags( - subspaces: subspaces, - spaceTagModels: spaceModel?.tags ?? []), + subspaces: subspaces, spaceTagModels: spaceModel?.tags ?? []), title: 'Edit Device', - addedProducts: - TagHelper.createInitialSelectedProducts( - spaceModel?.tags ?? [], subspaces), + addedProducts: TagHelper.createInitialSelectedProducts( + spaceModel?.tags ?? [], subspaces), spaceName: spaceModel?.modelName ?? '', + projectTags: projectTags, )); }) ], @@ -134,6 +132,7 @@ class TagChipDisplay extends StatelessWidget { isCreate: true, spaceModel: spaceModel, otherSpaceModels: otherSpaceModels, + projectTags: projectTags, ), ); }, diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart index f45471cd..c4e27051 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart @@ -1,6 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; abstract class AddDeviceModelState extends Equatable { const AddDeviceModelState(); @@ -15,7 +15,7 @@ class AddDeviceModelLoading extends AddDeviceModelState {} class AddDeviceModelLoaded extends AddDeviceModelState { final List selectedProducts; - final List initialTag; + final List initialTag; const AddDeviceModelLoaded({ required this.selectedProducts, diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart index 9b3a8b1e..b9018b2b 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart @@ -1,7 +1,7 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; abstract class AddDeviceTypeModelEvent extends Equatable { const AddDeviceTypeModelEvent(); @@ -25,7 +25,7 @@ class UpdateProductCountEvent extends AddDeviceTypeModelEvent { class InitializeDeviceTypeModel extends AddDeviceTypeModelEvent { - final List initialTags; + final List initialTags; final List addedProducts; const InitializeDeviceTypeModel({ diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 9d0eac96..c0226ba8 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -2,13 +2,13 @@ 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/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart'; @@ -20,7 +20,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? products; final List? initialSelectedProducts; final List? subspaces; - final List? spaceTagModels; + final List? spaceTagModels; final List? allTags; final String spaceName; final bool isCreate; @@ -28,6 +28,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final BuildContext? pageContext; final SpaceTemplateModel? spaceModel; final List? allSpaceModels; + final List projectTags; const AddDeviceTypeModelWidget( {super.key, @@ -41,7 +42,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { this.pageContext, this.otherSpaceModels, this.spaceModel, - this.allSpaceModels}); + this.allSpaceModels, + required this.projectTags}); @override Widget build(BuildContext context) { @@ -78,8 +80,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { const SizedBox(height: 16), Expanded( child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 20.0), + padding: const EdgeInsets.symmetric(horizontal: 20.0), child: ScrollableGridViewWidget( isCreate: isCreate, products: products, @@ -112,6 +113,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { allSpaceModels: allSpaceModels, products: products, allTags: allTags, + projectTags: projectTags, pageContext: pageContext, otherSpaceModels: otherSpaceModels, spaceModel: SpaceTemplateModel( @@ -137,6 +139,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { subspaces: subspaces, addedProducts: initialSelectedProducts ?? [], allTags: allTags, + projectTags: projectTags, spaceName: spaceName, initialTags: initialTags, otherSpaceModels: otherSpaceModels, @@ -149,11 +152,10 @@ class AddDeviceTypeModelWidget extends StatelessWidget { ), SizedBox( width: 140, - child: - BlocBuilder( + child: BlocBuilder( builder: (context, state) { - final isDisabled = state is AddDeviceModelLoaded && - state.selectedProducts.isEmpty; + final isDisabled = + state is AddDeviceModelLoaded && state.selectedProducts.isEmpty; return DefaultButton( backgroundColor: ColorsManager.secondaryColor, @@ -166,15 +168,13 @@ class AddDeviceTypeModelWidget extends StatelessWidget { : () async { if (state is AddDeviceModelLoaded && state.selectedProducts.isNotEmpty) { - final initialTags = - TagHelper.generateInitialTags( + final initialTags = TagHelper.generateInitialTags( spaceTagModels: spaceTagModels, subspaces: subspaces, ); - final dialogTitle = initialTags.isNotEmpty - ? 'Edit Device' - : 'Assign Tags'; + final dialogTitle = + initialTags.isNotEmpty ? 'Edit Device' : 'Assign Tags'; Navigator.of(context).pop(); await showDialog( context: context, @@ -184,6 +184,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { subspaces: subspaces, addedProducts: state.selectedProducts, allTags: allTags, + projectTags: projectTags, spaceName: spaceName, initialTags: initialTags, otherSpaceModels: otherSpaceModels, diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 655c6a83..25a0177f 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -9,10 +9,10 @@ import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_ import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; -import 'package:syncrow_web/utils/constants/temp_const.dart'; class DevicesManagementApi { - Future> fetchDevices(String communityId, String spaceId, String projectId) async { + Future> fetchDevices( + String communityId, String spaceId, String projectId) async { try { final response = await HTTPService().get( path: communityId.isNotEmpty && spaceId.isNotEmpty diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index 15764083..5ae3e4d9 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -1,8 +1,8 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; -import 'package:syncrow_web/utils/constants/temp_const.dart'; class SpaceModelManagementApi { Future> listSpaceModels( @@ -33,8 +33,8 @@ class SpaceModelManagementApi { return response; } - Future updateSpaceModel(CreateSpaceTemplateBodyModel spaceModel, - String spaceModelUuid, String projectId) async { + Future updateSpaceModel( + CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid, String projectId) async { final response = await HTTPService().put( path: ApiEndpoints.updateSpaceModel .replaceAll('{projectId}', projectId) @@ -47,8 +47,7 @@ class SpaceModelManagementApi { return response; } - Future getSpaceModel( - String spaceModelUuid, String projectId) async { + Future getSpaceModel(String spaceModelUuid, String projectId) async { final response = await HTTPService().get( path: ApiEndpoints.getSpaceModel .replaceAll('{projectId}', projectId) @@ -61,6 +60,36 @@ class SpaceModelManagementApi { return response; } + Future linkSpaceModel( + {required String spaceModelUuid, + required String projectId, + required List spaceUuids, + required bool isOverWrite}) async { + final response = await HTTPService().post( + path: ApiEndpoints.linkSpaceModel + .replaceAll('{projectId}', projectId) + .replaceAll('{spaceModelUuid}', spaceModelUuid), + showServerMessage: true, + body: {"spaceUuids": spaceUuids, "overwrite": isOverWrite}, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + + Future validateSpaceModel(String projectId, List spaceUuids) async { + final response = await HTTPService().post( + path: ApiEndpoints.validateSpaceModel + .replaceAll('{projectId}', projectId), + showServerMessage: true, + body: {"spacesUuids": spaceUuids}, + expectedResponseModel: (json) { + return json; + }); + return response; + } + Future deleteSpaceModel(String spaceModelUuid, String projectId) async { final response = await HTTPService().delete( path: ApiEndpoints.getSpaceModel @@ -73,4 +102,17 @@ class SpaceModelManagementApi { ); return response; } + + Future> listTags({required String projectId}) async { + final response = await HTTPService().get( + path: ApiEndpoints.listTags.replaceAll('{projectId}', projectId), + expectedResponseModel: (json) { + List jsonData = json['data']; + return jsonData.map((jsonItem) { + return Tag.fromJson(jsonItem); + }).toList(); + }, + ); + return response; + } } diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index a4bcc0da..4e506093 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -52,7 +52,7 @@ abstract class ColorsManager { static const Color semiTransparentBlackColor = Color(0x3F000000); static const Color transparentColor = Color(0x00000000); static const Color spaceColor = Color(0xB2023DFE); - static const Color counterBackgroundColor = Color(0xCCF4F4F4); + static const Color counterBackgroundColor = Color.fromARGB(204, 105, 2, 2); static const Color neutralGray = Color(0xFFE5E5E5); static const Color warningRed = Color(0xFFFF6465); static const Color borderColor = Color(0xFFE5E5E5); diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 1022d734..5aa188d9 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -89,6 +89,13 @@ abstract class ApiEndpoints { static const String createSpaceModel = '/projects/{projectId}/space-models'; static const String getSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}'; static const String updateSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}'; + //tag + static const String listTags = '/projects/{projectId}/tags'; + + static const String linkSpaceModel = + '/projects/{projectId}/space-models/{spaceModelUuid}/spaces/link'; + + static const String validateSpaceModel = '/projects/{projectId}/spaces/validate'; static const String roleTypes = '/role/types'; static const String permission = '/permission/{roleUuid}'; diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index d7b4d283..b7a0115f 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -402,5 +402,10 @@ class Assets { static const String link = 'assets/icons/link.svg'; static const String duplicate = 'assets/icons/duplicate.svg'; static const String spaceDelete = 'assets/icons/space_delete.svg'; + + static const String deleteSpaceLinkIcon = + 'assets/icons/delete_space_link_icon.svg'; + static const String spaceLinkIcon = 'assets/icons/space_link_icon.svg'; + static const String successIcon = 'assets/icons/success_icon.svg'; + } -//user_management.svg