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 1b5692c6..31e19af4 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 @@ -179,6 +179,7 @@ class SpaceManagementBloc final updatedCommunities = await Future.wait(communities.map((community) async { final spaces = await _fetchSpacesForCommunity(community.uuid); + return CommunityModel( uuid: community.uuid, createdAt: community.createdAt, @@ -313,6 +314,7 @@ class SpaceManagementBloc SelectSpaceEvent event, Emitter emit, ) { + _handleCommunitySpaceStateUpdate( emit: emit, selectedCommunity: event.selectedCommunity, diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index 6ad91dad..71d365ca 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -95,6 +95,9 @@ class SpaceModel { icon: json['icon'] ?? Assets.location, position: Offset(json['x'] ?? 0, json['y'] ?? 0), isHovered: false, + spaceModel: json['spaceModel'] != null + ? SpaceTemplateModel.fromJson(json['spaceModel']) + : null, tags: (json['tags'] as List?) ?.where((item) => item is Map) // Validate type .map((item) => Tag.fromJson(item as Map)) 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 3a208a0d..795a140f 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 @@ -22,6 +22,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/curved_li import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_card_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/space_helper.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -195,7 +196,6 @@ class _CommunityStructureAreaState extends State { screenSize, position: spaces[index].position + newPosition, - parentIndex: index, direction: direction, ); @@ -351,11 +351,14 @@ class _CommunityStructureAreaState extends State { spaceModels: widget.spaceModels, name: widget.selectedSpace!.name, icon: widget.selectedSpace!.icon, + 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: _getAllTagValues(spaces), + allTags: _getAllTagValues(spaces), onCreateSpace: (String name, String icon, List selectedProducts, @@ -374,6 +377,15 @@ class _CommunityStructureAreaState extends State { widget.selectedSpace!.status = SpaceStatus.modified; // Mark as modified } + + for (var space in spaces) { + if (space.internalId == widget.selectedSpace?.internalId) { + space.status = SpaceStatus.modified; + space.subspaces = subspaces; + space.tags = tags; + space.name = name; + } + } }); }, key: Key(widget.selectedSpace!.name), @@ -452,7 +464,6 @@ class _CommunityStructureAreaState extends State { }).toList(); if (spacesToSave.isEmpty) { - debugPrint("No new or modified spaces to save."); return; } @@ -643,12 +654,6 @@ class _CommunityStructureAreaState extends State { final Map nameCounters = {}; - String _generateCopyName(String originalName) { - final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim(); - nameCounters[baseName] = (nameCounters[baseName] ?? 0) + 1; - return "$baseName(${nameCounters[baseName]})"; - } - SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition, SpaceModel? duplicatedParent) { Offset newPosition = parentPosition + Offset(horizontalGap, 0); @@ -659,7 +664,8 @@ class _CommunityStructureAreaState extends State { newPosition += Offset(horizontalGap, 0); } - final duplicatedName = _generateCopyName(original.name); + final duplicatedName = + SpaceHelper.generateUniqueSpaceName(original.name, spaces); final duplicated = SpaceModel( name: duplicatedName, @@ -748,7 +754,7 @@ class _CommunityStructureAreaState extends State { } } - List _getAllTagValues(List spaces) { + List _getAllTagValues(List spaces) { final List allTags = []; for (final space in spaces) { if (space.tags != null) { 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 ada60850..a435a8fc 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 @@ -40,6 +40,7 @@ class CreateSpaceDialog extends StatefulWidget { final List? subspaces; final List? tags; final List? allTags; + final SpaceTemplateModel? currentSpaceModel; const CreateSpaceDialog( {super.key, @@ -54,7 +55,8 @@ class CreateSpaceDialog extends StatefulWidget { this.selectedProducts = const [], this.spaceModels, this.subspaces, - this.tags}); + this.tags, + this.currentSpaceModel}); @override CreateSpaceDialogState createState() => CreateSpaceDialogState(); @@ -83,6 +85,7 @@ class CreateSpaceDialogState extends State { enteredName.isNotEmpty || nameController.text.isNotEmpty; tags = widget.tags ?? []; subspaces = widget.subspaces ?? []; + selectedSpaceModel = widget.currentSpaceModel; } @override @@ -457,7 +460,7 @@ class CreateSpaceDialogState extends State { context: context, builder: (context) => AssignTagDialog( products: widget.products, - subspaces: widget.subspaces, + subspaces: subspaces, addedProducts: TagHelper .createInitialSelectedProductsForTags( tags ?? [], subspaces), @@ -488,7 +491,6 @@ class CreateSpaceDialogState extends State { enteredName, widget.isEdit, widget.products, - subspaces, ); }, style: TextButton.styleFrom( @@ -571,12 +573,23 @@ class CreateSpaceDialogState extends State { } bool _isNameConflict(String value) { - return (widget.parentSpace?.children.any((child) => child.name == value) ?? - false) || - (widget.parentSpace?.name == value) || - (widget.editSpace?.parent?.name == value) || - (widget.editSpace?.children.any((child) => child.name == value) ?? - false); + final parentSpace = widget.parentSpace; + final editSpace = widget.editSpace; + final siblings = parentSpace?.children + .where((child) => child.uuid != editSpace?.uuid) + .toList() ?? + []; + final siblingConflict = siblings.any((child) => child.name == value); + final parentConflict = + parentSpace?.name == value && parentSpace?.uuid != editSpace?.uuid; + final parentOfEditSpaceConflict = editSpace?.parent?.name == value && + editSpace?.parent?.uuid != editSpace?.uuid; + final childConflict = + editSpace?.children.any((child) => child.name == value) ?? false; + return siblingConflict || + parentConflict || + parentOfEditSpaceConflict || + childConflict; } void _showLinkSpaceModelDialog(BuildContext context) { @@ -617,9 +630,26 @@ class CreateSpaceDialogState extends State { products: products, existingSubSpaces: existingSubSpaces, onSave: (slectedSubspaces) { + final List tagsToAppendToSpace = []; + + if (slectedSubspaces != null) { + final updatedIds = + slectedSubspaces.map((s) => s.internalId).toSet(); + if (existingSubSpaces != null) { + final deletedSubspaces = existingSubSpaces + .where((s) => !updatedIds.contains(s.internalId)) + .toList(); + for (var s in deletedSubspaces) { + if (s.tags != null) { + tagsToAppendToSpace.addAll(s.tags!); + } + } + } + } if (slectedSubspaces != null) { setState(() { subspaces = slectedSubspaces; + tags?.addAll(tagsToAppendToSpace); selectedSpaceModel = null; }); } @@ -629,7 +659,7 @@ class CreateSpaceDialogState extends State { } void _showTagCreateDialog(BuildContext context, String name, bool isEdit, - List? products, List? subspaces) { + List? products) { isEdit ? showDialog( context: context, 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 cdba0a5a..0ce06c7c 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 @@ -11,7 +11,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_tem import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; -class LoadedSpaceView extends StatelessWidget { +class LoadedSpaceView extends StatefulWidget { final List communities; final CommunityModel? selectedCommunity; final SpaceModel? selectedSpace; @@ -26,41 +26,73 @@ class LoadedSpaceView extends StatelessWidget { this.selectedSpace, this.products, this.spaceModels, - required this.shouldNavigateToSpaceModelPage + required this.shouldNavigateToSpaceModelPage, }); @override - Widget build(BuildContext context) { + _LoadedSpaceViewState createState() => _LoadedSpaceViewState(); +} +class _LoadedSpaceViewState extends State { + late List _spaceModels; + + @override + void initState() { + super.initState(); + _spaceModels = List.from(widget.spaceModels ?? []); + } + + @override + void didUpdateWidget(covariant LoadedSpaceView oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.spaceModels != oldWidget.spaceModels) { + setState(() { + _spaceModels = List.from(widget.spaceModels ?? []); + }); + } + } + + void _onSpaceModelsUpdated(List updatedModels) { + if (mounted && updatedModels != _spaceModels) { + setState(() { + _spaceModels = updatedModels; + }); + } + } + + @override + Widget build(BuildContext context) { return Stack( clipBehavior: Clip.none, children: [ Row( children: [ SidebarWidget( - communities: communities, - selectedSpaceUuid: - selectedSpace?.uuid ?? selectedCommunity?.uuid ?? '', + communities: widget.communities, + selectedSpaceUuid: widget.selectedSpace?.uuid ?? + widget.selectedCommunity?.uuid ?? + '', ), - shouldNavigateToSpaceModelPage + widget.shouldNavigateToSpaceModelPage ? Expanded( child: BlocProvider( create: (context) => SpaceModelBloc( api: SpaceModelManagementApi(), - initialSpaceModels: spaceModels ?? [], + initialSpaceModels: _spaceModels, ), child: SpaceModelPage( - products: products, + products: widget.products, + onSpaceModelsUpdated: _onSpaceModelsUpdated, ), ), ) : CommunityStructureArea( - selectedCommunity: selectedCommunity, - selectedSpace: selectedSpace, - spaces: selectedCommunity?.spaces ?? [], - products: products, - communities: communities, - spaceModels: spaceModels, + selectedCommunity: widget.selectedCommunity, + selectedSpace: widget.selectedSpace, + spaces: widget.selectedCommunity?.spaces ?? [], + products: widget.products, + communities: widget.communities, + spaceModels: _spaceModels, ), ], ), @@ -68,13 +100,4 @@ class LoadedSpaceView extends StatelessWidget { ], ); } - - SpaceModel? findSpaceByUuid(String? uuid, List communities) { - for (var community in communities) { - for (var space in community.spaces) { - if (space.uuid == uuid) return space; - } - } - return null; - } } 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 dd56c0bb..0e08de53 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 @@ -233,7 +233,8 @@ class AssignTagDialog extends StatelessWidget { label: 'Add New Device', onPressed: () async { final updatedTags = List.from(state.tags); - final result = processTags(updatedTags, subspaces); + final result = + TagHelper.processTags(updatedTags, subspaces); final processedTags = result['updatedTags'] as List; @@ -271,8 +272,8 @@ class AssignTagDialog extends StatelessWidget { onPressed: state.isSaveEnabled ? () async { final updatedTags = List.from(state.tags); - final result = - processTags(updatedTags, subspaces); + final result = TagHelper.processTags( + updatedTags, subspaces); final processedTags = result['updatedTags'] as List; @@ -311,33 +312,4 @@ class AssignTagDialog extends StatelessWidget { ); return availableTagsForTagModel; } - - Map processTags( - List updatedTags, List? subspaces) { - return TagHelper.updateTags( - updatedTags: updatedTags, - subspaces: subspaces, - getInternalId: (tag) => tag.internalId, - getLocation: (tag) => tag.location, - setLocation: (tag, location) => tag.location = location, - getSubspaceName: (subspace) => subspace.subspaceName, - getSubspaceTags: (subspace) => subspace.tags, - setSubspaceTags: (subspace, tags) => subspace.tags = tags, - checkTagExistInSubspace: checkTagExistInSubspace, - ); - } - - int? checkTagExistInSubspace(Tag tag, List? subspaces) { - if (subspaces == null) return null; - for (int i = 0; i < subspaces.length; i++) { - final subspace = subspaces[i]; - if (subspace.tags == null) continue; - for (var t in subspace.tags!) { - if (tag.internalId == t.internalId) { - return i; - } - } - } - return null; - } } diff --git a/lib/pages/spaces_management/helper/space_helper.dart b/lib/pages/spaces_management/helper/space_helper.dart new file mode 100644 index 00000000..75aebce9 --- /dev/null +++ b/lib/pages/spaces_management/helper/space_helper.dart @@ -0,0 +1,43 @@ +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'; + +class SpaceHelper { + static SpaceModel? findSpaceByUuid( + String? uuid, List communities) { + for (var community in communities) { + for (var space in community.spaces) { + if (space.uuid == uuid) return space; + } + } + return null; + } + + static SpaceModel? findSpaceByInternalId( + String? internalId, List spaces) { + if (internalId != null) { + for (var space in spaces) { + if (space.internalId == internalId) return space; + } + } + + return null; + } + + static String generateUniqueSpaceName( + String originalName, List spaces) { + final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim(); + int maxNumber = 0; + + for (var space in spaces) { + final match = RegExp(r'^(.*?)\((\d+)\)$').firstMatch(space.name); + if (match != null && match.group(1)?.trim() == baseName) { + int existingNumber = int.parse(match.group(2)!); + if (existingNumber > maxNumber) { + maxNumber = existingNumber; + } + } + } + + return "$baseName(${maxNumber + 1})"; + } +} diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index c50e6ba8..757f46e1 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -278,7 +278,8 @@ class TagHelper { .toList(); } - static int? checkTagExistInSubspaceModels(TagModel tag, List? subspaces) { + static int? checkTagExistInSubspaceModels( + TagModel tag, List? subspaces) { if (subspaces == null) return null; for (int i = 0; i < subspaces.length; i++) { @@ -293,7 +294,7 @@ class TagHelper { return null; } - static Map updateSubspaceTagModels( + static Map updateSubspaceTagModels( List updatedTags, List? subspaces) { return TagHelper.updateTags( updatedTags: updatedTags, @@ -308,4 +309,32 @@ class TagHelper { ); } + static int? checkTagExistInSubspace(Tag tag, List? subspaces) { + if (subspaces == null) return null; + for (int i = 0; i < subspaces.length; i++) { + final subspace = subspaces[i]; + if (subspace.tags == null) continue; + for (var t in subspace.tags!) { + if (tag.internalId == t.internalId) { + return i; + } + } + } + return null; + } + + static Map processTags( + List updatedTags, List? subspaces) { + return TagHelper.updateTags( + updatedTags: updatedTags, + subspaces: subspaces, + getInternalId: (tag) => tag.internalId, + getLocation: (tag) => tag.location, + setLocation: (tag, location) => tag.location = location, + getSubspaceName: (subspace) => subspace.subspaceName, + getSubspaceTags: (subspace) => subspace.tags, + setSubspaceTags: (subspace, tags) => subspace.tags = tags, + checkTagExistInSubspace: checkTagExistInSubspace, + ); + } } 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 5edf912f..22378f1f 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,5 +1,4 @@ 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/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'; 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 b1fce7a1..8f466a5e 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 @@ -11,8 +11,10 @@ import 'package:syncrow_web/utils/color_manager.dart'; class SpaceModelPage extends StatelessWidget { final List? products; + final Function(List)? onSpaceModelsUpdated; - const SpaceModelPage({Key? key, this.products}) : super(key: key); + const SpaceModelPage({Key? key, this.products, this.onSpaceModelsUpdated}) + : super(key: key); @override Widget build(BuildContext context) { @@ -25,6 +27,10 @@ class SpaceModelPage extends StatelessWidget { final allTagValues = _getAllTagValues(spaceModels); final allSpaceModelNames = _getAllSpaceModelName(spaceModels); + if (onSpaceModelsUpdated != null) { + onSpaceModelsUpdated!(spaceModels); + } + return Scaffold( backgroundColor: ColorsManager.whiteColors, body: Padding(