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),
const SizedBox(height: 4),
CounterWidget(
isCreate: false,
initialCount: selectedProduct.count,
onCountChanged: (newCount) {
context.read<AddDeviceTypeBloc>().add(

View File

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

View File

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

View File

@ -66,4 +66,25 @@ class ProductModel {
String toString() {
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),
const SizedBox(height: 4),
CounterWidget(
isCreate: false,
initialCount: selectedProduct.count,
onCountChanged: (newCount) {
setState(() {

View File

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

View File

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

View File

@ -47,14 +47,13 @@ class AssignTagModelBloc
}
emit(AssignTagModelLoaded(
tags: allTags,
isSaveEnabled: _validateTags(allTags),
));
tags: allTags,
isSaveEnabled: _validateTags(allTags),
errorMessage: ''));
});
on<UpdateTag>((event, emit) {
final currentState = state;
if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) {
final tags = List<TagModel>.from(currentState.tags);
@ -122,9 +121,7 @@ class AssignTagModelBloc
}
bool _validateTags(List<TagModel> tags) {
if (tags.isEmpty) {
return false;
}
final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet();
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
final isValid = uniqueTags.length == tags.length && !hasEmptyTag;
@ -133,7 +130,11 @@ class AssignTagModelBloc
String? _getValidationError(List<TagModel> tags) {
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
if (hasEmptyTag) return 'Tags cannot be empty.';
if (hasEmptyTag) {
return 'Tags cannot be empty.';
}
// Check for duplicate tags
final duplicateTags = tags
.map((tag) => tag.tag?.trim() ?? '')
.fold<Map<String, int>>({}, (map, tag) {

View File

@ -5,7 +5,7 @@ abstract class AssignTagModelState extends Equatable {
const AssignTagModelState();
@override
List<Object> get props => [];
List<Object?> get props => [];
}
class AssignTagModelInitial extends AssignTagModelState {}
@ -15,7 +15,7 @@ class AssignTagModelLoading extends AssignTagModelState {}
class AssignTagModelLoaded extends AssignTagModelState {
final List<TagModel> tags;
final bool isSaveEnabled;
final String? errorMessage;
final String? errorMessage;
const AssignTagModelLoaded({
required this.tags,
@ -24,7 +24,7 @@ class AssignTagModelLoaded extends AssignTagModelState {
});
@override
List<Object> get props => [tags, isSaveEnabled];
List<Object?> get props => [tags, isSaveEnabled, errorMessage];
}
class AssignTagModelError extends AssignTagModelState {
@ -33,5 +33,5 @@ class AssignTagModelError extends AssignTagModelState {
const AssignTagModelError(this.errorMessage);
@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_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/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/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/utils/color_manager.dart';
class AssignTagModelsDialog extends StatelessWidget {
final List<ProductModel>? products;
final List<SubspaceTemplateModel>? subspaces;
final SpaceTemplateModel? spaceModel;
final List<TagModel> initialTags;
final ValueChanged<List<TagModel>>? onTagsAssigned;
final List<SelectedProduct> addedProducts;
final List<String>? allTags;
final String spaceName;
final String title;
final void Function(
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces)? onUpdate;
final BuildContext? pageContext;
final List<String>? otherSpaceModels;
const AssignTagModelsDialog(
{Key? key,
@ -36,7 +40,9 @@ class AssignTagModelsDialog extends StatelessWidget {
this.allTags,
required this.spaceName,
required this.title,
this.onUpdate})
this.pageContext,
this.otherSpaceModels,
this.spaceModel})
: super(key: key);
@override
@ -47,249 +53,202 @@ class AssignTagModelsDialog extends StatelessWidget {
..add('Main Space');
return BlocProvider(
create: (_) => AssignTagModelBloc()
..add(InitializeTagModels(
initialTags: initialTags,
addedProducts: addedProducts,
)),
child: BlocBuilder<AssignTagModelBloc, AssignTagModelState>(
builder: (context, state) {
if (state is AssignTagModelLoaded) {
final controllers = List.generate(
state.tags.length,
(index) => TextEditingController(text: state.tags[index].tag),
);
create: (_) => AssignTagModelBloc()
..add(InitializeTagModels(
initialTags: initialTags,
addedProducts: addedProducts,
)),
child: BlocListener<AssignTagModelBloc, AssignTagModelState>(
listener: (context, state) {},
child: BlocBuilder<AssignTagModelBloc, AssignTagModelState>(
builder: (context, state) {
if (state is AssignTagModelLoaded) {
final controllers = List.generate(
state.tags.length,
(index) => TextEditingController(text: state.tags[index].tag),
);
return AlertDialog(
title: Text(title),
backgroundColor: ColorsManager.whiteColors,
content: SingleChildScrollView(
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: DataTable(
headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey),
border: TableBorder.all(
color: ColorsManager.dataHeaderGrey,
width: 1,
return AlertDialog(
title: Text(title),
backgroundColor: ColorsManager.whiteColors,
content: SingleChildScrollView(
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(20),
),
columns: [
DataColumn(
label: Text('#',
style:
Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Device',
style:
Theme.of(context).textTheme.bodyMedium)),
DataColumn(
numeric: false,
headingRowAlignment: MainAxisAlignment.start,
label: Text('Tag',
style:
Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Location',
style:
Theme.of(context).textTheme.bodyMedium)),
],
rows: state.tags.isEmpty
? [
const DataRow(cells: [
DataCell(
Center(
child: Text(
'No Data Available',
style: TextStyle(
fontSize: 14,
color: ColorsManager.lightGrayColor,
child: DataTable(
headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey),
border: TableBorder.all(
color: ColorsManager.dataHeaderGrey,
width: 1,
borderRadius: BorderRadius.circular(20),
),
columns: [
DataColumn(
label: Text('#',
style: Theme.of(context)
.textTheme
.bodyMedium)),
DataColumn(
label: Text('Device',
style: Theme.of(context)
.textTheme
.bodyMedium)),
DataColumn(
numeric: false,
headingRowAlignment: MainAxisAlignment.start,
label: Text('Tag',
style: Theme.of(context)
.textTheme
.bodyMedium)),
DataColumn(
label: Text('Location',
style: Theme.of(context)
.textTheme
.bodyMedium)),
],
rows: state.tags.isEmpty
? [
const DataRow(cells: [
DataCell(
Center(
child: Text(
'No Data Available',
style: TextStyle(
fontSize: 14,
color:
ColorsManager.lightGrayColor,
),
),
),
),
),
),
DataCell(SizedBox()),
DataCell(SizedBox()),
DataCell(SizedBox()),
])
]
: List.generate(state.tags.length, (index) {
final tag = state.tags[index];
final controller = controllers[index];
final availableTags = getAvailableTags(
allTags ?? [], state.tags, tag);
DataCell(SizedBox()),
DataCell(SizedBox()),
DataCell(SizedBox()),
])
]
: List.generate(state.tags.length, (index) {
final tag = state.tags[index];
final controller = controllers[index];
final availableTags = getAvailableTags(
allTags ?? [], state.tags, tag);
return DataRow(
cells: [
DataCell(Text(index.toString())),
DataCell(
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
tag.product?.name ?? 'Unknown',
overflow: TextOverflow.ellipsis,
)),
const SizedBox(width: 10),
return DataRow(
cells: [
DataCell(Text((index + 1).toString())),
DataCell(
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
tag.product?.name ?? 'Unknown',
overflow: TextOverflow.ellipsis,
)),
const SizedBox(width: 10),
Container(
width: 20.0,
height: 20.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager
.lightGrayColor,
width: 1.0,
),
),
child: IconButton(
icon: const Icon(
Icons.close,
color: ColorsManager
.lightGreyColor,
size: 16,
),
onPressed: () {
context
.read<
AssignTagModelBloc>()
.add(DeleteTagModel(
tagToDelete: tag,
tags: state.tags));
},
tooltip: 'Delete Tag',
padding: EdgeInsets.zero,
constraints:
const BoxConstraints(),
),
),
],
),
),
DataCell(
Container(
width: 20.0,
height: 20.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager
.lightGrayColor,
width: 1.0,
alignment: Alignment
.centerLeft, // Align cell content to the left
child: SizedBox(
width: double
.infinity, // Ensure full width for dropdown
child: DialogTextfieldDropdown(
items: availableTags,
initialValue: tag.tag,
onSelected: (value) {
controller.text = value;
context
.read<
AssignTagModelBloc>()
.add(UpdateTag(
index: index,
tag: value,
));
},
),
),
child: IconButton(
icon: const Icon(
Icons.close,
color: ColorsManager
.lightGreyColor,
size: 16,
),
onPressed: () {
context
.read<AssignTagModelBloc>()
.add(DeleteTagModel(
tagToDelete: tag,
tags: state.tags));
},
tooltip: 'Delete Tag',
padding: EdgeInsets.zero,
constraints:
const BoxConstraints(),
),
),
],
),
),
DataCell(
Container(
alignment: Alignment
.centerLeft, // Align cell content to the left
child: SizedBox(
width: double
.infinity, // Ensure full width for dropdown
child: DialogTextfieldDropdown(
items: availableTags,
initialValue: tag.tag,
onSelected: (value) {
controller.text = value;
context
.read<AssignTagModelBloc>()
.add(UpdateTag(
index: index,
tag: value,
));
},
),
),
),
),
DataCell(
SizedBox(
width: double.infinity,
child: DialogDropdown(
items: locations,
selectedValue:
tag.location ?? 'None',
onSelected: (value) {
context
.read<AssignTagModelBloc>()
.add(UpdateLocation(
index: index,
location: value,
));
},
)),
),
],
);
}),
),
),
if (state.errorMessage != null)
Text(
state.errorMessage!,
style: const TextStyle(color: ColorsManager.warningRed),
),
],
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const SizedBox(width: 10),
Expanded(
child: Builder(
builder: (buttonContext) => CancelButton(
label: 'Add New Device',
onPressed: () async {
for (var tag in state.tags) {
if (tag.location == null || subspaces == null) {
continue;
}
final previousTagSubspace =
checkTagExistInSubspace(tag, subspaces ?? []);
if (tag.location == 'Main Space') {
removeTagFromSubspace(tag, previousTagSubspace);
} else if (tag.location !=
previousTagSubspace?.subspaceName) {
removeTagFromSubspace(tag, previousTagSubspace);
moveToNewSubspace(tag, subspaces ?? []);
state.tags.removeWhere(
(t) => t.internalId == tag.internalId);
} else {
updateTagInSubspace(tag, previousTagSubspace);
state.tags.removeWhere(
(t) => t.internalId == tag.internalId);
}
}
if (context.mounted) {
await showDialog<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();
},
),
);
}
},
DataCell(
SizedBox(
width: double.infinity,
child: DialogDropdown(
items: locations,
selectedValue:
tag.location ?? 'Main Space',
onSelected: (value) {
context
.read<
AssignTagModelBloc>()
.add(UpdateLocation(
index: index,
location: value,
));
},
)),
),
],
);
}),
),
),
),
if (state.errorMessage != null)
Text(
state.errorMessage!,
style: const TextStyle(
color: ColorsManager.warningRed),
),
],
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
borderRadius: 10,
backgroundColor: state.isSaveEnabled
? ColorsManager.secondaryColor
: ColorsManager.grayColor,
foregroundColor: ColorsManager.whiteColors,
onPressed: state.isSaveEnabled
? () async {
Navigator.of(context).pop();
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const SizedBox(width: 10),
Expanded(
child: Builder(
builder: (buttonContext) => CancelButton(
label: 'Add New Device',
onPressed: () async {
for (var tag in state.tags) {
if (tag.location == null ||
subspaces == null) {
@ -317,26 +276,113 @@ class AssignTagModelsDialog extends StatelessWidget {
(t) => t.internalId == tag.internalId);
}
}
if (context.mounted) {
Navigator.of(context).pop();
onUpdate?.call(state.tags, subspaces);
}
: null,
child: const Text('Save'),
),
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,
child: const Text('Save'),
),
),
const SizedBox(width: 10),
],
),
const SizedBox(width: 10),
],
),
],
);
} else if (state is AssignTagModelLoading) {
return const Center(child: CircularProgressIndicator());
} else {
return const Center(child: Text('Something went wrong.'));
}
},
),
);
);
} else if (state is AssignTagModelLoading) {
return const Center(child: CircularProgressIndicator());
} else {
return const Center(child: Text('Something went wrong.'));
}
},
),
));
}
List<String> getAvailableTags(

View File

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

View File

@ -37,7 +37,8 @@ class TagHelper {
final Map<ProductModel, int> groupedTags = {};
for (var tag in tags) {
if (tag.product != null) {
groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1;
final product = tag.product!;
groupedTags[product] = (groupedTags[product] ?? 0) + 1;
}
}
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/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_update_model.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart';
class CreateSpaceModelBloc
extends Bloc<CreateSpaceModelEvent, CreateSpaceModelState> {
@ -172,7 +174,12 @@ class CreateSpaceModelBloc
on<UpdateSpaceTemplateName>((event, emit) {
final currentState = state;
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(
currentState.space,
errorMessage: "Model name cannot be empty",
@ -187,5 +194,160 @@ class CreateSpaceModelBloc
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 Function(SpaceTemplateModel)? onCreate;
const CreateSpaceTemplate({
required this.spaceTemplate,
this.onCreate,
@ -34,11 +33,12 @@ class CreateSpaceTemplate extends CreateSpaceModelEvent {
class UpdateSpaceTemplateName extends CreateSpaceModelEvent {
final String name;
final List<String> allModels;
UpdateSpaceTemplateName({required this.name});
UpdateSpaceTemplateName({required this.name, required this.allModels});
@override
List<Object> get props => [name];
List<Object> get props => [name, allModels];
}
class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent {
@ -53,9 +53,19 @@ class AddTagsToSpaceTemplate extends CreateSpaceModelEvent {
AddTagsToSpaceTemplate(this.tags);
}
class ValidateSpaceTemplateName extends CreateSpaceModelEvent {
final String 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,
}) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) {
on<CreateSpaceModel>(_onCreateSpaceModel);
on<UpdateSpaceModel>(_onUpdateSpaceModel);
}
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
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 {
final String modelName;
final String? modelName;
final List<dynamic>? tags;
final List<dynamic>? subspaceModels;
CreateSpaceTemplateBodyModel({
required this.modelName,
this.modelName,
this.tags,
this.subspaceModels,
});
@ -47,4 +47,9 @@ class CreateSpaceTemplateBodyModel {
'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/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_update_model.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart';
import 'package:uuid/uuid.dart';
@ -70,14 +71,14 @@ class SpaceTemplateModel extends Equatable {
}
class UpdateSubspaceTemplateModel {
final String uuid;
final String? uuid;
final Action action;
final String? subspaceName;
final List<UpdateTagModel>? tags;
final List<TagModelUpdate>? tags;
UpdateSubspaceTemplateModel({
required this.action,
required this.uuid,
this.uuid,
this.subspaceName,
this.tags,
});
@ -88,7 +89,7 @@ class UpdateSubspaceTemplateModel {
uuid: json['uuid'] ?? '',
subspaceName: json['subspaceName'] ?? '',
tags: (json['tags'] as List)
.map((item) => UpdateTagModel.fromJson(item))
.map((item) => TagModelUpdate.fromJson(item))
.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 {
List<String> listAllTagValues() {
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: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_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/models/space_template_model.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) {
final spaceModels = state.spaceModels;
final allTagValues = _getAllTagValues(spaceModels);
final allSpaceModelNames = _getAllSpaceModelName(spaceModels);
return Scaffold(
backgroundColor: ColorsManager.whiteColors,
@ -52,12 +52,8 @@ class SpaceModelPage extends StatelessWidget {
return CreateSpaceModelDialog(
products: products,
allTags: allTagValues,
onLoad: (newModel) {
context.read<SpaceModelBloc>().add(
CreateSpaceModel(
newSpaceModel: newModel),
);
},
pageContext: context,
otherSpaceModels: allSpaceModelNames,
);
},
);
@ -67,6 +63,8 @@ class SpaceModelPage extends StatelessWidget {
}
// Render existing space model
final model = spaceModels[index];
final otherModel = List<String>.from(allSpaceModelNames);
otherModel.remove(model.modelName);
return GestureDetector(
onTap: () {
showDialog(
@ -76,7 +74,8 @@ class SpaceModelPage extends StatelessWidget {
products: products,
allTags: allTagValues,
spaceModel: model,
onLoad: (newModel) {},
otherSpaceModels: otherModel,
pageContext: context,
);
},
);
@ -128,4 +127,12 @@ class SpaceModelPage extends StatelessWidget {
}
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_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/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/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/services/space_model_mang_api.dart';
@ -17,15 +20,17 @@ class CreateSpaceModelDialog extends StatelessWidget {
final List<ProductModel>? products;
final List<String>? allTags;
final SpaceTemplateModel? spaceModel;
final void Function(SpaceTemplateModel newModel)? onLoad;
final BuildContext? pageContext;
final List<String>? otherSpaceModels;
const CreateSpaceModelDialog({
Key? key,
this.products,
this.allTags,
this.spaceModel,
this.onLoad,
}) : super(key: key);
const CreateSpaceModelDialog(
{Key? key,
this.products,
this.allTags,
this.spaceModel,
this.pageContext,
this.otherSpaceModels})
: super(key: key);
@override
Widget build(BuildContext context) {
@ -44,17 +49,19 @@ class CreateSpaceModelDialog extends StatelessWidget {
child: BlocProvider(
create: (_) {
final bloc = CreateSpaceModelBloc(_spaceModelApi);
if (spaceModel != null) {
bloc.add(UpdateSpaceTemplate(spaceModel!));
} else {
bloc.add(UpdateSpaceTemplate(SpaceTemplateModel(
modelName: '',
subspaceModels: const [],
)));
}
if (spaceModel != null) {
bloc.add(UpdateSpaceTemplate(spaceModel!));
} else {
bloc.add(UpdateSpaceTemplate(SpaceTemplateModel(
modelName: '',
subspaceModels: const [],
)));
}
spaceNameController.addListener(() {
bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text));
bloc.add(UpdateSpaceTemplateName(
name: spaceNameController.text,
allModels: otherSpaceModels ?? []));
});
return bloc;
@ -86,9 +93,10 @@ class CreateSpaceModelDialog extends StatelessWidget {
child: TextField(
controller: spaceNameController,
onChanged: (value) {
context
.read<CreateSpaceModelBloc>()
.add(UpdateSpaceTemplateName(name: value));
context.read<CreateSpaceModelBloc>().add(
UpdateSpaceTemplateName(
name: value,
allModels: otherSpaceModels ?? []));
},
style: const TextStyle(color: ColorsManager.blackColor),
decoration: InputDecoration(
@ -128,21 +136,8 @@ class CreateSpaceModelDialog extends StatelessWidget {
subspaces: subspaces,
allTags: allTags,
spaceNameController: spaceNameController,
onLoad: (tags, subspaces) {
if (context.read<CreateSpaceModelBloc>().state
is CreateSpaceModelLoaded) {
if (subspaces != null) {
context
.read<CreateSpaceModelBloc>()
.add(AddSubspacesToSpaceTemplate(subspaces));
}
if (tags != null) {
context
.read<CreateSpaceModelBloc>()
.add(AddTagsToSpaceTemplate(tags));
}
}
},
pageContext: pageContext,
otherSpaceModels: otherSpaceModels,
),
const SizedBox(height: 20),
SizedBox(
@ -166,17 +161,73 @@ class CreateSpaceModelDialog extends StatelessWidget {
modelName:
spaceNameController.text.trim(),
);
context.read<CreateSpaceModelBloc>().add(
CreateSpaceTemplate(
spaceTemplate:
updatedSpaceTemplate,
onCreate: (newModel) {
onLoad!(newModel);
Navigator.of(context)
.pop(); // Close the dialog
},
),
);
if (updatedSpaceModel.uuid == null) {
context
.read<CreateSpaceModelBloc>()
.add(
CreateSpaceTemplate(
spaceTemplate:
updatedSpaceTemplate,
onCreate: (newModel) {
if (pageContext != null) {
pageContext!
.read<SpaceModelBloc>()
.add(CreateSpaceModel(
newSpaceModel:
newModel));
}
Navigator.of(context)
.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,
backgroundColor: ColorsManager.secondaryColor,

View File

@ -1,9 +1,6 @@
import 'package:flutter/material.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 {
final String subspace;

View File

@ -46,23 +46,23 @@ class SubspaceModelCreate extends StatelessWidget {
spacing: 8.0,
runSpacing: 8.0,
children: [
...subspaces.map(
(subspace) => Chip(
label: Text(
subspace.subspaceName,
style: const TextStyle(
color: ColorsManager.spaceColor), // Text color
),
backgroundColor:
ColorsManager.whiteColors, // Chip background color
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(16), // Rounded chip
side: const BorderSide(
color: ColorsManager.spaceColor), // Border color
),
),
),
...subspaces.map((subspace) => Container(
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 4.0),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: ColorsManager.transparentColor),
),
child: Text(
subspace.subspaceName,
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.spaceColor),
),
)),
GestureDetector(
onTap: () async {
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/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/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/utils/color_manager.dart';
@ -17,8 +16,8 @@ class TagChipDisplay extends StatelessWidget {
final List<SubspaceTemplateModel>? subspaces;
final List<String>? allTags;
final TextEditingController spaceNameController;
final void Function(
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces)? onLoad;
final BuildContext? pageContext;
final List<String>? otherSpaceModels;
const TagChipDisplay(BuildContext context,
{Key? key,
@ -28,7 +27,8 @@ class TagChipDisplay extends StatelessWidget {
required this.subspaces,
required this.allTags,
required this.spaceNameController,
this.onLoad})
this.pageContext,
this.otherSpaceModels})
: super(key: key);
@override
@ -91,24 +91,23 @@ class TagChipDisplay extends StatelessWidget {
if (navigatorContext != null) {
await showDialog<bool>(
barrierDismissible: false,
context: navigatorContext,
builder: (context) => AssignTagModelsDialog(
products: products,
subspaces: subspaces,
allTags: allTags,
initialTags: TagHelper.generateInitialTags(
barrierDismissible: false,
context: navigatorContext,
builder: (context) => AssignTagModelsDialog(
products: products,
subspaces: subspaces,
spaceTagModels: spaceModel?.tags ?? []),
title: 'Edit Device',
addedProducts:
TagHelper.createInitialSelectedProducts(
spaceModel?.tags ?? [], subspaces),
spaceName: spaceModel?.modelName ?? '',
onUpdate: (tags, subspaces) {
onLoad?.call(tags, subspaces);
}),
);
pageContext: pageContext,
allTags: allTags,
spaceModel: spaceModel,
initialTags: TagHelper.generateInitialTags(
subspaces: subspaces,
spaceTagModels: spaceModel?.tags ?? []),
title: 'Edit Device',
addedProducts:
TagHelper.createInitialSelectedProducts(
spaceModel?.tags ?? [], subspaces),
spaceName: spaceModel?.modelName ?? '',
));
}
},
child: Chip(
@ -129,6 +128,8 @@ class TagChipDisplay extends StatelessWidget {
)
: TextButton(
onPressed: () async {
Navigator.of(context).pop();
await showDialog<bool>(
barrierDismissible: false,
context: context,
@ -137,13 +138,9 @@ class TagChipDisplay extends StatelessWidget {
subspaces: subspaces,
allTags: allTags,
spaceName: spaceNameController.text,
pageContext: pageContext,
isCreate: true,
onUpdate: (tags, subspaces) {
onLoad?.call(tags, subspaces);
},
onLoad: (tags, subspaces) {
onLoad?.call(tags, subspaces);
},
spaceModel: spaceModel,
),
);
},

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/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/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/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_state.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart';
@ -21,28 +23,25 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
final List<String>? allTags;
final String spaceName;
final bool isCreate;
final void Function(
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces)? onLoad;
final void Function(
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces)? onUpdate;
final List<String>? otherSpaceModels;
final BuildContext? pageContext;
final SpaceTemplateModel? spaceModel;
const AddDeviceTypeModelWidget({
super.key,
this.products,
this.initialSelectedProducts,
this.subspaces,
this.allTags,
this.spaceTagModels,
required this.spaceName,
required this.isCreate,
this.onLoad,
this.onUpdate,
});
const AddDeviceTypeModelWidget(
{super.key,
this.products,
this.initialSelectedProducts,
this.subspaces,
this.allTags,
this.spaceTagModels,
required this.spaceName,
required this.isCreate,
this.pageContext,
this.otherSpaceModels,
this.spaceModel});
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final crossAxisCount = size.width > 1200
? 8
@ -79,6 +78,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
padding:
const EdgeInsets.symmetric(horizontal: 20.0),
child: ScrollableGridViewWidget(
isCreate: isCreate,
products: products,
crossAxisCount: crossAxisCount,
initialProductCounts: state.selectedProducts,
@ -102,6 +102,44 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
onPressed: () async {
if (isCreate) {
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,
spaceName: spaceName,
initialTags: state.initialTag,
otherSpaceModels: otherSpaceModels,
title: dialogTitle,
onUpdate: (tags, subspaces) {
onUpdate?.call(tags, subspaces);
},
spaceModel: spaceModel,
pageContext: pageContext,
),
);
}

View File

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

View File

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

View File

@ -34,6 +34,20 @@ class SpaceModelManagementApi {
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 {
final response = await HTTPService().get(
path: ApiEndpoints.getSpaceModel

View File

@ -101,8 +101,11 @@ abstract class ApiEndpoints {
//space model
static const String listSpaceModels = '/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 permission = '/permission/{roleUuid}';
static const String inviteUser = '/invite-user';