updated tag issue for subspace

This commit is contained in:
hannathkadher
2025-01-23 00:02:28 +04:00
parent 830725254f
commit 65d00c923a
19 changed files with 408 additions and 349 deletions

39
lib/common/edit_chip.dart Normal file
View File

@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class EditChip extends StatelessWidget {
final String label;
final VoidCallback onTap;
final Color labelColor;
final Color backgroundColor;
final Color borderColor;
final double borderRadius;
const EditChip({
Key? key,
this.label = 'Edit',
required this.onTap,
this.labelColor = ColorsManager.spaceColor,
this.backgroundColor = ColorsManager.whiteColors,
this.borderColor = ColorsManager.spaceColor,
this.borderRadius = 16.0,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Chip(
label: Text(
label,
style: TextStyle(color: labelColor),
),
backgroundColor: backgroundColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius),
side: BorderSide(color: borderColor),
),
),
);
}
}

View File

@ -0,0 +1,26 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:uuid/uuid.dart';
abstract class BaseTag {
String? uuid;
String? tag;
final ProductModel? product;
String internalId;
String? location;
BaseTag({
this.uuid,
required this.tag,
this.product,
String? internalId,
this.location,
}) : internalId = internalId ?? const Uuid().v4();
Map<String, dynamic> toJson();
BaseTag copyWith({
String? tag,
ProductModel? product,
String? location,
String? internalId,
});
}

View File

@ -1,22 +1,23 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.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/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class Tag { class Tag extends BaseTag {
String? uuid; Tag({
String? tag; String? uuid,
final ProductModel? product; required String? tag,
String internalId; ProductModel? product,
String? location; String? internalId,
String? location,
Tag( }) : super(
{this.uuid, uuid: uuid,
required this.tag, tag: tag,
this.product, product: product,
String? internalId, internalId: internalId,
this.location}) location: location,
: internalId = internalId ?? const Uuid().v4(); );
factory Tag.fromJson(Map<String, dynamic> json) { factory Tag.fromJson(Map<String, dynamic> json) {
final String internalId = json['internalId'] ?? const Uuid().v4(); final String internalId = json['internalId'] ?? const Uuid().v4();
@ -31,15 +32,19 @@ class Tag {
); );
} }
@override
Tag copyWith({ Tag copyWith({
String? tag, String? tag,
ProductModel? product, ProductModel? product,
String? location, String? location,
String? internalId,
}) { }) {
return Tag( return Tag(
uuid: uuid,
tag: tag ?? this.tag, tag: tag ?? this.tag,
product: product ?? this.product, product: product ?? this.product,
location: location ?? this.location, location: location ?? this.location,
internalId: internalId ?? this.internalId,
); );
} }
@ -60,7 +65,7 @@ extension TagModelExtensions on Tag {
..productUuid = product?.uuid; ..productUuid = product?.uuid;
} }
CreateTagBodyModel toCreateTagBodyModel() { CreateTagBodyModel toCreateTagBodyModel() {
return CreateTagBodyModel() return CreateTagBodyModel()
..tag = tag ?? '' ..tag = tag ?? ''
..productUuid = product?.uuid; ..productUuid = product?.uuid;

View File

@ -344,6 +344,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
builder: (BuildContext context) { builder: (BuildContext context) {
return CreateSpaceDialog( return CreateSpaceDialog(
products: widget.products, products: widget.products,
spaceModels: widget.spaceModels,
name: space.name, name: space.name,
icon: space.icon, icon: space.icon,
editSpace: space, editSpace: space,
@ -463,7 +464,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
_markChildrenAsDeleted(space); _markChildrenAsDeleted(space);
} }
} }
_removeConnectionsForDeletedSpaces(); _removeConnectionsForDeletedSpaces();
}); });
} }

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/common/edit_chip.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';
@ -10,15 +11,23 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_mo
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/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.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/pages/spaces_management/space_model/widgets/button_content_widget.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/space_icon_const.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart';
class CreateSpaceDialog extends StatefulWidget { class CreateSpaceDialog extends StatefulWidget {
final Function(String, String, List<SelectedProduct> selectedProducts, final Function(
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) onCreateSpace; String,
String,
List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
List<Tag>? tags) onCreateSpace;
final List<ProductModel>? products; final List<ProductModel>? products;
final String? name; final String? name;
final String? icon; final String? icon;
@ -211,42 +220,13 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
selectedSpaceModel == null selectedSpaceModel == null
? DefaultButton( ? TextButton(
onPressed: () { onPressed: () {
_showLinkSpaceModelDialog(context); _showLinkSpaceModelDialog(context);
}, },
backgroundColor: ColorsManager.textFieldGreyColor, child: const ButtonContentWidget(
foregroundColor: Colors.black, svgAssets: Assets.link,
borderColor: ColorsManager.neutralGray, label: 'Link a space model',
borderRadius: 16.0,
padding: 10.0, // Reduced padding for smaller size
child: Align(
alignment: Alignment.centerLeft,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(left: 6.0),
child: SvgPicture.asset(
Assets.link,
width: screenWidth *
0.015, // Adjust icon size
height: screenWidth * 0.015,
),
),
const SizedBox(width: 3),
Flexible(
child: Text(
'Link a space model',
overflow: TextOverflow
.ellipsis, // Prevent overflow
style: Theme.of(context)
.textTheme
.bodyMedium,
),
),
],
),
), ),
) )
: Container( : Container(
@ -307,7 +287,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
), ),
), ),
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0), padding: EdgeInsets.symmetric(horizontal: 6.0),
child: Text( child: Text(
'OR', 'OR',
style: TextStyle( style: TextStyle(
@ -326,47 +306,21 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
), ),
const SizedBox(height: 25), const SizedBox(height: 25),
subspaces == null subspaces == null
? DefaultButton( ? TextButton(
onPressed: () { style: TextButton.styleFrom(
overlayColor: ColorsManager.transparentColor,
),
onPressed: () async {
_showSubSpaceDialog(context, enteredName, [], _showSubSpaceDialog(context, enteredName, [],
false, widget.products, subspaces); false, widget.products, subspaces);
}, },
backgroundColor: ColorsManager.textFieldGreyColor, child: const ButtonContentWidget(
foregroundColor: Colors.black, icon: Icons.add,
borderColor: ColorsManager.neutralGray, label: 'Create Sub Space',
borderRadius: 16.0,
padding: 10.0, // Reduced padding for smaller size
child: Align(
alignment: Alignment.centerLeft,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(left: 6.0),
child: SvgPicture.asset(
Assets.addIcon,
width: screenWidth *
0.015, // Adjust icon size
height: screenWidth * 0.015,
),
),
const SizedBox(width: 3),
Flexible(
child: Text(
'Create sub space',
overflow: TextOverflow
.ellipsis, // Prevent overflow
style: Theme.of(context)
.textTheme
.bodyMedium,
),
),
],
),
), ),
) )
: SizedBox( : SizedBox(
width: screenWidth * 0.35, width: screenWidth * 0.25,
child: Container( child: Container(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -383,49 +337,15 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
children: [ children: [
if (subspaces != null) if (subspaces != null)
...subspaces!.map( ...subspaces!.map(
(subspace) => Chip( (subspace) => SubspaceNameDisplayWidget(
label: Text( text: subspace.subspaceName,
subspace.subspaceName, )),
style: const TextStyle( EditChip(
color: ColorsManager
.spaceColor), // Text color
),
backgroundColor: ColorsManager
.whiteColors, // Chip background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
16), // Rounded chip
side: const BorderSide(
color: ColorsManager
.spaceColor), // Border color
),
),
),
GestureDetector(
onTap: () async { onTap: () async {
_showSubSpaceDialog( _showSubSpaceDialog(context, enteredName,
context, [], true, widget.products, subspaces);
enteredName,
[],
false,
widget.products,
subspaces);
}, },
child: Chip( )
label: const Text(
'Edit',
style: TextStyle(
color: ColorsManager.spaceColor),
),
backgroundColor:
ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(
color: ColorsManager.spaceColor),
),
),
),
], ],
), ),
), ),
@ -452,7 +372,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
runSpacing: 8.0, runSpacing: 8.0,
children: [ children: [
// Combine tags from spaceModel and subspaces // Combine tags from spaceModel and subspaces
..._groupTags([ ...TagHelper.groupTags([
...?tags, ...?tags,
...?subspaces?.expand( ...?subspaces?.expand(
(subspace) => subspace.tags ?? []) (subspace) => subspace.tags ?? [])
@ -484,70 +404,31 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
), ),
), ),
), ),
GestureDetector(
onTap: () async { EditChip(onTap: () async {
_showTagCreateDialog(context, enteredName, _showTagCreateDialog(
widget.products); context,
// Edit action enteredName,
}, widget.products,
child: Chip( );
label: const Text( // Edit action
'Edit', })
style: TextStyle(
color: ColorsManager.spaceColor),
),
backgroundColor:
ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(
color: ColorsManager.spaceColor),
),
),
),
], ],
), ),
), ),
) )
: DefaultButton( : TextButton(
onPressed: () { onPressed: () {
_showTagCreateDialog( _showTagCreateDialog(
context, enteredName, widget.products); context, enteredName, widget.products);
}, },
backgroundColor: ColorsManager.textFieldGreyColor, style: TextButton.styleFrom(
foregroundColor: Colors.black, padding: EdgeInsets.zero,
borderColor: ColorsManager.neutralGray,
borderRadius: 16.0,
padding: 10.0, // Reduced padding for smaller size
child: Align(
alignment: Alignment.centerLeft,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(left: 6.0),
child: SvgPicture.asset(
Assets.addIcon,
width: screenWidth *
0.015, // Adjust icon size
height: screenWidth * 0.015,
),
),
const SizedBox(width: 3),
Flexible(
child: Text(
'Add devices',
overflow: TextOverflow
.ellipsis, // Prevent overflow
style: Theme.of(context)
.textTheme
.bodyMedium,
),
),
],
),
), ),
) child: const ButtonContentWidget(
icon: Icons.add,
label: 'Add Devices',
))
], ],
), ),
), ),
@ -579,8 +460,13 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
? enteredName ? enteredName
: (widget.name ?? ''); : (widget.name ?? '');
if (newName.isNotEmpty) { if (newName.isNotEmpty) {
widget.onCreateSpace(newName, selectedIcon, widget.onCreateSpace(
selectedProducts, selectedSpaceModel,subspaces,tags); newName,
selectedIcon,
selectedProducts,
selectedSpaceModel,
subspaces,
tags);
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
} }
@ -655,7 +541,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
builder: (BuildContext context) { builder: (BuildContext context) {
return CreateSubSpaceDialog( return CreateSubSpaceDialog(
spaceName: name, spaceName: name,
dialogTitle: 'Create Sub-space', dialogTitle: isEdit ? 'Edit Sub-space' : 'Create Sub-space',
spaceTags: spaceTags, spaceTags: spaceTags,
isEdit: isEdit, isEdit: isEdit,
products: products, products: products,

View File

@ -29,6 +29,7 @@ class AssignTagModelsDialog extends StatelessWidget {
final String title; final String title;
final BuildContext? pageContext; final BuildContext? pageContext;
final List<String>? otherSpaceModels; final List<String>? otherSpaceModels;
final List<SpaceTemplateModel>? allSpaceModels;
const AssignTagModelsDialog( const AssignTagModelsDialog(
{Key? key, {Key? key,
@ -42,7 +43,8 @@ class AssignTagModelsDialog extends StatelessWidget {
required this.title, required this.title,
this.pageContext, this.pageContext,
this.otherSpaceModels, this.otherSpaceModels,
this.spaceModel}) this.spaceModel,
this.allSpaceModels})
: super(key: key); : super(key: key);
@override @override
@ -212,8 +214,8 @@ class AssignTagModelsDialog extends StatelessWidget {
width: double.infinity, width: double.infinity,
child: DialogDropdown( child: DialogDropdown(
items: locations, items: locations,
selectedValue: selectedValue: tag.location ??
tag.location ?? 'Main Space', 'Main Space',
onSelected: (value) { onSelected: (value) {
context context
.read< .read<
@ -250,8 +252,7 @@ class AssignTagModelsDialog extends StatelessWidget {
label: 'Add New Device', label: 'Add New Device',
onPressed: () async { onPressed: () async {
for (var tag in state.tags) { for (var tag in state.tags) {
if (tag.location == null || if (tag.location == null) {
subspaces == null) {
continue; continue;
} }
@ -352,6 +353,7 @@ class AssignTagModelsDialog extends StatelessWidget {
builder: (BuildContext dialogContext) { builder: (BuildContext dialogContext) {
return CreateSpaceModelDialog( return CreateSpaceModelDialog(
products: products, products: products,
allSpaceModels: allSpaceModels,
allTags: allTags, allTags: allTags,
pageContext: pageContext, pageContext: pageContext,
otherSpaceModels: otherSpaceModels, otherSpaceModels: otherSpaceModels,

View File

@ -6,18 +6,23 @@ import 'subspace_event.dart';
import 'subspace_state.dart'; import 'subspace_state.dart';
class SubSpaceBloc extends Bloc<SubSpaceEvent, SubSpaceState> { class SubSpaceBloc extends Bloc<SubSpaceEvent, SubSpaceState> {
SubSpaceBloc() : super(SubSpaceState([], [], '')) { SubSpaceBloc() : super(SubSpaceState([], [], '',{})) {
on<AddSubSpace>((event, emit) { on<AddSubSpace>((event, emit) {
final existingNames = final existingNames = state.subSpaces.map((e) => e.subspaceName).toSet();
state.subSpaces.map((e) => e.subspaceName).toSet();
if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) { if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) {
final updatedDuplicates = Set<String>.from(state.duplicates)
..add(event.subSpace.subspaceName.toLowerCase());
final updatedSubSpaces = List<SubspaceModel>.from(state.subSpaces)
..add(event.subSpace);
emit(SubSpaceState( emit(SubSpaceState(
state.subSpaces, updatedSubSpaces,
state.updatedSubSpaceModels, state.updatedSubSpaceModels,
'Subspace name already exists.', '*Duplicated sub-space name',
updatedDuplicates,
)); ));
} else { } else {
// Add subspace if no duplicate exists
final updatedSubSpaces = List<SubspaceModel>.from(state.subSpaces) final updatedSubSpaces = List<SubspaceModel>.from(state.subSpaces)
..add(event.subSpace); ..add(event.subSpace);
@ -25,6 +30,8 @@ class SubSpaceBloc extends Bloc<SubSpaceEvent, SubSpaceState> {
updatedSubSpaces, updatedSubSpaces,
state.updatedSubSpaceModels, state.updatedSubSpaceModels,
'', '',
state.duplicates,
// Clear error message
)); ));
} }
}); });
@ -38,6 +45,16 @@ class SubSpaceBloc extends Bloc<SubSpaceEvent, SubSpaceState> {
state.updatedSubSpaceModels, state.updatedSubSpaceModels,
); );
final nameOccurrences = <String, int>{};
for (final subSpace in updatedSubSpaces) {
final lowerName = subSpace.subspaceName.toLowerCase();
nameOccurrences[lowerName] = (nameOccurrences[lowerName] ?? 0) + 1;
}
final updatedDuplicates = nameOccurrences.entries
.where((entry) => entry.value > 1)
.map((entry) => entry.key)
.toSet();
if (event.subSpace.uuid?.isNotEmpty ?? false) { if (event.subSpace.uuid?.isNotEmpty ?? false) {
updatedSubspaceModels.add(UpdateSubspaceModel( updatedSubspaceModels.add(UpdateSubspaceModel(
action: Action.delete, action: Action.delete,
@ -45,13 +62,36 @@ class SubSpaceBloc extends Bloc<SubSpaceEvent, SubSpaceState> {
)); ));
} }
emit(SubSpaceState( emit(SubSpaceState(updatedSubSpaces, updatedSubspaceModels, '',
updatedSubSpaces, updatedDuplicates // Clear error message
updatedSubspaceModels, ));
'', // Clear error message
));
}); });
// Handle UpdateSubSpace Event // Handle UpdateSubSpace Event
on<UpdateSubSpace>((event, emit) {
final updatedSubSpaces = state.subSpaces.map((subSpace) {
if (subSpace.uuid == event.updatedSubSpace.uuid) {
return event.updatedSubSpace;
}
return subSpace;
}).toList();
final updatedSubspaceModels = List<UpdateSubspaceModel>.from(
state.updatedSubSpaceModels,
);
updatedSubspaceModels.add(UpdateSubspaceModel(
action: Action.update,
uuid: event.updatedSubSpace.uuid!,
));
emit(SubSpaceState(
updatedSubSpaces,
updatedSubspaceModels,
'',
state.duplicates,
));
});
} }
} }

View File

@ -4,23 +4,26 @@ class SubSpaceState {
final List<SubspaceModel> subSpaces; final List<SubspaceModel> subSpaces;
final List<UpdateSubspaceModel> updatedSubSpaceModels; final List<UpdateSubspaceModel> updatedSubSpaceModels;
final String errorMessage; final String errorMessage;
final Set<String> duplicates;
SubSpaceState( SubSpaceState(
this.subSpaces, this.subSpaces,
this.updatedSubSpaceModels, this.updatedSubSpaceModels,
this.errorMessage, this.errorMessage,
this.duplicates,
); );
SubSpaceState copyWith({ SubSpaceState copyWith({
List<SubspaceModel>? subSpaces, List<SubspaceModel>? subSpaces,
List<UpdateSubspaceModel>? updatedSubSpaceModels, List<UpdateSubspaceModel>? updatedSubSpaceModels,
String? errorMessage, String? errorMessage,
Set<String>? duplicates,
}) { }) {
return SubSpaceState( return SubSpaceState(
subSpaces ?? this.subSpaces, subSpaces ?? this.subSpaces,
updatedSubSpaceModels ?? this.updatedSubSpaceModels, updatedSubSpaceModels ?? this.updatedSubSpaceModels,
errorMessage ?? this.errorMessage, errorMessage ?? this.errorMessage,
duplicates ?? this.duplicates,
); );
} }
} }

View File

@ -81,41 +81,64 @@ class CreateSubSpaceDialog extends StatelessWidget {
spacing: 8.0, spacing: 8.0,
runSpacing: 8.0, runSpacing: 8.0,
children: [ children: [
...state.subSpaces.map( ...state.subSpaces.asMap().entries.map(
(subSpace) => Chip( (entry) {
label: Text( final index = entry.key;
subSpace.subspaceName, final subSpace = entry.value;
style: const TextStyle(
color: ColorsManager.spaceColor), final lowerName =
), subSpace.subspaceName.toLowerCase();
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder( final duplicateIndices = state.subSpaces
borderRadius: BorderRadius.circular(8), .asMap()
side: const BorderSide( .entries
color: ColorsManager.transparentColor, .where((e) =>
width: 0, e.value.subspaceName.toLowerCase() ==
), lowerName)
), .map((e) => e.key)
deleteIcon: Container( .toList();
width: 24, final isDuplicate =
height: 24, duplicateIndices.length > 1 &&
decoration: BoxDecoration( duplicateIndices.indexOf(index) != 0;
shape: BoxShape.circle,
border: Border.all( return Chip(
color: ColorsManager.lightGrayColor, label: Text(
width: 1.5, subSpace.subspaceName,
style: const TextStyle(
color: ColorsManager.spaceColor,
), ),
), ),
child: const Icon( backgroundColor: ColorsManager.whiteColors,
Icons.close, shape: RoundedRectangleBorder(
size: 16, borderRadius: BorderRadius.circular(10),
color: ColorsManager.lightGrayColor, side: BorderSide(
color: isDuplicate
? ColorsManager.red
: ColorsManager.transparentColor,
width: 0,
),
), ),
), deleteIcon: Container(
onDeleted: () => context width: 24,
.read<SubSpaceBloc>() height: 24,
.add(RemoveSubSpace(subSpace)), decoration: BoxDecoration(
), shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.lightGrayColor,
width: 1.5,
),
),
child: const Icon(
Icons.close,
size: 16,
color: ColorsManager.lightGrayColor,
),
),
onDeleted: () => context
.read<SubSpaceBloc>()
.add(RemoveSubSpace(subSpace)),
);
},
), ),
SizedBox( SizedBox(
width: 200, width: 200,
@ -142,27 +165,29 @@ class CreateSubSpaceDialog extends StatelessWidget {
color: ColorsManager.blackColor), color: ColorsManager.blackColor),
), ),
), ),
if (state.errorMessage.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
state.errorMessage,
style: const TextStyle(
color: ColorsManager.warningRed,
fontSize: 12,
),
),
),
], ],
), ),
), ),
if (state.errorMessage.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
state.errorMessage,
style: const TextStyle(
color: ColorsManager.warningRed,
fontSize: 12,
),
),
),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
children: [ children: [
Expanded( Expanded(
child: CancelButton( child: CancelButton(
label: 'Cancel', label: 'Cancel',
onPressed: () async {}, onPressed: () async {
Navigator.of(context).pop();
},
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),

View File

@ -1,3 +1,4 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.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/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
@ -33,7 +34,7 @@ class TagHelper {
return initialTags; return initialTags;
} }
static Map<ProductModel, int> groupTags(List<TagModel> tags) { static Map<ProductModel, int> groupTags(List<BaseTag> tags) {
final Map<ProductModel, int> groupedTags = {}; final Map<ProductModel, int> groupedTags = {};
for (var tag in tags) { for (var tag in tags) {
if (tag.product != null) { if (tag.product != null) {

View File

@ -301,7 +301,7 @@ class CreateSpaceModelBloc
if (newTags != null || prevTags != null) { if (newTags != null || prevTags != null) {
// Case 1: Tags deleted // Case 1: Tags deleted
if (prevTags != null && newTags != null) { if (prevTags != null && newTags != null) {
for (var prevTag in prevTags!) { for (var prevTag in prevTags) {
final existsInNew = final existsInNew =
newTags!.any((newTag) => newTag.uuid == prevTag.uuid); newTags!.any((newTag) => newTag.uuid == prevTag.uuid);
if (!existsInNew) { if (!existsInNew) {

View File

@ -1,22 +1,22 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.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/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class TagModel { class TagModel extends BaseTag {
String? uuid; TagModel({
String? tag; String? uuid,
final ProductModel? product; required String? tag,
String internalId; ProductModel? product,
String? location; String? internalId,
String? location,
TagModel( }) : super(
{this.uuid, uuid: uuid,
required this.tag, tag: tag,
this.product, product: product,
String? internalId, internalId: internalId,
this.location}) location: location,
: internalId = internalId ?? const Uuid().v4(); );
factory TagModel.fromJson(Map<String, dynamic> json) { factory TagModel.fromJson(Map<String, dynamic> json) {
final String internalId = json['internalId'] ?? const Uuid().v4(); final String internalId = json['internalId'] ?? const Uuid().v4();
@ -30,16 +30,19 @@ class TagModel {
); );
} }
@override
TagModel copyWith( TagModel copyWith(
{String? tag, {String? tag,
ProductModel? product, ProductModel? product,
String? uuid,
String? location, String? location,
String? internalId}) { String? internalId}) {
return TagModel( return TagModel(
tag: tag ?? this.tag, tag: tag ?? this.tag,
product: product ?? this.product, product: product ?? this.product,
location: location ?? this.location, location: location ?? this.location,
internalId: internalId ?? this.internalId, internalId: internalId ?? this.internalId,
uuid:uuid?? this.uuid
); );
} }

View File

@ -63,7 +63,8 @@ class SpaceModelPage extends StatelessWidget {
} }
// Render existing space model // Render existing space model
final model = spaceModels[index]; final model = spaceModels[index];
final otherModel = List<String>.from(allSpaceModelNames); final otherModel =
List<String>.from(allSpaceModelNames);
otherModel.remove(model.modelName); otherModel.remove(model.modelName);
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
@ -76,6 +77,7 @@ class SpaceModelPage extends StatelessWidget {
spaceModel: model, spaceModel: model,
otherSpaceModels: otherModel, otherSpaceModels: otherModel,
pageContext: context, pageContext: context,
allSpaceModels: spaceModels,
); );
}, },
); );

View File

@ -1,15 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class ButtonContentWidget extends StatelessWidget { class ButtonContentWidget extends StatelessWidget {
final IconData icon; final IconData? icon;
final String label; final String label;
final String? svgAssets;
const ButtonContentWidget({ const ButtonContentWidget(
Key? key, {Key? key, this.icon, required this.label, this.svgAssets})
required this.icon, : super(key: key);
required this.label,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -30,10 +30,20 @@ class ButtonContentWidget extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
child: Row( child: Row(
children: [ children: [
Icon( if (icon != null)
icon, Icon(
color: ColorsManager.spaceColor, icon,
), color: ColorsManager.spaceColor,
),
if (svgAssets != null)
Padding(
padding: const EdgeInsets.only(left: 6.0),
child: SvgPicture.asset(
svgAssets!,
width: screenWidth * 0.015, // Adjust icon size
height: screenWidth * 0.015,
),
),
const SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
child: Text( child: Text(

View File

@ -22,6 +22,7 @@ class CreateSpaceModelDialog extends StatelessWidget {
final SpaceTemplateModel? spaceModel; final SpaceTemplateModel? spaceModel;
final BuildContext? pageContext; final BuildContext? pageContext;
final List<String>? otherSpaceModels; final List<String>? otherSpaceModels;
final List<SpaceTemplateModel>? allSpaceModels;
const CreateSpaceModelDialog( const CreateSpaceModelDialog(
{Key? key, {Key? key,
@ -29,7 +30,8 @@ class CreateSpaceModelDialog extends StatelessWidget {
this.allTags, this.allTags,
this.spaceModel, this.spaceModel,
this.pageContext, this.pageContext,
this.otherSpaceModels}) this.otherSpaceModels,
this.allSpaceModels})
: super(key: key); : super(key: key);
@override @override
@ -138,6 +140,7 @@ class CreateSpaceModelDialog extends StatelessWidget {
spaceNameController: spaceNameController, spaceNameController: spaceNameController,
pageContext: pageContext, pageContext: pageContext,
otherSpaceModels: otherSpaceModels, otherSpaceModels: otherSpaceModels,
allSpaceModels: allSpaceModels,
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
SizedBox( SizedBox(
@ -147,7 +150,8 @@ class CreateSpaceModelDialog extends StatelessWidget {
Expanded( Expanded(
child: CancelButton( child: CancelButton(
label: 'Cancel', label: 'Cancel',
onPressed: () => Navigator.of(context).pop(), onPressed: (){
Navigator.of(context).pop();},
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/common/edit_chip.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/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class SubspaceModelCreate extends StatelessWidget { class SubspaceModelCreate extends StatelessWidget {
@ -46,39 +48,13 @@ class SubspaceModelCreate extends StatelessWidget {
spacing: 8.0, spacing: 8.0,
runSpacing: 8.0, runSpacing: 8.0,
children: [ children: [
...subspaces.map((subspace) => Container( ...subspaces.map((subspace) => SubspaceNameDisplayWidget(
padding: const EdgeInsets.symmetric( text: subspace.subspaceName,
horizontal: 8.0, vertical: 4.0),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: ColorsManager.transparentColor),
),
child: Text(
subspace.subspaceName,
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.spaceColor),
),
)), )),
GestureDetector( EditChip(
onTap: () async { onTap: () async {
await _openDialog(context, 'Edit Sub-space'); await _openDialog(context, 'Edit Sub-space');
}, },
child: Chip(
label: const Text(
'Edit',
style: TextStyle(color: ColorsManager.spaceColor),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side:
const BorderSide(color: ColorsManager.spaceColor),
),
),
), ),
], ],
), ),

View File

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
class SubspaceNameDisplayWidget extends StatelessWidget {
final String text;
final TextStyle? textStyle;
final Color backgroundColor;
final Color borderColor;
final EdgeInsetsGeometry padding;
final BorderRadiusGeometry borderRadius;
const SubspaceNameDisplayWidget({
Key? key,
required this.text,
this.textStyle,
this.backgroundColor = Colors.white,
this.borderColor = Colors.transparent,
this.padding = const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
this.borderRadius = const BorderRadius.all(Radius.circular(10)),
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: padding,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: borderRadius,
border: Border.all(color: borderColor),
),
child: Text(
text,
style: textStyle ??
Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: Colors.black),
),
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/common/edit_chip.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/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
@ -18,6 +19,7 @@ class TagChipDisplay extends StatelessWidget {
final TextEditingController spaceNameController; final TextEditingController spaceNameController;
final BuildContext? pageContext; final BuildContext? pageContext;
final List<String>? otherSpaceModels; final List<String>? otherSpaceModels;
final List<SpaceTemplateModel>? allSpaceModels;
const TagChipDisplay(BuildContext context, const TagChipDisplay(BuildContext context,
{Key? key, {Key? key,
@ -28,7 +30,8 @@ class TagChipDisplay extends StatelessWidget {
required this.allTags, required this.allTags,
required this.spaceNameController, required this.spaceNameController,
this.pageContext, this.pageContext,
this.otherSpaceModels}) this.otherSpaceModels,
this.allSpaceModels})
: super(key: key); : super(key: key);
@override @override
@ -83,45 +86,31 @@ class TagChipDisplay extends StatelessWidget {
), ),
), ),
), ),
GestureDetector( EditChip(onTap: () async {
onTap: () async { // Use the Navigator's context for showDialog
// Use the Navigator's context for showDialog Navigator.of(context).pop();
final navigatorContext =
Navigator.of(context).overlay?.context;
if (navigatorContext != null) { await showDialog<bool>(
await showDialog<bool>( barrierDismissible: false,
barrierDismissible: false, context: context,
context: navigatorContext, builder: (context) => AssignTagModelsDialog(
builder: (context) => AssignTagModelsDialog( products: products,
products: products, allSpaceModels: allSpaceModels,
subspaces: subspaces,
pageContext: pageContext,
allTags: allTags,
spaceModel: spaceModel,
otherSpaceModels: otherSpaceModels,
initialTags: TagHelper.generateInitialTags(
subspaces: subspaces, subspaces: subspaces,
pageContext: pageContext, spaceTagModels: spaceModel?.tags ?? []),
allTags: allTags, title: 'Edit Device',
spaceModel: spaceModel, addedProducts:
initialTags: TagHelper.generateInitialTags( TagHelper.createInitialSelectedProducts(
subspaces: subspaces, spaceModel?.tags ?? [], subspaces),
spaceTagModels: spaceModel?.tags ?? []), spaceName: spaceModel?.modelName ?? '',
title: 'Edit Device', ));
addedProducts: })
TagHelper.createInitialSelectedProducts(
spaceModel?.tags ?? [], subspaces),
spaceName: spaceModel?.modelName ?? '',
));
}
},
child: Chip(
label: const Text(
'Edit',
style: TextStyle(color: ColorsManager.spaceColor),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(color: ColorsManager.spaceColor),
),
),
),
], ],
), ),
), ),
@ -141,6 +130,7 @@ class TagChipDisplay extends StatelessWidget {
pageContext: pageContext, pageContext: pageContext,
isCreate: true, isCreate: true,
spaceModel: spaceModel, spaceModel: spaceModel,
otherSpaceModels: otherSpaceModels,
), ),
); );
}, },

View File

@ -26,6 +26,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
final List<String>? otherSpaceModels; final List<String>? otherSpaceModels;
final BuildContext? pageContext; final BuildContext? pageContext;
final SpaceTemplateModel? spaceModel; final SpaceTemplateModel? spaceModel;
final List<SpaceTemplateModel>? allSpaceModels;
const AddDeviceTypeModelWidget( const AddDeviceTypeModelWidget(
{super.key, {super.key,
@ -38,7 +39,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
required this.isCreate, required this.isCreate,
this.pageContext, this.pageContext,
this.otherSpaceModels, this.otherSpaceModels,
this.spaceModel}); this.spaceModel,
this.allSpaceModels});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -106,6 +108,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
context: context, context: context,
builder: (BuildContext dialogContext) { builder: (BuildContext dialogContext) {
return CreateSpaceModelDialog( return CreateSpaceModelDialog(
allSpaceModels: allSpaceModels,
products: products, products: products,
allTags: allTags, allTags: allTags,
pageContext: pageContext, pageContext: pageContext,
@ -175,11 +178,12 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
context: context, context: context,
builder: (context) => AssignTagModelsDialog( builder: (context) => AssignTagModelsDialog(
products: products, products: products,
allSpaceModels: allSpaceModels,
subspaces: subspaces, subspaces: subspaces,
addedProducts: state.selectedProducts, addedProducts: state.selectedProducts,
allTags: allTags, allTags: allTags,
spaceName: spaceName, spaceName: spaceName,
initialTags: state.initialTag, initialTags: initialTags,
otherSpaceModels: otherSpaceModels, otherSpaceModels: otherSpaceModels,
title: dialogTitle, title: dialogTitle,
spaceModel: spaceModel, spaceModel: spaceModel,
@ -216,13 +220,15 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
if (subspace.tags != null) { if (subspace.tags != null) {
initialTags.addAll( initialTags.addAll(
subspace.tags!.map( subspace.tags!.map(
(tag) => tag.copyWith(location: subspace.subspaceName), (tag) => tag.copyWith(
location: subspace.subspaceName,
tag: tag.tag,
internalId: tag.internalId),
), ),
); );
} }
} }
} }
return initialTags; return initialTags;
} }
} }