mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 15:17:31 +00:00
Merge pull request #85 from SyncrowIOT/bugfix/fix-tag-repeat
Fixed tag repeat
This commit is contained in:
@ -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<AssignTagEvent, AssignTagState> {
|
||||
AssignTagBloc() : super(AssignTagInitial()) {
|
||||
final List<String> allTags;
|
||||
|
||||
AssignTagBloc(this.allTags) : super(AssignTagInitial()) {
|
||||
on<InitializeTags>((event, emit) {
|
||||
final initialTags = event.initialTags ?? [];
|
||||
|
||||
@ -16,25 +18,25 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
||||
}
|
||||
}
|
||||
|
||||
final allTags = <Tag>[];
|
||||
final tags = <Tag>[];
|
||||
|
||||
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<AssignTagEvent, AssignTagState> {
|
||||
}
|
||||
}
|
||||
|
||||
final updatedTags = _calculateAvailableTags(allTags, tags);
|
||||
|
||||
emit(AssignTagLoaded(
|
||||
tags: allTags,
|
||||
isSaveEnabled: _validateTags(allTags),
|
||||
errorMessage: ''));
|
||||
tags: tags,
|
||||
updatedTags: updatedTags,
|
||||
isSaveEnabled: _validateTags(tags),
|
||||
errorMessage: '',
|
||||
));
|
||||
});
|
||||
|
||||
on<UpdateTagEvent>((event, emit) {
|
||||
@ -56,10 +62,13 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
||||
|
||||
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
|
||||
final tags = List<Tag>.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<AssignTagEvent, AssignTagState> {
|
||||
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
|
||||
final tags = List<Tag>.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<AssignTagEvent, AssignTagState> {
|
||||
|
||||
emit(AssignTagLoaded(
|
||||
tags: tags,
|
||||
updatedTags: _calculateAvailableTags(allTags, tags),
|
||||
isSaveEnabled: _validateTags(tags),
|
||||
errorMessage: _getValidationError(tags),
|
||||
));
|
||||
@ -102,38 +115,37 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
||||
final currentState = state;
|
||||
|
||||
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
|
||||
final updatedTags = List<Tag>.from(currentState.tags)
|
||||
final tags = List<Tag>.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<Tag> 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<Tag> 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<String, int>>({}, (map, tag) {
|
||||
map[tag] = (map[tag] ?? 0) + 1;
|
||||
return map;
|
||||
@ -149,4 +161,15 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> _calculateAvailableTags(List<String> allTags, List<Tag> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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<Tag> tags;
|
||||
final List<String> updatedTags;
|
||||
|
||||
final bool isSaveEnabled;
|
||||
final String? errorMessage;
|
||||
|
||||
const AssignTagLoaded({
|
||||
required this.tags,
|
||||
required this.isSaveEnabled,
|
||||
required this.updatedTags,
|
||||
required this.errorMessage,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [tags, isSaveEnabled, errorMessage ?? ''];
|
||||
List<Object> get props =>
|
||||
[tags, updatedTags, isSaveEnabled, errorMessage ?? ''];
|
||||
}
|
||||
|
||||
class AssignTagError extends AssignTagState {
|
||||
|
@ -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<ProductModel>? 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<String> getAvailableTags(
|
||||
List<String> allTags, List<Tag> currentTags, Tag currentTag) {
|
||||
List<String> availableTagsForTagModel = TagHelper.getAvailableTags<Tag>(
|
||||
allTags: allTags,
|
||||
currentTags: currentTags,
|
||||
currentTag: currentTag,
|
||||
getTag: (tag) => tag.tag ?? '',
|
||||
);
|
||||
return availableTagsForTagModel;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,9 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model
|
||||
|
||||
class AssignTagModelBloc
|
||||
extends Bloc<AssignTagModelEvent, AssignTagModelState> {
|
||||
AssignTagModelBloc() : super(AssignTagModelInitial()) {
|
||||
final List<String> allTags;
|
||||
|
||||
AssignTagModelBloc(this.allTags) : super(AssignTagModelInitial()) {
|
||||
on<InitializeTagModels>((event, emit) {
|
||||
final initialTags = event.initialTags ?? [];
|
||||
|
||||
@ -17,25 +19,25 @@ class AssignTagModelBloc
|
||||
}
|
||||
}
|
||||
|
||||
final allTags = <TagModel>[];
|
||||
final tags = <TagModel>[];
|
||||
|
||||
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<TagModel>.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<TagModel>.from(currentState.tags)
|
||||
final tags = List<TagModel>.from(currentState.tags)
|
||||
..remove(event.tagToDelete);
|
||||
|
||||
final updatedTags = _calculateAvailableTags(allTags, tags);
|
||||
|
||||
emit(AssignTagModelLoaded(
|
||||
tags: updatedTags,
|
||||
isSaveEnabled: _validateTags(updatedTags),
|
||||
));
|
||||
} else {
|
||||
emit(const AssignTagModelLoaded(
|
||||
tags: [],
|
||||
isSaveEnabled: false,
|
||||
tags: tags,
|
||||
updatedTags: updatedTags,
|
||||
isSaveEnabled: _validateTags(tags),
|
||||
errorMessage: _getValidationError(tags),
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool _validateTags(List<TagModel> 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<TagModel> 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<String, int>>({}, (map, tag) {
|
||||
map[tag] = (map[tag] ?? 0) + 1;
|
||||
return map;
|
||||
@ -152,4 +163,16 @@ class AssignTagModelBloc
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> _calculateAvailableTags(
|
||||
List<String> allTags, List<TagModel> 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;
|
||||
}
|
||||
}
|
||||
|
@ -17,14 +17,17 @@ class AssignTagModelLoaded extends AssignTagModelState {
|
||||
final bool isSaveEnabled;
|
||||
final String? errorMessage;
|
||||
|
||||
final List<String> updatedTags;
|
||||
|
||||
const AssignTagModelLoaded({
|
||||
required this.tags,
|
||||
required this.isSaveEnabled,
|
||||
required this.updatedTags,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [tags, isSaveEnabled, errorMessage];
|
||||
List<Object?> get props => [tags, updatedTags, isSaveEnabled, errorMessage];
|
||||
}
|
||||
|
||||
class AssignTagModelError extends AssignTagModelState {
|
||||
|
@ -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<ProductModel>? 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;
|
||||
|
Reference in New Issue
Block a user