mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 07:07:19 +00:00
Merge pull request #79 from SyncrowIOT/bugfix/fix-duplicate-space
Bugfix/fix-duplicate-space
This commit is contained in:
@ -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<SpaceManagementState> emit,
|
||||
) {
|
||||
|
||||
_handleCommunitySpaceStateUpdate(
|
||||
emit: emit,
|
||||
selectedCommunity: event.selectedCommunity,
|
||||
|
@ -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<dynamic>?)
|
||||
?.where((item) => item is Map<String, dynamic>) // Validate type
|
||||
.map((item) => Tag.fromJson(item as Map<String, dynamic>))
|
||||
|
@ -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<CommunityStructureArea> {
|
||||
screenSize,
|
||||
position:
|
||||
spaces[index].position + newPosition,
|
||||
|
||||
parentIndex: index,
|
||||
direction: direction,
|
||||
);
|
||||
@ -351,7 +351,10 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
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,
|
||||
@ -374,6 +377,15 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
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<CommunityStructureArea> {
|
||||
}).toList();
|
||||
|
||||
if (spacesToSave.isEmpty) {
|
||||
debugPrint("No new or modified spaces to save.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -643,12 +654,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
|
||||
final Map<String, int> 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<CommunityStructureArea> {
|
||||
newPosition += Offset(horizontalGap, 0);
|
||||
}
|
||||
|
||||
final duplicatedName = _generateCopyName(original.name);
|
||||
final duplicatedName =
|
||||
SpaceHelper.generateUniqueSpaceName(original.name, spaces);
|
||||
|
||||
final duplicated = SpaceModel(
|
||||
name: duplicatedName,
|
||||
|
@ -40,6 +40,7 @@ class CreateSpaceDialog extends StatefulWidget {
|
||||
final List<SubspaceModel>? subspaces;
|
||||
final List<Tag>? tags;
|
||||
final List<String>? 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<CreateSpaceDialog> {
|
||||
enteredName.isNotEmpty || nameController.text.isNotEmpty;
|
||||
tags = widget.tags ?? [];
|
||||
subspaces = widget.subspaces ?? [];
|
||||
selectedSpaceModel = widget.currentSpaceModel;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -457,7 +460,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
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<CreateSpaceDialog> {
|
||||
enteredName,
|
||||
widget.isEdit,
|
||||
widget.products,
|
||||
subspaces,
|
||||
);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
@ -571,12 +573,23 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
}
|
||||
|
||||
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<CreateSpaceDialog> {
|
||||
products: products,
|
||||
existingSubSpaces: existingSubSpaces,
|
||||
onSave: (slectedSubspaces) {
|
||||
final List<Tag> 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<CreateSpaceDialog> {
|
||||
}
|
||||
|
||||
void _showTagCreateDialog(BuildContext context, String name, bool isEdit,
|
||||
List<ProductModel>? products, List<SubspaceModel>? subspaces) {
|
||||
List<ProductModel>? products) {
|
||||
isEdit
|
||||
? showDialog(
|
||||
context: context,
|
||||
|
@ -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<CommunityModel> 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<LoadedSpaceView> {
|
||||
late List<SpaceTemplateModel> _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<SpaceTemplateModel> 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<CommunityModel> communities) {
|
||||
for (var community in communities) {
|
||||
for (var space in community.spaces) {
|
||||
if (space.uuid == uuid) return space;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -233,7 +233,8 @@ class AssignTagDialog extends StatelessWidget {
|
||||
label: 'Add New Device',
|
||||
onPressed: () async {
|
||||
final updatedTags = List<Tag>.from(state.tags);
|
||||
final result = processTags(updatedTags, subspaces);
|
||||
final result =
|
||||
TagHelper.processTags(updatedTags, subspaces);
|
||||
|
||||
final processedTags =
|
||||
result['updatedTags'] as List<Tag>;
|
||||
@ -271,8 +272,8 @@ class AssignTagDialog extends StatelessWidget {
|
||||
onPressed: state.isSaveEnabled
|
||||
? () async {
|
||||
final updatedTags = List<Tag>.from(state.tags);
|
||||
final result =
|
||||
processTags(updatedTags, subspaces);
|
||||
final result = TagHelper.processTags(
|
||||
updatedTags, subspaces);
|
||||
|
||||
final processedTags =
|
||||
result['updatedTags'] as List<Tag>;
|
||||
@ -311,33 +312,4 @@ class AssignTagDialog extends StatelessWidget {
|
||||
);
|
||||
return availableTagsForTagModel;
|
||||
}
|
||||
|
||||
Map<String, dynamic> processTags(
|
||||
List<Tag> updatedTags, List<SubspaceModel>? subspaces) {
|
||||
return TagHelper.updateTags<Tag>(
|
||||
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<dynamic>? 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;
|
||||
}
|
||||
}
|
||||
|
43
lib/pages/spaces_management/helper/space_helper.dart
Normal file
43
lib/pages/spaces_management/helper/space_helper.dart
Normal file
@ -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<CommunityModel> 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<SpaceModel> spaces) {
|
||||
if (internalId != null) {
|
||||
for (var space in spaces) {
|
||||
if (space.internalId == internalId) return space;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static String generateUniqueSpaceName(
|
||||
String originalName, List<SpaceModel> 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})";
|
||||
}
|
||||
}
|
@ -278,7 +278,8 @@ class TagHelper {
|
||||
.toList();
|
||||
}
|
||||
|
||||
static int? checkTagExistInSubspaceModels(TagModel tag, List<dynamic>? subspaces) {
|
||||
static int? checkTagExistInSubspaceModels(
|
||||
TagModel tag, List<dynamic>? subspaces) {
|
||||
if (subspaces == null) return null;
|
||||
|
||||
for (int i = 0; i < subspaces.length; i++) {
|
||||
@ -308,4 +309,32 @@ class TagHelper {
|
||||
);
|
||||
}
|
||||
|
||||
static int? checkTagExistInSubspace(Tag tag, List<dynamic>? 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<String, dynamic> processTags(
|
||||
List<Tag> updatedTags, List<SubspaceModel>? subspaces) {
|
||||
return TagHelper.updateTags<Tag>(
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -11,8 +11,10 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SpaceModelPage extends StatelessWidget {
|
||||
final List<ProductModel>? products;
|
||||
final Function(List<SpaceTemplateModel>)? 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(
|
||||
|
Reference in New Issue
Block a user