This commit is contained in:
hannathkadher
2025-03-06 12:52:41 +04:00
parent 9795517a3f
commit d624dd767b
11 changed files with 214 additions and 310 deletions

View File

@ -24,6 +24,8 @@ class AddDeviceTypeWidget extends StatelessWidget {
final String spaceName; final String spaceName;
final bool isCreate; final bool isCreate;
final Function(List<Tag>, List<SubspaceModel>?)? onSave; final Function(List<Tag>, List<SubspaceModel>?)? onSave;
final List<Tag> projectTags;
const AddDeviceTypeWidget( const AddDeviceTypeWidget(
{super.key, {super.key,
@ -35,7 +37,8 @@ class AddDeviceTypeWidget extends StatelessWidget {
this.allTags, this.allTags,
this.spaceTags, this.spaceTags,
this.onSave, this.onSave,
required this.spaceName}); required this.spaceName,
required this.projectTags});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -134,7 +137,8 @@ class AddDeviceTypeWidget extends StatelessWidget {
spaceName: spaceName, spaceName: spaceName,
initialTags: initialTags, initialTags: initialTags,
title: dialogTitle, title: dialogTitle,
onSave: onSave), onSave: onSave,
projectTags: projectTags),
); );
} }
}, },

View File

@ -299,8 +299,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
communities: communities, communities: communities,
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
spaceModels: prevSpaceModels, spaceModels: prevSpaceModels,
allTags: _cachedTags ?? [] allTags: _cachedTags ?? []));
));
} }
Future<List<CommunityModel>> _waitForCommunityList(SpaceTreeBloc spaceBloc) async { Future<List<CommunityModel>> _waitForCommunityList(SpaceTreeBloc spaceBloc) async {
@ -492,7 +491,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
selectedCommunity: community, selectedCommunity: community,
selectedSpace: null, selectedSpace: null,
spaceModels: prevSpaceModels, spaceModels: prevSpaceModels,
allTags: _cachedTags ??[])); allTags: _cachedTags ?? []));
return; return;
} }
} }
@ -557,7 +556,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
for (var tag in newSubspace.tags!) { for (var tag in newSubspace.tags!) {
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(
action: custom_action.Action.add, action: custom_action.Action.add,
uuid: tag.uuid == '' ? null : tag.uuid, newTagUuid: tag.uuid == '' ? null : tag.uuid,
tag: tag.tag, tag: tag.tag,
productUuid: tag.product?.uuid)); productUuid: tag.product?.uuid));
} }
@ -698,8 +697,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
communities: communities, communities: communities,
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
spaceModels: prevSpaceModels, spaceModels: prevSpaceModels,
allTags: _cachedTags ?? [] allTags: _cachedTags ?? []));
));
} catch (e) { } catch (e) {
emit(SpaceManagementError('Error loading communities and spaces: $e')); emit(SpaceManagementError('Error loading communities and spaces: $e'));
} }
@ -717,7 +715,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(
action: custom_action.Action.add, action: custom_action.Action.add,
tag: newTag.tag, tag: newTag.tag,
uuid: newTag.uuid, newTagUuid: newTag.uuid,
productUuid: newTag.product?.uuid, productUuid: newTag.product?.uuid,
)); ));
} }
@ -750,7 +748,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(
action: custom_action.Action.add, action: custom_action.Action.add,
tag: newTag.tag, tag: newTag.tag,
uuid: newTag.uuid == '' ? null : newTag.uuid, newTagUuid: newTag.uuid == '' ? null : newTag.uuid,
productUuid: newTag.product?.uuid)); productUuid: newTag.product?.uuid));
processedTags.add(newTag.tag); processedTags.add(newTag.tag);
} }
@ -767,6 +765,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(
action: custom_action.Action.update, action: custom_action.Action.update,
uuid: newTag.uuid, uuid: newTag.uuid,
newTagUuid: newTag.uuid,
tag: newTag.tag, tag: newTag.tag,
)); ));
} else {} } else {}

View File

@ -36,16 +36,17 @@ class CommunityStructureArea extends StatefulWidget {
final List<CommunityModel> communities; final List<CommunityModel> communities;
final List<SpaceModel> spaces; final List<SpaceModel> spaces;
final List<SpaceTemplateModel>? spaceModels; final List<SpaceTemplateModel>? spaceModels;
final List<Tag> projectTags;
CommunityStructureArea({ CommunityStructureArea(
this.selectedCommunity, {this.selectedCommunity,
this.selectedSpace, this.selectedSpace,
required this.communities, required this.communities,
this.products, this.products,
required this.spaces, required this.spaces,
this.onSpaceSelected, this.onSpaceSelected,
this.spaceModels, this.spaceModels,
}); required this.projectTags});
@override @override
_CommunityStructureAreaState createState() => _CommunityStructureAreaState(); _CommunityStructureAreaState createState() => _CommunityStructureAreaState();
@ -64,8 +65,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void initState() { void initState() {
super.initState(); super.initState();
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces(); _adjustCanvasSizeForSpaces();
_nameController = TextEditingController( _nameController = TextEditingController(
text: widget.selectedCommunity?.name ?? '', text: widget.selectedCommunity?.name ?? '',
@ -92,14 +92,12 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (oldWidget.spaces != widget.spaces) { if (oldWidget.spaces != widget.spaces) {
setState(() { setState(() {
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces(); _adjustCanvasSizeForSpaces();
}); });
} }
if (widget.selectedSpace != oldWidget.selectedSpace && if (widget.selectedSpace != oldWidget.selectedSpace && widget.selectedSpace != null) {
widget.selectedSpace != null) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_moveToSpace(widget.selectedSpace!); _moveToSpace(widget.selectedSpace!);
}); });
@ -182,8 +180,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
connection, widget.selectedSpace) connection, widget.selectedSpace)
? 1.0 ? 1.0
: 0.3, // Adjust opacity : 0.3, // Adjust opacity
child: CustomPaint( child: CustomPaint(painter: CurvedLinePainter([connection])),
painter: CurvedLinePainter([connection])),
), ),
for (var entry in spaces.asMap().entries) for (var entry in spaces.asMap().entries)
if (entry.value.status != SpaceStatus.deleted && if (entry.value.status != SpaceStatus.deleted &&
@ -193,15 +190,12 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
top: entry.value.position.dy, top: entry.value.position.dy,
child: SpaceCardWidget( child: SpaceCardWidget(
index: entry.key, index: entry.key,
onButtonTap: (int index, Offset newPosition, onButtonTap: (int index, Offset newPosition, String direction) {
String direction) { _showCreateSpaceDialog(screenSize,
_showCreateSpaceDialog( position: spaces[index].position + newPosition,
screenSize,
position:
spaces[index].position + newPosition,
parentIndex: index, parentIndex: index,
direction: direction, direction: direction,
); projectTags: widget.projectTags);
}, },
position: entry.value.position, position: entry.value.position,
isHovered: entry.value.isHovered, isHovered: entry.value.isHovered,
@ -211,8 +205,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
_updateNodePosition(entry.value, newPosition); _updateNodePosition(entry.value, newPosition);
}, },
buildSpaceContainer: (int index) { buildSpaceContainer: (int index) {
final bool isHighlighted = final bool isHighlighted = SpaceHelper.isHighlightedSpace(
SpaceHelper.isHighlightedSpace(
spaces[index], widget.selectedSpace); spaces[index], widget.selectedSpace);
return Opacity( return Opacity(
@ -238,7 +231,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
onTap: () { onTap: () {
_showCreateSpaceDialog(screenSize, _showCreateSpaceDialog(screenSize,
canvasHeight: canvasHeight, canvasHeight: canvasHeight,
canvasWidth: canvasWidth); canvasWidth: canvasWidth,
projectTags: widget.projectTags);
}, },
), ),
), ),
@ -292,26 +286,22 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
int? parentIndex, int? parentIndex,
String? direction, String? direction,
double? canvasWidth, double? canvasWidth,
double? canvasHeight}) { double? canvasHeight,
required List<Tag> projectTags}) {
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return CreateSpaceDialog( return CreateSpaceDialog(
products: widget.products, products: widget.products,
spaceModels: widget.spaceModels, spaceModels: widget.spaceModels,
allTags: allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
parentSpace: parentIndex != null ? spaces[parentIndex] : null, parentSpace: parentIndex != null ? spaces[parentIndex] : null,
onCreateSpace: (String name, projectTags: projectTags,
String icon, onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts,
List<SelectedProduct> selectedProducts, SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
List<Tag>? tags) {
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 ?? ConnectionHelper.getCenterPosition(screenSize);
position ?? ConnectionHelper.getCenterPosition(screenSize);
SpaceModel newSpace = SpaceModel( SpaceModel newSpace = SpaceModel(
name: name, name: name,
icon: icon, icon: icon,
@ -356,21 +346,17 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
spaceModels: widget.spaceModels, spaceModels: widget.spaceModels,
name: widget.selectedSpace!.name, name: widget.selectedSpace!.name,
icon: widget.selectedSpace!.icon, icon: widget.selectedSpace!.icon,
parentSpace: SpaceHelper.findSpaceByInternalId( projectTags: widget.projectTags,
widget.selectedSpace?.parent?.internalId, spaces), parentSpace:
SpaceHelper.findSpaceByInternalId(widget.selectedSpace?.parent?.internalId, spaces),
editSpace: widget.selectedSpace, editSpace: widget.selectedSpace,
currentSpaceModel: widget.selectedSpace?.spaceModel, currentSpaceModel: widget.selectedSpace?.spaceModel,
tags: widget.selectedSpace?.tags, tags: widget.selectedSpace?.tags,
subspaces: widget.selectedSpace?.subspaces, subspaces: widget.selectedSpace?.subspaces,
isEdit: true, isEdit: true,
allTags: TagHelper.getAllTagValues( allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
widget.communities, widget.spaceModels), onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts,
onCreateSpace: (String name, SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
String icon,
List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
List<Tag>? tags) {
setState(() { setState(() {
// Update the space's properties // Update the space's properties
widget.selectedSpace!.name = name; widget.selectedSpace!.name = name;
@ -380,8 +366,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
widget.selectedSpace!.tags = tags; widget.selectedSpace!.tags = tags;
if (widget.selectedSpace!.status != SpaceStatus.newSpace) { if (widget.selectedSpace!.status != SpaceStatus.newSpace) {
widget.selectedSpace!.status = widget.selectedSpace!.status = SpaceStatus.modified; // Mark as modified
SpaceStatus.modified; // Mark as modified
} }
for (var space in spaces) { for (var space in spaces) {
@ -411,8 +396,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
List<SpaceModel> result = []; List<SpaceModel> result = [];
void flatten(SpaceModel space) { void flatten(SpaceModel space) {
if (space.status == SpaceStatus.deleted || if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) {
space.status == SpaceStatus.parentDeleted) {
return; return;
} }
result.add(space); result.add(space);
@ -527,16 +511,13 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void _selectSpace(BuildContext context, SpaceModel space) { void _selectSpace(BuildContext context, SpaceModel space) {
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent( SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: space),
selectedCommunity: widget.selectedCommunity,
selectedSpace: space),
); );
} }
void _deselectSpace(BuildContext context) { void _deselectSpace(BuildContext context) {
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent( SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: null),
selectedCommunity: widget.selectedCommunity, selectedSpace: null),
); );
} }
@ -625,19 +606,16 @@ 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;
SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition, SpaceModel duplicateRecursive(
SpaceModel? duplicatedParent) { SpaceModel original, Offset parentPosition, SpaceModel? duplicatedParent) {
Offset newPosition = Offset newPosition = Offset(parentPosition.dx + horizontalGap, original.position.dy);
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 && s.status != SpaceStatus.deleted)) {
s.status != SpaceStatus.deleted)) {
newPosition += Offset(horizontalGap, 0); newPosition += Offset(horizontalGap, 0);
} }
final duplicatedName = final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
SpaceHelper.generateUniqueSpaceName(original.name, spaces);
final List<SubspaceModel>? duplicatedSubspaces; final List<SubspaceModel>? duplicatedSubspaces;
final List<Tag>? duplicatedTags; final List<Tag>? duplicatedTags;
@ -681,8 +659,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (original.parent != null && duplicatedParent == null) { if (original.parent != null && duplicatedParent == null) {
final originalParent = original.parent!; final originalParent = original.parent!;
final duplicatedParent = final duplicatedParent = originalToDuplicate[originalParent] ?? originalParent;
originalToDuplicate[originalParent] ?? originalParent;
final parentConnection = Connection( final parentConnection = Connection(
startSpace: duplicatedParent, startSpace: duplicatedParent,
@ -698,8 +675,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final childrenWithDownDirection = original.children final childrenWithDownDirection = original.children
.where((child) => .where((child) =>
child.incomingConnection?.direction == "down" && child.incomingConnection?.direction == "down" && child.status != SpaceStatus.deleted)
child.status != SpaceStatus.deleted)
.toList(); .toList();
Offset childStartPosition = childrenWithDownDirection.length == 1 Offset childStartPosition = childrenWithDownDirection.length == 1
@ -707,8 +683,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
: newPosition + Offset(0, verticalGap); : newPosition + Offset(0, verticalGap);
for (final child in original.children) { for (final child in original.children) {
final isDownDirection = final isDownDirection = child.incomingConnection?.direction == "down" ?? false;
child.incomingConnection?.direction == "down" ?? false;
if (isDownDirection && childrenWithDownDirection.length == 1) { if (isDownDirection && childrenWithDownDirection.length == 1) {
childStartPosition = duplicated.position + Offset(0, verticalGap); childStartPosition = duplicated.position + Offset(0, verticalGap);
@ -716,8 +691,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
childStartPosition = duplicated.position + Offset(horizontalGap, 0); childStartPosition = duplicated.position + Offset(horizontalGap, 0);
} }
final duplicatedChild = final duplicatedChild = duplicateRecursive(child, childStartPosition, duplicated);
duplicateRecursive(child, childStartPosition, duplicated);
duplicated.children.add(duplicatedChild); duplicated.children.add(duplicatedChild);
childStartPosition += Offset(0, verticalGap); childStartPosition += Offset(0, verticalGap);
} }
@ -728,8 +702,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (space.parent == null) { if (space.parent == null) {
duplicateRecursive(space, space.position, null); duplicateRecursive(space, space.position, null);
} else { } else {
final duplicatedParent = final duplicatedParent = originalToDuplicate[space.parent!] ?? space.parent!;
originalToDuplicate[space.parent!] ?? space.parent!;
duplicateRecursive(space, space.position, duplicatedParent); duplicateRecursive(space, space.position, duplicatedParent);
} }
} }

View File

@ -42,6 +42,7 @@ class CreateSpaceDialog extends StatefulWidget {
final List<Tag>? tags; final List<Tag>? tags;
final List<String>? allTags; final List<String>? allTags;
final SpaceTemplateModel? currentSpaceModel; final SpaceTemplateModel? currentSpaceModel;
final List<Tag> projectTags;
const CreateSpaceDialog( const CreateSpaceDialog(
{super.key, {super.key,
@ -57,7 +58,8 @@ class CreateSpaceDialog extends StatefulWidget {
this.spaceModels, this.spaceModels,
this.subspaces, this.subspaces,
this.tags, this.tags,
this.currentSpaceModel}); this.currentSpaceModel,
required this.projectTags});
@override @override
CreateSpaceDialogState createState() => CreateSpaceDialogState(); CreateSpaceDialogState createState() => CreateSpaceDialogState();
@ -80,10 +82,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
super.initState(); super.initState();
selectedIcon = widget.icon ?? Assets.location; selectedIcon = widget.icon ?? Assets.location;
nameController = TextEditingController(text: widget.name ?? ''); nameController = TextEditingController(text: widget.name ?? '');
selectedProducts = selectedProducts = widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty;
isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty;
if (widget.currentSpaceModel != null) { if (widget.currentSpaceModel != null) {
subspaces = []; subspaces = [];
tags = []; tags = [];
@ -96,15 +96,13 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isSpaceModelDisabled = (tags != null && tags!.isNotEmpty || bool isSpaceModelDisabled =
subspaces != null && subspaces!.isNotEmpty); (tags != null && tags!.isNotEmpty || subspaces != null && subspaces!.isNotEmpty);
bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null); bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null);
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
return AlertDialog( return AlertDialog(
title: widget.isEdit title: widget.isEdit ? const Text('Edit Space') : const Text('Create New Space'),
? const Text('Edit Space')
: const Text('Create New Space'),
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
content: SizedBox( content: SizedBox(
width: screenWidth * 0.5, width: screenWidth * 0.5,
@ -178,7 +176,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
isNameFieldInvalid = value.isEmpty; isNameFieldInvalid = value.isEmpty;
if (!isNameFieldInvalid) { if (!isNameFieldInvalid) {
if (SpaceHelper.isNameConflict(value, widget.parentSpace, widget.editSpace)) { if (SpaceHelper.isNameConflict(
value, widget.parentSpace, widget.editSpace)) {
isNameFieldExist = true; isNameFieldExist = true;
isOkButtonEnabled = false; isOkButtonEnabled = false;
} else { } else {
@ -245,9 +244,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
), ),
onPressed: () { onPressed: () {
isSpaceModelDisabled isSpaceModelDisabled ? null : _showLinkSpaceModelDialog(context);
? null
: _showLinkSpaceModelDialog(context);
}, },
child: ButtonContentWidget( child: ButtonContentWidget(
svgAssets: Assets.link, svgAssets: Assets.link,
@ -257,8 +254,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
) )
: Container( : Container(
width: screenWidth * 0.25, width: screenWidth * 0.25,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
vertical: 10.0, horizontal: 16.0),
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorsManager.boxColor, color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
@ -273,8 +269,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyMedium! .bodyMedium!
.copyWith( .copyWith(color: ColorsManager.spaceColor),
color: ColorsManager.spaceColor),
), ),
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -343,8 +338,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
onPressed: () async { onPressed: () async {
isTagsAndSubspaceModelDisabled isTagsAndSubspaceModelDisabled
? null ? null
: _showSubSpaceDialog(context, enteredName, : _showSubSpaceDialog(
[], false, widget.products, subspaces); context, enteredName, [], false, widget.products, subspaces);
}, },
child: ButtonContentWidget( child: ButtonContentWidget(
icon: Icons.add, icon: Icons.add,
@ -371,22 +366,16 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) if (subspaces != null)
...subspaces!.map((subspace) { ...subspaces!.map((subspace) {
return Column( return Column(
crossAxisAlignment: crossAxisAlignment: CrossAxisAlignment.start,
CrossAxisAlignment.start,
children: [ children: [
SubspaceNameDisplayWidget( SubspaceNameDisplayWidget(
text: subspace.subspaceName, text: subspace.subspaceName,
validateName: (updatedName) { validateName: (updatedName) {
bool nameExists = bool nameExists = subspaces!.any((s) {
subspaces!.any((s) { bool isSameId = s.internalId == subspace.internalId;
bool isSameId = s.internalId == bool isSameName =
subspace.internalId; s.subspaceName.trim().toLowerCase() ==
bool isSameName = s.subspaceName updatedName.trim().toLowerCase();
.trim()
.toLowerCase() ==
updatedName
.trim()
.toLowerCase();
return !isSameId && isSameName; return !isSameId && isSameName;
}); });
@ -395,8 +384,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
}, },
onNameChanged: (updatedName) { onNameChanged: (updatedName) {
setState(() { setState(() {
subspace.subspaceName = subspace.subspaceName = updatedName;
updatedName;
}); });
}, },
), ),
@ -405,8 +393,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
}), }),
EditChip( EditChip(
onTap: () async { onTap: () async {
_showSubSpaceDialog(context, enteredName, _showSubSpaceDialog(context, enteredName, [], true,
[], true, widget.products, subspaces); widget.products, subspaces);
}, },
) )
], ],
@ -415,9 +403,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
(tags?.isNotEmpty == true || (tags?.isNotEmpty == true ||
subspaces?.any((subspace) => subspaces?.any((subspace) => subspace.tags?.isNotEmpty == true) == true)
subspace.tags?.isNotEmpty == true) ==
true)
? SizedBox( ? SizedBox(
width: screenWidth * 0.25, width: screenWidth * 0.25,
child: Container( child: Container(
@ -437,16 +423,14 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
// Combine tags from spaceModel and subspaces // Combine tags from spaceModel and subspaces
...TagHelper.groupTags([ ...TagHelper.groupTags([
...?tags, ...?tags,
...?subspaces?.expand( ...?subspaces?.expand((subspace) => subspace.tags ?? [])
(subspace) => subspace.tags ?? [])
]).entries.map( ]).entries.map(
(entry) => Chip( (entry) => Chip(
avatar: SizedBox( avatar: SizedBox(
width: 24, width: 24,
height: 24, height: 24,
child: SvgPicture.asset( child: SvgPicture.asset(
entry.key.icon ?? entry.key.icon ?? 'assets/icons/gateway.svg',
'assets/icons/gateway.svg',
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
@ -455,15 +439,11 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodySmall .bodySmall
?.copyWith( ?.copyWith(color: ColorsManager.spaceColor),
color: ColorsManager
.spaceColor),
), ),
backgroundColor: backgroundColor: ColorsManager.whiteColors,
ColorsManager.whiteColors,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: borderRadius: BorderRadius.circular(16),
BorderRadius.circular(16),
side: const BorderSide( side: const BorderSide(
color: ColorsManager.spaceColor, color: ColorsManager.spaceColor,
), ),
@ -472,23 +452,21 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
), ),
EditChip(onTap: () async { EditChip(onTap: () async {
final result = await showDialog( await showDialog(
context: context, context: context,
builder: (context) => AssignTagDialog( builder: (context) => AssignTagDialog(
products: widget.products, products: widget.products,
subspaces: subspaces, subspaces: subspaces,
allTags: widget.allTags, allTags: widget.allTags,
addedProducts: TagHelper addedProducts:
.createInitialSelectedProductsForTags( TagHelper.createInitialSelectedProductsForTags(
tags ?? [], subspaces), tags ?? [], subspaces),
title: 'Edit Device', title: 'Edit Device',
initialTags: initialTags: TagHelper.generateInitialForTags(
TagHelper.generateInitialForTags( spaceTags: tags, subspaces: subspaces),
spaceTags: tags,
subspaces: subspaces),
spaceName: widget.name ?? '', spaceName: widget.name ?? '',
onSave: projectTags: widget.projectTags,
(updatedTags, updatedSubspaces) { onSave: (updatedTags, updatedSubspaces) {
setState(() { setState(() {
tags = updatedTags; tags = updatedTags;
subspaces = updatedSubspaces; subspaces = updatedSubspaces;
@ -547,25 +525,17 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
}); });
return; return;
} else { } else {
String newName = enteredName.isNotEmpty String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? '');
? enteredName
: (widget.name ?? '');
if (newName.isNotEmpty) { if (newName.isNotEmpty) {
widget.onCreateSpace( widget.onCreateSpace(newName, selectedIcon, selectedProducts,
newName, selectedSpaceModel, subspaces, tags);
selectedIcon,
selectedProducts,
selectedSpaceModel,
subspaces,
tags);
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
} }
}, },
borderRadius: 10, borderRadius: 10,
backgroundColor: isOkButtonEnabled backgroundColor:
? ColorsManager.secondaryColor isOkButtonEnabled ? ColorsManager.secondaryColor : ColorsManager.grayColor,
: ColorsManager.grayColor,
foregroundColor: ColorsManager.whiteColors, foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'), child: const Text('OK'),
), ),
@ -592,7 +562,6 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
void _showLinkSpaceModelDialog(BuildContext context) { void _showLinkSpaceModelDialog(BuildContext context) {
showDialog( showDialog(
context: context, context: context,
@ -613,13 +582,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
void _showSubSpaceDialog( void _showSubSpaceDialog(BuildContext context, String name, final List<Tag>? spaceTags,
BuildContext context, bool isEdit, List<ProductModel>? products, final List<SubspaceModel>? existingSubSpaces) {
String name,
final List<Tag>? spaceTags,
bool isEdit,
List<ProductModel>? products,
final List<SubspaceModel>? existingSubSpaces) {
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
@ -634,12 +598,10 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
final List<Tag> tagsToAppendToSpace = []; final List<Tag> tagsToAppendToSpace = [];
if (slectedSubspaces != null) { if (slectedSubspaces != null) {
final updatedIds = final updatedIds = slectedSubspaces.map((s) => s.internalId).toSet();
slectedSubspaces.map((s) => s.internalId).toSet();
if (existingSubSpaces != null) { if (existingSubSpaces != null) {
final deletedSubspaces = existingSubSpaces final deletedSubspaces =
.where((s) => !updatedIds.contains(s.internalId)) existingSubSpaces.where((s) => !updatedIds.contains(s.internalId)).toList();
.toList();
for (var s in deletedSubspaces) { for (var s in deletedSubspaces) {
if (s.tags != null) { if (s.tags != null) {
tagsToAppendToSpace.addAll(s.tags!); tagsToAppendToSpace.addAll(s.tags!);
@ -659,20 +621,20 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
void _showTagCreateDialog(BuildContext context, String name, bool isEdit, void _showTagCreateDialog(
List<ProductModel>? products) { BuildContext context, String name, bool isEdit, List<ProductModel>? products) {
isEdit isEdit
? showDialog( ? showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return AssignTagDialog( return AssignTagDialog(
title: 'Edit Device', title: 'Edit Device',
addedProducts: TagHelper.createInitialSelectedProductsForTags( addedProducts: TagHelper.createInitialSelectedProductsForTags(tags, subspaces),
tags, subspaces),
spaceName: name, spaceName: name,
products: products, products: products,
subspaces: subspaces, subspaces: subspaces,
allTags: widget.allTags, allTags: widget.allTags,
projectTags: widget.projectTags,
onSave: (selectedSpaceTags, selectedSubspaces) { onSave: (selectedSpaceTags, selectedSubspaces) {
setState(() { setState(() {
tags = selectedSpaceTags; tags = selectedSpaceTags;
@ -682,8 +644,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) { if (subspaces != null) {
for (final subspace in subspaces!) { for (final subspace in subspaces!) {
for (final selectedSubspace in selectedSubspaces) { for (final selectedSubspace in selectedSubspaces) {
if (subspace.subspaceName == if (subspace.subspaceName == selectedSubspace.subspaceName) {
selectedSubspace.subspaceName) {
subspace.tags = selectedSubspace.tags; subspace.tags = selectedSubspace.tags;
} }
} }
@ -705,9 +666,9 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
spaceTags: tags, spaceTags: tags,
isCreate: true, isCreate: true,
allTags: widget.allTags, allTags: widget.allTags,
projectTags: widget.projectTags,
initialSelectedProducts: initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags( TagHelper.createInitialSelectedProductsForTags(tags, subspaces),
tags, subspaces),
onSave: (selectedSpaceTags, selectedSubspaces) { onSave: (selectedSpaceTags, selectedSubspaces) {
setState(() { setState(() {
tags = selectedSpaceTags; tags = selectedSpaceTags;
@ -717,8 +678,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) { if (subspaces != null) {
for (final subspace in subspaces!) { for (final subspace in subspaces!) {
for (final selectedSubspace in selectedSubspaces) { for (final selectedSubspace in selectedSubspaces) {
if (subspace.subspaceName == if (subspace.subspaceName == selectedSubspace.subspaceName) {
selectedSubspace.subspaceName) {
subspace.tags = selectedSubspace.tags; subspace.tags = selectedSubspace.tags;
} }
} }

View File

@ -114,6 +114,7 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
products: widget.products, products: widget.products,
communities: widget.communities, communities: widget.communities,
spaceModels: _spaceModels, spaceModels: _spaceModels,
projectTags: widget.projectTags,
), ),
], ],
), ),

View File

@ -4,17 +4,16 @@ 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/assign_tag/bloc/assign_tag_state.dart';
class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> { class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final List<String> allTags; final List<Tag> projectTags;
AssignTagBloc(this.allTags) : super(AssignTagInitial()) { AssignTagBloc(this.projectTags) : super(AssignTagInitial()) {
on<InitializeTags>((event, emit) { on<InitializeTags>((event, emit) {
final initialTags = event.initialTags ?? []; final initialTags = event.initialTags ?? [];
final existingTagCounts = <String, int>{}; final existingTagCounts = <String, int>{};
for (var tag in initialTags) { for (var tag in initialTags) {
if (tag.product != null) { if (tag.product != null) {
existingTagCounts[tag.product!.uuid] = existingTagCounts[tag.product!.uuid] = (existingTagCounts[tag.product!.uuid] ?? 0) + 1;
(existingTagCounts[tag.product!.uuid] ?? 0) + 1;
} }
} }
@ -23,17 +22,14 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
for (var selectedProduct in event.addedProducts) { for (var selectedProduct in event.addedProducts) {
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
if (selectedProduct.count == 0 || if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) {
selectedProduct.count <= existingCount) { tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
continue; continue;
} }
final missingCount = selectedProduct.count - existingCount; final missingCount = selectedProduct.count - existingCount;
tags.addAll(initialTags tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (missingCount > 0) { if (missingCount > 0) {
tags.addAll(List.generate( tags.addAll(List.generate(
@ -47,7 +43,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
} }
} }
final updatedTags = _calculateAvailableTags(allTags, tags); final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
@ -62,9 +58,16 @@ 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] = tags[event.index].copyWith(tag: event.tag); if (event.index < 0 || event.index >= tags.length) return;
final updatedTags = _calculateAvailableTags(allTags, tags); tags[event.index] = tags[event.index].copyWith(
tag: event.tag.tag,
uuid: event.tag.uuid,
product: event.tag.product,
internalId: event.tag.internalId,
location: event.tag.location,
);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
@ -82,10 +85,9 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final tags = List<Tag>.from(currentState.tags); final tags = List<Tag>.from(currentState.tags);
// Update the location // Update the location
tags[event.index] = tags[event.index] = tags[event.index].copyWith(location: event.location);
tags[event.index].copyWith(location: event.location);
final updatedTags = _calculateAvailableTags(allTags, tags); final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
@ -104,7 +106,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
updatedTags: _calculateAvailableTags(allTags, tags), updatedTags: _calculateAvailableTags(projectTags, tags),
isSaveEnabled: _validateTags(tags), isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags), errorMessage: _getValidationError(tags),
)); ));
@ -115,11 +117,10 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final currentState = state; final currentState = state;
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)..remove(event.tagToDelete);
..remove(event.tagToDelete);
// Recalculate available tags // Recalculate available tags
final updatedTags = _calculateAvailableTags(allTags, tags); final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
@ -140,10 +141,8 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
// Get validation error for duplicate tags // Get validation error for duplicate tags
String? _getValidationError(List<Tag> tags) { String? _getValidationError(List<Tag> tags) {
final nonEmptyTags = tags final nonEmptyTags =
.map((tag) => tag.tag?.trim() ?? '') tags.map((tag) => tag.tag?.trim() ?? '').where((tag) => tag.isNotEmpty).toList();
.where((tag) => tag.isNotEmpty)
.toList();
final duplicateTags = nonEmptyTags final duplicateTags = nonEmptyTags
.fold<Map<String, int>>({}, (map, tag) { .fold<Map<String, int>>({}, (map, tag) {
@ -162,14 +161,16 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
return null; return null;
} }
List<String> _calculateAvailableTags(List<String> allTags, List<Tag> tags) { List<Tag> _calculateAvailableTags(List<Tag> allTags, List<Tag> selectedTags) {
final selectedTags = tags final selectedTagSet = selectedTags
.where((tag) => (tag.tag?.trim().isNotEmpty ?? false)) .where((tag) => (tag.tag?.trim().isNotEmpty ?? false))
.map((tag) => tag.tag!.trim()) .map((tag) => tag.tag!.trim())
.toSet(); .toSet();
final availableTags = final availableTags = allTags
allTags.where((tag) => !selectedTags.contains(tag.trim())).toList(); .where((tag) => tag.tag != null && !selectedTagSet.contains(tag.tag!.trim()))
.toList();
return availableTags; return availableTags;
} }
} }

View File

@ -24,7 +24,7 @@ class InitializeTags extends AssignTagEvent {
class UpdateTagEvent extends AssignTagEvent { class UpdateTagEvent extends AssignTagEvent {
final int index; final int index;
final String tag; final Tag tag;
const UpdateTagEvent({required this.index, required this.tag}); const UpdateTagEvent({required this.index, required this.tag});

View File

@ -5,7 +5,7 @@ abstract class AssignTagState extends Equatable {
const AssignTagState(); const AssignTagState();
@override @override
List<Object> get props => []; List<Object?> get props => [];
} }
class AssignTagInitial extends AssignTagState {} class AssignTagInitial extends AssignTagState {}
@ -14,7 +14,7 @@ class AssignTagLoading extends AssignTagState {}
class AssignTagLoaded extends AssignTagState { class AssignTagLoaded extends AssignTagState {
final List<Tag> tags; final List<Tag> tags;
final List<String> updatedTags; final List<Tag> updatedTags;
final bool isSaveEnabled; final bool isSaveEnabled;
final String? errorMessage; final String? errorMessage;
@ -27,8 +27,7 @@ class AssignTagLoaded extends AssignTagState {
}); });
@override @override
List<Object> get props => List<Object?> get props => [tags, updatedTags, isSaveEnabled, errorMessage ?? ''];
[tags, updatedTags, isSaveEnabled, errorMessage ?? ''];
} }
class AssignTagError extends AssignTagState { class AssignTagError extends AssignTagState {
@ -37,5 +36,5 @@ class AssignTagError extends AssignTagState {
const AssignTagError(this.errorMessage); const AssignTagError(this.errorMessage);
@override @override
List<Object> get props => [errorMessage]; List<Object?> get props => [errorMessage];
} }

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/dialog_dropdown.dart'; import 'package:syncrow_web/common/dialog_dropdown.dart';
import 'package:syncrow_web/common/dialog_textfield_dropdown.dart'; import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart';
@ -26,6 +26,7 @@ class AssignTagDialog extends StatelessWidget {
final String spaceName; final String spaceName;
final String title; final String title;
final Function(List<Tag>, List<SubspaceModel>?)? onSave; final Function(List<Tag>, List<SubspaceModel>?)? onSave;
final List<Tag> projectTags;
const AssignTagDialog( const AssignTagDialog(
{Key? key, {Key? key,
@ -37,18 +38,17 @@ class AssignTagDialog extends StatelessWidget {
this.allTags, this.allTags,
required this.spaceName, required this.spaceName,
required this.title, required this.title,
this.onSave}) this.onSave,
required this.projectTags})
: super(key: key); : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<String> locations = (subspaces ?? []) final List<String> locations =
.map((subspace) => subspace.subspaceName) (subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space');
.toList()
..add('Main Space');
return BlocProvider( return BlocProvider(
create: (_) => AssignTagBloc(allTags ?? []) create: (_) => AssignTagBloc(projectTags)
..add(InitializeTags( ..add(InitializeTags(
initialTags: initialTags, initialTags: initialTags,
addedProducts: addedProducts, addedProducts: addedProducts,
@ -70,8 +70,7 @@ class AssignTagDialog extends StatelessWidget {
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
child: DataTable( child: DataTable(
headingRowColor: WidgetStateProperty.all( headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
ColorsManager.dataHeaderGrey),
key: ValueKey(state.tags.length), key: ValueKey(state.tags.length),
border: TableBorder.all( border: TableBorder.all(
color: ColorsManager.dataHeaderGrey, color: ColorsManager.dataHeaderGrey,
@ -80,22 +79,15 @@ class AssignTagDialog extends StatelessWidget {
), ),
columns: [ columns: [
DataColumn( DataColumn(
label: Text('#', label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
style:
Theme.of(context).textTheme.bodyMedium)),
DataColumn( DataColumn(
label: Text('Device', label: Text('Device', style: Theme.of(context).textTheme.bodyMedium)),
style:
Theme.of(context).textTheme.bodyMedium)),
DataColumn( DataColumn(
numeric: false, numeric: false,
label: Text('Tag', label: Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
style:
Theme.of(context).textTheme.bodyMedium)),
DataColumn( DataColumn(
label: Text('Location', label:
style: Text('Location', style: Theme.of(context).textTheme.bodyMedium)),
Theme.of(context).textTheme.bodyMedium)),
], ],
rows: state.tags.isEmpty rows: state.tags.isEmpty
? [ ? [
@ -103,12 +95,8 @@ class AssignTagDialog extends StatelessWidget {
DataCell( DataCell(
Center( Center(
child: Text('No Data Available', child: Text('No Data Available',
style: Theme.of(context) style: Theme.of(context).textTheme.bodyMedium?.copyWith(
.textTheme color: ColorsManager.lightGrayColor,
.bodyMedium
?.copyWith(
color: ColorsManager
.lightGrayColor,
)), )),
), ),
), ),
@ -126,8 +114,7 @@ class AssignTagDialog extends StatelessWidget {
DataCell(Text((index + 1).toString())), DataCell(Text((index + 1).toString())),
DataCell( DataCell(
Row( Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.spaceBetween,
MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
@ -141,31 +128,25 @@ class AssignTagDialog extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: ColorsManager color: ColorsManager.lightGrayColor,
.lightGrayColor,
width: 1.0, width: 1.0,
), ),
), ),
child: IconButton( child: IconButton(
icon: const Icon( icon: const Icon(
Icons.close, Icons.close,
color: ColorsManager color: ColorsManager.lightGreyColor,
.lightGreyColor,
size: 16, size: 16,
), ),
onPressed: () { onPressed: () {
context context.read<AssignTagBloc>().add(
.read<AssignTagBloc>() DeleteTag(tagToDelete: tag, tags: state.tags));
.add(DeleteTag(
tagToDelete: tag,
tags: state.tags));
controllers.removeAt(index); controllers.removeAt(index);
}, },
tooltip: 'Delete Tag', tooltip: 'Delete Tag',
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: constraints: const BoxConstraints(),
const BoxConstraints(),
), ),
), ),
], ],
@ -173,23 +154,20 @@ class AssignTagDialog extends StatelessWidget {
), ),
DataCell( DataCell(
Container( Container(
alignment: Alignment alignment:
.centerLeft, // Align cell content to the left Alignment.centerLeft, // Align cell content to the left
child: SizedBox( child: SizedBox(
width: double width: double.infinity,
.infinity, // Ensure full width for dropdown child: TagDialogTextfieldDropdown(
child: DialogTextfieldDropdown( key: ValueKey('dropdown_${const Uuid().v4()}_$index'),
key: ValueKey(
'dropdown_${Uuid().v4()}_${index}'),
items: state.updatedTags, items: state.updatedTags,
initialValue: tag.tag, product: tag.product?.uuid ?? 'Unknown',
initialValue: tag,
onSelected: (value) { onSelected: (value) {
controller.text = value; controller.text = value.tag ?? '';
context context.read<AssignTagBloc>().add(UpdateTagEvent(
.read<AssignTagBloc>()
.add(UpdateTagEvent(
index: index, index: index,
tag: value.trim(), tag: value,
)); ));
}, },
), ),
@ -201,12 +179,9 @@ class AssignTagDialog extends StatelessWidget {
width: double.infinity, width: double.infinity,
child: DialogDropdown( child: DialogDropdown(
items: locations, items: locations,
selectedValue: selectedValue: tag.location ?? 'Main Space',
tag.location ?? 'Main Space',
onSelected: (value) { onSelected: (value) {
context context.read<AssignTagBloc>().add(UpdateLocation(
.read<AssignTagBloc>()
.add(UpdateLocation(
index: index, index: index,
location: value, location: value,
)); ));
@ -238,13 +213,11 @@ class AssignTagDialog extends StatelessWidget {
label: 'Add New Device', label: 'Add New Device',
onPressed: () async { onPressed: () async {
final updatedTags = List<Tag>.from(state.tags); final updatedTags = List<Tag>.from(state.tags);
final result = final result = TagHelper.processTags(updatedTags, subspaces);
TagHelper.processTags(updatedTags, subspaces);
final processedTags = final processedTags = result['updatedTags'] as List<Tag>;
result['updatedTags'] as List<Tag>; final processedSubspaces =
final processedSubspaces = List<SubspaceModel>.from( List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
result['subspaces'] as List<dynamic>);
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -253,8 +226,9 @@ class AssignTagDialog extends StatelessWidget {
builder: (context) => AddDeviceTypeWidget( builder: (context) => AddDeviceTypeWidget(
products: products, products: products,
subspaces: processedSubspaces, subspaces: processedSubspaces,
initialSelectedProducts: TagHelper projectTags: projectTags,
.createInitialSelectedProductsForTags( initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags(
processedTags, processedSubspaces), processedTags, processedSubspaces),
spaceName: spaceName, spaceName: spaceName,
spaceTags: processedTags, spaceTags: processedTags,
@ -278,14 +252,11 @@ class AssignTagDialog extends StatelessWidget {
onPressed: state.isSaveEnabled onPressed: state.isSaveEnabled
? () async { ? () async {
final updatedTags = List<Tag>.from(state.tags); final updatedTags = List<Tag>.from(state.tags);
final result = TagHelper.processTags( final result = TagHelper.processTags(updatedTags, subspaces);
updatedTags, subspaces);
final processedTags = final processedTags = result['updatedTags'] as List<Tag>;
result['updatedTags'] as List<Tag>;
final processedSubspaces = final processedSubspaces =
List<SubspaceModel>.from( List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
result['subspaces'] as List<dynamic>);
onSave?.call(processedTags, processedSubspaces); onSave?.call(processedTags, processedSubspaces);
Navigator.of(context).pop(); Navigator.of(context).pop();
} }

View File

@ -8,7 +8,7 @@ class AssignTagModelBloc extends Bloc<AssignTagModelEvent, AssignTagModelState>
AssignTagModelBloc(this.projectTags) : super(AssignTagModelInitial()) { AssignTagModelBloc(this.projectTags) : super(AssignTagModelInitial()) {
on<InitializeTagModels>((event, emit) { on<InitializeTagModels>((event, emit) {
final initialTags = event.initialTags ?? []; final initialTags = event.initialTags;
final existingTagCounts = <String, int>{}; final existingTagCounts = <String, int>{};
for (var tag in initialTags) { for (var tag in initialTags) {
@ -67,7 +67,7 @@ class AssignTagModelBloc extends Bloc<AssignTagModelEvent, AssignTagModelState>
location: event.tag.location, location: event.tag.location,
); );
final updatedTags = _calculateAvailableTags(projectTags ?? [], tags); final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagModelLoaded( emit(AssignTagModelLoaded(
tags: tags, tags: tags,
@ -83,12 +83,9 @@ class AssignTagModelBloc extends Bloc<AssignTagModelEvent, AssignTagModelState>
if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags); final tags = List<Tag>.from(currentState.tags);
for (var t in tags) {
}
// Use copyWith for immutability // Use copyWith for immutability
tags[event.index] = tags[event.index].copyWith(location: event.location); tags[event.index] = tags[event.index].copyWith(location: event.location);
final updatedTags = _calculateAvailableTags(projectTags, tags); final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagModelLoaded( emit(AssignTagModelLoaded(

View File

@ -171,8 +171,7 @@ class AssignTagModelsDialog extends StatelessWidget {
alignment: Alignment alignment: Alignment
.centerLeft, // Align cell content to the left .centerLeft, // Align cell content to the left
child: SizedBox( child: SizedBox(
width: width: double.infinity,
double.infinity, // Ensure full width for dropdown
child: TagDialogTextfieldDropdown( child: TagDialogTextfieldDropdown(
key: ValueKey( key: ValueKey(
'dropdown_${const Uuid().v4()}_$index'), 'dropdown_${const Uuid().v4()}_$index'),