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 3de906d3..a06e6977 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,7 +4,9 @@ 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 { - AssignTagBloc() : super(AssignTagInitial()) { + final List allTags; + + AssignTagBloc(this.allTags) : super(AssignTagInitial()) { on((event, emit) { final initialTags = event.initialTags ?? []; @@ -16,25 +18,25 @@ class AssignTagBloc extends Bloc { } } - final allTags = []; + final tags = []; for (var selectedProduct in event.addedProducts) { final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) { - allTags.addAll(initialTags + tags.addAll(initialTags .where((tag) => tag.product?.uuid == selectedProduct.productId)); continue; } final missingCount = selectedProduct.count - existingCount; - allTags.addAll(initialTags + tags.addAll(initialTags .where((tag) => tag.product?.uuid == selectedProduct.productId)); if (missingCount > 0) { - allTags.addAll(List.generate( + tags.addAll(List.generate( missingCount, (index) => Tag( tag: '', @@ -45,10 +47,14 @@ class AssignTagBloc extends Bloc { } } + final updatedTags = _calculateAvailableTags(allTags, tags); + emit(AssignTagLoaded( - tags: allTags, - isSaveEnabled: _validateTags(allTags), - errorMessage: '')); + tags: tags, + updatedTags: updatedTags, + isSaveEnabled: _validateTags(tags), + errorMessage: '', + )); }); on((event, emit) { @@ -56,10 +62,13 @@ class AssignTagBloc extends Bloc { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); - tags[event.index].tag = event.tag; + tags[event.index] = tags[event.index].copyWith(tag: event.tag); + + final updatedTags = _calculateAvailableTags(allTags, tags); emit(AssignTagLoaded( tags: tags, + updatedTags: updatedTags, isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); @@ -72,12 +81,15 @@ class AssignTagBloc extends Bloc { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); - // Use copyWith for immutability + // Update the location tags[event.index] = tags[event.index].copyWith(location: event.location); + final updatedTags = _calculateAvailableTags(allTags, tags); + emit(AssignTagLoaded( tags: tags, + updatedTags: updatedTags, isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); @@ -92,6 +104,7 @@ class AssignTagBloc extends Bloc { emit(AssignTagLoaded( tags: tags, + updatedTags: _calculateAvailableTags(allTags, tags), isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); @@ -102,38 +115,37 @@ class AssignTagBloc extends Bloc { final currentState = state; if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { - final updatedTags = List.from(currentState.tags) + final tags = List.from(currentState.tags) ..remove(event.tagToDelete); + // Recalculate available tags + final updatedTags = _calculateAvailableTags(allTags, tags); + emit(AssignTagLoaded( - tags: updatedTags, - isSaveEnabled: _validateTags(updatedTags), - errorMessage: _getValidationError(updatedTags), + tags: tags, + updatedTags: updatedTags, + isSaveEnabled: _validateTags(tags), + errorMessage: _getValidationError(tags), )); - } else { - emit(const AssignTagLoaded( - tags: [], - isSaveEnabled: false, - errorMessage: 'Failed to delete tag')); } }); } + // Validate the tags for duplicates or empty values 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; + return uniqueTags.length == tags.length && !hasEmptyTag; } + // Get validation error for duplicate tags String? _getValidationError(List tags) { - final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); - if (hasEmptyTag) { - return 'Tags cannot be empty.'; - } - - final duplicateTags = tags + final nonEmptyTags = tags .map((tag) => tag.tag?.trim() ?? '') + .where((tag) => tag.isNotEmpty) + .toList(); + + final duplicateTags = nonEmptyTags .fold>({}, (map, tag) { map[tag] = (map[tag] ?? 0) + 1; return map; @@ -149,4 +161,15 @@ class AssignTagBloc extends Bloc { return null; } + + List _calculateAvailableTags(List allTags, List tags) { + final selectedTags = tags + .where((tag) => (tag.tag?.trim().isNotEmpty ?? false)) + .map((tag) => tag.tag!.trim()) + .toSet(); + + final availableTags = + allTags.where((tag) => !selectedTags.contains(tag.trim())).toList(); + return availableTags; + } } 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 1ae0ea90..6a2dae4b 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 @@ -1,6 +1,5 @@ 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/tag_model.dart'; abstract class AssignTagState extends Equatable { const AssignTagState(); @@ -15,17 +14,21 @@ class AssignTagLoading extends AssignTagState {} class AssignTagLoaded extends AssignTagState { final List tags; + final List updatedTags; + final bool isSaveEnabled; - final String? errorMessage; + final String? errorMessage; const AssignTagLoaded({ required this.tags, required this.isSaveEnabled, + required this.updatedTags, required this.errorMessage, }); @override - List get props => [tags, isSaveEnabled, errorMessage ?? '']; + List get props => + [tags, updatedTags, isSaveEnabled, errorMessage ?? '']; } class AssignTagError extends AssignTagState { 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 9e2fee32..3b794b61 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 @@ -14,6 +14,7 @@ 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'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:uuid/uuid.dart'; class AssignTagDialog extends StatelessWidget { final List? products; @@ -47,7 +48,7 @@ class AssignTagDialog extends StatelessWidget { ..add('Main Space'); return BlocProvider( - create: (_) => AssignTagBloc() + create: (_) => AssignTagBloc(allTags ?? []) ..add(InitializeTags( initialTags: initialTags, addedProducts: addedProducts, @@ -119,8 +120,6 @@ class AssignTagDialog extends StatelessWidget { : List.generate(state.tags.length, (index) { final tag = state.tags[index]; final controller = controllers[index]; - final availableTags = getAvailableTags( - allTags ?? [], state.tags, tag); return DataRow( cells: [ @@ -180,7 +179,9 @@ class AssignTagDialog extends StatelessWidget { width: double .infinity, // Ensure full width for dropdown child: DialogTextfieldDropdown( - items: availableTags, + key: ValueKey( + 'dropdown_${Uuid().v4()}_${index}'), + items: state.updatedTags, initialValue: tag.tag, onSelected: (value) { controller.text = value; @@ -306,15 +307,4 @@ class AssignTagDialog extends StatelessWidget { ), ); } - - List getAvailableTags( - List allTags, List currentTags, Tag currentTag) { - List availableTagsForTagModel = TagHelper.getAvailableTags( - allTags: allTags, - currentTags: currentTags, - currentTag: currentTag, - getTag: (tag) => tag.tag ?? '', - ); - return availableTagsForTagModel; - } } 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 92d7c0e1..d0e37f6a 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 @@ -5,7 +5,9 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model class AssignTagModelBloc extends Bloc { - AssignTagModelBloc() : super(AssignTagModelInitial()) { + final List allTags; + + AssignTagModelBloc(this.allTags) : super(AssignTagModelInitial()) { on((event, emit) { final initialTags = event.initialTags ?? []; @@ -17,25 +19,25 @@ class AssignTagModelBloc } } - final allTags = []; + final tags = []; for (var selectedProduct in event.addedProducts) { final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) { - allTags.addAll(initialTags + tags.addAll(initialTags .where((tag) => tag.product?.uuid == selectedProduct.productId)); continue; } final missingCount = selectedProduct.count - existingCount; - allTags.addAll(initialTags + tags.addAll(initialTags .where((tag) => tag.product?.uuid == selectedProduct.productId)); if (missingCount > 0) { - allTags.addAll(List.generate( + tags.addAll(List.generate( missingCount, (index) => TagModel( tag: '', @@ -46,9 +48,12 @@ class AssignTagModelBloc } } + final updatedTags = _calculateAvailableTags(allTags, tags); + emit(AssignTagModelLoaded( - tags: allTags, - isSaveEnabled: _validateTags(allTags), + tags: tags, + updatedTags: updatedTags, + isSaveEnabled: _validateTags(tags), errorMessage: '')); }); @@ -57,9 +62,12 @@ class AssignTagModelBloc if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); - tags[event.index].tag = event.tag; + tags[event.index] = tags[event.index].copyWith(tag: event.tag); + final updatedTags = _calculateAvailableTags(allTags, tags); + emit(AssignTagModelLoaded( tags: tags, + updatedTags: updatedTags, isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); @@ -77,9 +85,13 @@ class AssignTagModelBloc tags[event.index] = tags[event.index].copyWith(location: event.location); + final updatedTags = _calculateAvailableTags(allTags, tags); + emit(AssignTagModelLoaded( tags: tags, + updatedTags: updatedTags, isSaveEnabled: _validateTags(tags), + errorMessage: _getValidationError(tags), )); } }); @@ -93,6 +105,7 @@ class AssignTagModelBloc emit(AssignTagModelLoaded( tags: tags, + updatedTags: _calculateAvailableTags(allTags, tags), isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); @@ -104,24 +117,22 @@ class AssignTagModelBloc if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { - final updatedTags = List.from(currentState.tags) + final tags = List.from(currentState.tags) ..remove(event.tagToDelete); + final updatedTags = _calculateAvailableTags(allTags, tags); + emit(AssignTagModelLoaded( - tags: updatedTags, - isSaveEnabled: _validateTags(updatedTags), + tags: tags, + updatedTags: updatedTags, + isSaveEnabled: _validateTags(tags), + errorMessage: _getValidationError(tags), )); - } else { - emit(const AssignTagModelLoaded( - tags: [], - isSaveEnabled: false, - )); - } + } }); } 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; @@ -129,14 +140,14 @@ class AssignTagModelBloc } String? _getValidationError(List tags) { - final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); - if (hasEmptyTag) { - return 'Tags cannot be empty.'; - } - // Check for duplicate tags - final duplicateTags = tags + + final nonEmptyTags = tags .map((tag) => tag.tag?.trim() ?? '') + .where((tag) => tag.isNotEmpty) + .toList(); + + final duplicateTags = nonEmptyTags .fold>({}, (map, tag) { map[tag] = (map[tag] ?? 0) + 1; return map; @@ -152,4 +163,16 @@ class AssignTagModelBloc return null; } + + List _calculateAvailableTags( + List allTags, List tags) { + final selectedTags = tags + .where((tag) => (tag.tag?.trim().isNotEmpty ?? false)) + .map((tag) => tag.tag!.trim()) + .toSet(); + + final availableTags = + allTags.where((tag) => !selectedTags.contains(tag.trim())).toList(); + return availableTags; + } } 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 a51a9e8f..167a6ac2 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 @@ -17,14 +17,17 @@ class AssignTagModelLoaded extends AssignTagModelState { final bool isSaveEnabled; final String? errorMessage; + final List updatedTags; + const AssignTagModelLoaded({ required this.tags, required this.isSaveEnabled, + required this.updatedTags, this.errorMessage, }); @override - List get props => [tags, isSaveEnabled, errorMessage]; + List get props => [tags, updatedTags, isSaveEnabled, errorMessage]; } class AssignTagModelError extends AssignTagModelState { 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 3e302d2d..d13766d4 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 @@ -16,6 +16,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/c import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; +import 'package:uuid/uuid.dart'; class AssignTagModelsDialog extends StatelessWidget { final List? products; @@ -56,7 +57,7 @@ class AssignTagModelsDialog extends StatelessWidget { ..add('Main Space'); return BlocProvider( - create: (_) => AssignTagModelBloc() + create: (_) => AssignTagModelBloc(allTags ?? []) ..add(InitializeTagModels( initialTags: initialTags, addedProducts: addedProducts, @@ -134,9 +135,6 @@ class AssignTagModelsDialog extends StatelessWidget { : List.generate(state.tags.length, (index) { final tag = state.tags[index]; final controller = controllers[index]; - final availableTags = - TagHelper.getAvailableTagModels( - allTags ?? [], state.tags, tag); return DataRow( cells: [ @@ -196,7 +194,9 @@ class AssignTagModelsDialog extends StatelessWidget { width: double .infinity, // Ensure full width for dropdown child: DialogTextfieldDropdown( - items: availableTags, + key: ValueKey( + 'dropdown_${Uuid().v4()}_${index}'), + items: state.updatedTags, initialValue: tag.tag, onSelected: (value) { controller.text = value;