diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index b5251868..92d7c0e1 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -47,14 +47,13 @@ class AssignTagModelBloc } emit(AssignTagModelLoaded( - tags: allTags, - isSaveEnabled: _validateTags(allTags), - )); + tags: allTags, + isSaveEnabled: _validateTags(allTags), + errorMessage: '')); }); on((event, emit) { final currentState = state; - if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); @@ -122,9 +121,7 @@ class AssignTagModelBloc } bool _validateTags(List tags) { - if (tags.isEmpty) { - return false; - } + final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); final isValid = uniqueTags.length == tags.length && !hasEmptyTag; @@ -133,7 +130,11 @@ class AssignTagModelBloc String? _getValidationError(List tags) { final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); - if (hasEmptyTag) return 'Tags cannot be empty.'; + if (hasEmptyTag) { + return 'Tags cannot be empty.'; + } + + // Check for duplicate tags final duplicateTags = tags .map((tag) => tag.tag?.trim() ?? '') .fold>({}, (map, tag) { diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart index 9812a293..a51a9e8f 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart @@ -5,7 +5,7 @@ abstract class AssignTagModelState extends Equatable { const AssignTagModelState(); @override - List get props => []; + List get props => []; } class AssignTagModelInitial extends AssignTagModelState {} @@ -15,7 +15,7 @@ class AssignTagModelLoading extends AssignTagModelState {} class AssignTagModelLoaded extends AssignTagModelState { final List tags; final bool isSaveEnabled; - final String? errorMessage; + final String? errorMessage; const AssignTagModelLoaded({ required this.tags, @@ -24,7 +24,7 @@ class AssignTagModelLoaded extends AssignTagModelState { }); @override - List get props => [tags, isSaveEnabled]; + List get props => [tags, isSaveEnabled, errorMessage]; } class AssignTagModelError extends AssignTagModelState { @@ -33,5 +33,5 @@ class AssignTagModelError extends AssignTagModelState { const AssignTagModelError(this.errorMessage); @override - List get props => [errorMessage]; + List get props => [errorMessage]; } diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index a6b6b7f0..3a2499f9 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -47,249 +47,202 @@ class AssignTagModelsDialog extends StatelessWidget { ..add('Main Space'); return BlocProvider( - create: (_) => AssignTagModelBloc() - ..add(InitializeTagModels( - initialTags: initialTags, - addedProducts: addedProducts, - )), - child: BlocBuilder( - builder: (context, state) { - if (state is AssignTagModelLoaded) { - final controllers = List.generate( - state.tags.length, - (index) => TextEditingController(text: state.tags[index].tag), - ); + create: (_) => AssignTagModelBloc() + ..add(InitializeTagModels( + initialTags: initialTags, + addedProducts: addedProducts, + )), + child: BlocListener( + listener: (context, state) {}, + child: BlocBuilder( + builder: (context, state) { + if (state is AssignTagModelLoaded) { + final controllers = List.generate( + state.tags.length, + (index) => TextEditingController(text: state.tags[index].tag), + ); - return AlertDialog( - title: Text(title), - backgroundColor: ColorsManager.whiteColors, - content: SingleChildScrollView( - child: Column( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(20), - child: DataTable( - headingRowColor: WidgetStateProperty.all( - ColorsManager.dataHeaderGrey), - border: TableBorder.all( - color: ColorsManager.dataHeaderGrey, - width: 1, + return AlertDialog( + title: Text(title), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Column( + children: [ + ClipRRect( borderRadius: BorderRadius.circular(20), - ), - columns: [ - DataColumn( - label: Text('#', - style: - Theme.of(context).textTheme.bodyMedium)), - DataColumn( - label: Text('Device', - style: - Theme.of(context).textTheme.bodyMedium)), - DataColumn( - numeric: false, - headingRowAlignment: MainAxisAlignment.start, - label: Text('Tag', - style: - Theme.of(context).textTheme.bodyMedium)), - DataColumn( - label: Text('Location', - style: - Theme.of(context).textTheme.bodyMedium)), - ], - rows: state.tags.isEmpty - ? [ - const DataRow(cells: [ - DataCell( - Center( - child: Text( - 'No Data Available', - style: TextStyle( - fontSize: 14, - color: ColorsManager.lightGrayColor, + child: DataTable( + headingRowColor: WidgetStateProperty.all( + ColorsManager.dataHeaderGrey), + border: TableBorder.all( + color: ColorsManager.dataHeaderGrey, + width: 1, + borderRadius: BorderRadius.circular(20), + ), + columns: [ + DataColumn( + label: Text('#', + style: Theme.of(context) + .textTheme + .bodyMedium)), + DataColumn( + label: Text('Device', + style: Theme.of(context) + .textTheme + .bodyMedium)), + DataColumn( + numeric: false, + headingRowAlignment: MainAxisAlignment.start, + label: Text('Tag', + style: Theme.of(context) + .textTheme + .bodyMedium)), + DataColumn( + label: Text('Location', + style: Theme.of(context) + .textTheme + .bodyMedium)), + ], + rows: state.tags.isEmpty + ? [ + const DataRow(cells: [ + DataCell( + Center( + child: Text( + 'No Data Available', + style: TextStyle( + fontSize: 14, + color: + ColorsManager.lightGrayColor, + ), + ), ), ), - ), - ), - DataCell(SizedBox()), - DataCell(SizedBox()), - DataCell(SizedBox()), - ]) - ] - : List.generate(state.tags.length, (index) { - final tag = state.tags[index]; - final controller = controllers[index]; - final availableTags = getAvailableTags( - allTags ?? [], state.tags, tag); + DataCell(SizedBox()), + DataCell(SizedBox()), + DataCell(SizedBox()), + ]) + ] + : List.generate(state.tags.length, (index) { + final tag = state.tags[index]; + final controller = controllers[index]; + final availableTags = getAvailableTags( + allTags ?? [], state.tags, tag); - return DataRow( - cells: [ - DataCell(Text(index.toString())), - DataCell( - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - tag.product?.name ?? 'Unknown', - overflow: TextOverflow.ellipsis, - )), - const SizedBox(width: 10), + return DataRow( + cells: [ + DataCell(Text(index.toString())), + DataCell( + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + tag.product?.name ?? 'Unknown', + overflow: TextOverflow.ellipsis, + )), + const SizedBox(width: 10), + Container( + width: 20.0, + height: 20.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager + .lightGrayColor, + width: 1.0, + ), + ), + child: IconButton( + icon: const Icon( + Icons.close, + color: ColorsManager + .lightGreyColor, + size: 16, + ), + onPressed: () { + context + .read< + AssignTagModelBloc>() + .add(DeleteTagModel( + tagToDelete: tag, + tags: state.tags)); + }, + tooltip: 'Delete Tag', + padding: EdgeInsets.zero, + constraints: + const BoxConstraints(), + ), + ), + ], + ), + ), + DataCell( Container( - width: 20.0, - height: 20.0, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: ColorsManager - .lightGrayColor, - width: 1.0, + alignment: Alignment + .centerLeft, // Align cell content to the left + child: SizedBox( + width: double + .infinity, // Ensure full width for dropdown + child: DialogTextfieldDropdown( + items: availableTags, + initialValue: tag.tag, + onSelected: (value) { + controller.text = value; + context + .read< + AssignTagModelBloc>() + .add(UpdateTag( + index: index, + tag: value, + )); + }, ), ), - child: IconButton( - icon: const Icon( - Icons.close, - color: ColorsManager - .lightGreyColor, - size: 16, - ), - onPressed: () { - context - .read() - .add(DeleteTagModel( - tagToDelete: tag, - tags: state.tags)); - }, - tooltip: 'Delete Tag', - padding: EdgeInsets.zero, - constraints: - const BoxConstraints(), - ), - ), - ], - ), - ), - DataCell( - Container( - alignment: Alignment - .centerLeft, // Align cell content to the left - child: SizedBox( - width: double - .infinity, // Ensure full width for dropdown - child: DialogTextfieldDropdown( - items: availableTags, - initialValue: tag.tag, - onSelected: (value) { - controller.text = value; - context - .read() - .add(UpdateTag( - index: index, - tag: value, - )); - }, ), ), - ), - ), - DataCell( - SizedBox( - width: double.infinity, - child: DialogDropdown( - items: locations, - selectedValue: - tag.location ?? 'None', - onSelected: (value) { - context - .read() - .add(UpdateLocation( - index: index, - location: value, - )); - }, - )), - ), - ], - ); - }), - ), - ), - if (state.errorMessage != null) - Text( - state.errorMessage!, - style: const TextStyle(color: ColorsManager.warningRed), - ), - ], - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - const SizedBox(width: 10), - Expanded( - child: Builder( - builder: (buttonContext) => CancelButton( - label: 'Add New Device', - onPressed: () async { - for (var tag in state.tags) { - if (tag.location == null || subspaces == null) { - continue; - } - - final previousTagSubspace = - checkTagExistInSubspace(tag, subspaces ?? []); - - if (tag.location == 'Main Space') { - removeTagFromSubspace(tag, previousTagSubspace); - } else if (tag.location != - previousTagSubspace?.subspaceName) { - removeTagFromSubspace(tag, previousTagSubspace); - moveToNewSubspace(tag, subspaces ?? []); - state.tags.removeWhere( - (t) => t.internalId == tag.internalId); - } else { - updateTagInSubspace(tag, previousTagSubspace); - state.tags.removeWhere( - (t) => t.internalId == tag.internalId); - } - } - if (context.mounted) { - await showDialog( - barrierDismissible: false, - context: context, - builder: (dialogContext) => - AddDeviceTypeModelWidget( - products: products, - subspaces: subspaces, - isCreate: false, - initialSelectedProducts: addedProducts, - allTags: allTags, - spaceName: spaceName, - spaceTagModels: state.tags, - onUpdate: (tags, subspaces) { - onUpdate?.call(state.tags, subspaces); - Navigator.of(context).pop(); - }, - ), - ); - } - }, + DataCell( + SizedBox( + width: double.infinity, + child: DialogDropdown( + items: locations, + selectedValue: + tag.location ?? 'None', + onSelected: (value) { + context + .read< + AssignTagModelBloc>() + .add(UpdateLocation( + index: index, + location: value, + )); + }, + )), + ), + ], + ); + }), + ), ), - ), + if (state.errorMessage != null) + Text( + state.errorMessage!, + style: const TextStyle( + color: ColorsManager.warningRed), + ), + ], ), - const SizedBox(width: 10), - Expanded( - child: DefaultButton( - borderRadius: 10, - backgroundColor: state.isSaveEnabled - ? ColorsManager.secondaryColor - : ColorsManager.grayColor, - foregroundColor: ColorsManager.whiteColors, - onPressed: state.isSaveEnabled - ? () async { - Navigator.of(context).pop(); - + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + const SizedBox(width: 10), + Expanded( + child: Builder( + builder: (buttonContext) => CancelButton( + label: 'Add New Device', + onPressed: () async { for (var tag in state.tags) { if (tag.location == null || subspaces == null) { @@ -317,26 +270,89 @@ class AssignTagModelsDialog extends StatelessWidget { (t) => t.internalId == tag.internalId); } } + if (context.mounted) { + await showDialog( + barrierDismissible: false, + context: context, + builder: (dialogContext) => + AddDeviceTypeModelWidget( + products: products, + subspaces: subspaces, + isCreate: false, + initialSelectedProducts: addedProducts, + allTags: allTags, + spaceName: spaceName, + spaceTagModels: state.tags, + onUpdate: (tags, subspaces) { + onUpdate?.call(state.tags, subspaces); + Navigator.of(context).pop(); + }, + ), + ); + } + }, + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + borderRadius: 10, + backgroundColor: state.isSaveEnabled + ? ColorsManager.secondaryColor + : ColorsManager.grayColor, + foregroundColor: ColorsManager.whiteColors, + onPressed: state.isSaveEnabled + ? () async { + Navigator.of(context).pop(); - onUpdate?.call(state.tags, subspaces); - } - : null, - child: const Text('Save'), - ), + for (var tag in state.tags) { + if (tag.location == null || + subspaces == null) { + continue; + } + + final previousTagSubspace = + checkTagExistInSubspace( + tag, subspaces ?? []); + + if (tag.location == 'Main Space') { + removeTagFromSubspace( + tag, previousTagSubspace); + } else if (tag.location != + previousTagSubspace?.subspaceName) { + removeTagFromSubspace( + tag, previousTagSubspace); + moveToNewSubspace(tag, subspaces ?? []); + state.tags.removeWhere((t) => + t.internalId == tag.internalId); + } else { + updateTagInSubspace( + tag, previousTagSubspace); + state.tags.removeWhere((t) => + t.internalId == tag.internalId); + } + } + + onUpdate?.call(state.tags, subspaces); + } + : null, + child: const Text('Save'), + ), + ), + const SizedBox(width: 10), + ], ), - const SizedBox(width: 10), ], - ), - ], - ); - } else if (state is AssignTagModelLoading) { - return const Center(child: CircularProgressIndicator()); - } else { - return const Center(child: Text('Something went wrong.')); - } - }, - ), - ); + ); + } else if (state is AssignTagModelLoading) { + return const Center(child: CircularProgressIndicator()); + } else { + return const Center(child: Text('Something went wrong.')); + } + }, + ), + )); } List getAvailableTags( diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 6f2f2018..33defb52 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -3,7 +3,6 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.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/space_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; @@ -53,6 +52,8 @@ class CreateSpaceModelBloc } }); + + on((event, emit) { emit(CreateSpaceModelLoading()); Future.delayed(const Duration(seconds: 1), () { diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index e671410c..a3f34e31 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -166,6 +166,10 @@ class CreateSpaceModelDialog extends StatelessWidget { modelName: spaceNameController.text.trim(), ); + if(updatedSpaceTemplate.uuid != null){ + + } + context.read().add( CreateSpaceTemplate( spaceTemplate: