fixed the edit flow for space model

This commit is contained in:
hannathkadher
2025-01-22 12:49:47 +04:00
parent 7ffdc67016
commit f35b699d4c
24 changed files with 517 additions and 127 deletions

View File

@ -48,6 +48,7 @@ class DeviceTypeTileWidget extends StatelessWidget {
DeviceNameWidget(name: product.name), DeviceNameWidget(name: product.name),
const SizedBox(height: 4), const SizedBox(height: 4),
CounterWidget( CounterWidget(
isCreate: false,
initialCount: selectedProduct.count, initialCount: selectedProduct.count,
onCountChanged: (newCount) { onCountChanged: (newCount) {
context.read<AddDeviceTypeBloc>().add( context.read<AddDeviceTypeBloc>().add(

View File

@ -27,8 +27,7 @@ class SpaceManagementLoaded extends SpaceManagementState {
required this.products, required this.products,
this.selectedCommunity, this.selectedCommunity,
this.selectedSpace, this.selectedSpace,
this.spaceModels this.spaceModels});
});
} }
class SpaceModelManagenetLoaded extends SpaceManagementState { class SpaceModelManagenetLoaded extends SpaceManagementState {
@ -38,14 +37,10 @@ class SpaceModelManagenetLoaded extends SpaceManagementState {
class BlankState extends SpaceManagementState { class BlankState extends SpaceManagementState {
final List<CommunityModel> communities; final List<CommunityModel> communities;
final List<ProductModel> products; final List<ProductModel> products;
List<SpaceTemplateModel>? spaceModels; List<SpaceTemplateModel>? spaceModels;
BlankState(
BlankState({ {required this.communities, required this.products, this.spaceModels});
required this.communities,
required this.products,
this.spaceModels
});
} }
class SpaceCreationSuccess extends SpaceManagementState { class SpaceCreationSuccess extends SpaceManagementState {
@ -67,7 +62,7 @@ class SpaceManagementError extends SpaceManagementState {
} }
class SpaceModelLoaded extends SpaceManagementState { class SpaceModelLoaded extends SpaceManagementState {
final List<SpaceTemplateModel> spaceModels; List<SpaceTemplateModel> spaceModels;
final List<ProductModel> products; final List<ProductModel> products;
final List<CommunityModel> communities; final List<CommunityModel> communities;

View File

@ -137,6 +137,7 @@ class _AddDeviceWidgetState extends State<AddDeviceWidget> {
_buildDeviceName(product, size), _buildDeviceName(product, size),
const SizedBox(height: 4), const SizedBox(height: 4),
CounterWidget( CounterWidget(
isCreate: false,
initialCount: selectedProduct.count, initialCount: selectedProduct.count,
onCountChanged: (newCount) { onCountChanged: (newCount) {
setState(() { setState(() {

View File

@ -4,12 +4,14 @@ import 'package:syncrow_web/utils/color_manager.dart';
class CounterWidget extends StatefulWidget { class CounterWidget extends StatefulWidget {
final int initialCount; final int initialCount;
final ValueChanged<int> onCountChanged; final ValueChanged<int> onCountChanged;
final bool isCreate;
const CounterWidget({ const CounterWidget(
Key? key, {Key? key,
this.initialCount = 0, this.initialCount = 0,
required this.onCountChanged, required this.onCountChanged,
}) : super(key: key); required this.isCreate})
: super(key: key);
@override @override
State<CounterWidget> createState() => _CounterWidgetState(); State<CounterWidget> createState() => _CounterWidgetState();
@ -53,25 +55,26 @@ class _CounterWidgetState extends State<CounterWidget> {
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
_buildCounterButton(Icons.remove, _decrementCounter), _buildCounterButton(Icons.remove, _decrementCounter,!widget.isCreate ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'$_counter', '$_counter',
style: theme.textTheme.bodyLarge?.copyWith(color: ColorsManager.spaceColor), style: theme.textTheme.bodyLarge
?.copyWith(color: ColorsManager.spaceColor),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
_buildCounterButton(Icons.add, _incrementCounter), _buildCounterButton(Icons.add, _incrementCounter, false),
], ],
), ),
); );
} }
Widget _buildCounterButton(IconData icon, VoidCallback onPressed) { Widget _buildCounterButton(IconData icon, VoidCallback onPressed, bool isDisabled) {
return GestureDetector( return GestureDetector(
onTap: onPressed, onTap: isDisabled? null: onPressed,
child: Icon( child: Icon(
icon, icon,
color: ColorsManager.spaceColor, color: isDisabled? ColorsManager.spaceColor.withOpacity(0.3): ColorsManager.spaceColor,
size: 18, size: 18,
), ),
); );

View File

@ -19,6 +19,8 @@ import 'package:syncrow_web/utils/color_manager.dart';
class AssignTagModelsDialog extends StatelessWidget { class AssignTagModelsDialog extends StatelessWidget {
final List<ProductModel>? products; final List<ProductModel>? products;
final List<SubspaceTemplateModel>? subspaces; final List<SubspaceTemplateModel>? subspaces;
final SpaceTemplateModel? spaceModel;
final List<TagModel> initialTags; final List<TagModel> initialTags;
final ValueChanged<List<TagModel>>? onTagsAssigned; final ValueChanged<List<TagModel>>? onTagsAssigned;
final List<SelectedProduct> addedProducts; final List<SelectedProduct> addedProducts;
@ -39,7 +41,8 @@ class AssignTagModelsDialog extends StatelessWidget {
required this.spaceName, required this.spaceName,
required this.title, required this.title,
this.pageContext, this.pageContext,
this.otherSpaceModels}) this.otherSpaceModels,
this.spaceModel})
: super(key: key); : super(key: key);
@override @override
@ -210,7 +213,7 @@ class AssignTagModelsDialog extends StatelessWidget {
child: DialogDropdown( child: DialogDropdown(
items: locations, items: locations,
selectedValue: selectedValue:
tag.location ?? 'None', tag.location ?? 'Main Space',
onSelected: (value) { onSelected: (value) {
context context
.read< .read<
@ -281,16 +284,23 @@ class AssignTagModelsDialog extends StatelessWidget {
context: context, context: context,
builder: (dialogContext) => builder: (dialogContext) =>
AddDeviceTypeModelWidget( AddDeviceTypeModelWidget(
products: products, products: products,
subspaces: subspaces, subspaces: subspaces,
isCreate: false, isCreate: false,
initialSelectedProducts: addedProducts, initialSelectedProducts:
allTags: allTags, addedProducts,
spaceName: spaceName, allTags: allTags,
otherSpaceModels: otherSpaceModels, spaceName: spaceName,
spaceTagModels: state.tags, otherSpaceModels: otherSpaceModels,
pageContext: pageContext, spaceTagModels: state.tags,
), pageContext: pageContext,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: state.tags,
uuid: spaceModel?.uuid,
internalId:
spaceModel?.internalId,
subspaceModels: subspaces)),
); );
} }
}, },
@ -348,6 +358,9 @@ class AssignTagModelsDialog extends StatelessWidget {
spaceModel: SpaceTemplateModel( spaceModel: SpaceTemplateModel(
modelName: spaceName, modelName: spaceName,
tags: state.tags, tags: state.tags,
uuid: spaceModel?.uuid,
internalId:
spaceModel?.internalId,
subspaceModels: subspaces), subspaceModels: subspaces),
); );
}, },

View File

@ -186,8 +186,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget {
const SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
child: DefaultButton( child: DefaultButton(
onPressed: (state.subSpaces.isEmpty || onPressed: (state.errorMessage.isNotEmpty)
state.errorMessage.isNotEmpty)
? null ? null
: () async { : () async {
final subSpaces = context final subSpaces = context
@ -201,8 +200,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget {
}, },
backgroundColor: ColorsManager.secondaryColor, backgroundColor: ColorsManager.secondaryColor,
borderRadius: 10, borderRadius: 10,
foregroundColor: state.subSpaces.isEmpty || foregroundColor: state.errorMessage.isNotEmpty
state.errorMessage.isNotEmpty
? ColorsManager.whiteColorsWithOpacity ? ColorsManager.whiteColorsWithOpacity
: ColorsManager.whiteColors, : ColorsManager.whiteColors,
child: const Text('OK'), child: const Text('OK'),

View File

@ -37,7 +37,8 @@ class TagHelper {
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) {
groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1; final product = tag.product!;
groupedTags[product] = (groupedTags[product] ?? 0) + 1;
} }
} }
return groupedTags; return groupedTags;

View File

@ -1,12 +1,12 @@
import 'dart:math';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; 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/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/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart';
class CreateSpaceModelBloc class CreateSpaceModelBloc
extends Bloc<CreateSpaceModelEvent, CreateSpaceModelState> { extends Bloc<CreateSpaceModelEvent, CreateSpaceModelState> {
@ -193,5 +193,229 @@ class CreateSpaceModelBloc
emit(CreateSpaceModelError("Space template not initialized")); emit(CreateSpaceModelError("Space template not initialized"));
} }
}); });
on<ModifySpaceTemplate>((event, emit) async {
try {
final prevSpaceModel = event.spaceTemplate;
final newSpaceModel = event.updatedSpaceTemplate;
String? spaceModelName;
if (prevSpaceModel.modelName != newSpaceModel.modelName) {
spaceModelName = newSpaceModel.modelName;
}
List<TagModelUpdate> tagUpdates = [];
final List<UpdateSubspaceTemplateModel> subspaceUpdates = [];
tagUpdates = processTagUpdates(prevSpaceModel.tags, newSpaceModel.tags);
if (prevSpaceModel.subspaceModels != null) {
for (var prevSubspace in prevSpaceModel.subspaceModels!) {
if (newSpaceModel.subspaceModels == null) {
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.delete,
uuid: prevSubspace.uuid,
));
continue;
}
final subspaceExistsInNew = newSpaceModel.subspaceModels!
.any((newSubspace) => newSubspace.uuid == prevSubspace.uuid);
if (!subspaceExistsInNew) {
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.delete,
uuid: prevSubspace.uuid,
));
}
}
}
if (newSpaceModel.subspaceModels != null) {
for (var newSubspaceModel in newSpaceModel.subspaceModels!) {
if (newSubspaceModel.uuid == null ||
newSubspaceModel.uuid!.isEmpty) {
final List<TagModelUpdate> tagUpdatesInSubspace = [];
if (newSubspaceModel.tags != null) {
for (var tag in newSubspaceModel.tags!) {
tagUpdatesInSubspace.add(TagModelUpdate(
action: Action.add,
uuid: tag.uuid,
tag: tag.tag,
productUuid: tag.product?.uuid,
));
}
}
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.add,
subspaceName: newSubspaceModel.subspaceName,
tags: tagUpdatesInSubspace,
));
}
}
}
if (newSpaceModel.subspaceModels != null &&
prevSpaceModel.subspaceModels != null) {
final prevSubspaceMap = {
for (var subspace in prevSpaceModel.subspaceModels!)
subspace.uuid: subspace
};
for (var newSubspace in newSpaceModel.subspaceModels!) {
if (newSubspace.uuid != null &&
prevSubspaceMap.containsKey(newSubspace.uuid)) {
final prevSubspace = prevSubspaceMap[newSubspace.uuid]!;
// Check if modelName has changed
if (newSubspace.subspaceName != prevSubspace.subspaceName) {
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.update,
uuid: newSubspace.uuid,
subspaceName: newSubspace.subspaceName,
));
}
// Compare tags within the subspace
final List<TagModelUpdate> tagUpdatesInSubspace = [];
if (prevSubspace.tags != null && newSubspace.tags != null) {
final prevTagMap = {
for (var tag in prevSubspace.tags!) tag.uuid: tag
};
// Check for deleted tags
for (var prevTag in prevSubspace.tags!) {
if (!newSubspace.tags!
.any((newTag) => newTag.uuid == prevTag.uuid)) {
tagUpdatesInSubspace.add(TagModelUpdate(
action: Action.delete,
uuid: prevTag.uuid,
));
}
}
// Check for added tags, including those without a UUID
for (var newTag in newSubspace.tags!) {
if (newTag.uuid == null || newTag.uuid!.isEmpty) {
// Add new tag without UUID
tagUpdatesInSubspace.add(TagModelUpdate(
action: Action.add,
uuid: null, // or generate a new UUID if required
tag: newTag.tag,
productUuid: newTag.product?.uuid,
));
} else if (!prevSubspace.tags!
.any((prevTag) => prevTag.uuid == newTag.uuid)) {
// Add new tag with UUID
tagUpdatesInSubspace.add(TagModelUpdate(
action: Action.add,
uuid: newTag.uuid,
tag: newTag.tag,
productUuid: newTag.product?.uuid,
));
}
}
// Check for updated tags
for (var prevTag in prevSubspace.tags!) {
final newTag = newSubspace.tags!.cast<TagModel?>().firstWhere(
(tag) => tag?.uuid == prevTag.uuid,
orElse: () => null,
);
if (newTag != null && newTag.tag != prevTag.tag) {
tagUpdatesInSubspace.add(TagModelUpdate(
action: Action.update,
uuid: newTag.uuid,
tag: newTag.tag,
));
}
}
}
// Add the subspace with updated tags if necessary
if (tagUpdatesInSubspace.isNotEmpty) {
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.update,
uuid: newSubspace.uuid,
subspaceName: newSubspace.subspaceName,
tags: tagUpdatesInSubspace,
));
}
}
}
}
final spaceModelBody = CreateSpaceTemplateBodyModel(
modelName: spaceModelName,
tags: tagUpdates,
subspaceModels: subspaceUpdates);
final res = await _api.updateSpaceModel(
spaceModelBody, prevSpaceModel.uuid ?? '');
if (res != null) {
emit(CreateSpaceModelLoaded(newSpaceModel));
if (event.onUpdate != null) {
event.onUpdate!(event.updatedSpaceTemplate);
}
}
} catch (e) {
emit(CreateSpaceModelError('Error creating space model'));
}
});
}
List<TagModelUpdate> processTagUpdates(
List<TagModel>? prevTags,
List<TagModel>? newTags,
) {
final List<TagModelUpdate> tagUpdates = [];
final processedTags = <String?>{};
if (newTags != null || prevTags != null) {
// Case 1: Tags deleted
if (prevTags != null && newTags != null) {
for (var prevTag in prevTags!) {
final existsInNew =
newTags!.any((newTag) => newTag.uuid == prevTag.uuid);
if (!existsInNew) {
tagUpdates
.add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid));
}
}
}
// Case 2: Tags added
if (newTags != null) {
for (var newTag in newTags!) {
// Tag without UUID
if ((newTag.uuid == null || newTag.uuid!.isEmpty) &&
!processedTags.contains(newTag.tag)) {
tagUpdates.add(TagModelUpdate(
action: Action.add,
tag: newTag.tag,
productUuid: newTag.product?.uuid));
processedTags.add(newTag.tag);
}
}
}
// Case 3: Tags updated
if (prevTags != null && newTags != null) {
final newTagMap = {for (var tag in newTags!) tag.uuid: tag};
for (var prevTag in prevTags!) {
final newTag = newTagMap[prevTag.uuid];
if (newTag != null) {
tagUpdates.add(TagModelUpdate(
action: Action.update,
uuid: newTag.uuid,
tag: newTag.tag,
));
} else {}
}
}
}
return tagUpdates;
} }
} }

View File

@ -22,7 +22,6 @@ class CreateSpaceTemplate extends CreateSpaceModelEvent {
final SpaceTemplateModel spaceTemplate; final SpaceTemplateModel spaceTemplate;
final Function(SpaceTemplateModel)? onCreate; final Function(SpaceTemplateModel)? onCreate;
const CreateSpaceTemplate({ const CreateSpaceTemplate({
required this.spaceTemplate, required this.spaceTemplate,
this.onCreate, this.onCreate,
@ -39,7 +38,7 @@ class UpdateSpaceTemplateName extends CreateSpaceModelEvent {
UpdateSpaceTemplateName({required this.name, required this.allModels}); UpdateSpaceTemplateName({required this.name, required this.allModels});
@override @override
List<Object> get props => [name,allModels]; List<Object> get props => [name, allModels];
} }
class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent {
@ -54,9 +53,19 @@ class AddTagsToSpaceTemplate extends CreateSpaceModelEvent {
AddTagsToSpaceTemplate(this.tags); AddTagsToSpaceTemplate(this.tags);
} }
class ValidateSpaceTemplateName extends CreateSpaceModelEvent { class ValidateSpaceTemplateName extends CreateSpaceModelEvent {
final String name; final String name;
ValidateSpaceTemplateName({required this.name}); ValidateSpaceTemplateName({required this.name});
} }
class ModifySpaceTemplate extends CreateSpaceModelEvent {
final SpaceTemplateModel spaceTemplate;
final SpaceTemplateModel updatedSpaceTemplate;
final Function(SpaceTemplateModel)? onUpdate;
ModifySpaceTemplate(
{required this.spaceTemplate,
required this.updatedSpaceTemplate,
this.onUpdate});
}

View File

@ -12,6 +12,7 @@ class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
required List<SpaceTemplateModel> initialSpaceModels, required List<SpaceTemplateModel> initialSpaceModels,
}) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) { }) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) {
on<CreateSpaceModel>(_onCreateSpaceModel); on<CreateSpaceModel>(_onCreateSpaceModel);
on<UpdateSpaceModel>(_onUpdateSpaceModel);
} }
Future<void> _onCreateSpaceModel( Future<void> _onCreateSpaceModel(
@ -33,4 +34,23 @@ class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
} }
} }
} }
Future<void> _onUpdateSpaceModel(
UpdateSpaceModel event, Emitter<SpaceModelState> emit) async {
final currentState = state;
if (currentState is SpaceModelLoaded) {
try {
final newSpaceModel =
await api.getSpaceModel(event.spaceModelUuid ?? '');
if (newSpaceModel != null) {
final updatedSpaceModels = currentState.spaceModels.map((model) {
return model.uuid == event.spaceModelUuid ? newSpaceModel : model;
}).toList();
emit(SpaceModelLoaded(spaceModels: updatedSpaceModels));
}
} catch (e) {
emit(SpaceModelError(message: e.toString()));
}
}
}
} }

View File

@ -16,3 +16,21 @@ class CreateSpaceModel extends SpaceModelEvent {
@override @override
List<Object?> get props => [newSpaceModel]; List<Object?> get props => [newSpaceModel];
} }
class GetSpaceModel extends SpaceModelEvent {
final String spaceModelUuid;
GetSpaceModel({required this.spaceModelUuid});
@override
List<Object?> get props => [spaceModelUuid];
}
class UpdateSpaceModel extends SpaceModelEvent {
final String spaceModelUuid;
UpdateSpaceModel({required this.spaceModelUuid});
@override
List<Object?> get props => [spaceModelUuid];
}

View File

@ -30,12 +30,12 @@ class CreateSubspaceTemplateModel {
} }
class CreateSpaceTemplateBodyModel { class CreateSpaceTemplateBodyModel {
final String modelName; final String? modelName;
final List<dynamic>? tags; final List<dynamic>? tags;
final List<dynamic>? subspaceModels; final List<dynamic>? subspaceModels;
CreateSpaceTemplateBodyModel({ CreateSpaceTemplateBodyModel({
required this.modelName, this.modelName,
this.tags, this.tags,
this.subspaceModels, this.subspaceModels,
}); });

View File

@ -2,6 +2,7 @@ import 'package:equatable/equatable.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/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/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@ -70,14 +71,14 @@ class SpaceTemplateModel extends Equatable {
} }
class UpdateSubspaceTemplateModel { class UpdateSubspaceTemplateModel {
final String uuid; final String? uuid;
final Action action; final Action action;
final String? subspaceName; final String? subspaceName;
final List<UpdateTagModel>? tags; final List<TagModelUpdate>? tags;
UpdateSubspaceTemplateModel({ UpdateSubspaceTemplateModel({
required this.action, required this.action,
required this.uuid, this.uuid,
this.subspaceName, this.subspaceName,
this.tags, this.tags,
}); });
@ -88,7 +89,7 @@ class UpdateSubspaceTemplateModel {
uuid: json['uuid'] ?? '', uuid: json['uuid'] ?? '',
subspaceName: json['subspaceName'] ?? '', subspaceName: json['subspaceName'] ?? '',
tags: (json['tags'] as List) tags: (json['tags'] as List)
.map((item) => UpdateTagModel.fromJson(item)) .map((item) => TagModelUpdate.fromJson(item))
.toList(), .toList(),
); );
} }
@ -103,44 +104,6 @@ class UpdateSubspaceTemplateModel {
} }
} }
class UpdateTagModel {
final Action action;
final String? uuid;
final String tag;
final bool disabled;
final ProductModel? product;
UpdateTagModel({
required this.action,
this.uuid,
required this.tag,
required this.disabled,
this.product,
});
factory UpdateTagModel.fromJson(Map<String, dynamic> json) {
return UpdateTagModel(
action: ActionExtension.fromValue(json['action']),
uuid: json['uuid'] ?? '',
tag: json['tag'] ?? '',
disabled: json['disabled'] ?? false,
product: json['product'] != null
? ProductModel.fromMap(json['product'])
: null,
);
}
Map<String, dynamic> toJson() {
return {
'action': action.value,
'uuid': uuid,
'tag': tag,
'disabled': disabled,
'product': product?.toMap(),
};
}
}
extension SpaceTemplateExtensions on SpaceTemplateModel { extension SpaceTemplateExtensions on SpaceTemplateModel {
List<String> listAllTagValues() { List<String> listAllTagValues() {
final List<String> tagValues = []; final List<String> tagValues = [];

View File

@ -0,0 +1,34 @@
import 'package:syncrow_web/utils/constants/action_enum.dart';
class TagModelUpdate {
final Action action;
final String? uuid;
final String? tag;
final String? productUuid;
TagModelUpdate({
required this.action,
this.uuid,
this.tag,
this.productUuid,
});
factory TagModelUpdate.fromJson(Map<String, dynamic> json) {
return TagModelUpdate(
action: json['action'],
uuid: json['uuid'],
tag: json['tag'],
productUuid: json['productUuid'],
);
}
// Method to convert an instance to JSON
Map<String, dynamic> toJson() {
return {
'action': action.value,
'uuid': uuid, // Nullable field
'tag': tag,
'productUuid': productUuid,
};
}
}

View File

@ -65,7 +65,6 @@ class SpaceModelPage extends StatelessWidget {
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: () {
showDialog( showDialog(
@ -76,6 +75,7 @@ class SpaceModelPage extends StatelessWidget {
allTags: allTagValues, allTags: allTagValues,
spaceModel: model, spaceModel: model,
otherSpaceModels: otherModel, otherSpaceModels: otherModel,
pageContext: context,
); );
}, },
); );

View File

@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.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/tag_chips_display_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart';
@ -58,7 +59,9 @@ class CreateSpaceModelDialog extends StatelessWidget {
} }
spaceNameController.addListener(() { spaceNameController.addListener(() {
bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text,allModels: otherSpaceModels ??[])); bloc.add(UpdateSpaceTemplateName(
name: spaceNameController.text,
allModels: otherSpaceModels ?? []));
}); });
return bloc; return bloc;
@ -153,15 +156,12 @@ class CreateSpaceModelDialog extends StatelessWidget {
onPressed: state.errorMessage == null || onPressed: state.errorMessage == null ||
isNameValid isNameValid
? () { ? () {
final updatedSpaceTemplate =
updatedSpaceModel.copyWith(
modelName:
spaceNameController.text.trim(),
);
if (updatedSpaceModel.uuid == null) { if (updatedSpaceModel.uuid == null) {
final updatedSpaceTemplate =
updatedSpaceModel.copyWith(
modelName:
spaceNameController.text.trim(),
);
if (updatedSpaceTemplate.uuid !=
null) {}
context context
.read<CreateSpaceModelBloc>() .read<CreateSpaceModelBloc>()
.add( .add(
@ -181,6 +181,52 @@ class CreateSpaceModelDialog extends StatelessWidget {
}, },
), ),
); );
} else {
if (pageContext != null) {
final currentState = pageContext!
.read<SpaceModelBloc>()
.state;
if (currentState
is SpaceModelLoaded) {
final spaceModels =
List<SpaceTemplateModel>.from(
currentState.spaceModels);
final SpaceTemplateModel?
currentSpaceModel = spaceModels
.cast<SpaceTemplateModel?>()
.firstWhere(
(sm) =>
sm?.uuid ==
updatedSpaceModel
.uuid,
orElse: () => null,
);
if (currentSpaceModel != null) {
context
.read<CreateSpaceModelBloc>()
.add(ModifySpaceTemplate(
spaceTemplate:
currentSpaceModel,
updatedSpaceTemplate:
updatedSpaceTemplate,
onUpdate: (newModel) {
if (pageContext !=
null) {
pageContext!
.read<
SpaceModelBloc>()
.add(UpdateSpaceModel(
spaceModelUuid:
newModel.uuid ??
''));
}
Navigator.of(context)
.pop();
}));
}
}
}
} }
} }
: null, : null,

View File

@ -1,9 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class SubspaceChipWidget extends StatelessWidget { class SubspaceChipWidget extends StatelessWidget {
final String subspace; final String subspace;

View File

@ -46,23 +46,23 @@ class SubspaceModelCreate extends StatelessWidget {
spacing: 8.0, spacing: 8.0,
runSpacing: 8.0, runSpacing: 8.0,
children: [ children: [
...subspaces.map( ...subspaces.map((subspace) => Container(
(subspace) => Chip( padding: const EdgeInsets.symmetric(
label: Text( horizontal: 8.0, vertical: 4.0),
subspace.subspaceName, decoration: BoxDecoration(
style: const TextStyle( color: ColorsManager.whiteColors,
color: ColorsManager.spaceColor), // Text color borderRadius: BorderRadius.circular(10),
), border: Border.all(
backgroundColor: color: ColorsManager.transparentColor),
ColorsManager.whiteColors, // Chip background color ),
shape: RoundedRectangleBorder( child: Text(
borderRadius: subspace.subspaceName,
BorderRadius.circular(16), // Rounded chip style: Theme.of(context)
side: const BorderSide( .textTheme
color: ColorsManager.spaceColor), // Border color .bodySmall
), ?.copyWith(color: ColorsManager.spaceColor),
), ),
), )),
GestureDetector( GestureDetector(
onTap: () async { onTap: () async {
await _openDialog(context, 'Edit Sub-space'); await _openDialog(context, 'Edit Sub-space');

View File

@ -98,6 +98,7 @@ class TagChipDisplay extends StatelessWidget {
subspaces: subspaces, subspaces: subspaces,
pageContext: pageContext, pageContext: pageContext,
allTags: allTags, allTags: allTags,
spaceModel: spaceModel,
initialTags: TagHelper.generateInitialTags( initialTags: TagHelper.generateInitialTags(
subspaces: subspaces, subspaces: subspaces,
spaceTagModels: spaceModel?.tags ?? []), spaceTagModels: spaceModel?.tags ?? []),
@ -139,6 +140,7 @@ class TagChipDisplay extends StatelessWidget {
spaceName: spaceNameController.text, spaceName: spaceNameController.text,
pageContext: pageContext, pageContext: pageContext,
isCreate: true, isCreate: true,
spaceModel: spaceModel,
), ),
); );
}, },

View File

@ -5,9 +5,10 @@ import 'package:syncrow_web/pages/common/buttons/default_button.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/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/link_space_model/bloc/link_space_model_bloc.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/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart';
@ -24,6 +25,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
final bool isCreate; final bool isCreate;
final List<String>? otherSpaceModels; final List<String>? otherSpaceModels;
final BuildContext? pageContext; final BuildContext? pageContext;
final SpaceTemplateModel? spaceModel;
const AddDeviceTypeModelWidget( const AddDeviceTypeModelWidget(
{super.key, {super.key,
@ -35,7 +37,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
required this.spaceName, required this.spaceName,
required this.isCreate, required this.isCreate,
this.pageContext, this.pageContext,
this.otherSpaceModels}); this.otherSpaceModels,
this.spaceModel});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -75,6 +78,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
padding: padding:
const EdgeInsets.symmetric(horizontal: 20.0), const EdgeInsets.symmetric(horizontal: 20.0),
child: ScrollableGridViewWidget( child: ScrollableGridViewWidget(
isCreate: isCreate,
products: products, products: products,
crossAxisCount: crossAxisCount, crossAxisCount: crossAxisCount,
initialProductCounts: state.selectedProducts, initialProductCounts: state.selectedProducts,
@ -98,6 +102,44 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
onPressed: () async { onPressed: () async {
if (isCreate) { if (isCreate) {
Navigator.of(context).pop(); Navigator.of(context).pop();
await showDialog(
context: context,
builder: (BuildContext dialogContext) {
return CreateSpaceModelDialog(
products: products,
allTags: allTags,
pageContext: pageContext,
otherSpaceModels: otherSpaceModels,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: spaceModel?.tags ?? [],
uuid: spaceModel?.uuid,
internalId: spaceModel?.internalId,
subspaceModels: subspaces),
);
},
);
} else {
final initialTags = generateInitialTags(
spaceTagModels: spaceTagModels,
subspaces: subspaces,
);
Navigator.of(context).pop();
await showDialog<bool>(
context: context,
builder: (context) => AssignTagModelsDialog(
products: products,
subspaces: subspaces,
addedProducts: initialSelectedProducts ?? [],
allTags: allTags,
spaceName: spaceName,
initialTags: initialTags,
otherSpaceModels: otherSpaceModels,
title: 'Edit Device',
spaceModel: spaceModel,
pageContext: pageContext,
));
} }
}, },
), ),
@ -140,6 +182,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
initialTags: state.initialTag, initialTags: state.initialTag,
otherSpaceModels: otherSpaceModels, otherSpaceModels: otherSpaceModels,
title: dialogTitle, title: dialogTitle,
spaceModel: spaceModel,
pageContext: pageContext, pageContext: pageContext,
), ),
); );

View File

@ -13,12 +13,13 @@ import 'package:syncrow_web/utils/constants/assets.dart';
class DeviceTypeTileWidget extends StatelessWidget { class DeviceTypeTileWidget extends StatelessWidget {
final ProductModel product; final ProductModel product;
final List<SelectedProduct> productCounts; final List<SelectedProduct> productCounts;
final bool isCreate;
const DeviceTypeTileWidget({ const DeviceTypeTileWidget(
super.key, {super.key,
required this.product, required this.product,
required this.productCounts, required this.productCounts,
}); required this.isCreate});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -48,6 +49,7 @@ class DeviceTypeTileWidget extends StatelessWidget {
DeviceNameWidget(name: product.name), DeviceNameWidget(name: product.name),
const SizedBox(height: 4), const SizedBox(height: 4),
CounterWidget( CounterWidget(
isCreate: isCreate,
initialCount: selectedProduct.count, initialCount: selectedProduct.count,
onCountChanged: (newCount) { onCountChanged: (newCount) {
context.read<AddDeviceTypeModelBloc>().add( context.read<AddDeviceTypeModelBloc>().add(

View File

@ -10,12 +10,14 @@ class ScrollableGridViewWidget extends StatelessWidget {
final List<ProductModel>? products; final List<ProductModel>? products;
final int crossAxisCount; final int crossAxisCount;
final List<SelectedProduct>? initialProductCounts; final List<SelectedProduct>? initialProductCounts;
final bool isCreate;
const ScrollableGridViewWidget({ const ScrollableGridViewWidget({
super.key, super.key,
required this.products, required this.products,
required this.crossAxisCount, required this.crossAxisCount,
this.initialProductCounts, this.initialProductCounts,
required this.isCreate
}); });
@override @override
@ -30,7 +32,7 @@ class ScrollableGridViewWidget extends StatelessWidget {
final productCounts = state is AddDeviceModelLoaded final productCounts = state is AddDeviceModelLoaded
? state.selectedProducts ? state.selectedProducts
: <SelectedProduct>[]; : <SelectedProduct>[];
return GridView.builder( return GridView.builder(
controller: scrollController, controller: scrollController,
shrinkWrap: true, shrinkWrap: true,
@ -47,6 +49,7 @@ class ScrollableGridViewWidget extends StatelessWidget {
return DeviceTypeTileWidget( return DeviceTypeTileWidget(
product: product, product: product,
isCreate: isCreate,
productCounts: initialProductCount != null productCounts: initialProductCount != null
? [...productCounts, initialProductCount] ? [...productCounts, initialProductCount]
: productCounts, : productCounts,

View File

@ -34,6 +34,20 @@ class SpaceModelManagementApi {
return response; return response;
} }
Future<String?> updateSpaceModel(
CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid) async {
final response = await HTTPService().put(
path: ApiEndpoints.updateSpaceModel
.replaceAll('{projectId}', TempConst.projectId).replaceAll('{spaceModelUuid}', spaceModelUuid),
body: spaceModel.toJson(),
expectedResponseModel: (json) {
return json['message'];
},
);
return response;
}
Future<SpaceTemplateModel?> getSpaceModel(String spaceModelUuid) async { Future<SpaceTemplateModel?> getSpaceModel(String spaceModelUuid) async {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getSpaceModel path: ApiEndpoints.getSpaceModel

View File

@ -101,8 +101,11 @@ abstract class ApiEndpoints {
//space model //space model
static const String listSpaceModels = '/projects/{projectId}/space-models'; static const String listSpaceModels = '/projects/{projectId}/space-models';
static const String createSpaceModel = '/projects/{projectId}/space-models'; static const String createSpaceModel = '/projects/{projectId}/space-models';
static const String getSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}'; static const String getSpaceModel =
'/projects/{projectId}/space-models/{spaceModelUuid}';
static const String updateSpaceModel =
'/projects/{projectId}/space-models/{spaceModelUuid}';
static const String roleTypes = '/role/types'; static const String roleTypes = '/role/types';
static const String permission = '/permission/{roleUuid}'; static const String permission = '/permission/{roleUuid}';
static const String inviteUser = '/invite-user'; static const String inviteUser = '/invite-user';