Merge pull request #81 from SyncrowIOT/bugifx/tag-validation

Bugifx/tag-validation
This commit is contained in:
hannathkadher
2025-02-04 12:36:15 +04:00
committed by GitHub
9 changed files with 104 additions and 69 deletions

View File

@ -87,6 +87,7 @@ class SpaceManagementBloc
prevSpaceModels = List<SpaceTemplateModel>.from( prevSpaceModels = List<SpaceTemplateModel>.from(
(previousState as dynamic).spaceModels ?? [], (previousState as dynamic).spaceModels ?? [],
); );
allSpaces.addAll(prevSpaceModels);
} }
if (prevSpaceModels.isEmpty) { if (prevSpaceModels.isEmpty) {
@ -314,7 +315,6 @@ class SpaceManagementBloc
SelectSpaceEvent event, SelectSpaceEvent event,
Emitter<SpaceManagementState> emit, Emitter<SpaceManagementState> emit,
) { ) {
_handleCommunitySpaceStateUpdate( _handleCommunitySpaceStateUpdate(
emit: emit, emit: emit,
selectedCommunity: event.selectedCommunity, selectedCommunity: event.selectedCommunity,

View File

@ -64,11 +64,11 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
); );
} else if (state is SpaceModelLoaded) { } else if (state is SpaceModelLoaded) {
return LoadedSpaceView( return LoadedSpaceView(
communities: state.communities, communities: state.communities,
products: state.products, products: state.products,
spaceModels: state.spaceModels, spaceModels: state.spaceModels,
shouldNavigateToSpaceModelPage: true, shouldNavigateToSpaceModelPage: true,
); );
} else if (state is SpaceManagementError) { } else if (state is SpaceManagementError) {
return Center(child: Text('Error: ${state.errorMessage}')); return Center(child: Text('Error: ${state.errorMessage}'));
} }

View File

@ -22,7 +22,9 @@ 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/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_card_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart';
import 'package:syncrow_web/pages/spaces_management/helper/connection_helper.dart';
import 'package:syncrow_web/pages/spaces_management/helper/space_helper.dart'; import 'package:syncrow_web/pages/spaces_management/helper/space_helper.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/space_template_model.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@ -131,7 +133,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
communities: widget.communities, communities: widget.communities,
communityName: widget.selectedCommunity?.name, communityName: widget.selectedCommunity?.name,
community: widget.selectedCommunity, community: widget.selectedCommunity,
isSave: isSave(spaces), isSave: SpaceHelper.isSave(spaces),
isEditingName: isEditingName, isEditingName: isEditingName,
nameController: _nameController, nameController: _nameController,
onSave: _saveSpaces, onSave: _saveSpaces,
@ -176,7 +178,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
children: [ children: [
for (var connection in connections) for (var connection in connections)
Opacity( Opacity(
opacity: _isHighlightedConnection(connection) opacity: ConnectionHelper.isHighlightedConnection(
connection, widget.selectedSpace)
? 1.0 ? 1.0
: 0.3, // Adjust opacity : 0.3, // Adjust opacity
child: CustomPaint( child: CustomPaint(
@ -209,7 +212,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
}, },
buildSpaceContainer: (int index) { buildSpaceContainer: (int index) {
final bool isHighlighted = final bool isHighlighted =
_isHighlightedSpace(spaces[index]); SpaceHelper.isHighlightedSpace(
spaces[index], widget.selectedSpace);
return Opacity( return Opacity(
opacity: isHighlighted ? 1.0 : 0.3, opacity: isHighlighted ? 1.0 : 0.3,
@ -295,7 +299,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
return CreateSpaceDialog( return CreateSpaceDialog(
products: widget.products, products: widget.products,
spaceModels: widget.spaceModels, spaceModels: widget.spaceModels,
allTags: _getAllTagValues(spaces), allTags:
TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
parentSpace: parentIndex != null ? spaces[parentIndex] : null, parentSpace: parentIndex != null ? spaces[parentIndex] : null,
onCreateSpace: (String name, onCreateSpace: (String name,
String icon, String icon,
@ -306,7 +311,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
setState(() { setState(() {
// Set the first space in the center or use passed position // Set the first space in the center or use passed position
Offset centerPosition = Offset centerPosition =
position ?? _getCenterPosition(screenSize); position ?? ConnectionHelper.getCenterPosition(screenSize);
SpaceModel newSpace = SpaceModel( SpaceModel newSpace = SpaceModel(
name: name, name: name,
icon: icon, icon: icon,
@ -358,7 +363,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
tags: widget.selectedSpace?.tags, tags: widget.selectedSpace?.tags,
subspaces: widget.selectedSpace?.subspaces, subspaces: widget.selectedSpace?.subspaces,
isEdit: true, isEdit: true,
allTags: _getAllTagValues(spaces), allTags: TagHelper.getAllTagValues(
widget.communities, widget.spaceModels),
onCreateSpace: (String name, onCreateSpace: (String name,
String icon, String icon,
List<SelectedProduct> selectedProducts, List<SelectedProduct> selectedProducts,
@ -527,17 +533,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
); );
} }
bool _isHighlightedSpace(SpaceModel space) {
final selectedSpace = widget.selectedSpace;
if (selectedSpace == null) return true;
return space == selectedSpace ||
selectedSpace.parent?.internalId == space.internalId ||
selectedSpace.children
?.any((child) => child.internalId == space.internalId) ==
true;
}
void _deselectSpace(BuildContext context) { void _deselectSpace(BuildContext context) {
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent( SelectSpaceEvent(
@ -545,28 +540,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
); );
} }
bool _isHighlightedConnection(Connection connection) {
if (widget.selectedSpace == null) return true;
return connection.startSpace == widget.selectedSpace ||
connection.endSpace == widget.selectedSpace;
}
Offset _getCenterPosition(Size screenSize) {
return Offset(
screenSize.width / 2 - 260,
screenSize.height / 2 - 200,
);
}
bool isSave(List<SpaceModel> spaces) {
return spaces.isNotEmpty &&
spaces.any((space) =>
space.status == SpaceStatus.newSpace ||
space.status == SpaceStatus.modified ||
space.status == SpaceStatus.deleted);
}
void _onDuplicate(BuildContext parentContext) { void _onDuplicate(BuildContext parentContext) {
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
@ -652,11 +625,10 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
const double horizontalGap = 200.0; const double horizontalGap = 200.0;
const double verticalGap = 100.0; const double verticalGap = 100.0;
final Map<String, int> nameCounters = {};
SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition, SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition,
SpaceModel? duplicatedParent) { SpaceModel? duplicatedParent) {
Offset newPosition = parentPosition + Offset(horizontalGap, 0); Offset newPosition =
Offset(parentPosition.dx + horizontalGap, original.position.dy);
while (spaces.any((s) => while (spaces.any((s) =>
(s.position - newPosition).distance < horizontalGap && (s.position - newPosition).distance < horizontalGap &&
@ -690,7 +662,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final newConnection = Connection( final newConnection = Connection(
startSpace: duplicatedParent, startSpace: duplicatedParent,
endSpace: duplicated, endSpace: duplicated,
direction: "down", direction: original.incomingConnection?.direction ?? 'down',
); );
connections.add(newConnection); connections.add(newConnection);
duplicated.incomingConnection = newConnection; duplicated.incomingConnection = newConnection;
@ -729,10 +701,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
child.incomingConnection?.direction == "down" ?? false; child.incomingConnection?.direction == "down" ?? false;
if (isDownDirection && childrenWithDownDirection.length == 1) { if (isDownDirection && childrenWithDownDirection.length == 1) {
// Place the only "down" child vertically aligned with the parent
childStartPosition = duplicated.position + Offset(0, verticalGap); childStartPosition = duplicated.position + Offset(0, verticalGap);
} else if (!isDownDirection) { } else if (!isDownDirection) {
// Position children with other directions horizontally
childStartPosition = duplicated.position + Offset(horizontalGap, 0); childStartPosition = duplicated.position + Offset(horizontalGap, 0);
} }
@ -753,14 +723,4 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
duplicateRecursive(space, space.position, duplicatedParent); duplicateRecursive(space, space.position, duplicatedParent);
} }
} }
List<String> _getAllTagValues(List<SpaceModel> spaces) {
final List<String> allTags = [];
for (final space in spaces) {
if (space.tags != null) {
allTags.addAll(space.listAllTagValues());
}
}
return allTags;
}
} }

View File

@ -83,8 +83,13 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled = isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty; enteredName.isNotEmpty || nameController.text.isNotEmpty;
tags = widget.tags ?? []; if (widget.currentSpaceModel != null) {
subspaces = widget.subspaces ?? []; subspaces = [];
tags = [];
} else {
tags = widget.tags ?? [];
subspaces = widget.subspaces ?? [];
}
selectedSpaceModel = widget.currentSpaceModel; selectedSpaceModel = widget.currentSpaceModel;
} }
@ -461,6 +466,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
builder: (context) => AssignTagDialog( builder: (context) => AssignTagDialog(
products: widget.products, products: widget.products,
subspaces: subspaces, subspaces: subspaces,
allTags: widget.allTags,
addedProducts: TagHelper addedProducts: TagHelper
.createInitialSelectedProductsForTags( .createInitialSelectedProductsForTags(
tags ?? [], subspaces), tags ?? [], subspaces),

View File

@ -57,6 +57,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags); final tags = List<Tag>.from(currentState.tags);
tags[event.index].tag = event.tag; tags[event.index].tag = event.tag;
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
isSaveEnabled: _validateTags(tags), isSaveEnabled: _validateTags(tags),
@ -78,6 +79,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
isSaveEnabled: _validateTags(tags), isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
)); ));
} }
}); });
@ -106,12 +108,13 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: updatedTags, tags: updatedTags,
isSaveEnabled: _validateTags(updatedTags), isSaveEnabled: _validateTags(updatedTags),
errorMessage: _getValidationError(updatedTags),
)); ));
} else { } else {
emit(const AssignTagLoaded( emit(const AssignTagLoaded(
tags: [], tags: [],
isSaveEnabled: false, isSaveEnabled: false,
)); errorMessage: 'Failed to delete tag'));
} }
}); });
} }
@ -125,7 +128,10 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
String? _getValidationError(List<Tag> tags) { String? _getValidationError(List<Tag> tags) {
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
if (hasEmptyTag) return 'Tags cannot be empty.'; if (hasEmptyTag) {
return 'Tags cannot be empty.';
}
final duplicateTags = tags final duplicateTags = tags
.map((tag) => tag.tag?.trim() ?? '') .map((tag) => tag.tag?.trim() ?? '')
.fold<Map<String, int>>({}, (map, tag) { .fold<Map<String, int>>({}, (map, tag) {

View File

@ -21,11 +21,11 @@ class AssignTagLoaded extends AssignTagState {
const AssignTagLoaded({ const AssignTagLoaded({
required this.tags, required this.tags,
required this.isSaveEnabled, required this.isSaveEnabled,
this.errorMessage, required this.errorMessage,
}); });
@override @override
List<Object> get props => [tags, isSaveEnabled]; List<Object> get props => [tags, isSaveEnabled, errorMessage ?? ''];
} }
class AssignTagError extends AssignTagState { class AssignTagError extends AssignTagState {

View File

@ -0,0 +1,21 @@
import 'dart:ui';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
class ConnectionHelper {
static Offset getCenterPosition(Size screenSize) {
return Offset(
screenSize.width / 2 - 260,
screenSize.height / 2 - 200,
);
}
static bool isHighlightedConnection(
Connection connection, SpaceModel? selectedSpace) {
if (selectedSpace == null) return true;
return connection.startSpace == selectedSpace ||
connection.endSpace == selectedSpace;
}
}

View File

@ -40,4 +40,22 @@ class SpaceHelper {
return "$baseName(${maxNumber + 1})"; return "$baseName(${maxNumber + 1})";
} }
static bool isSave(List<SpaceModel> spaces) {
return spaces.isNotEmpty &&
spaces.any((space) =>
space.status == SpaceStatus.newSpace ||
space.status == SpaceStatus.modified ||
space.status == SpaceStatus.deleted);
}
static bool isHighlightedSpace(SpaceModel space, SpaceModel? selectedSpace) {
if (selectedSpace == null) return true;
return space == selectedSpace ||
selectedSpace.parent?.internalId == space.internalId ||
selectedSpace.children
?.any((child) => child.internalId == space.internalId) ==
true;
}
} }

View File

@ -1,8 +1,11 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.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/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/selected_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/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.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/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_model.dart';
@ -337,4 +340,25 @@ class TagHelper {
checkTagExistInSubspace: checkTagExistInSubspace, checkTagExistInSubspace: checkTagExistInSubspace,
); );
} }
static List<String> getAllTagValues(
List<CommunityModel> communities, List<SpaceTemplateModel>? spaceModels) {
final Set<String> allTags = {};
if (spaceModels != null) {
for (var model in spaceModels) {
allTags.addAll(model.listAllTagValues());
}
}
for (final community in communities) {
for (final space in community.spaces) {
if (space.tags != null) {
allTags.addAll(space.listAllTagValues());
}
}
}
return allTags.toList();
}
} }