edit flow

This commit is contained in:
hannathkadher
2025-01-20 21:47:22 +04:00
parent 2f6bd31aa2
commit eb53671e3a
11 changed files with 315 additions and 172 deletions

View File

@ -3,8 +3,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
class AssignTagBloc
extends Bloc<AssignTagEvent, AssignTagState> {
class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
AssignTagBloc() : super(AssignTagInitial()) {
on<InitializeTags>((event, emit) {
final initialTags = event.initialTags ?? [];
@ -40,7 +39,7 @@ class AssignTagBloc
(index) => Tag(
tag: '',
product: selectedProduct.product,
location: 'None',
location: 'Main Space',
),
));
}
@ -55,8 +54,7 @@ class AssignTagBloc
on<UpdateTagEvent>((event, emit) {
final currentState = state;
if (currentState is AssignTagLoaded &&
currentState.tags.isNotEmpty) {
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags);
tags[event.index].tag = event.tag;
emit(AssignTagLoaded(
@ -70,8 +68,7 @@ class AssignTagBloc
on<UpdateLocation>((event, emit) {
final currentState = state;
if (currentState is AssignTagLoaded &&
currentState.tags.isNotEmpty) {
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags);
// Use copyWith for immutability
@ -88,8 +85,7 @@ class AssignTagBloc
on<ValidateTags>((event, emit) {
final currentState = state;
if (currentState is AssignTagLoaded &&
currentState.tags.isNotEmpty) {
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags);
emit(AssignTagLoaded(
@ -103,8 +99,7 @@ class AssignTagBloc
on<DeleteTag>((event, emit) {
final currentState = state;
if (currentState is AssignTagLoaded &&
currentState.tags.isNotEmpty) {
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final updatedTags = List<Tag>.from(currentState.tags)
..remove(event.tagToDelete);

View File

@ -224,7 +224,7 @@ class AssignTagDialog extends StatelessWidget {
DataCell(
DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: tag.location ?? 'None',
value: tag.location ?? 'Main',
dropdownColor: ColorsManager
.whiteColors, // Dropdown background
style: const TextStyle(
@ -232,9 +232,9 @@ class AssignTagDialog extends StatelessWidget {
.black), // Style for selected text
items: [
const DropdownMenuItem<String>(
value: 'None',
value: 'Main Space',
child: Text(
'None',
'Main Space',
style: TextStyle(
color: ColorsManager
.textPrimaryColor),

View File

@ -40,7 +40,7 @@ class AssignTagModelBloc
(index) => TagModel(
tag: '',
product: selectedProduct.product,
location: 'None',
location: 'Main Space',
),
));
}
@ -123,6 +123,7 @@ class AssignTagModelBloc
bool _validateTags(List<TagModel> tags) {
if (tags.isEmpty) {
print("tags empty");
return false;
}
final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet();

View File

@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assig
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/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.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 {
@ -40,8 +41,11 @@ class AssignTagModelsDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
final List<String> locations =
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList();
final List<String> locations = (subspaces ?? [])
.map((subspace) => subspace.subspaceName)
.toList()
..add('Main Space');
return BlocProvider(
create: (_) => AssignTagModelBloc()
..add(InitializeTagModels(
@ -173,7 +177,8 @@ class AssignTagModelsDialog extends StatelessWidget {
width: double
.infinity, // Ensure full width for dropdown
child: DialogTextfieldDropdown(
items: availableTags ?? [],
items: availableTags,
initialValue: tag.tag,
onSelected: (value) {
controller.text = value;
context
@ -223,27 +228,56 @@ class AssignTagModelsDialog extends StatelessWidget {
children: [
const SizedBox(width: 10),
Expanded(
child: CancelButton(
child: Builder(
builder: (buttonContext) => CancelButton(
label: 'Add New Device',
onPressed: () async {
Navigator.of(context).pop();
final assignedTags = <TagModel>{};
for (var tag in state.tags) {
if (tag.location == null || subspaces == null) {
continue;
}
for (var subspace in subspaces!) {
if (tag.location == subspace.subspaceName) {
subspace.tags ??= [];
subspace.tags!.add(tag);
assignedTags.add(tag);
break;
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);
}
}
}
state.tags.removeWhere(assignedTags.contains);
await showDialog<bool>(
barrierDismissible: false,
context:
Navigator.of(context, rootNavigator: true)
.context,
builder: (context) => AddDeviceTypeModelWidget(
products: products,
subspaces: subspaces,
isCreate: false,
initialSelectedProducts: addedProducts,
allTags: allTags,
spaceName: spaceName,
spaceTagModels: state.tags,
onUpdate: (tags, subspaces) {
onUpdate?.call(state.tags, subspaces);
},
),
);
}
},
),
),
),
const SizedBox(width: 10),
Expanded(
@ -256,23 +290,38 @@ class AssignTagModelsDialog extends StatelessWidget {
onPressed: state.isSaveEnabled
? () async {
Navigator.of(context).pop();
final assignedTags = <TagModel>{};
for (var tag in state.tags) {
if (tag.location == null ||
subspaces == null) {
continue;
}
for (var subspace in subspaces!) {
if (tag.location == subspace.subspaceName) {
subspace.tags ??= [];
subspace.tags!.add(tag);
assignedTags.add(tag);
break;
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);
}
}
}
state.tags.removeWhere(assignedTags.contains);
onUpdate!(state.tags,subspaces);
print("tryinh yo save");
onUpdate?.call(state.tags, subspaces);
}
: null,
child: const Text('Save'),
@ -302,4 +351,40 @@ class AssignTagModelsDialog extends StatelessWidget {
.contains(tagValue))
.toList();
}
void removeTagFromSubspace(TagModel tag, SubspaceTemplateModel? subspace) {
subspace?.tags?.removeWhere((t) => t.internalId == tag.internalId);
}
SubspaceTemplateModel? checkTagExistInSubspace(
TagModel tag, List<SubspaceTemplateModel>? subspaces) {
if (subspaces == null) return null;
for (var subspace in subspaces) {
if (subspace.tags == null) return null;
for (var t in subspace.tags!) {
if (tag.internalId == t.internalId) return subspace;
}
}
return null;
}
void moveToNewSubspace(TagModel tag, List<SubspaceTemplateModel> subspaces) {
final targetSubspace = subspaces
.firstWhere((subspace) => subspace.subspaceName == tag.location);
targetSubspace.tags ??= [];
if (targetSubspace.tags?.any((t) => t.internalId == tag.internalId) !=
true) {
targetSubspace.tags?.add(tag);
}
}
void updateTagInSubspace(TagModel tag, SubspaceTemplateModel? subspace) {
final currentTag = subspace?.tags?.firstWhere(
(t) => t.internalId == tag.internalId,
);
if (currentTag != null) {
currentTag.tag = tag.tag;
}
}
}

View File

@ -17,15 +17,19 @@ class TagHelper {
if (subspaces != null) {
for (var subspace in subspaces) {
if (subspace.tags != null) {
for (var existingTag in subspace.tags!) {
initialTags.addAll(
subspace.tags!.map(
(tag) => tag.copyWith(location: subspace.subspaceName),
(tag) => tag.copyWith(
location: subspace.subspaceName,
internalId: existingTag.internalId,
tag: existingTag.tag),
),
);
}
}
}
}
return initialTags;
}

View File

@ -3,6 +3,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
@ -67,36 +68,102 @@ class CreateSpaceModelBloc
_space = event.spaceTemplate;
emit(CreateSpaceModelLoaded(_space!));
});
on<AddSubspacesToSpaceTemplate>((event, emit) {
final currentState = state;
if (currentState is CreateSpaceModelLoaded) {
final updatedSpace = currentState.space.copyWith(
subspaceModels: [
...(_space!.subspaceModels ?? []),
...event.subspaces,
],
final eventSubspaceIds =
event.subspaces.map((e) => e.internalId).toSet();
// Update or retain subspaces
final updatedSubspaces = currentState.space.subspaceModels
?.where((subspace) =>
eventSubspaceIds.contains(subspace.internalId))
.map((subspace) {
final matchingEventSubspace = event.subspaces.firstWhere(
(e) => e.internalId == subspace.internalId,
orElse: () => subspace,
);
// Update the subspace's tags
final eventTagIds = matchingEventSubspace.tags
?.map((e) => e.internalId)
.toSet() ??
{};
final updatedTags = [
...?subspace.tags?.map<TagModel>((tag) {
final matchingTag =
matchingEventSubspace.tags?.firstWhere(
(e) => e.internalId == tag.internalId,
orElse: () => tag,
);
final isUpdated = matchingTag != tag;
return isUpdated
? tag.copyWith(tag: matchingTag?.tag)
: tag;
}) ??
<TagModel>[],
...?matchingEventSubspace.tags?.where(
(e) =>
subspace.tags
?.every((t) => t.internalId != e.internalId) ??
true,
) ??
<TagModel>[],
];
return subspace.copyWith(
subspaceName: matchingEventSubspace.subspaceName,
tags: updatedTags,
);
}).toList() ??
[];
// Add new subspaces
event.subspaces
.where((e) =>
updatedSubspaces.every((s) => s.internalId != e.internalId))
.forEach((newSubspace) {
updatedSubspaces.add(newSubspace);
});
final updatedSpace =
currentState.space.copyWith(subspaceModels: updatedSubspaces);
emit(CreateSpaceModelLoaded(updatedSpace));
} else {
emit(CreateSpaceModelError("Space template not initialized"));
}
});
on<AddTagsToSpaceTemplate>((event, emit) {
final currentState = state;
if (currentState is CreateSpaceModelLoaded) {
final updatedTags = currentState.space.copyWith(
tags: [
...(_space!.tags ?? []),
...event.tags,
],
final eventTagIds = event.tags.map((e) => e.internalId).toSet();
final updatedTags = currentState.space.tags
?.where((tag) => eventTagIds.contains(tag.internalId))
.map((tag) {
final matchingEventTag = event.tags.firstWhere(
(e) => e.internalId == tag.internalId,
orElse: () => tag,
);
emit(CreateSpaceModelLoaded(updatedTags));
return matchingEventTag != tag
? tag.copyWith(tag: matchingEventTag.tag)
: tag;
}).toList() ??
[];
event.tags
.where(
(e) => updatedTags.every((t) => t.internalId != e.internalId))
.forEach((e) {
updatedTags.add(e);
});
emit(CreateSpaceModelLoaded(
currentState.space.copyWith(tags: updatedTags)));
} else {
emit(CreateSpaceModelError("Space template not initialized"));
}
@ -106,7 +173,6 @@ class CreateSpaceModelBloc
final currentState = state;
if (currentState is CreateSpaceModelLoaded) {
if (event.name.trim().isEmpty) {
emit(CreateSpaceModelLoaded(
currentState.space,
errorMessage: "Model name cannot be empty",

View File

@ -1,22 +1,28 @@
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:uuid/uuid.dart';
class SubspaceTemplateModel {
final String? uuid;
String subspaceName;
final bool disabled;
List<TagModel>? tags;
String internalId;
SubspaceTemplateModel({
this.uuid,
required this.subspaceName,
required this.disabled,
this.tags,
});
String? internalId,
}) : internalId = internalId ?? const Uuid().v4();
factory SubspaceTemplateModel.fromJson(Map<String, dynamic> json) {
final String internalId = json['internalId'] ?? const Uuid().v4();
return SubspaceTemplateModel(
uuid: json['uuid'] ?? '',
subspaceName: json['subspaceName'] ?? '',
internalId: internalId,
disabled: json['disabled'] ?? false,
tags: (json['tags'] as List<dynamic>?)
?.map((item) => TagModel.fromJson(item))
@ -33,4 +39,20 @@ class SubspaceTemplateModel {
'tags': tags?.map((e) => e.toJson()).toList() ?? [],
};
}
SubspaceTemplateModel copyWith({
String? uuid,
String? subspaceName,
bool? disabled,
List<TagModel>? tags,
String? internalId,
}) {
return SubspaceTemplateModel(
uuid: uuid ?? this.uuid,
subspaceName: subspaceName ?? this.subspaceName,
disabled: disabled ?? this.disabled,
tags: tags ?? this.tags,
internalId: internalId ?? this.internalId,
);
}
}

View File

@ -30,15 +30,16 @@ class TagModel {
);
}
TagModel copyWith({
String? tag,
TagModel copyWith(
{String? tag,
ProductModel? product,
String? location,
}) {
String? internalId}) {
return TagModel(
tag: tag ?? this.tag,
product: product ?? this.product,
location: location ?? this.location,
internalId: internalId ?? this.internalId,
);
}

View File

@ -72,7 +72,9 @@ class CreateSpaceModelDialog extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Text(
spaceModel?.uuid == null ? 'Create New Space Model': 'Edit Space Model',
spaceModel?.uuid == null
? 'Create New Space Model'
: 'Edit Space Model',
style: Theme.of(context)
.textTheme
.headlineLarge
@ -127,16 +129,18 @@ class CreateSpaceModelDialog extends StatelessWidget {
allTags: allTags,
spaceNameController: spaceNameController,
onLoad: (tags, subspaces) {
if(subspaces!=null){
if (context.read<CreateSpaceModelBloc>().state
is CreateSpaceModelLoaded) {
if (subspaces != null) {
context
.read<CreateSpaceModelBloc>()
.add(AddSubspacesToSpaceTemplate(subspaces));
}
if(tags!=null){
if (tags != null) {
context
.read<CreateSpaceModelBloc>()
.add(AddTagsToSpaceTemplate(tags));
}
}
},
),

View File

@ -1,7 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.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/assign_tag_models/views/assign_tag_models_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:syncrow_web/pages/spaces_management/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';
@ -53,7 +54,7 @@ class TagChipDisplay extends StatelessWidget {
runSpacing: 8.0,
children: [
// Combine tags from spaceModel and subspaces
..._groupTags([
...TagHelper.groupTags([
...?spaceModel?.tags,
...?spaceModel?.subspaceModels
?.expand((subspace) => subspace.tags ?? [])
@ -84,24 +85,32 @@ class TagChipDisplay extends StatelessWidget {
),
GestureDetector(
onTap: () async {
Navigator.of(context).pop();
// Use the Navigator's context for showDialog
final navigatorContext =
Navigator.of(context).overlay?.context;
if (navigatorContext != null) {
await showDialog<bool>(
barrierDismissible: false,
context: context,
builder: (context) => AddDeviceTypeModelWidget(
isCreate: false,
context: navigatorContext,
builder: (context) => AssignTagModelsDialog(
products: products,
subspaces: subspaces,
allTags: allTags,
spaceName: spaceNameController.text,
spaceTagModels: spaceModel?.tags,
initialSelectedProducts:
_createInitialSelectedProducts(
spaceModel?.tags, spaceModel?.subspaceModels),
initialTags: TagHelper.generateInitialTags(
subspaces: subspaces,
spaceTagModels: spaceModel?.tags ?? []),
title: 'Edit Device',
addedProducts:
TagHelper.createInitialSelectedProducts(
spaceModel?.tags ?? [], subspaces),
spaceName: spaceModel?.modelName ?? '',
onUpdate: (tags, subspaces){
print("here");
onLoad?.call(tags, subspaces);}
),
);
// Edit action
}
},
child: Chip(
label: const Text(
@ -130,11 +139,7 @@ class TagChipDisplay extends StatelessWidget {
allTags: allTags,
spaceName: spaceNameController.text,
isCreate: true,
onLoad: (tags, subspaces) {
if (onLoad != null) {
onLoad!(tags, subspaces);
}
},
onLoad: (tags, subspaces) => onLoad?.call(tags, subspaces),
),
);
},
@ -147,49 +152,4 @@ class TagChipDisplay extends StatelessWidget {
),
);
}
Map<ProductModel, int> _groupTags(List<TagModel> tags) {
final Map<ProductModel, int> groupedTags = {};
for (var tag in tags) {
if (tag.product != null) {
groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1;
}
}
return groupedTags;
}
List<SelectedProduct> _createInitialSelectedProducts(
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces) {
final Map<ProductModel, int> productCounts = {};
if (tags != null) {
for (var tag in tags) {
if (tag.product != null) {
productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1;
}
}
}
if (subspaces != null) {
for (var subspace in subspaces) {
if (subspace.tags != null) {
for (var tag in subspace.tags!) {
if (tag.product != null) {
productCounts[tag.product!] =
(productCounts[tag.product!] ?? 0) + 1;
}
}
}
}
}
return productCounts.entries
.map((entry) => SelectedProduct(
productId: entry.key.uuid,
count: entry.value,
productName: entry.key.name ?? 'Unnamed',
product: entry.key,
))
.toList();
}
}

View File

@ -23,6 +23,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
final bool isCreate;
final void Function(
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces)? onLoad;
final void Function(
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces)? onUpdate;
const AddDeviceTypeModelWidget({
super.key,
@ -34,10 +36,18 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
required this.spaceName,
required this.isCreate,
this.onLoad,
this.onUpdate,
});
@override
Widget build(BuildContext context) {
if (spaceTagModels != null) {
for (var tag in spaceTagModels!) {
print(tag.tag);
}
}
final size = MediaQuery.of(context).size;
final crossAxisCount = size.width > 1200
? 8
@ -123,15 +133,12 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
spaceTagModels: spaceTagModels,
subspaces: subspaces,
);
if (isCreate) {
Navigator.of(context).pop();
}
final dialogTitle = initialTags.isNotEmpty
? 'Edit Device'
: 'Assign Tags';
Navigator.of(context).pop();
await showDialog<bool>(
barrierDismissible: false,
context: context,
builder: (context) => AssignTagModelsDialog(
products: products,
@ -142,9 +149,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
initialTags: state.initialTag,
title: dialogTitle,
onUpdate: (tags, subspaces) {
if (onLoad != null) {
onLoad!(tags, subspaces);
}
onLoad?.call(tags, subspaces);
},
),
);