Merge pull request #69 from SyncrowIOT/bugfix/space-edit

Bugfix/space edit
This commit is contained in:
Abdullah
2025-01-23 00:37:03 +03:00
committed by GitHub
29 changed files with 868 additions and 483 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

@ -420,9 +420,10 @@ class SpaceManagementBloc
await _api.deleteSpace(communityUuid, parent.uuid!); await _api.deleteSpace(communityUuid, parent.uuid!);
} }
} catch (e) { } catch (e) {
rethrow; // Decide whether to stop execution or continue rethrow;
} }
} }
orderedSpaces.removeWhere((space) => parentsToDelete.contains(space));
for (var space in orderedSpaces) { for (var space in orderedSpaces) {
try { try {

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 {
@ -40,12 +39,8 @@ class BlankState extends SpaceManagementState {
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

@ -66,4 +66,25 @@ class ProductModel {
String toString() { String toString() {
return 'ProductModel(uuid: $uuid, catName: $catName, prodId: $prodId, prodType: $prodType, name: $name, icon: $icon)'; return 'ProductModel(uuid: $uuid, catName: $catName, prodId: $prodId, prodType: $prodType, name: $name, icon: $icon)';
} }
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ProductModel &&
runtimeType == other.runtimeType &&
uuid == other.uuid &&
catName == other.catName &&
prodId == other.prodId &&
prodType == other.prodType &&
name == other.name &&
icon == other.icon;
@override
int get hashCode =>
uuid.hashCode ^
catName.hashCode ^
prodId.hashCode ^
prodType.hashCode ^
name.hashCode ^
icon.hashCode;
} }

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

@ -177,7 +177,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
painter: CurvedLinePainter([connection])), painter: CurvedLinePainter([connection])),
), ),
for (var entry in spaces.asMap().entries) for (var entry in spaces.asMap().entries)
if (entry.value.status != SpaceStatus.deleted || if (entry.value.status != SpaceStatus.deleted &&
entry.value.status != SpaceStatus.parentDeleted) entry.value.status != SpaceStatus.parentDeleted)
Positioned( Positioned(
left: entry.value.position.dx, left: entry.value.position.dx,
@ -301,7 +301,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
List<Tag>? tags) { List<Tag>? tags) {
setState(() { setState(() {
// Set the first space in the center or use passed position // Set the first space in the center or use passed position
Offset centerPosition = Offset centerPosition =
position ?? _getCenterPosition(screenSize); position ?? _getCenterPosition(screenSize);
SpaceModel newSpace = SpaceModel( SpaceModel newSpace = SpaceModel(
@ -385,10 +384,10 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void flatten(SpaceModel space) { void flatten(SpaceModel space) {
if (space.status == SpaceStatus.deleted || if (space.status == SpaceStatus.deleted ||
space.status == SpaceStatus.parentDeleted) return; space.status == SpaceStatus.parentDeleted) {
return;
}
result.add(space); result.add(space);
for (var child in space.children) { for (var child in space.children) {
flatten(child); flatten(child);
} }
@ -456,21 +455,15 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
} }
void _onDelete() { void _onDelete() {
if (widget.selectedCommunity != null &&
widget.selectedCommunity?.uuid != null &&
widget.selectedSpace == null) {
context.read<SpaceManagementBloc>().add(DeleteCommunityEvent(
communityUuid: widget.selectedCommunity!.uuid,
));
}
if (widget.selectedSpace != null) { if (widget.selectedSpace != null) {
setState(() { setState(() {
for (var space in spaces) { for (var space in spaces) {
if (space.uuid == widget.selectedSpace?.uuid) { if (space.internalId == widget.selectedSpace?.internalId) {
space.status = SpaceStatus.deleted; space.status = SpaceStatus.deleted;
_markChildrenAsDeleted(space); _markChildrenAsDeleted(space);
} }
} }
_removeConnectionsForDeletedSpaces(); _removeConnectionsForDeletedSpaces();
}); });
} }

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

@ -49,12 +49,11 @@ class AssignTagModelBloc
emit(AssignTagModelLoaded( emit(AssignTagModelLoaded(
tags: allTags, tags: allTags,
isSaveEnabled: _validateTags(allTags), isSaveEnabled: _validateTags(allTags),
)); errorMessage: ''));
}); });
on<UpdateTag>((event, emit) { on<UpdateTag>((event, emit) {
final currentState = state; final currentState = state;
if (currentState is AssignTagModelLoaded && if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) { currentState.tags.isNotEmpty) {
final tags = List<TagModel>.from(currentState.tags); final tags = List<TagModel>.from(currentState.tags);
@ -122,9 +121,7 @@ class AssignTagModelBloc
} }
bool _validateTags(List<TagModel> tags) { bool _validateTags(List<TagModel> tags) {
if (tags.isEmpty) {
return false;
}
final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet();
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
final isValid = uniqueTags.length == tags.length && !hasEmptyTag; final isValid = uniqueTags.length == tags.length && !hasEmptyTag;
@ -133,7 +130,11 @@ class AssignTagModelBloc
String? _getValidationError(List<TagModel> tags) { String? _getValidationError(List<TagModel> tags) {
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); 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 final duplicateTags = tags
.map((tag) => tag.tag?.trim() ?? '') .map((tag) => tag.tag?.trim() ?? '')
.fold<Map<String, int>>({}, (map, tag) { .fold<Map<String, int>>({}, (map, tag) {

View File

@ -5,7 +5,7 @@ abstract class AssignTagModelState extends Equatable {
const AssignTagModelState(); const AssignTagModelState();
@override @override
List<Object> get props => []; List<Object?> get props => [];
} }
class AssignTagModelInitial extends AssignTagModelState {} class AssignTagModelInitial extends AssignTagModelState {}
@ -24,7 +24,7 @@ class AssignTagModelLoaded extends AssignTagModelState {
}); });
@override @override
List<Object> get props => [tags, isSaveEnabled]; List<Object?> get props => [tags, isSaveEnabled, errorMessage];
} }
class AssignTagModelError extends AssignTagModelState { class AssignTagModelError extends AssignTagModelState {
@ -33,5 +33,5 @@ class AssignTagModelError extends AssignTagModelState {
const AssignTagModelError(this.errorMessage); const AssignTagModelError(this.errorMessage);
@override @override
List<Object> get props => [errorMessage]; List<Object?> get props => [errorMessage];
} }

View File

@ -9,22 +9,26 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_pr
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_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/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/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/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;
final List<String>? allTags; final List<String>? allTags;
final String spaceName; final String spaceName;
final String title; final String title;
final void Function( final BuildContext? pageContext;
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces)? onUpdate; final List<String>? otherSpaceModels;
const AssignTagModelsDialog( const AssignTagModelsDialog(
{Key? key, {Key? key,
@ -36,7 +40,9 @@ class AssignTagModelsDialog extends StatelessWidget {
this.allTags, this.allTags,
required this.spaceName, required this.spaceName,
required this.title, required this.title,
this.onUpdate}) this.pageContext,
this.otherSpaceModels,
this.spaceModel})
: super(key: key); : super(key: key);
@override @override
@ -52,6 +58,8 @@ class AssignTagModelsDialog extends StatelessWidget {
initialTags: initialTags, initialTags: initialTags,
addedProducts: addedProducts, addedProducts: addedProducts,
)), )),
child: BlocListener<AssignTagModelBloc, AssignTagModelState>(
listener: (context, state) {},
child: BlocBuilder<AssignTagModelBloc, AssignTagModelState>( child: BlocBuilder<AssignTagModelBloc, AssignTagModelState>(
builder: (context, state) { builder: (context, state) {
if (state is AssignTagModelLoaded) { if (state is AssignTagModelLoaded) {
@ -79,22 +87,26 @@ class AssignTagModelsDialog extends StatelessWidget {
columns: [ columns: [
DataColumn( DataColumn(
label: Text('#', label: Text('#',
style: style: Theme.of(context)
Theme.of(context).textTheme.bodyMedium)), .textTheme
.bodyMedium)),
DataColumn( DataColumn(
label: Text('Device', label: Text('Device',
style: style: Theme.of(context)
Theme.of(context).textTheme.bodyMedium)), .textTheme
.bodyMedium)),
DataColumn( DataColumn(
numeric: false, numeric: false,
headingRowAlignment: MainAxisAlignment.start, headingRowAlignment: MainAxisAlignment.start,
label: Text('Tag', label: Text('Tag',
style: style: Theme.of(context)
Theme.of(context).textTheme.bodyMedium)), .textTheme
.bodyMedium)),
DataColumn( DataColumn(
label: Text('Location', label: Text('Location',
style: style: Theme.of(context)
Theme.of(context).textTheme.bodyMedium)), .textTheme
.bodyMedium)),
], ],
rows: state.tags.isEmpty rows: state.tags.isEmpty
? [ ? [
@ -105,7 +117,8 @@ class AssignTagModelsDialog extends StatelessWidget {
'No Data Available', 'No Data Available',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: ColorsManager.lightGrayColor, color:
ColorsManager.lightGrayColor,
), ),
), ),
), ),
@ -123,7 +136,7 @@ class AssignTagModelsDialog extends StatelessWidget {
return DataRow( return DataRow(
cells: [ cells: [
DataCell(Text(index.toString())), DataCell(Text((index + 1).toString())),
DataCell( DataCell(
Row( Row(
mainAxisAlignment: mainAxisAlignment:
@ -155,7 +168,8 @@ class AssignTagModelsDialog extends StatelessWidget {
), ),
onPressed: () { onPressed: () {
context context
.read<AssignTagModelBloc>() .read<
AssignTagModelBloc>()
.add(DeleteTagModel( .add(DeleteTagModel(
tagToDelete: tag, tagToDelete: tag,
tags: state.tags)); tags: state.tags));
@ -182,7 +196,8 @@ class AssignTagModelsDialog extends StatelessWidget {
onSelected: (value) { onSelected: (value) {
controller.text = value; controller.text = value;
context context
.read<AssignTagModelBloc>() .read<
AssignTagModelBloc>()
.add(UpdateTag( .add(UpdateTag(
index: index, index: index,
tag: value, tag: value,
@ -198,10 +213,11 @@ 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<AssignTagModelBloc>() .read<
AssignTagModelBloc>()
.add(UpdateLocation( .add(UpdateLocation(
index: index, index: index,
location: value, location: value,
@ -217,7 +233,8 @@ class AssignTagModelsDialog extends StatelessWidget {
if (state.errorMessage != null) if (state.errorMessage != null)
Text( Text(
state.errorMessage!, state.errorMessage!,
style: const TextStyle(color: ColorsManager.warningRed), style: const TextStyle(
color: ColorsManager.warningRed),
), ),
], ],
), ),
@ -232,64 +249,6 @@ class AssignTagModelsDialog extends StatelessWidget {
builder: (buttonContext) => CancelButton( builder: (buttonContext) => CancelButton(
label: 'Add New Device', label: 'Add New Device',
onPressed: () async { 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<bool>(
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();
for (var tag in state.tags) { for (var tag in state.tags) {
if (tag.location == null || if (tag.location == null ||
subspaces == null) { subspaces == null) {
@ -317,8 +276,95 @@ class AssignTagModelsDialog extends StatelessWidget {
(t) => t.internalId == tag.internalId); (t) => t.internalId == tag.internalId);
} }
} }
if (context.mounted) {
Navigator.of(context).pop();
onUpdate?.call(state.tags, subspaces); await showDialog<bool>(
barrierDismissible: false,
context: context,
builder: (dialogContext) =>
AddDeviceTypeModelWidget(
products: products,
subspaces: subspaces,
isCreate: false,
initialSelectedProducts:
addedProducts,
allTags: allTags,
spaceName: spaceName,
otherSpaceModels: otherSpaceModels,
spaceTagModels: state.tags,
pageContext: pageContext,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: state.tags,
uuid: spaceModel?.uuid,
internalId:
spaceModel?.internalId,
subspaceModels: subspaces)),
);
}
},
),
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
borderRadius: 10,
backgroundColor: state.isSaveEnabled
? ColorsManager.secondaryColor
: ColorsManager.grayColor,
foregroundColor: ColorsManager.whiteColors,
onPressed: state.isSaveEnabled
? () 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);
}
}
Navigator.of(context)
.popUntil((route) => route.isFirst);
await showDialog(
context: context,
builder: (BuildContext dialogContext) {
return CreateSpaceModelDialog(
products: products,
allTags: allTags,
pageContext: pageContext,
otherSpaceModels: otherSpaceModels,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: state.tags,
uuid: spaceModel?.uuid,
internalId:
spaceModel?.internalId,
subspaceModels: subspaces),
);
},
);
} }
: null, : null,
child: const Text('Save'), child: const Text('Save'),
@ -336,7 +382,7 @@ class AssignTagModelsDialog extends StatelessWidget {
} }
}, },
), ),
); ));
} }
List<String> getAvailableTags( List<String> getAvailableTags(

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

@ -5,7 +5,9 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/create_sp
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/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/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> {
@ -172,7 +174,12 @@ class CreateSpaceModelBloc
on<UpdateSpaceTemplateName>((event, emit) { on<UpdateSpaceTemplateName>((event, emit) {
final currentState = state; final currentState = state;
if (currentState is CreateSpaceModelLoaded) { if (currentState is CreateSpaceModelLoaded) {
if (event.name.trim().isEmpty) { if (event.allModels.contains(event.name) == true) {
emit(CreateSpaceModelLoaded(
currentState.space,
errorMessage: "Duplicate Model name",
));
} else if (event.name.trim().isEmpty) {
emit(CreateSpaceModelLoaded( emit(CreateSpaceModelLoaded(
currentState.space, currentState.space,
errorMessage: "Model name cannot be empty", errorMessage: "Model name cannot be empty",
@ -187,5 +194,160 @@ 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 = [];
final List<SubspaceTemplateModel>? prevSubspaces =
prevSpaceModel.subspaceModels;
final List<SubspaceTemplateModel>? newSubspaces =
newSpaceModel.subspaceModels;
tagUpdates = processTagUpdates(prevSpaceModel.tags, newSpaceModel.tags);
if (prevSubspaces != null || newSubspaces != null) {
if (prevSubspaces != null && newSubspaces != null) {
for (var prevSubspace in prevSubspaces!) {
final existsInNew = newSubspaces!
.any((newTag) => newTag.uuid == prevSubspace.uuid);
if (!existsInNew) {
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.delete, uuid: prevSubspace.uuid));
}
}
} else if (prevSubspaces != null && newSubspaces == null) {
for (var prevSubspace in prevSubspaces) {
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.delete, uuid: prevSubspace.uuid));
}
}
if (newSubspaces != null) {
for (var newSubspace in newSubspaces!) {
// Tag without UUID
if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) {
final List<TagModelUpdate> tagUpdates = [];
if (newSubspace.tags != null) {
for (var tag in newSubspace.tags!) {
tagUpdates.add(TagModelUpdate(
action: Action.add,
tag: tag.tag,
productUuid: tag.product?.uuid));
}
}
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.add,
subspaceName: newSubspace.subspaceName,
tags: tagUpdates));
}
}
}
if (prevSubspaces != null && newSubspaces != null) {
final newSubspaceMap = {
for (var subspace in newSubspaces!) subspace.uuid: subspace
};
for (var prevSubspace in prevSubspaces!) {
final newSubspace = newSubspaceMap[prevSubspace.uuid];
if (newSubspace != null) {
final List<TagModelUpdate> tagSubspaceUpdates =
processTagUpdates(prevSubspace.tags, newSubspace.tags);
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.update,
uuid: newSubspace.uuid,
subspaceName: newSubspace.subspaceName,
tags: tagSubspaceUpdates));
} else {}
}
}
}
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));
}
}
} else if (prevTags != null && newTags == null) {
for (var prevTag in prevTags) {
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,
@ -34,11 +33,12 @@ class CreateSpaceTemplate extends CreateSpaceModelEvent {
class UpdateSpaceTemplateName extends CreateSpaceModelEvent { class UpdateSpaceTemplateName extends CreateSpaceModelEvent {
final String name; final String name;
final List<String> allModels;
UpdateSpaceTemplateName({required this.name}); UpdateSpaceTemplateName({required this.name, required this.allModels});
@override @override
List<Object> get props => [name]; List<Object> get props => [name, allModels];
} }
class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent {
@ -53,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,
}); });
@ -47,4 +47,9 @@ class CreateSpaceTemplateBodyModel {
'subspaceModels': subspaceModels, 'subspaceModels': subspaceModels,
}; };
} }
@override
String toString() {
return toJson().toString();
}
} }

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

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.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/bloc/space_model_bloc.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_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/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/add_space_model_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/add_space_model_widget.dart';
@ -24,6 +23,7 @@ class SpaceModelPage extends StatelessWidget {
} else if (state is SpaceModelLoaded) { } else if (state is SpaceModelLoaded) {
final spaceModels = state.spaceModels; final spaceModels = state.spaceModels;
final allTagValues = _getAllTagValues(spaceModels); final allTagValues = _getAllTagValues(spaceModels);
final allSpaceModelNames = _getAllSpaceModelName(spaceModels);
return Scaffold( return Scaffold(
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
@ -52,12 +52,8 @@ class SpaceModelPage extends StatelessWidget {
return CreateSpaceModelDialog( return CreateSpaceModelDialog(
products: products, products: products,
allTags: allTagValues, allTags: allTagValues,
onLoad: (newModel) { pageContext: context,
context.read<SpaceModelBloc>().add( otherSpaceModels: allSpaceModelNames,
CreateSpaceModel(
newSpaceModel: newModel),
);
},
); );
}, },
); );
@ -67,6 +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);
otherModel.remove(model.modelName);
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
showDialog( showDialog(
@ -76,7 +74,8 @@ class SpaceModelPage extends StatelessWidget {
products: products, products: products,
allTags: allTagValues, allTags: allTagValues,
spaceModel: model, spaceModel: model,
onLoad: (newModel) {}, otherSpaceModels: otherModel,
pageContext: context,
); );
}, },
); );
@ -128,4 +127,12 @@ class SpaceModelPage extends StatelessWidget {
} }
return allTags; return allTags;
} }
List<String> _getAllSpaceModelName(List<SpaceTemplateModel> spaceModels) {
final List<String> names = [];
for (final spaceModel in spaceModels) {
names.add(spaceModel.modelName);
}
return names;
}
} }

View File

@ -6,8 +6,11 @@ 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_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart';
@ -17,15 +20,17 @@ class CreateSpaceModelDialog extends StatelessWidget {
final List<ProductModel>? products; final List<ProductModel>? products;
final List<String>? allTags; final List<String>? allTags;
final SpaceTemplateModel? spaceModel; final SpaceTemplateModel? spaceModel;
final void Function(SpaceTemplateModel newModel)? onLoad; final BuildContext? pageContext;
final List<String>? otherSpaceModels;
const CreateSpaceModelDialog({ const CreateSpaceModelDialog(
Key? key, {Key? key,
this.products, this.products,
this.allTags, this.allTags,
this.spaceModel, this.spaceModel,
this.onLoad, this.pageContext,
}) : super(key: key); this.otherSpaceModels})
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -54,7 +59,9 @@ class CreateSpaceModelDialog extends StatelessWidget {
} }
spaceNameController.addListener(() { spaceNameController.addListener(() {
bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text)); bloc.add(UpdateSpaceTemplateName(
name: spaceNameController.text,
allModels: otherSpaceModels ?? []));
}); });
return bloc; return bloc;
@ -86,9 +93,10 @@ class CreateSpaceModelDialog extends StatelessWidget {
child: TextField( child: TextField(
controller: spaceNameController, controller: spaceNameController,
onChanged: (value) { onChanged: (value) {
context context.read<CreateSpaceModelBloc>().add(
.read<CreateSpaceModelBloc>() UpdateSpaceTemplateName(
.add(UpdateSpaceTemplateName(name: value)); name: value,
allModels: otherSpaceModels ?? []));
}, },
style: const TextStyle(color: ColorsManager.blackColor), style: const TextStyle(color: ColorsManager.blackColor),
decoration: InputDecoration( decoration: InputDecoration(
@ -128,21 +136,8 @@ class CreateSpaceModelDialog extends StatelessWidget {
subspaces: subspaces, subspaces: subspaces,
allTags: allTags, allTags: allTags,
spaceNameController: spaceNameController, spaceNameController: spaceNameController,
onLoad: (tags, subspaces) { pageContext: pageContext,
if (context.read<CreateSpaceModelBloc>().state otherSpaceModels: otherSpaceModels,
is CreateSpaceModelLoaded) {
if (subspaces != null) {
context
.read<CreateSpaceModelBloc>()
.add(AddSubspacesToSpaceTemplate(subspaces));
}
if (tags != null) {
context
.read<CreateSpaceModelBloc>()
.add(AddTagsToSpaceTemplate(tags));
}
}
},
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
SizedBox( SizedBox(
@ -166,17 +161,73 @@ class CreateSpaceModelDialog extends StatelessWidget {
modelName: modelName:
spaceNameController.text.trim(), spaceNameController.text.trim(),
); );
context.read<CreateSpaceModelBloc>().add( if (updatedSpaceModel.uuid == null) {
context
.read<CreateSpaceModelBloc>()
.add(
CreateSpaceTemplate( CreateSpaceTemplate(
spaceTemplate: spaceTemplate:
updatedSpaceTemplate, updatedSpaceTemplate,
onCreate: (newModel) { onCreate: (newModel) {
onLoad!(newModel); if (pageContext != null) {
pageContext!
.read<SpaceModelBloc>()
.add(CreateSpaceModel(
newSpaceModel:
newModel));
}
Navigator.of(context) Navigator.of(context)
.pop(); // Close the dialog .pop(); // Close the dialog
}, },
), ),
); );
} 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,
backgroundColor: ColorsManager.secondaryColor, backgroundColor: ColorsManager.secondaryColor,

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),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: ColorsManager.transparentColor),
),
child: Text(
subspace.subspaceName, subspace.subspaceName,
style: const TextStyle( style: Theme.of(context)
color: ColorsManager.spaceColor), // Text color .textTheme
), .bodySmall
backgroundColor: ?.copyWith(color: ColorsManager.spaceColor),
ColorsManager.whiteColors, // Chip background color
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(16), // Rounded chip
side: const BorderSide(
color: ColorsManager.spaceColor), // Border color
),
),
), ),
)),
GestureDetector( GestureDetector(
onTap: () async { onTap: () async {
await _openDialog(context, 'Edit Sub-space'); await _openDialog(context, 'Edit Sub-space');

View File

@ -5,7 +5,6 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assi
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.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/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/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/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@ -17,8 +16,8 @@ class TagChipDisplay extends StatelessWidget {
final List<SubspaceTemplateModel>? subspaces; final List<SubspaceTemplateModel>? subspaces;
final List<String>? allTags; final List<String>? allTags;
final TextEditingController spaceNameController; final TextEditingController spaceNameController;
final void Function( final BuildContext? pageContext;
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces)? onLoad; final List<String>? otherSpaceModels;
const TagChipDisplay(BuildContext context, const TagChipDisplay(BuildContext context,
{Key? key, {Key? key,
@ -28,7 +27,8 @@ class TagChipDisplay extends StatelessWidget {
required this.subspaces, required this.subspaces,
required this.allTags, required this.allTags,
required this.spaceNameController, required this.spaceNameController,
this.onLoad}) this.pageContext,
this.otherSpaceModels})
: super(key: key); : super(key: key);
@override @override
@ -96,7 +96,9 @@ class TagChipDisplay extends StatelessWidget {
builder: (context) => AssignTagModelsDialog( builder: (context) => AssignTagModelsDialog(
products: products, products: products,
subspaces: subspaces, subspaces: subspaces,
pageContext: pageContext,
allTags: allTags, allTags: allTags,
spaceModel: spaceModel,
initialTags: TagHelper.generateInitialTags( initialTags: TagHelper.generateInitialTags(
subspaces: subspaces, subspaces: subspaces,
spaceTagModels: spaceModel?.tags ?? []), spaceTagModels: spaceModel?.tags ?? []),
@ -105,10 +107,7 @@ class TagChipDisplay extends StatelessWidget {
TagHelper.createInitialSelectedProducts( TagHelper.createInitialSelectedProducts(
spaceModel?.tags ?? [], subspaces), spaceModel?.tags ?? [], subspaces),
spaceName: spaceModel?.modelName ?? '', spaceName: spaceModel?.modelName ?? '',
onUpdate: (tags, subspaces) { ));
onLoad?.call(tags, subspaces);
}),
);
} }
}, },
child: Chip( child: Chip(
@ -129,6 +128,8 @@ class TagChipDisplay extends StatelessWidget {
) )
: TextButton( : TextButton(
onPressed: () async { onPressed: () async {
Navigator.of(context).pop();
await showDialog<bool>( await showDialog<bool>(
barrierDismissible: false, barrierDismissible: false,
context: context, context: context,
@ -137,13 +138,9 @@ class TagChipDisplay extends StatelessWidget {
subspaces: subspaces, subspaces: subspaces,
allTags: allTags, allTags: allTags,
spaceName: spaceNameController.text, spaceName: spaceNameController.text,
pageContext: pageContext,
isCreate: true, isCreate: true,
onUpdate: (tags, subspaces) { spaceModel: spaceModel,
onLoad?.call(tags, subspaces);
},
onLoad: (tags, subspaces) {
onLoad?.call(tags, subspaces);
},
), ),
); );
}, },

View File

@ -5,8 +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/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';
@ -21,13 +23,12 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
final List<String>? allTags; final List<String>? allTags;
final String spaceName; final String spaceName;
final bool isCreate; final bool isCreate;
final void Function( final List<String>? otherSpaceModels;
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces)? onLoad; final BuildContext? pageContext;
final void Function( final SpaceTemplateModel? spaceModel;
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces)? onUpdate;
const AddDeviceTypeModelWidget({ const AddDeviceTypeModelWidget(
super.key, {super.key,
this.products, this.products,
this.initialSelectedProducts, this.initialSelectedProducts,
this.subspaces, this.subspaces,
@ -35,14 +36,12 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
this.spaceTagModels, this.spaceTagModels,
required this.spaceName, required this.spaceName,
required this.isCreate, required this.isCreate,
this.onLoad, this.pageContext,
this.onUpdate, this.otherSpaceModels,
}); this.spaceModel});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
final crossAxisCount = size.width > 1200 final crossAxisCount = size.width > 1200
? 8 ? 8
@ -79,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,
@ -102,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,
));
} }
}, },
), ),
@ -142,10 +180,10 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
allTags: allTags, allTags: allTags,
spaceName: spaceName, spaceName: spaceName,
initialTags: state.initialTag, initialTags: state.initialTag,
otherSpaceModels: otherSpaceModels,
title: dialogTitle, title: dialogTitle,
onUpdate: (tags, subspaces) { spaceModel: spaceModel,
onUpdate?.call(tags, subspaces); 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
@ -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,7 +101,10 @@ 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}';