Space managment refactoring merge (#239) with dev

<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-0000](https://syncrow.atlassian.net/browse/SP-0000)

## Description
merge latest working updates
<!--- Describe your changes in detail -->

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [x]  Breaking change (fix or feature that would cause existing
functionality to change)
- [x] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
This commit is contained in:
raf-dev1
2025-06-11 13:08:52 +03:00
committed by GitHub
36 changed files with 1754 additions and 1244 deletions

View File

@ -17,7 +17,8 @@ class TagDialogTextfieldDropdown extends StatefulWidget {
}) : super(key: key); }) : super(key: key);
@override @override
_DialogTextfieldDropdownState createState() => _DialogTextfieldDropdownState(); _DialogTextfieldDropdownState createState() =>
_DialogTextfieldDropdownState();
} }
class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> { class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
@ -36,6 +37,12 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
_focusNode.addListener(() { _focusNode.addListener(() {
if (!_focusNode.hasFocus) { if (!_focusNode.hasFocus) {
// Call onSelected when focus is lost
final selectedTag = _filteredItems.firstWhere(
(tag) => tag.tag == _controller.text,
orElse: () => Tag(tag: _controller.text),
);
widget.onSelected(selectedTag);
_closeDropdown(); _closeDropdown();
} }
}); });
@ -43,7 +50,9 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
void _filterItems() { void _filterItems() {
setState(() { setState(() {
_filteredItems = widget.items.where((tag) => tag.product?.uuid == widget.product).toList(); _filteredItems = widget.items;
// .where((tag) => tag.product?.uuid == widget.product)
// .toList();
}); });
} }
@ -112,7 +121,9 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyMedium .bodyMedium
?.copyWith(color: ColorsManager.textPrimaryColor)), ?.copyWith(
color: ColorsManager
.textPrimaryColor)),
onTap: () { onTap: () {
_controller.text = tag.tag ?? ''; _controller.text = tag.tag ?? '';
widget.onSelected(tag); widget.onSelected(tag);
@ -156,13 +167,15 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
controller: _controller, controller: _controller,
focusNode: _focusNode, focusNode: _focusNode,
onFieldSubmitted: (value) { onFieldSubmitted: (value) {
final selectedTag = _filteredItems.firstWhere((tag) => tag.tag == value, final selectedTag = _filteredItems.firstWhere(
(tag) => tag.tag == value,
orElse: () => Tag(tag: value)); orElse: () => Tag(tag: value));
widget.onSelected(selectedTag); widget.onSelected(selectedTag);
_closeDropdown(); _closeDropdown();
}, },
onTapOutside: (event) { onTapOutside: (event) {
widget.onSelected(_filteredItems.firstWhere((tag) => tag.tag == _controller.text, widget.onSelected(_filteredItems.firstWhere(
(tag) => tag.tag == _controller.text,
orElse: () => Tag(tag: _controller.text))); orElse: () => Tag(tag: _controller.text)));
_closeDropdown(); _closeDropdown();
}, },

View File

@ -30,12 +30,13 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async { Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async {
try { try {
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); var uuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
user = await HomeApi().fetchUserInfo(uuid); user = await HomeApi().fetchUserInfo(uuid);
if (user != null && user!.project != null) { if (user != null && user!.project != null) {
await ProjectManager.setProjectUUID(user!.project!.uuid); await ProjectManager.setProjectUUID(user!.project!.uuid);
NavigationService.navigatorKey.currentContext!.read<SpaceTreeBloc>().add(InitialEvent()); // NavigationService.navigatorKey.currentContext!.read<SpaceTreeBloc>().add(InitialEvent());
} }
add(FetchTermEvent()); add(FetchTermEvent());
add(FetchPolicyEvent()); add(FetchPolicyEvent());
@ -67,10 +68,12 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
} }
} }
Future _confirmUserAgreement(ConfirmUserAgreementEvent event, Emitter<HomeState> emit) async { Future _confirmUserAgreement(
ConfirmUserAgreementEvent event, Emitter<HomeState> emit) async {
try { try {
emit(LoadingHome()); emit(LoadingHome());
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); var uuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
policy = await HomeApi().confirmUserAgreements(uuid); policy = await HomeApi().confirmUserAgreements(uuid);
emit(PolicyAgreement()); emit(PolicyAgreement());
} catch (e) { } catch (e) {
@ -123,5 +126,41 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
}, },
color: const Color(0xFF023DFE), color: const Color(0xFF023DFE),
), ),
// HomeItemModel(
// title: 'Move in',
// icon: Assets.moveinIcon,
// active: false,
// onPress: (context) {},
// color: ColorsManager.primaryColor,
// ),
// HomeItemModel(
// title: 'Construction',
// icon: Assets.constructionIcon,
// active: false,
// onPress: (context) {},
// color: ColorsManager.primaryColor,
// ),
// HomeItemModel(
// title: 'Energy',
// icon: Assets.energyIcon,
// active: false,
// onPress: (context) {},
// color: ColorsManager.slidingBlueColor.withOpacity(0.2),
// ),
// HomeItemModel(
// title: 'Integrations',
// icon: Assets.integrationsIcon,
// active: false,
// onPress: (context) {},
// color: ColorsManager.slidingBlueColor.withOpacity(0.2),
// ),
// HomeItemModel(
// title: 'Asset',
// icon: Assets.assetIcon,
// active: false,
// onPress: (context) {},
// color: ColorsManager.slidingBlueColor.withOpacity(0.2),
// ),
]; ];
} }

View File

@ -24,7 +24,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
void initState() { void initState() {
super.initState(); super.initState();
final homeBloc = BlocProvider.of<HomeBloc>(context); final homeBloc = BlocProvider.of<HomeBloc>(context);
homeBloc.add(const FetchUserInfo()); // homeBloc.add(const FetchUserInfo());
} }
@override @override
@ -38,8 +38,10 @@ class _HomeWebPageState extends State<HomeWebPage> {
child: BlocConsumer<HomeBloc, HomeState>( child: BlocConsumer<HomeBloc, HomeState>(
listener: (BuildContext context, state) { listener: (BuildContext context, state) {
if (state is HomeInitial) { if (state is HomeInitial) {
if (homeBloc.user!.hasAcceptedWebAgreement == false && !_dialogShown) { if (homeBloc.user!.hasAcceptedWebAgreement == false &&
_dialogShown = true; // Set the flag to true to indicate the dialog is showing. !_dialogShown) {
_dialogShown =
true; // Set the flag to true to indicate the dialog is showing.
Future.delayed(const Duration(seconds: 1), () { Future.delayed(const Duration(seconds: 1), () {
showDialog( showDialog(
context: context, context: context,
@ -54,7 +56,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
_dialogShown = false; _dialogShown = false;
if (v != null) { if (v != null) {
homeBloc.add(ConfirmUserAgreementEvent()); homeBloc.add(ConfirmUserAgreementEvent());
homeBloc.add(const FetchUserInfo()); // homeBloc.add(const FetchUserInfo());
} }
}); });
}); });
@ -98,7 +100,8 @@ class _HomeWebPageState extends State<HomeWebPage> {
width: size.width * 0.68, width: size.width * 0.68,
child: GridView.builder( child: GridView.builder(
itemCount: homeBloc.homeItems.length, itemCount: homeBloc.homeItems.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // Adjust as needed. crossAxisCount: 3, // Adjust as needed.
crossAxisSpacing: 20.0, crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0, mainAxisSpacing: 20.0,
@ -111,7 +114,8 @@ class _HomeWebPageState extends State<HomeWebPage> {
active: homeBloc.homeItems[index].active!, active: homeBloc.homeItems[index].active!,
name: homeBloc.homeItems[index].title!, name: homeBloc.homeItems[index].title!,
img: homeBloc.homeItems[index].icon!, img: homeBloc.homeItems[index].icon!,
onTap: () => homeBloc.homeItems[index].onPress(context), onTap: () =>
homeBloc.homeItems[index].onPress(context),
); );
}, },
), ),

View File

@ -21,7 +21,8 @@ import 'package:syncrow_web/services/space_mana_api.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' as custom_action; import 'package:syncrow_web/utils/constants/action_enum.dart' as custom_action;
class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementState> { class SpaceManagementBloc
extends Bloc<SpaceManagementEvent, SpaceManagementState> {
final CommunitySpaceManagementApi _api; final CommunitySpaceManagementApi _api;
final ProductApi _productApi; final ProductApi _productApi;
final SpaceModelManagementApi _spaceModelApi; final SpaceModelManagementApi _spaceModelApi;
@ -62,7 +63,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
int page = 1; int page = 1;
while (hasNext) { while (hasNext) {
final spaceModels = await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid); final spaceModels = await _spaceModelApi.listSpaceModels(
page: page, projectId: projectUuid);
if (spaceModels.isNotEmpty) { if (spaceModels.isNotEmpty) {
allSpaceModels.addAll(spaceModels); allSpaceModels.addAll(spaceModels);
page++; page++;
@ -75,26 +77,29 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
await fetchTags(); await fetchTags();
emit(SpaceModelLoaded( emit(SpaceModelLoaded(
communities: communities: state is SpaceManagementLoaded
state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [], ? (state as SpaceManagementLoaded).communities
: [],
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
spaceModels: List.from(_cachedSpaceModels ?? []), spaceModels: List.from(_cachedSpaceModels ?? []),
allTags: _cachedTags ?? [])); allTags: _cachedTags ?? []));
} }
void _deleteSpaceModelFromCache( void _deleteSpaceModelFromCache(DeleteSpaceModelFromCache event,
DeleteSpaceModelFromCache event, Emitter<SpaceManagementState> emit) async { Emitter<SpaceManagementState> emit) async {
if (_cachedSpaceModels != null) { if (_cachedSpaceModels != null) {
_cachedSpaceModels = _cachedSpaceModels = _cachedSpaceModels!
_cachedSpaceModels!.where((model) => model.uuid != event.deletedUuid).toList(); .where((model) => model.uuid != event.deletedUuid)
.toList();
} else { } else {
_cachedSpaceModels = await fetchSpaceModels(); _cachedSpaceModels = await fetchSpaceModels();
} }
await fetchTags(); await fetchTags();
emit(SpaceModelLoaded( emit(SpaceModelLoaded(
communities: communities: state is SpaceManagementLoaded
state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [], ? (state as SpaceManagementLoaded).communities
: [],
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
spaceModels: List.from(_cachedSpaceModels ?? []), spaceModels: List.from(_cachedSpaceModels ?? []),
allTags: _cachedTags ?? [])); allTags: _cachedTags ?? []));
@ -122,8 +127,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
int page = 1; int page = 1;
while (hasNext) { while (hasNext) {
final spaceModels = final spaceModels = await _spaceModelApi.listSpaceModels(
await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid); page: page, projectId: projectUuid);
if (spaceModels.isNotEmpty) { if (spaceModels.isNotEmpty) {
allSpaceModels.addAll(spaceModels); allSpaceModels.addAll(spaceModels);
page++; page++;
@ -164,10 +169,12 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
await fetchTags(); await fetchTags();
emit(SpaceManagementLoading()); emit(SpaceManagementLoading());
final success = await _api.updateCommunity(event.communityUuid, event.name, projectUuid); final success = await _api.updateCommunity(
event.communityUuid, event.name, projectUuid);
if (success) { if (success) {
if (previousState is SpaceManagementLoaded) { if (previousState is SpaceManagementLoaded) {
final updatedCommunities = List<CommunityModel>.from(previousState.communities); final updatedCommunities =
List<CommunityModel>.from(previousState.communities);
for (var community in updatedCommunities) { for (var community in updatedCommunities) {
if (community.uuid == event.communityUuid) { if (community.uuid == event.communityUuid) {
community.name = event.name; community.name = event.name;
@ -212,7 +219,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
} }
} }
Future<List<SpaceModel>> _fetchSpacesForCommunity(String communityUuid) async { Future<List<SpaceModel>> _fetchSpacesForCommunity(
String communityUuid) async {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
return await _api.getSpaceHierarchy(communityUuid, projectUuid); return await _api.getSpaceHierarchy(communityUuid, projectUuid);
@ -242,20 +250,23 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
} }
} }
Future<void> _onBlankState(BlankStateEvent event, Emitter<SpaceManagementState> emit) async { Future<void> _onBlankState(
BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
try { try {
final previousState = state; final previousState = state;
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
var spaceBloc = event.context.read<SpaceTreeBloc>(); var spaceBloc = event.context.read<SpaceTreeBloc>();
var spaceTreeState = event.context.read<SpaceTreeBloc>().state; var spaceTreeState = event.context.read<SpaceTreeBloc>().state;
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState); List<CommunityModel> communities =
await _waitForCommunityList(spaceBloc, spaceTreeState);
await fetchSpaceModels(); await fetchSpaceModels();
await fetchTags(); // await fetchTags();
var prevSpaceModels = await fetchSpaceModels(); var prevSpaceModels = await fetchSpaceModels();
if (previousState is SpaceManagementLoaded || previousState is BlankState) { if (previousState is SpaceManagementLoaded ||
previousState is BlankState) {
final prevCommunities = (previousState as dynamic).communities ?? []; final prevCommunities = (previousState as dynamic).communities ?? [];
emit(BlankState( emit(BlankState(
communities: List<CommunityModel>.from(prevCommunities), communities: List<CommunityModel>.from(prevCommunities),
@ -286,7 +297,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
_onloadProducts(); _onloadProducts();
await fetchTags(); await fetchTags();
// Wait until `communityList` is loaded // Wait until `communityList` is loaded
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState); List<CommunityModel> communities =
await _waitForCommunityList(spaceBloc, spaceTreeState);
// Fetch space models after communities are available // Fetch space models after communities are available
final prevSpaceModels = await fetchSpaceModels(); final prevSpaceModels = await fetchSpaceModels();
@ -310,8 +322,9 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
final completer = Completer<List<CommunityModel>>(); final completer = Completer<List<CommunityModel>>();
final subscription = spaceBloc.stream.listen((state) { final subscription = spaceBloc.stream.listen((state) {
if (!completer.isCompleted && state.communityList.isNotEmpty) { if (!completer.isCompleted && state.communityList.isNotEmpty) {
completer completer.complete(state.searchQuery.isNotEmpty
.complete(state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList); ? state.filteredCommunity
: state.communityList);
} }
}); });
try { try {
@ -339,7 +352,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
emit(SpaceManagementLoading()); emit(SpaceManagementLoading());
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final success = await _api.deleteCommunity(event.communityUuid, projectUuid); final success =
await _api.deleteCommunity(event.communityUuid, projectUuid);
if (success) { if (success) {
// add(LoadCommunityAndSpacesEvent()); // add(LoadCommunityAndSpacesEvent());
} else { } else {
@ -361,12 +375,13 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
await fetchTags(); await fetchTags();
CommunityModel? newCommunity = CommunityModel? newCommunity = await _api.createCommunity(
await _api.createCommunity(event.name, event.description, projectUuid); event.name, event.description, projectUuid);
var prevSpaceModels = await fetchSpaceModels(); var prevSpaceModels = await fetchSpaceModels();
if (newCommunity != null) { if (newCommunity != null) {
if (previousState is SpaceManagementLoaded || previousState is BlankState) { if (previousState is SpaceManagementLoaded ||
previousState is BlankState) {
final prevCommunities = List<CommunityModel>.from( final prevCommunities = List<CommunityModel>.from(
(previousState as dynamic).communities, (previousState as dynamic).communities,
); );
@ -459,12 +474,15 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
try { try {
final spaceTreeState = event.context.read<SpaceTreeBloc>().state; final spaceTreeState = event.context.read<SpaceTreeBloc>().state;
final updatedSpaces = final updatedSpaces = await saveSpacesHierarchically(
await saveSpacesHierarchically(event.context, event.spaces, event.communityUuid); event.context, event.spaces, event.communityUuid);
final allSpaces = await _fetchSpacesForCommunity(event.communityUuid); final allSpaces = await _fetchSpacesForCommunity(event.communityUuid);
emit(SpaceCreationSuccess(spaces: updatedSpaces)); // emit(SpaceCreationSuccess(spaces: updatedSpaces));
// updatedSpaces.forEach(
// (element) => element.uuid,
// );
// final lastUpdatedSpaced = updatedSpaces..addAll(allSpaces);
if (previousState is SpaceManagementLoaded) { if (previousState is SpaceManagementLoaded) {
await _updateLoadedState( await _updateLoadedState(
spaceTreeState, spaceTreeState,
@ -475,7 +493,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
); );
} }
} catch (e) { } catch (e) {
emit(SpaceManagementError('Error saving spaces: $e')); // emit(SpaceManagementError('Error saving spaces: $e'));
if (previousState is SpaceManagementLoaded) { if (previousState is SpaceManagementLoaded) {
emit(previousState); emit(previousState);
@ -515,13 +533,15 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
return; return;
} }
} }
emit(previousState);
} catch (e, stackTrace) { } catch (e, stackTrace) {
rethrow; emit(previousState);
// rethrow;
} }
} }
Future<List<SpaceModel>> saveSpacesHierarchically( Future<List<SpaceModel>> saveSpacesHierarchically(BuildContext context,
BuildContext context, List<SpaceModel> spaces, String communityUuid) async { List<SpaceModel> spaces, String communityUuid) async {
final orderedSpaces = flattenHierarchy(spaces); final orderedSpaces = flattenHierarchy(spaces);
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
@ -534,6 +554,14 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
selectedCommunity = filteredCommunities.firstWhere( selectedCommunity = filteredCommunities.firstWhere(
(community) => community.uuid == communityUuid, (community) => community.uuid == communityUuid,
orElse: () => CommunityModel(
uuid: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
name: '',
description: '',
spaces: spaces,
),
); );
} catch (e) { } catch (e) {
return []; return [];
@ -548,9 +576,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
if (parent.uuid != null) { if (parent.uuid != null) {
await _api.deleteSpace(communityUuid, parent.uuid!, projectUuid); await _api.deleteSpace(communityUuid, parent.uuid!, projectUuid);
} }
} catch (e) { } catch (e) {}
rethrow;
}
} }
orderedSpaces.removeWhere((space) => parentsToDelete.contains(space)); orderedSpaces.removeWhere((space) => parentsToDelete.contains(space));
@ -564,7 +590,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
if (matchedSpaces.isEmpty) continue; if (matchedSpaces.isEmpty) continue;
final prevSpace = matchedSpaces[0]; final prevSpace = matchedSpaces.elementAtOrNull(0);
final List<UpdateSubspaceTemplateModel> subspaceUpdates = []; final List<UpdateSubspaceTemplateModel> subspaceUpdates = [];
final List<SubspaceModel>? prevSubspaces = prevSpace?.subspaces; final List<SubspaceModel>? prevSubspaces = prevSpace?.subspaces;
@ -575,17 +601,19 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
if (prevSubspaces != null || newSubspaces != null) { if (prevSubspaces != null || newSubspaces != null) {
if (prevSubspaces != null && newSubspaces != null) { if (prevSubspaces != null && newSubspaces != null) {
for (var prevSubspace in prevSubspaces) { for (var prevSubspace in prevSubspaces) {
final existsInNew = final existsInNew = newSubspaces
newSubspaces.any((subspace) => subspace.uuid == prevSubspace.uuid); .any((subspace) => subspace.uuid == prevSubspace.uuid);
if (!existsInNew) { if (!existsInNew) {
subspaceUpdates.add(UpdateSubspaceTemplateModel( subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: custom_action.Action.delete, uuid: prevSubspace.uuid)); action: custom_action.Action.delete,
uuid: prevSubspace.uuid));
} }
} }
} else if (prevSubspaces != null && newSubspaces == null) { } else if (prevSubspaces != null && newSubspaces == null) {
for (var prevSubspace in prevSubspaces) { for (var prevSubspace in prevSubspaces) {
subspaceUpdates.add(UpdateSubspaceTemplateModel( subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: custom_action.Action.delete, uuid: prevSubspace.uuid)); action: custom_action.Action.delete,
uuid: prevSubspace.uuid));
} }
} }
@ -613,7 +641,9 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
} }
if (prevSubspaces != null && newSubspaces != null) { if (prevSubspaces != null && newSubspaces != null) {
final newSubspaceMap = {for (var subspace in newSubspaces) subspace.uuid: subspace}; final newSubspaceMap = {
for (var subspace in newSubspaces) subspace.uuid: subspace
};
for (var prevSubspace in prevSubspaces) { for (var prevSubspace in prevSubspaces) {
final newSubspace = newSubspaceMap[prevSubspace.uuid]; final newSubspace = newSubspaceMap[prevSubspace.uuid];
@ -639,9 +669,10 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
isPrivate: space.isPrivate, isPrivate: space.isPrivate,
position: space.position, position: space.position,
icon: space.icon, icon: space.icon,
subspaces: subspaceUpdates, subspaces: space.subspaces,
tags: tagUpdates, // subspaceUpdates,
direction: space.incomingConnection?.direction, tags: space.tags,
// tagUpdates,
spaceModelUuid: space.spaceModel?.uuid, spaceModelUuid: space.spaceModel?.uuid,
projectId: projectUuid); projectId: projectUuid);
} else { } else {
@ -651,8 +682,10 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
: []; : [];
var createSubspaceBodyModels = space.subspaces?.map((subspace) { var createSubspaceBodyModels = space.subspaces?.map((subspace) {
final tagBodyModels = final tagBodyModels = subspace.tags
subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? []; ?.map((tag) => tag.toCreateTagBodyModel())
.toList() ??
[];
return CreateSubspaceModel() return CreateSubspaceModel()
..subspaceName = subspace.subspaceName ..subspaceName = subspace.subspaceName
..tags = tagBodyModels; ..tags = tagBodyModels;
@ -671,7 +704,6 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
isPrivate: space.isPrivate, isPrivate: space.isPrivate,
position: space.position, position: space.position,
icon: space.icon, icon: space.icon,
direction: space.incomingConnection?.direction,
spaceModelUuid: space.spaceModel?.uuid, spaceModelUuid: space.spaceModel?.uuid,
tags: tagBodyModels, tags: tagBodyModels,
subspaces: createSubspaceBodyModels, subspaces: createSubspaceBodyModels,
@ -679,7 +711,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
space.uuid = response?.uuid; space.uuid = response?.uuid;
} }
} catch (e) { } catch (e) {
rethrow; // Stop further execution on failure return [];
// Stop further execution on failure
} }
} }
return spaces; return spaces;
@ -710,7 +743,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
return result.toList(); // Convert back to a list return result.toList(); // Convert back to a list
} }
void _onLoadSpaceModel(SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async { void _onLoadSpaceModel(
SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
emit(SpaceManagementLoading()); emit(SpaceManagementLoading());
try { try {
@ -757,14 +791,17 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
// Case 1: Tags deleted // Case 1: Tags deleted
if (prevTags != null && newTags != null) { if (prevTags != null && newTags != null) {
for (var prevTag in prevTags) { for (var prevTag in prevTags) {
final existsInNew = newTags.any((newTag) => newTag.uuid == prevTag.uuid); final existsInNew =
newTags.any((newTag) => newTag.uuid == prevTag.uuid);
if (!existsInNew) { if (!existsInNew) {
tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid)); tagUpdates.add(TagModelUpdate(
action: custom_action.Action.delete, uuid: prevTag.uuid));
} }
} }
} else if (prevTags != null && newTags == null) { } else if (prevTags != null && newTags == null) {
for (var prevTag in prevTags) { for (var prevTag in prevTags) {
tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid)); tagUpdates.add(TagModelUpdate(
action: custom_action.Action.delete, uuid: prevTag.uuid));
} }
} }
@ -807,15 +844,16 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
return tagUpdates; return tagUpdates;
} }
List<SpaceModel> findMatchingSpaces(List<SpaceModel> spaces, String targetUuid) { List<SpaceModel> findMatchingSpaces(
List<SpaceModel> spaces, String targetUuid) {
List<SpaceModel> matched = []; List<SpaceModel> matched = [];
for (var space in spaces) { for (var space in spaces) {
if (space.uuid == targetUuid) { if (space.uuid == targetUuid) {
matched.add(space); matched.add(space);
} }
matched matched.addAll(findMatchingSpaces(
.addAll(findMatchingSpaces(space.children, targetUuid)); // Recursively search in children space.children, targetUuid)); // Recursively search in children
} }
return matched; return matched;

View File

@ -3,23 +3,26 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model
class Connection { class Connection {
final SpaceModel startSpace; final SpaceModel startSpace;
final SpaceModel endSpace; final SpaceModel endSpace;
final String direction;
Connection({required this.startSpace, required this.endSpace, required this.direction}); Connection({
required this.startSpace,
required this.endSpace,
});
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return { return {
'startUuid': startSpace.uuid ?? 'unsaved-start-space-${startSpace.name}', // Fallback for unsaved spaces 'startUuid': startSpace.uuid ??
'endUuid': endSpace.uuid ?? 'unsaved-end-space-${endSpace.name}', // Fallback for unsaved spaces 'unsaved-start-space-${startSpace.name}', // Fallback for unsaved spaces
'direction': direction, 'endUuid': endSpace.uuid ??
'unsaved-end-space-${endSpace.name}', // Fallback for unsaved spaces
}; };
} }
static Connection fromMap(Map<String, dynamic> map, Map<String, SpaceModel> spaces) { static Connection fromMap(
Map<String, dynamic> map, Map<String, SpaceModel> spaces) {
return Connection( return Connection(
startSpace: spaces[map['startUuid']]!, startSpace: spaces[map['startUuid']]!,
endSpace: spaces[map['endUuid']]!, endSpace: spaces[map['endUuid']]!,
direction: map['direction'],
); );
} }
} }

View File

@ -1,5 +1,7 @@
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'selected_product_model.dart';
class ProductModel { class ProductModel {
final String uuid; final String uuid;
final String catName; final String catName;
@ -38,6 +40,15 @@ class ProductModel {
}; };
} }
SelectedProduct toSelectedProduct(int count) {
return SelectedProduct(
productId: uuid,
count: count,
productName: name!,
product: this,
);
}
static String _mapIconToProduct(String prodType) { static String _mapIconToProduct(String prodType) {
const iconMapping = { const iconMapping = {
'1G': Assets.Gang1SwitchIcon, '1G': Assets.Gang1SwitchIcon,

View File

@ -101,7 +101,7 @@ class SpaceModel {
spaceModel: json['spaceModel'] != null spaceModel: json['spaceModel'] != null
? SpaceTemplateModel.fromJson(json['spaceModel']) ? SpaceTemplateModel.fromJson(json['spaceModel'])
: null, : null,
tags: (json['tags'] as List<dynamic>?) tags: (json['productAllocations'] as List<dynamic>?)
?.where((item) => item is Map<String, dynamic>) // Validate type ?.where((item) => item is Map<String, dynamic>) // Validate type
.map((item) => Tag.fromJson(item as Map<String, dynamic>)) .map((item) => Tag.fromJson(item as Map<String, dynamic>))
.toList() ?? .toList() ??
@ -116,7 +116,6 @@ class SpaceModel {
instance.incomingConnection = Connection( instance.incomingConnection = Connection(
startSpace: instance.parent ?? instance, // Parent space startSpace: instance.parent ?? instance, // Parent space
endSpace: instance, // This space instance endSpace: instance, // This space instance
direction: conn['direction'],
); );
} }

View File

@ -27,7 +27,7 @@ class SubspaceModel {
subspaceName: json['subspaceName'] ?? '', subspaceName: json['subspaceName'] ?? '',
disabled: json['disabled'] ?? false, disabled: json['disabled'] ?? false,
internalId: internalId, internalId: internalId,
tags: (json['tags'] as List<dynamic>?) tags: (json['productAllocations'] as List<dynamic>?)
?.map((item) => Tag.fromJson(item)) ?.map((item) => Tag.fromJson(item))
.toList() ?? .toList() ??
[], [],
@ -36,7 +36,7 @@ class SubspaceModel {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'uuid': uuid, if (uuid != null) 'uuid': uuid,
'subspaceName': subspaceName, 'subspaceName': subspaceName,
'disabled': disabled, 'disabled': disabled,
'tags': tags?.map((e) => e.toJson()).toList() ?? [], 'tags': tags?.map((e) => e.toJson()).toList() ?? [],

View File

@ -23,10 +23,13 @@ class Tag extends BaseTag {
final String internalId = json['internalId'] ?? const Uuid().v4(); final String internalId = json['internalId'] ?? const Uuid().v4();
return Tag( return Tag(
uuid: json['uuid'] ?? '', //TODO:insure UUId for tag or prodAlloc
uuid: json['name'] != null ? json['uuid'] : json['tag']?['uuid'] ?? '',
internalId: internalId, internalId: internalId,
tag: json['name'] ?? '', tag: json['name'] ?? json['tag']?['name'] ?? '',
product: json['product'] != null ? ProductModel.fromMap(json['product']) : null, product: json['product'] != null
? ProductModel.fromMap(json['product'])
: null,
); );
} }
@ -49,9 +52,10 @@ class Tag extends BaseTag {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'uuid': uuid, if (uuid != null) 'uuid': uuid,
'tag': tag, 'name': tag,
'product': product?.toMap(), 'productUuid': product?.uuid,
// .toMap(),
}; };
} }
} }

View File

@ -67,7 +67,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void initState() { void initState() {
super.initState(); super.initState();
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; connections =
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces(); _adjustCanvasSizeForSpaces();
_nameController = TextEditingController( _nameController = TextEditingController(
text: widget.selectedCommunity?.name ?? '', text: widget.selectedCommunity?.name ?? '',
@ -96,13 +97,15 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (oldWidget.spaces != widget.spaces) { if (oldWidget.spaces != widget.spaces) {
setState(() { setState(() {
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; connections =
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces(); _adjustCanvasSizeForSpaces();
realignTree(); realignTree();
}); });
} }
if (widget.selectedSpace != oldWidget.selectedSpace && widget.selectedSpace != null) { if (widget.selectedSpace != oldWidget.selectedSpace &&
widget.selectedSpace != null) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_moveToSpace(widget.selectedSpace!); _moveToSpace(widget.selectedSpace!);
}); });
@ -185,7 +188,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
connection, widget.selectedSpace) connection, widget.selectedSpace)
? 1.0 ? 1.0
: 0.3, // Adjust opacity : 0.3, // Adjust opacity
child: CustomPaint(painter: CurvedLinePainter([connection])), child: CustomPaint(
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 &&
@ -195,11 +199,11 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
top: entry.value.position.dy, top: entry.value.position.dy,
child: SpaceCardWidget( child: SpaceCardWidget(
index: entry.key, index: entry.key,
onButtonTap: (int index, Offset newPosition, String direction) { onButtonTap: (int index, Offset newPosition) {
_showCreateSpaceDialog(screenSize, _showCreateSpaceDialog(screenSize,
position: spaces[index].position + newPosition, position:
spaces[index].position + newPosition,
parentIndex: index, parentIndex: index,
direction: direction,
projectTags: widget.projectTags); projectTags: widget.projectTags);
}, },
position: entry.value.position, position: entry.value.position,
@ -210,8 +214,9 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
_updateNodePosition(entry.value, newPosition); _updateNodePosition(entry.value, newPosition);
}, },
buildSpaceContainer: (int index) { buildSpaceContainer: (int index) {
final bool isHighlighted = SpaceHelper.isHighlightedSpace( final bool isHighlighted =
spaces[index], widget.selectedSpace); SpaceHelper.isHighlightedSpace(
spaces[index], widget.selectedSpace);
return Opacity( return Opacity(
opacity: isHighlighted ? 1.0 : 0.3, opacity: isHighlighted ? 1.0 : 0.3,
@ -289,7 +294,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void _showCreateSpaceDialog(Size screenSize, void _showCreateSpaceDialog(Size screenSize,
{Offset? position, {Offset? position,
int? parentIndex, int? parentIndex,
String? direction,
double? canvasWidth, double? canvasWidth,
double? canvasHeight, double? canvasHeight,
required List<Tag> projectTags}) { required List<Tag> projectTags}) {
@ -299,19 +303,25 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
return CreateSpaceDialog( return CreateSpaceDialog(
products: widget.products, products: widget.products,
spaceModels: widget.spaceModels, spaceModels: widget.spaceModels,
allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels), allTags:
TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
parentSpace: parentIndex != null ? spaces[parentIndex] : null, parentSpace: parentIndex != null ? spaces[parentIndex] : null,
projectTags: projectTags, projectTags: projectTags,
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts, onCreateSpace: (String name,
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) { String icon,
List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
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 newPosition; Offset newPosition;
if (parentIndex != null) { if (parentIndex != null) {
newPosition = newPosition = getBalancedChildPosition(
getBalancedChildPosition(spaces[parentIndex]); // Ensure balanced position spaces[parentIndex]); // Ensure balanced position
} else { } else {
newPosition = position ?? ConnectionHelper.getCenterPosition(screenSize); newPosition =
position ?? ConnectionHelper.getCenterPosition(screenSize);
} }
SpaceModel newSpace = SpaceModel( SpaceModel newSpace = SpaceModel(
@ -325,14 +335,13 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
subspaces: subspaces, subspaces: subspaces,
tags: tags); tags: tags);
if (parentIndex != null && direction != null) { if (parentIndex != null) {
SpaceModel parentSpace = spaces[parentIndex]; SpaceModel parentSpace = spaces[parentIndex];
parentSpace.internalId = spaces[parentIndex].internalId; parentSpace.internalId = spaces[parentIndex].internalId;
newSpace.parent = parentSpace; newSpace.parent = parentSpace;
final newConnection = Connection( final newConnection = Connection(
startSpace: parentSpace, startSpace: parentSpace,
endSpace: newSpace, endSpace: newSpace,
direction: direction,
); );
connections.add(newConnection); connections.add(newConnection);
newSpace.incomingConnection = newConnection; newSpace.incomingConnection = newConnection;
@ -360,16 +369,21 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
name: widget.selectedSpace!.name, name: widget.selectedSpace!.name,
icon: widget.selectedSpace!.icon, icon: widget.selectedSpace!.icon,
projectTags: widget.projectTags, projectTags: widget.projectTags,
parentSpace: parentSpace: SpaceHelper.findSpaceByInternalId(
SpaceHelper.findSpaceByInternalId(widget.selectedSpace?.parent?.internalId, spaces), widget.selectedSpace?.parent?.internalId, spaces),
editSpace: widget.selectedSpace, editSpace: widget.selectedSpace,
currentSpaceModel: widget.selectedSpace?.spaceModel, currentSpaceModel: widget.selectedSpace?.spaceModel,
tags: widget.selectedSpace?.tags, tags: widget.selectedSpace?.tags,
subspaces: widget.selectedSpace?.subspaces, subspaces: widget.selectedSpace?.subspaces,
isEdit: true, isEdit: true,
allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels), allTags: TagHelper.getAllTagValues(
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts, widget.communities, widget.spaceModels),
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) { onCreateSpace: (String name,
String icon,
List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
List<Tag>? tags) {
setState(() { setState(() {
// Update the space's properties // Update the space's properties
widget.selectedSpace!.name = name; widget.selectedSpace!.name = name;
@ -379,7 +393,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
widget.selectedSpace!.tags = tags; widget.selectedSpace!.tags = tags;
if (widget.selectedSpace!.status != SpaceStatus.newSpace) { if (widget.selectedSpace!.status != SpaceStatus.newSpace) {
widget.selectedSpace!.status = SpaceStatus.modified; // Mark as modified widget.selectedSpace!.status =
SpaceStatus.modified; // Mark as modified
} }
for (var space in spaces) { for (var space in spaces) {
@ -410,7 +425,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
Map<String, SpaceModel> idToSpace = {}; Map<String, SpaceModel> idToSpace = {};
void flatten(SpaceModel space) { void flatten(SpaceModel space) {
if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) { if (space.status == SpaceStatus.deleted ||
space.status == SpaceStatus.parentDeleted) {
return; return;
} }
result.add(space); result.add(space);
@ -447,7 +463,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
Connection( Connection(
startSpace: parent, startSpace: parent,
endSpace: child, endSpace: child,
direction: "down",
), ),
); );
@ -532,13 +547,16 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void _selectSpace(BuildContext context, SpaceModel space) { void _selectSpace(BuildContext context, SpaceModel space) {
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: space), SelectSpaceEvent(
selectedCommunity: widget.selectedCommunity,
selectedSpace: space),
); );
} }
void _deselectSpace(BuildContext context) { void _deselectSpace(BuildContext context) {
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: null), SelectSpaceEvent(
selectedCommunity: widget.selectedCommunity, selectedSpace: null),
); );
} }
@ -708,7 +726,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
SpaceModel duplicated = _deepCloneSpaceTree(space, parent: parent); SpaceModel duplicated = _deepCloneSpaceTree(space, parent: parent);
duplicated.position = Offset(space.position.dx + 300, space.position.dy + 100); duplicated.position =
Offset(space.position.dx + 300, space.position.dy + 100);
List<SpaceModel> duplicatedSubtree = []; List<SpaceModel> duplicatedSubtree = [];
void collectSubtree(SpaceModel node) { void collectSubtree(SpaceModel node) {
duplicatedSubtree.add(node); duplicatedSubtree.add(node);
@ -726,7 +745,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final newConnection = Connection( final newConnection = Connection(
startSpace: parent, startSpace: parent,
endSpace: duplicated, endSpace: duplicated,
direction: "down",
); );
connections.add(newConnection); connections.add(newConnection);
duplicated.incomingConnection = newConnection; duplicated.incomingConnection = newConnection;
@ -739,7 +757,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
} }
SpaceModel _deepCloneSpaceTree(SpaceModel original, {SpaceModel? parent}) { SpaceModel _deepCloneSpaceTree(SpaceModel original, {SpaceModel? parent}) {
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces); final duplicatedName =
SpaceHelper.generateUniqueSpaceName(original.name, spaces);
final newSpace = SpaceModel( final newSpace = SpaceModel(
name: duplicatedName, name: duplicatedName,
@ -761,7 +780,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final newConnection = Connection( final newConnection = Connection(
startSpace: newSpace, startSpace: newSpace,
endSpace: duplicatedChild, endSpace: duplicatedChild,
direction: "down",
); );
connections.add(newConnection); connections.add(newConnection);

View File

@ -0,0 +1,101 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import '../../../../../common/edit_chip.dart';
import '../../../../../utils/color_manager.dart';
import '../../../helper/tag_helper.dart';
import '../../../space_model/widgets/button_content_widget.dart';
import '../../model/subspace_model.dart';
import '../../model/tag.dart';
class DevicesPartWidget extends StatelessWidget {
const DevicesPartWidget({
super.key,
required this.tags,
required this.subspaces,
required this.screenWidth,
required this.onEditChip,
required this.onTextButtonPressed,
required this.isTagsAndSubspaceModelDisabled,
});
final bool isTagsAndSubspaceModelDisabled;
final void Function() onEditChip;
final void Function() onTextButtonPressed;
final double screenWidth;
final List<Tag>? tags;
final List<SubspaceModel>? subspaces;
@override
Widget build(BuildContext context) {
return Column(
children: [
(tags?.isNotEmpty == true ||
subspaces?.any(
(subspace) => subspace.tags?.isNotEmpty == true) ==
true)
? SizedBox(
width: screenWidth * 0.25,
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: ColorsManager.textFieldGreyColor,
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: ColorsManager.textFieldGreyColor,
width: 3.0, // Border width
),
),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
// Combine tags from spaceModel and subspaces
...TagHelper.groupTags([
...?tags,
...?subspaces?.expand((subspace) => subspace.tags ?? [])
]).entries.map(
(entry) => Chip(
avatar: SizedBox(
width: 24,
height: 24,
child: SvgPicture.asset(
entry.key.icon ?? 'assets/icons/gateway.svg',
fit: BoxFit.contain,
),
),
label: Text(
'x${entry.value}', // Show count
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.spaceColor),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(
color: ColorsManager.spaceColor,
),
),
),
),
EditChip(onTap: onEditChip)
],
),
),
)
: TextButton(
onPressed: onTextButtonPressed,
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
),
child: ButtonContentWidget(
icon: Icons.add,
label: 'Add Devices',
disabled: isTagsAndSubspaceModelDisabled,
),
)
],
);
}
}

View File

@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import '../../../../../utils/color_manager.dart';
import '../../../../../utils/constants/assets.dart';
class IconChoosePartWidget extends StatelessWidget {
const IconChoosePartWidget({
super.key,
required this.selectedIcon,
required this.showIconSelection,
required this.screenWidth,
});
final double screenWidth;
final String selectedIcon;
final void Function() showIconSelection;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 50),
Stack(
alignment: Alignment.center,
children: [
Container(
width: screenWidth * 0.1,
height: screenWidth * 0.1,
decoration: const BoxDecoration(
color: ColorsManager.boxColor,
shape: BoxShape.circle,
),
),
SvgPicture.asset(
selectedIcon,
width: screenWidth * 0.04,
height: screenWidth * 0.04,
),
Positioned(
top: 20,
right: 20,
child: InkWell(
onTap: showIconSelection,
child: Container(
width: 24,
height: 24,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: SvgPicture.asset(
Assets.iconEdit,
width: 16,
height: 16,
),
),
),
),
],
),
],
);
}
}

View File

@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import '../../../../../utils/color_manager.dart';
import '../../../../../utils/constants/assets.dart';
import '../../../space_model/models/space_template_model.dart';
import '../../../space_model/widgets/button_content_widget.dart';
class SpaceModelLinkingWidget extends StatelessWidget {
const SpaceModelLinkingWidget({
super.key,
required this.onDeleted,
required this.onPressed,
required this.screenWidth,
required this.selectedSpaceModel,
required this.isSpaceModelDisabled,
});
final bool isSpaceModelDisabled;
final void Function()? onDeleted;
final void Function()? onPressed;
final double screenWidth;
final SpaceTemplateModel? selectedSpaceModel;
@override
Widget build(BuildContext context) {
return Column(
children: [
selectedSpaceModel == null
? TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
),
onPressed: onPressed,
child: ButtonContentWidget(
svgAssets: Assets.link,
label: 'Link a space model',
disabled: isSpaceModelDisabled,
),
)
: Container(
width: screenWidth * 0.25,
padding: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 16.0),
decoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10),
),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
Chip(
label: Text(
selectedSpaceModel?.modelName ?? '',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: ColorsManager.spaceColor),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: const BorderSide(
color: ColorsManager.transparentColor,
width: 0,
),
),
deleteIcon: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.lightGrayColor,
width: 1.5,
),
),
child: const Icon(
Icons.close,
size: 16,
color: ColorsManager.lightGrayColor,
),
),
onDeleted: onDeleted),
],
),
),
],
);
}
}

View File

@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import '../../../../../utils/color_manager.dart';
class SpaceNameTextfieldWidget extends StatelessWidget {
SpaceNameTextfieldWidget({
super.key,
required this.isNameFieldExist,
required this.isNameFieldInvalid,
required this.onChange,
required this.screenWidth,
required this.nameController,
});
TextEditingController nameController;
final void Function(String value) onChange;
final double screenWidth;
bool isNameFieldExist;
bool isNameFieldInvalid;
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
width: screenWidth * 0.25,
child: TextField(
controller: nameController,
onChanged: onChange,
style: Theme.of(context).textTheme.bodyMedium,
decoration: InputDecoration(
hintText: 'Please enter the name',
hintStyle: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: ColorsManager.lightGrayColor),
filled: true,
fillColor: ColorsManager.boxColor,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(
color: isNameFieldInvalid || isNameFieldExist
? ColorsManager.red
: ColorsManager.boxColor,
width: 1.5,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(
color: ColorsManager.boxColor,
width: 1.5,
),
),
),
),
),
if (isNameFieldInvalid)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'*Space name should not be empty.',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.red),
),
),
if (isNameFieldExist)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'*Name already exist',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.red),
),
),
],
);
}
}

View File

@ -0,0 +1,101 @@
import 'package:flutter/material.dart';
import '../../../../../common/edit_chip.dart';
import '../../../../../utils/color_manager.dart';
import '../../../space_model/widgets/button_content_widget.dart';
import '../../../space_model/widgets/subspace_name_label_widget.dart';
import '../../model/subspace_model.dart';
class SubSpacePartWidget extends StatefulWidget {
SubSpacePartWidget({
super.key,
required this.subspaces,
required this.onPressed,
required this.isTagsAndSubspaceModelDisabled,
required this.screenWidth,
required this.editChipOnTap,
});
double screenWidth;
bool isTagsAndSubspaceModelDisabled;
final void Function() editChipOnTap;
List<SubspaceModel>? subspaces;
final void Function() onPressed;
@override
State<SubSpacePartWidget> createState() => _SubSpacePartWidgetState();
}
class _SubSpacePartWidgetState extends State<SubSpacePartWidget> {
@override
Widget build(BuildContext context) {
return Column(
children: [
widget.subspaces == null || widget.subspaces!.isEmpty
? TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
overlayColor: ColorsManager.transparentColor,
),
onPressed: widget.onPressed,
child: ButtonContentWidget(
icon: Icons.add,
label: 'Create Sub Spaces',
disabled: widget.isTagsAndSubspaceModelDisabled,
),
)
: SizedBox(
width: widget.screenWidth * 0.25,
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: ColorsManager.textFieldGreyColor,
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: ColorsManager.textFieldGreyColor,
width: 3.0, // Border width
),
),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
if (widget.subspaces != null)
...widget.subspaces!.map((subspace) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SubspaceNameDisplayWidget(
text: subspace.subspaceName,
validateName: (updatedName) {
bool nameExists = widget.subspaces!.any((s) {
bool isSameId =
s.internalId == subspace.internalId;
bool isSameName =
s.subspaceName.trim().toLowerCase() ==
updatedName.trim().toLowerCase();
return !isSameId && isSameName;
});
return !nameExists;
},
onNameChanged: (updatedName) {
setState(() {
subspace.subspaceName = updatedName;
});
},
),
],
);
}),
EditChip(
onTap: widget.editChipOnTap,
)
],
),
),
)
],
);
}
}

View File

@ -30,28 +30,13 @@ class CurvedLinePainter extends CustomPainter {
Offset end = connection.endSpace.position + Offset end = connection.endSpace.position +
const Offset(75, 0); // Center top of end space const Offset(75, 0); // Center top of end space
if (connection.direction == 'down') { // Curved line for down connections
// Curved line for down connections final controlPoint = Offset((start.dx + end.dx) / 2, start.dy + 50);
final controlPoint = Offset((start.dx + end.dx) / 2, start.dy + 50); final path = Path()
final path = Path() ..moveTo(start.dx, start.dy)
..moveTo(start.dx, start.dy) ..quadraticBezierTo(controlPoint.dx, controlPoint.dy, end.dx, end.dy);
..quadraticBezierTo(controlPoint.dx, controlPoint.dy, end.dx, end.dy); canvas.drawPath(path, paint);
canvas.drawPath(path, paint);
} else if (connection.direction == 'right') {
start = connection.startSpace.position +
const Offset(150, 30); // Right center
end = connection.endSpace.position + const Offset(0, 30); // Left center
canvas.drawLine(start, end, paint);
} else if (connection.direction == 'left') {
start =
connection.startSpace.position + const Offset(0, 30); // Left center
end = connection.endSpace.position +
const Offset(150, 30); // Right center
canvas.drawLine(start, end, paint);
}
final dotPaint = Paint()..color = ColorsManager.blackColor; final dotPaint = Paint()..color = ColorsManager.blackColor;
canvas.drawCircle(start, 5, dotPaint); // Start dot canvas.drawCircle(start, 5, dotPaint); // Start dot
canvas.drawCircle(end, 5, dotPaint); // End dot canvas.drawCircle(end, 5, dotPaint); // End dot

View File

@ -1,6 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/common/edit_chip.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart';
@ -9,6 +7,11 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_pr
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/devices_part_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/icon_choose_part_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/space_model_linking_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/space_name_textfield_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/sub_space_part_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart';
@ -16,8 +19,6 @@ import 'package:syncrow_web/pages/spaces_management/helper/space_helper.dart';
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/link_space_model/view/link_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.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/button_content_widget.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/space_icon_const.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart';
@ -82,8 +83,10 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
super.initState(); super.initState();
selectedIcon = widget.icon ?? Assets.location; selectedIcon = widget.icon ?? Assets.location;
nameController = TextEditingController(text: widget.name ?? ''); nameController = TextEditingController(text: widget.name ?? '');
selectedProducts = widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; selectedProducts =
isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty; widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty;
if (widget.currentSpaceModel != null) { if (widget.currentSpaceModel != null) {
subspaces = []; subspaces = [];
tags = []; tags = [];
@ -96,13 +99,15 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isSpaceModelDisabled = bool isSpaceModelDisabled = (tags != null && tags!.isNotEmpty ||
(tags != null && tags!.isNotEmpty || subspaces != null && subspaces!.isNotEmpty); subspaces != null && subspaces!.isNotEmpty);
bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null); bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null);
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
return AlertDialog( return AlertDialog(
title: widget.isEdit ? const Text('Edit Space') : const Text('Create New Space'), title: widget.isEdit
? const Text('Edit Space')
: const Text('Create New Space'),
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
content: SizedBox( content: SizedBox(
width: screenWidth * 0.5, width: screenWidth * 0.5,
@ -112,50 +117,10 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
children: [ children: [
Expanded( Expanded(
flex: 1, flex: 1,
child: Column( child: IconChoosePartWidget(
mainAxisAlignment: MainAxisAlignment.center, selectedIcon: selectedIcon,
// crossAxisAlignment: CrossAxisAlignment.center, showIconSelection: _showIconSelectionDialog,
children: [ screenWidth: screenWidth,
const SizedBox(height: 50),
Stack(
alignment: Alignment.center,
children: [
Container(
width: screenWidth * 0.1,
height: screenWidth * 0.1,
decoration: const BoxDecoration(
color: ColorsManager.boxColor,
shape: BoxShape.circle,
),
),
SvgPicture.asset(
selectedIcon,
width: screenWidth * 0.04,
height: screenWidth * 0.04,
),
Positioned(
top: 20,
right: 20,
child: InkWell(
onTap: _showIconSelectionDialog,
child: Container(
width: 24,
height: 24,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: SvgPicture.asset(
Assets.iconEdit,
width: 16,
height: 16,
),
),
),
),
],
),
],
), ),
), ),
const SizedBox(width: 20), const SizedBox(width: 20),
@ -164,342 +129,146 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox( SpaceNameTextfieldWidget(
width: screenWidth * 0.25, isNameFieldExist: isNameFieldExist,
child: TextField( isNameFieldInvalid: isNameFieldInvalid,
controller: nameController, nameController: nameController,
onChanged: (value) { screenWidth: screenWidth,
enteredName = value.trim(); onChange: (value) {
setState(() { enteredName = value.trim();
isNameFieldExist = false; setState(() {
isOkButtonEnabled = false; isNameFieldExist = false;
isNameFieldInvalid = value.isEmpty; isOkButtonEnabled = false;
isNameFieldInvalid = value.isEmpty;
if (!isNameFieldInvalid) { if (!isNameFieldInvalid) {
if (SpaceHelper.isNameConflict( if (SpaceHelper.isNameConflict(
value, widget.parentSpace, widget.editSpace)) { value, widget.parentSpace, widget.editSpace)) {
isNameFieldExist = true; isNameFieldExist = true;
isOkButtonEnabled = false; isOkButtonEnabled = false;
} else { } else {
isNameFieldExist = false; isNameFieldExist = false;
isOkButtonEnabled = true; isOkButtonEnabled = true;
}
} }
}); }
}, });
style: Theme.of(context).textTheme.bodyMedium, },
decoration: InputDecoration(
hintText: 'Please enter the name',
hintStyle: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: ColorsManager.lightGrayColor),
filled: true,
fillColor: ColorsManager.boxColor,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(
color: isNameFieldInvalid || isNameFieldExist
? ColorsManager.red
: ColorsManager.boxColor,
width: 1.5,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(
color: ColorsManager.boxColor,
width: 1.5,
),
),
),
),
), ),
if (isNameFieldInvalid)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'*Space name should not be empty.',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.red),
),
),
if (isNameFieldExist)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'*Name already exist',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.red),
),
),
const SizedBox(height: 10), const SizedBox(height: 10),
selectedSpaceModel == null // SpaceModelLinkingWidget(
? TextButton( // isSpaceModelDisabled: true,
style: TextButton.styleFrom( // // isSpaceModelDisabled,
padding: EdgeInsets.zero, // onPressed: () {
), // isSpaceModelDisabled
onPressed: () { // ? null
isSpaceModelDisabled ? null : _showLinkSpaceModelDialog(context); // : _showLinkSpaceModelDialog(context);
}, // },
child: ButtonContentWidget( // onDeleted: () => setState(() {
svgAssets: Assets.link, // selectedSpaceModel = null;
label: 'Link a space model', // subspaces = widget.subspaces ?? [];
disabled: isSpaceModelDisabled, // tags = widget.tags ?? [];
), // }),
) // screenWidth: screenWidth,
: Container( // selectedSpaceModel: selectedSpaceModel,
width: screenWidth * 0.25, // ),
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
decoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10),
),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
Chip(
label: Text(
selectedSpaceModel?.modelName ?? '',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: ColorsManager.spaceColor),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: const BorderSide(
color: ColorsManager.transparentColor,
width: 0,
),
),
deleteIcon: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.lightGrayColor,
width: 1.5,
),
),
child: const Icon(
Icons.close,
size: 16,
color: ColorsManager.lightGrayColor,
),
),
onDeleted: () => setState(() {
this.selectedSpaceModel = null;
subspaces = widget.subspaces ?? [];
tags = widget.tags ?? [];
})),
],
),
),
const SizedBox(height: 25), const SizedBox(height: 25),
Row( // Row(
children: [ // children: [
const Expanded( // const Expanded(
child: Divider( // child: Divider(
color: ColorsManager.neutralGray, // color: ColorsManager.neutralGray,
thickness: 1.0, // thickness: 1.0,
), // ),
), // ),
Padding( // Padding(
padding: const EdgeInsets.symmetric(horizontal: 6.0), // padding: const EdgeInsets.symmetric(horizontal: 6.0),
child: Text( // child: Text(
'OR', // 'OR',
style: Theme.of(context) // style: Theme.of(context)
.textTheme // .textTheme
.bodyMedium // .bodyMedium
?.copyWith(fontWeight: FontWeight.bold), // ?.copyWith(fontWeight: FontWeight.bold),
), // ),
), // ),
const Expanded( // const Expanded(
child: Divider( // child: Divider(
color: ColorsManager.neutralGray, // color: ColorsManager.neutralGray,
thickness: 1.0, // thickness: 1.0,
), // ),
), // ),
], // ],
// ),
const SizedBox(height: 25),
SubSpacePartWidget(
subspaces: subspaces,
onPressed: () {
isTagsAndSubspaceModelDisabled
? null
: _showSubSpaceDialog(
context,
enteredName,
[],
false,
widget.products,
subspaces,
);
},
isTagsAndSubspaceModelDisabled:
isTagsAndSubspaceModelDisabled,
screenWidth: screenWidth,
editChipOnTap: () async {
_showSubSpaceDialog(
context,
enteredName,
[],
true,
widget.products,
subspaces,
);
},
), ),
const SizedBox(height: 25),
subspaces == null || subspaces!.isEmpty
? TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
overlayColor: ColorsManager.transparentColor,
),
onPressed: () async {
isTagsAndSubspaceModelDisabled
? null
: _showSubSpaceDialog(
context, enteredName, [], false, widget.products, subspaces);
},
child: ButtonContentWidget(
icon: Icons.add,
label: 'Create Sub Space',
disabled: isTagsAndSubspaceModelDisabled,
),
)
: SizedBox(
width: screenWidth * 0.25,
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: ColorsManager.textFieldGreyColor,
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: ColorsManager.textFieldGreyColor,
width: 3.0, // Border width
),
),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
if (subspaces != null)
...subspaces!.map((subspace) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SubspaceNameDisplayWidget(
text: subspace.subspaceName,
validateName: (updatedName) {
bool nameExists = subspaces!.any((s) {
bool isSameId = s.internalId == subspace.internalId;
bool isSameName =
s.subspaceName.trim().toLowerCase() ==
updatedName.trim().toLowerCase();
return !isSameId && isSameName;
});
return !nameExists;
},
onNameChanged: (updatedName) {
setState(() {
subspace.subspaceName = updatedName;
});
},
),
],
);
}),
EditChip(
onTap: () async {
_showSubSpaceDialog(context, enteredName, [], true,
widget.products, subspaces);
},
)
],
),
),
),
const SizedBox(height: 10), const SizedBox(height: 10),
(tags?.isNotEmpty == true || DevicesPartWidget(
subspaces?.any((subspace) => subspace.tags?.isNotEmpty == true) == true) tags: tags,
? SizedBox( subspaces: subspaces,
width: screenWidth * 0.25, screenWidth: screenWidth,
child: Container( isTagsAndSubspaceModelDisabled:
padding: const EdgeInsets.all(8.0), isTagsAndSubspaceModelDisabled,
decoration: BoxDecoration( onEditChip: () async {
color: ColorsManager.textFieldGreyColor, await showDialog(
borderRadius: BorderRadius.circular(15), context: context,
border: Border.all( builder: (context) => AssignTagDialog(
color: ColorsManager.textFieldGreyColor, products: widget.products,
width: 3.0, // Border width subspaces: subspaces,
), allTags: widget.allTags,
), addedProducts:
child: Wrap( TagHelper.createInitialSelectedProductsForTags(
spacing: 8.0, tags ?? [], subspaces),
runSpacing: 8.0, title: 'Edit Device',
children: [ initialTags: TagHelper.generateInitialForTags(
// Combine tags from spaceModel and subspaces spaceTags: tags, subspaces: subspaces),
...TagHelper.groupTags([ spaceName: widget.name ?? '',
...?tags, projectTags: widget.projectTags,
...?subspaces?.expand((subspace) => subspace.tags ?? []) onSave: (updatedTags, updatedSubspaces) {
]).entries.map( setState(() {
(entry) => Chip( tags = updatedTags;
avatar: SizedBox( subspaces = updatedSubspaces;
width: 24, });
height: 24,
child: SvgPicture.asset(
entry.key.icon ?? 'assets/icons/gateway.svg',
fit: BoxFit.contain,
),
),
label: Text(
'x${entry.value}', // Show count
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.spaceColor),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(
color: ColorsManager.spaceColor,
),
),
),
),
EditChip(onTap: () async {
await showDialog(
context: context,
builder: (context) => AssignTagDialog(
products: widget.products,
subspaces: subspaces,
allTags: widget.allTags,
addedProducts:
TagHelper.createInitialSelectedProductsForTags(
tags ?? [], subspaces),
title: 'Edit Device',
initialTags: TagHelper.generateInitialForTags(
spaceTags: tags, subspaces: subspaces),
spaceName: widget.name ?? '',
projectTags: widget.projectTags,
onSave: (updatedTags, updatedSubspaces) {
setState(() {
tags = updatedTags;
subspaces = updatedSubspaces;
});
},
),
);
})
],
),
),
)
: TextButton(
onPressed: () {
isTagsAndSubspaceModelDisabled
? null
: _showTagCreateDialog(
context,
enteredName,
widget.isEdit,
widget.products,
);
}, },
style: TextButton.styleFrom( ),
padding: EdgeInsets.zero, );
), },
child: ButtonContentWidget( onTextButtonPressed: () {
icon: Icons.add, isTagsAndSubspaceModelDisabled
label: 'Add Devices', ? null
disabled: isTagsAndSubspaceModelDisabled, : _showTagCreateDialog(
)) context,
enteredName,
widget.isEdit,
widget.products,
);
},
)
], ],
), ),
), ),
@ -529,17 +298,32 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
} else if (isNameFieldExist) { } else if (isNameFieldExist) {
return; return;
} else { } else {
String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? ''); String newName = enteredName.isNotEmpty
? enteredName
: (widget.name ?? '');
if (newName.isNotEmpty) { if (newName.isNotEmpty) {
widget.onCreateSpace(newName, selectedIcon, selectedProducts, if (tags != null && tags!.isNotEmpty) {
selectedSpaceModel, subspaces, tags); if (tags!.any(
(tag) => tag.uuid == null || tag.uuid!.isEmpty,
)) {
return;
}
}
widget.onCreateSpace(
newName,
selectedIcon,
selectedProducts,
selectedSpaceModel,
subspaces,
tags);
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
} }
}, },
borderRadius: 10, borderRadius: 10,
backgroundColor: backgroundColor: isOkButtonEnabled
isOkButtonEnabled ? ColorsManager.secondaryColor : ColorsManager.grayColor, ? ColorsManager.secondaryColor
: ColorsManager.grayColor,
foregroundColor: ColorsManager.whiteColors, foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'), child: const Text('OK'),
), ),
@ -550,6 +334,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
//dialooogggs
void _showIconSelectionDialog() { void _showIconSelectionDialog() {
showDialog( showDialog(
context: context, context: context,
@ -586,26 +371,50 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
void _showSubSpaceDialog(BuildContext context, String name, final List<Tag>? spaceTags, void _showSubSpaceDialog(
bool isEdit, List<ProductModel>? products, final List<SubspaceModel>? existingSubSpaces) { BuildContext context,
String name,
final List<Tag>? spaceTags,
bool isEdit,
List<ProductModel>? products,
final List<SubspaceModel>? existingSubSpaces,
) {
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return CreateSubSpaceDialog( return CreateSubSpaceDialog(
spaceName: name, spaceName: name,
dialogTitle: isEdit ? 'Edit Sub-space' : 'Create Sub-space', dialogTitle: isEdit ? 'Edit Sub-spaces' : 'Create Sub-spaces',
products: products, products: products,
existingSubSpaces: existingSubSpaces, existingSubSpaces: existingSubSpaces,
onSave: (slectedSubspaces) { onSave: (slectedSubspaces, updatedSubSpaces) {
final List<Tag> tagsToAppendToSpace = []; final List<Tag> tagsToAppendToSpace = [];
if (slectedSubspaces != null) { if (slectedSubspaces != null && slectedSubspaces.isNotEmpty) {
final updatedIds = slectedSubspaces.map((s) => s.internalId).toSet(); final updatedIds =
slectedSubspaces.map((s) => s.internalId).toSet();
if (existingSubSpaces != null) { if (existingSubSpaces != null) {
final deletedSubspaces = final deletedSubspaces = existingSubSpaces
existingSubSpaces.where((s) => !updatedIds.contains(s.internalId)).toList(); .where((s) => !updatedIds.contains(s.internalId))
.toList();
for (var s in deletedSubspaces) { for (var s in deletedSubspaces) {
if (s.tags != null) { if (s.tags != null) {
s.tags!.forEach(
(tag) => tag.location = null,
);
tagsToAppendToSpace.addAll(s.tags!);
}
}
}
} else {
if (existingSubSpaces != null) {
final deletedSubspaces = existingSubSpaces;
for (var s in deletedSubspaces) {
if (s.tags != null) {
s.tags!.forEach(
(tag) => tag.location = null,
);
tagsToAppendToSpace.addAll(s.tags!); tagsToAppendToSpace.addAll(s.tags!);
} }
} }
@ -623,15 +432,16 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
void _showTagCreateDialog( void _showTagCreateDialog(BuildContext context, String name, bool isEdit,
BuildContext context, String name, bool isEdit, List<ProductModel>? products) { List<ProductModel>? products) {
isEdit isEdit
? showDialog( ? showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return AssignTagDialog( return AssignTagDialog(
title: 'Edit Device', title: 'Edit Device',
addedProducts: TagHelper.createInitialSelectedProductsForTags(tags, subspaces), addedProducts: TagHelper.createInitialSelectedProductsForTags(
tags, subspaces),
spaceName: name, spaceName: name,
products: products, products: products,
subspaces: subspaces, subspaces: subspaces,
@ -646,7 +456,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) { if (subspaces != null) {
for (final subspace in subspaces!) { for (final subspace in subspaces!) {
for (final selectedSubspace in selectedSubspaces) { for (final selectedSubspace in selectedSubspaces) {
if (subspace.subspaceName == selectedSubspace.subspaceName) { if (subspace.subspaceName ==
selectedSubspace.subspaceName) {
subspace.tags = selectedSubspace.tags; subspace.tags = selectedSubspace.tags;
} }
} }
@ -670,7 +481,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
allTags: widget.allTags, allTags: widget.allTags,
projectTags: widget.projectTags, projectTags: widget.projectTags,
initialSelectedProducts: initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags(tags, subspaces), TagHelper.createInitialSelectedProductsForTags(
tags, subspaces),
onSave: (selectedSpaceTags, selectedSubspaces) { onSave: (selectedSpaceTags, selectedSubspaces) {
setState(() { setState(() {
tags = selectedSpaceTags; tags = selectedSpaceTags;
@ -680,7 +492,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) { if (subspaces != null) {
for (final subspace in subspaces!) { for (final subspace in subspaces!) {
for (final selectedSubspace in selectedSubspaces) { for (final selectedSubspace in selectedSubspaces) {
if (subspace.subspaceName == selectedSubspace.subspaceName) { if (subspace.subspaceName ==
selectedSubspace.subspaceName) {
subspace.tags = selectedSubspace.tags; subspace.tags = selectedSubspace.tags;
} }
} }

View File

@ -5,7 +5,7 @@ class PlusButtonWidget extends StatelessWidget {
final int index; final int index;
final String direction; final String direction;
final Offset offset; final Offset offset;
final Function(int index, Offset newPosition, String direction) onButtonTap; final Function(int index, Offset newPosition) onButtonTap;
const PlusButtonWidget({ const PlusButtonWidget({
super.key, super.key,
@ -17,35 +17,21 @@ class PlusButtonWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Positioned( return GestureDetector(
left: offset.dx, onTap: () {
top: offset.dy, onButtonTap(index, const Offset(0, 150));
child: GestureDetector( },
onTap: () { child: Container(
Offset newPosition; width: 30,
switch (direction) { height: 30,
case 'left': decoration: const BoxDecoration(
newPosition = const Offset(-200, 0); color: ColorsManager.spaceColor,
break; shape: BoxShape.circle,
case 'right': ),
newPosition = const Offset(200, 0); child: const Icon(
break; Icons.add,
case 'down': color: ColorsManager.whiteColors,
newPosition = const Offset(0, 150); size: 20,
break;
default:
newPosition = Offset.zero;
}
onButtonTap(index, newPosition, direction);
},
child: Container(
width: 30,
height: 30,
decoration: const BoxDecoration(
color: ColorsManager.spaceColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.add, color: ColorsManager.whiteColors, size: 20),
), ),
), ),
); );

View File

@ -7,7 +7,7 @@ class SpaceCardWidget extends StatelessWidget {
final Offset position; final Offset position;
final bool isHovered; final bool isHovered;
final Function(int index, bool isHovered) onHoverChanged; final Function(int index, bool isHovered) onHoverChanged;
final Function(int index, Offset newPosition, String direction) onButtonTap; final Function(int index, Offset newPosition) onButtonTap;
final Widget Function(int index) buildSpaceContainer; final Widget Function(int index) buildSpaceContainer;
final ValueChanged<Offset> onPositionChanged; final ValueChanged<Offset> onPositionChanged;
@ -25,35 +25,34 @@ class SpaceCardWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return MouseRegion(
behavior: HitTestBehavior.opaque, onEnter: (_) => onHoverChanged(index, true),
onPanUpdate: (details) { onExit: (_) => onHoverChanged(index, false),
// Call the provided callback to update the position child: SizedBox(
final newPosition = position + details.delta; width: 140, // Make sure this covers both card and plus button
onPositionChanged(newPosition); height: 90,
},
child: MouseRegion(
onEnter: (_) {
// Call the provided callback to handle hover state
onHoverChanged(index, true);
},
onExit: (_) {
// Call the provided callback to handle hover state
onHoverChanged(index, false);
},
child: Stack( child: Stack(
clipBehavior: Clip clipBehavior: Clip.none,
.none, // Allow hovering elements to be displayed outside the boundary
children: [ children: [
buildSpaceContainer(index), // Build the space container // Main card
if (isHovered) ...[ Container(
PlusButtonWidget( width: 140,
index: index, height: 80,
direction: 'down', alignment: Alignment.center,
offset: const Offset(63, 50), color: Colors.transparent,
onButtonTap: onButtonTap, child: buildSpaceContainer(index),
),
// Plus button (NO inner Positioned!)
if (isHovered)
Align(
alignment: Alignment.bottomCenter,
child: PlusButtonWidget(
index: index,
direction: 'down',
offset: Offset.zero,
onButtonTap: onButtonTap,
),
), ),
],
], ],
), ),
), ),

View File

@ -13,7 +13,8 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final existingTagCounts = <String, int>{}; final existingTagCounts = <String, int>{};
for (var tag in initialTags) { for (var tag in initialTags) {
if (tag.product != null) { if (tag.product != null) {
existingTagCounts[tag.product!.uuid] = (existingTagCounts[tag.product!.uuid] ?? 0) + 1; existingTagCounts[tag.product!.uuid] =
(existingTagCounts[tag.product!.uuid] ?? 0) + 1;
} }
} }
@ -22,14 +23,17 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
for (var selectedProduct in event.addedProducts) { for (var selectedProduct in event.addedProducts) {
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) { if (selectedProduct.count == 0 ||
tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId)); selectedProduct.count <= existingCount) {
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
continue; continue;
} }
final missingCount = selectedProduct.count - existingCount; final missingCount = selectedProduct.count - existingCount;
tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId)); tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (missingCount > 0) { if (missingCount > 0) {
tags.addAll(List.generate( tags.addAll(List.generate(
@ -85,7 +89,8 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final tags = List<Tag>.from(currentState.tags); final tags = List<Tag>.from(currentState.tags);
// Update the location // Update the location
tags[event.index] = tags[event.index].copyWith(location: event.location); tags[event.index] =
tags[event.index].copyWith(location: event.location);
final updatedTags = _calculateAvailableTags(projectTags, tags); final updatedTags = _calculateAvailableTags(projectTags, tags);
@ -117,7 +122,8 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final currentState = state; final currentState = state;
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags)..remove(event.tagToDelete); final tags = List<Tag>.from(currentState.tags)
..remove(event.tagToDelete);
// Recalculate available tags // Recalculate available tags
final updatedTags = _calculateAvailableTags(projectTags, tags); final updatedTags = _calculateAvailableTags(projectTags, tags);
@ -141,8 +147,10 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
// Get validation error for duplicate tags // Get validation error for duplicate tags
String? _getValidationError(List<Tag> tags) { String? _getValidationError(List<Tag> tags) {
final nonEmptyTags = final nonEmptyTags = tags
tags.map((tag) => tag.tag?.trim() ?? '').where((tag) => tag.isNotEmpty).toList(); .map((tag) => tag.tag?.trim() ?? '')
.where((tag) => tag.isNotEmpty)
.toList();
final duplicateTags = nonEmptyTags final duplicateTags = nonEmptyTags
.fold<Map<String, int>>({}, (map, tag) { .fold<Map<String, int>>({}, (map, tag) {
@ -168,9 +176,11 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
.toSet(); .toSet();
final availableTags = allTags final availableTags = allTags
.where((tag) => tag.tag != null && !selectedTagSet.contains(tag.tag!.trim())) .where((tag) =>
tag.tag != null && !selectedTagSet.contains(tag.tag!.trim()))
.toList(); .toList();
return availableTags; return projectTags;
// availableTags;
} }
} }

View File

@ -1,10 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/dialog_dropdown.dart';
import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.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/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
@ -12,9 +7,10 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.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_event.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/widgets/assign_tags_tables_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:uuid/uuid.dart';
import 'widgets/save_add_device_row_widget.dart';
class AssignTagDialog extends StatelessWidget { class AssignTagDialog extends StatelessWidget {
final List<ProductModel>? products; final List<ProductModel>? products;
@ -29,7 +25,7 @@ class AssignTagDialog extends StatelessWidget {
final List<Tag> projectTags; final List<Tag> projectTags;
const AssignTagDialog( const AssignTagDialog(
{Key? key, {super.key,
required this.products, required this.products,
required this.subspaces, required this.subspaces,
required this.addedProducts, required this.addedProducts,
@ -39,13 +35,14 @@ class AssignTagDialog extends StatelessWidget {
required this.spaceName, required this.spaceName,
required this.title, required this.title,
this.onSave, this.onSave,
required this.projectTags}) required this.projectTags});
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<String> locations = final List<String> locations = (subspaces ?? [])
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space'); .map((subspace) => subspace.subspaceName)
.toList()
..add('Main Space');
return BlocProvider( return BlocProvider(
create: (_) => AssignTagBloc(projectTags) create: (_) => AssignTagBloc(projectTags)
@ -67,131 +64,31 @@ class AssignTagDialog extends StatelessWidget {
content: SingleChildScrollView( content: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
ClipRRect( AssignTagsTable(
borderRadius: BorderRadius.circular(20), controllers: controllers,
child: DataTable( locations: locations,
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey), tags: state.tags,
key: ValueKey(state.tags.length), updatedTags: state.updatedTags,
border: TableBorder.all( onDeleteDevice: ({required index, required tag}) {
color: ColorsManager.dataHeaderGrey, context
width: 1, .read<AssignTagBloc>()
borderRadius: BorderRadius.circular(20), .add(DeleteTag(tagToDelete: tag, tags: state.tags));
),
columns: [
DataColumn(
label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Device', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
numeric: false,
label: Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label:
Text('Location', style: Theme.of(context).textTheme.bodyMedium)),
],
rows: state.tags.isEmpty
? [
DataRow(cells: [
DataCell(
Center(
child: Text('No Data Available',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: ColorsManager.lightGrayColor,
)),
),
),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
])
]
: List.generate(state.tags.length, (index) {
final tag = state.tags[index];
final controller = controllers[index];
return DataRow( controllers.removeAt(index);
cells: [ },
DataCell(Text((index + 1).toString())), onTagDropDownSelected: ({required index, required tag}) {
DataCell( context.read<AssignTagBloc>().add(UpdateTagEvent(
Row( index: index,
mainAxisAlignment: MainAxisAlignment.spaceBetween, tag: tag,
children: [ ));
Expanded( },
child: Text( onLocationDropDownSelected: (
tag.product?.name ?? 'Unknown', {required index, required location}) {
overflow: TextOverflow.ellipsis, context.read<AssignTagBloc>().add(UpdateLocation(
)), index: index,
const SizedBox(width: 10), location: location,
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<AssignTagBloc>().add(
DeleteTag(tagToDelete: tag, tags: state.tags));
controllers.removeAt(index);
},
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,
child: TagDialogTextfieldDropdown(
key: ValueKey('dropdown_${const Uuid().v4()}_$index'),
items: state.updatedTags,
product: tag.product?.uuid ?? 'Unknown',
initialValue: tag,
onSelected: (value) {
controller.text = value.tag ?? '';
context.read<AssignTagBloc>().add(UpdateTagEvent(
index: index,
tag: value,
));
},
),
),
),
),
DataCell(
SizedBox(
width: double.infinity,
child: DialogDropdown(
items: locations,
selectedValue: tag.location ?? 'Main Space',
onSelected: (value) {
context.read<AssignTagBloc>().add(UpdateLocation(
index: index,
location: value,
));
},
)),
),
],
);
}),
),
), ),
if (state.errorMessage != null) if (state.errorMessage != null)
Text(state.errorMessage!, Text(state.errorMessage!,
@ -203,69 +100,15 @@ class AssignTagDialog extends StatelessWidget {
), ),
), ),
actions: [ actions: [
Row( SaveAddDeviceRowWidget(
mainAxisAlignment: MainAxisAlignment.spaceAround, subspaces: subspaces,
children: [ products: products,
const SizedBox(width: 10), projectTags: projectTags,
Expanded( spaceName: spaceName,
child: Builder( onSave: onSave,
builder: (buttonContext) => CancelButton( allTags: allTags,
label: 'Add New Device', tags: state.tags,
onPressed: () async { isSaveEnabled: state.isSaveEnabled,
final updatedTags = List<Tag>.from(state.tags);
final result = TagHelper.processTags(updatedTags, subspaces);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
Navigator.of(context).pop();
await showDialog(
context: context,
builder: (context) => AddDeviceTypeWidget(
products: products,
subspaces: processedSubspaces,
projectTags: projectTags,
initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags(
processedTags, processedSubspaces),
spaceName: spaceName,
spaceTags: processedTags,
isCreate: false,
onSave: onSave,
allTags: allTags,
),
);
},
),
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
borderRadius: 10,
backgroundColor: ColorsManager.secondaryColor,
foregroundColor: state.isSaveEnabled
? ColorsManager.whiteColors
: ColorsManager.whiteColorsWithOpacity,
onPressed: state.isSaveEnabled
? () async {
final updatedTags = List<Tag>.from(state.tags);
final result = TagHelper.processTags(updatedTags, subspaces);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
onSave?.call(processedTags, processedSubspaces);
Navigator.of(context).pop();
}
: null,
child: const Text('Save'),
),
),
const SizedBox(width: 10),
],
), ),
], ],
); );

View File

@ -0,0 +1,100 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import '../../../add_device_type/views/add_device_type_widget.dart';
import '../../../helper/tag_helper.dart';
class SaveAddDeviceRowWidget extends StatelessWidget {
const SaveAddDeviceRowWidget({
super.key,
required this.subspaces,
required this.products,
required this.projectTags,
required this.spaceName,
required this.onSave,
required this.allTags,
required this.tags,
required this.isSaveEnabled,
});
final List<Tag> tags;
final List<SubspaceModel>? subspaces;
final List<ProductModel>? products;
final List<Tag> projectTags;
final String spaceName;
final Function(List<Tag> p1, List<SubspaceModel>? p2)? onSave;
final List<String>? allTags;
final bool isSaveEnabled;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const SizedBox(width: 10),
Expanded(
child: Builder(
builder: (buttonContext) => CancelButton(
label: 'Add New Device',
onPressed: () async {
final updatedTags = List<Tag>.from(tags);
final result = TagHelper.processTags(updatedTags, subspaces);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceModel>.from(
result['subspaces'] as List<dynamic>);
Navigator.of(context).pop();
await showDialog(
context: context,
builder: (context) => AddDeviceTypeWidget(
products: products,
subspaces: processedSubspaces,
projectTags: projectTags,
initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags(
processedTags, processedSubspaces),
spaceName: spaceName,
spaceTags: processedTags,
isCreate: false,
onSave: onSave,
allTags: allTags,
),
);
},
),
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
borderRadius: 10,
backgroundColor: ColorsManager.secondaryColor,
foregroundColor: isSaveEnabled
? ColorsManager.whiteColors
: ColorsManager.whiteColorsWithOpacity,
onPressed: isSaveEnabled
? () async {
final updatedTags = List<Tag>.from(tags);
final result =
TagHelper.processTags(updatedTags, subspaces);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceModel>.from(
result['subspaces'] as List<dynamic>);
onSave?.call(processedTags, processedSubspaces);
Navigator.of(context).pop();
}
: null,
child: const Text('Save'),
),
),
const SizedBox(width: 10),
],
);
}
}

View File

@ -1,9 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/dialog_dropdown.dart';
import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.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/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
@ -12,11 +8,10 @@ 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/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/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/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'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:uuid/uuid.dart'; import 'widgets/RowOfCancelSaveWidget.dart';
import 'widgets/assign_tags_tables_widget.dart';
class AssignTagModelsDialog extends StatelessWidget { class AssignTagModelsDialog extends StatelessWidget {
final List<ProductModel>? products; final List<ProductModel>? products;
@ -53,8 +48,10 @@ class AssignTagModelsDialog extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<String> locations = final List<String> locations = (subspaces ?? [])
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space'); .map((subspace) => subspace.subspaceName)
.toList()
..add('Main Space');
return BlocProvider( return BlocProvider(
create: (_) => AssignTagModelBloc(projectTags) create: (_) => AssignTagModelBloc(projectTags)
@ -78,137 +75,38 @@ class AssignTagModelsDialog extends StatelessWidget {
content: SingleChildScrollView( content: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
ClipRRect( AssignTagsTable(
borderRadius: BorderRadius.circular(20), controllers: controllers,
child: DataTable( locations: locations,
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey), tags: state.tags,
key: ValueKey(state.tags.length), updatedTags: state.updatedTags,
border: TableBorder.all( onDeleteDevice: ({required index, required tag}) {
color: ColorsManager.dataHeaderGrey, context
width: 1, .read<AssignTagModelBloc>()
borderRadius: BorderRadius.circular(20), .add(DeleteTagModel(
), tagToDelete: tag,
columns: [ tags: state.tags,
DataColumn( ));
label: Text('#', style: Theme.of(context).textTheme.bodyMedium)), controllers.removeAt(index);
DataColumn( },
label: Text('Device', onTagDropDownSelected: (
style: Theme.of(context).textTheme.bodyMedium)), {required index, required tag}) {
DataColumn( context.read<AssignTagModelBloc>().add(
numeric: false, UpdateTag(
label: index: index,
Text('Tag', style: Theme.of(context).textTheme.bodyMedium)), tag: tag,
DataColumn( ),
label: Text('Location', );
style: Theme.of(context).textTheme.bodyMedium)), },
], onLocationDropDownSelected: (
rows: state.tags.isEmpty {required index, required location}) {
? [ context.read<AssignTagModelBloc>().add(
DataRow(cells: [ UpdateLocation(
DataCell( index: index,
Center( location: location,
child: Text('No Devices Available', ),
style: );
Theme.of(context).textTheme.bodyMedium?.copyWith( },
color: ColorsManager.lightGrayColor,
)),
),
),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
])
]
: List.generate(state.tags.length, (index) {
final tag = state.tags[index];
final controller = controllers[index];
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));
controllers.removeAt(index);
},
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,
child: TagDialogTextfieldDropdown(
key: ValueKey(
'dropdown_${const Uuid().v4()}_$index'),
product: tag.product?.uuid ?? 'Unknown',
items: state.updatedTags,
initialValue: tag,
onSelected: (value) {
controller.text = value.tag ?? '';
context.read<AssignTagModelBloc>().add(UpdateTag(
index: index,
tag: value,
));
},
),
),
),
),
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) if (state.errorMessage != null)
Text(state.errorMessage!, Text(state.errorMessage!,
@ -220,101 +118,16 @@ class AssignTagModelsDialog extends StatelessWidget {
), ),
), ),
actions: [ actions: [
Row( RowOfSaveCancelWidget(
mainAxisAlignment: MainAxisAlignment.spaceAround, subspaces: subspaces,
children: [ products: products,
const SizedBox(width: 10), allTags: allTags,
Expanded( spaceName: spaceName,
child: Builder( otherSpaceModels: otherSpaceModels,
builder: (buttonContext) => CancelButton( pageContext: pageContext,
label: 'Add New Device', projectTags: projectTags,
onPressed: () async { spaceModel: spaceModel,
final updatedTags = List<Tag>.from(state.tags); allSpaceModels: allSpaceModels,
final result =
TagHelper.updateSubspaceTagModels(updatedTags, subspaces);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
if (context.mounted) {
Navigator.of(context).pop();
await showDialog<bool>(
barrierDismissible: false,
context: context,
builder: (dialogContext) => AddDeviceTypeModelWidget(
products: products,
subspaces: processedSubspaces,
isCreate: false,
initialSelectedProducts:
TagHelper.createInitialSelectedProducts(
processedTags, processedSubspaces),
allTags: allTags,
spaceName: spaceName,
otherSpaceModels: otherSpaceModels,
spaceTagModels: processedTags,
pageContext: pageContext,
projectTags: projectTags,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: updatedTags,
uuid: spaceModel?.uuid,
internalId: spaceModel?.internalId,
subspaceModels: processedSubspaces)),
);
}
},
),
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
borderRadius: 10,
backgroundColor: ColorsManager.secondaryColor,
foregroundColor: state.isSaveEnabled
? ColorsManager.whiteColors
: ColorsManager.whiteColorsWithOpacity,
onPressed: state.isSaveEnabled
? () async {
final updatedTags = List<Tag>.from(state.tags);
final result =
TagHelper.updateSubspaceTagModels(updatedTags, subspaces);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
Navigator.of(context).popUntil((route) => route.isFirst);
await showDialog(
context: context,
builder: (BuildContext dialogContext) {
return CreateSpaceModelDialog(
products: products,
allSpaceModels: allSpaceModels,
allTags: allTags,
projectTags: projectTags,
pageContext: pageContext,
otherSpaceModels: otherSpaceModels,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: processedTags,
uuid: spaceModel?.uuid,
internalId: spaceModel?.internalId,
subspaceModels: processedSubspaces),
);
},
);
}
: null,
child: const Text('Save'),
),
),
const SizedBox(width: 10),
],
), ),
], ],
); );

View File

@ -0,0 +1,152 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../utils/color_manager.dart';
import '../../../../common/buttons/cancel_button.dart';
import '../../../../common/buttons/default_button.dart';
import '../../../all_spaces/model/product_model.dart';
import '../../../all_spaces/model/tag.dart';
import '../../../helper/tag_helper.dart';
import '../../../space_model/models/space_template_model.dart';
import '../../../space_model/models/subspace_template_model.dart';
import '../../../space_model/widgets/dialog/create_space_model_dialog.dart';
import '../../../tag_model/views/add_device_type_model_widget.dart';
import '../../bloc/assign_tag_model_bloc.dart';
import '../../bloc/assign_tag_model_state.dart';
class RowOfSaveCancelWidget extends StatelessWidget {
const RowOfSaveCancelWidget({
super.key,
required this.subspaces,
required this.products,
required this.allTags,
required this.spaceName,
required this.otherSpaceModels,
required this.pageContext,
required this.projectTags,
required this.spaceModel,
required this.allSpaceModels,
});
final List<SubspaceTemplateModel>? subspaces;
final List<ProductModel>? products;
final List<String>? allTags;
final String spaceName;
final List<String>? otherSpaceModels;
final BuildContext? pageContext;
final List<Tag> projectTags;
final SpaceTemplateModel? spaceModel;
final List<SpaceTemplateModel>? allSpaceModels;
@override
Widget build(BuildContext context) {
return BlocBuilder<AssignTagModelBloc, AssignTagModelState>(
builder: (context, state) {
if (state is AssignTagModelLoaded) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const SizedBox(width: 10),
Expanded(
child: Builder(
builder: (buttonContext) => CancelButton(
label: 'Add New Device',
onPressed: () async {
final updatedTags = List<Tag>.from(state.tags);
final result = TagHelper.updateSubspaceTagModels(
updatedTags, subspaces);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
if (context.mounted) {
Navigator.of(context).pop();
await showDialog<bool>(
barrierDismissible: false,
context: context,
builder: (dialogContext) => AddDeviceTypeModelWidget(
products: products,
subspaces: processedSubspaces,
isCreate: false,
initialSelectedProducts:
TagHelper.createInitialSelectedProducts(
processedTags, processedSubspaces),
allTags: allTags,
spaceName: spaceName,
otherSpaceModels: otherSpaceModels,
spaceTagModels: processedTags,
pageContext: pageContext,
projectTags: projectTags,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: updatedTags,
uuid: spaceModel?.uuid,
internalId: spaceModel?.internalId,
subspaceModels: processedSubspaces)),
);
}
},
),
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
borderRadius: 10,
backgroundColor: ColorsManager.secondaryColor,
foregroundColor: state.isSaveEnabled
? ColorsManager.whiteColors
: ColorsManager.whiteColorsWithOpacity,
onPressed: state.isSaveEnabled
? () async {
final updatedTags = List<Tag>.from(state.tags);
final result = TagHelper.updateSubspaceTagModels(
updatedTags, subspaces);
final processedTags =
result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
Navigator.of(context)
.popUntil((route) => route.isFirst);
await showDialog(
context: context,
builder: (BuildContext dialogContext) {
return CreateSpaceModelDialog(
products: products,
allSpaceModels: allSpaceModels,
allTags: allTags,
projectTags: projectTags,
pageContext: pageContext,
otherSpaceModels: otherSpaceModels,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: processedTags,
uuid: spaceModel?.uuid,
internalId: spaceModel?.internalId,
subspaceModels: processedSubspaces),
);
},
);
}
: null,
child: const Text('Save'),
),
),
const SizedBox(width: 10),
],
);
} else {
return const SizedBox();
}
},
);
}
}

View File

@ -0,0 +1,155 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:uuid/uuid.dart';
import '../../../../../common/dialog_dropdown.dart';
import '../../../../../common/tag_dialog_textfield_dropdown.dart';
import '../../../../../utils/color_manager.dart';
class AssignTagsTable extends StatelessWidget {
const AssignTagsTable({
super.key,
required this.controllers,
required this.locations,
required this.tags,
required this.updatedTags,
required this.onDeleteDevice,
required this.onLocationDropDownSelected,
required this.onTagDropDownSelected,
});
final void Function({required Tag tag, required int index})
onTagDropDownSelected;
final void Function({required String location, required int index})
onLocationDropDownSelected;
final void Function({required Tag tag, required int index}) onDeleteDevice;
final List<Tag> tags;
final List<Tag> updatedTags;
final List<TextEditingController> controllers;
final List<String> locations;
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(20),
child: DataTable(
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
key: ValueKey(tags.length),
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,
label:
Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Location',
style: Theme.of(context).textTheme.bodyMedium)),
],
rows: tags.isEmpty
? [
DataRow(cells: [
DataCell(
Center(
child: Text('No Devices Available',
style:
Theme.of(context).textTheme.bodyMedium?.copyWith(
color: ColorsManager.lightGrayColor,
)),
),
),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
])
]
: List.generate(tags.length, (index) {
final tag = tags[index];
final controller = controllers[index];
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: () {
onDeleteDevice(tag: tag, index: index);
},
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,
child: TagDialogTextfieldDropdown(
key: ValueKey(
'dropdown_${const Uuid().v4()}_$index'),
product: tag.product?.uuid ?? 'Unknown',
items: updatedTags,
initialValue: tag,
onSelected: (value) {
controller.text = value.tag ?? '';
onTagDropDownSelected(tag: value, index: index);
},
),
),
),
),
DataCell(
SizedBox(
width: double.infinity,
child: DialogDropdown(
items: locations,
selectedValue: tag.location ?? 'Main Space',
onSelected: (value) {
onLocationDropDownSelected(
location: value, index: index);
},
)),
),
],
);
}),
),
);
}
}

View File

@ -77,7 +77,7 @@ class SubSpaceBloc extends Bloc<SubSpaceEvent, SubSpaceState> {
emit(SubSpaceState( emit(SubSpaceState(
updatedSubSpaces, updatedSubSpaces,
updatedSubspaceModels, updatedSubspaceModels,
errorMessage, errorMessage ,
updatedDuplicates, updatedDuplicates,
)); ));
}); });

View File

@ -1,23 +1,23 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.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/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_event.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_event.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_state.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_state.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'widgets/ok_cancel_sub_space_widget.dart';
import 'widgets/textfield_sub_space_dialog_widget.dart';
class CreateSubSpaceDialog extends StatefulWidget { class CreateSubSpaceDialog extends StatefulWidget {
final String dialogTitle; final String dialogTitle;
final List<SubspaceModel>? existingSubSpaces; final List<SubspaceModel>? existingSubSpaces;
final String? spaceName; final String? spaceName;
final List<ProductModel>? products; final List<ProductModel>? products;
final void Function(List<SubspaceModel>?)? onSave; final void Function(
List<SubspaceModel>?, List<UpdateSubspaceModel> updatedSubSpaces)? onSave;
const CreateSubSpaceDialog({ const CreateSubSpaceDialog({
required this.dialogTitle, required this.dialogTitle,
@ -33,14 +33,7 @@ class CreateSubSpaceDialog extends StatefulWidget {
} }
class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> { class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
late final TextEditingController _subspaceNameController; final TextEditingController _subspaceNameController = TextEditingController();
@override
void initState() {
_subspaceNameController = TextEditingController();
super.initState();
}
@override @override
void dispose() { void dispose() {
_subspaceNameController.dispose(); _subspaceNameController.dispose();
@ -79,83 +72,26 @@ class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
), ),
), ),
Row(
children: [
Text(
'press Enter to Save',
style: context.textTheme.headlineLarge?.copyWith(
fontWeight: FontWeight.w500,
fontSize: 10,
color: ColorsManager.grayColor,
),
),
const SizedBox(
width: 5,
),
const Icon(Icons.save_as_sharp, size: 10),
],
),
const SizedBox(height: 16), const SizedBox(height: 16),
Container( TextFieldSubSpaceDialogWidget(
width: context.screenWidth * 0.35, subspaceNameController: _subspaceNameController,
padding: const EdgeInsets.symmetric( subSpaces: state.subSpaces,
vertical: 10,
horizontal: 16,
),
decoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10),
),
child: Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
...state.subSpaces.asMap().entries.map(
(entry) {
final index = entry.key;
final subSpace = entry.value;
final lowerName = subSpace.subspaceName.toLowerCase();
final duplicateIndices = state.subSpaces
.asMap()
.entries
.where((e) =>
e.value.subspaceName.toLowerCase() == lowerName)
.map((e) => e.key)
.toList();
final isDuplicate = duplicateIndices.length > 1 &&
duplicateIndices.indexOf(index) != 0;
return SubspaceChip(
subSpace: SubspaceTemplateModel(
subspaceName: entry.value.subspaceName,
disabled: entry.value.disabled,
),
isDuplicate: isDuplicate,
onDeleted: () => context.read<SubSpaceBloc>().add(
RemoveSubSpace(subSpace),
),
);
},
),
SizedBox(
width: 200,
child: TextField(
controller: _subspaceNameController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: state.subSpaces.isEmpty
? 'Please enter the name'
: null,
hintStyle: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.lightGrayColor,
),
),
onSubmitted: (value) {
final trimmedValue = value.trim();
if (trimmedValue.isNotEmpty) {
context.read<SubSpaceBloc>().add(
AddSubSpace(
SubspaceModel(
subspaceName: trimmedValue,
disabled: false,
),
),
);
_subspaceNameController.clear();
}
},
style: context.textTheme.bodyMedium,
),
),
],
),
), ),
if (state.errorMessage.isNotEmpty) if (state.errorMessage.isNotEmpty)
Padding( Padding(
@ -168,36 +104,10 @@ class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( OkCancelSubSpaceWidget(
children: [ subspaceNameController: _subspaceNameController,
Expanded( errorMessage: state.errorMessage,
child: CancelButton( onSave: widget.onSave,
label: 'Cancel',
onPressed: () async {
Navigator.of(context).pop();
},
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
onPressed: state.errorMessage.isEmpty
? () {
final subSpacesBloc = context.read<SubSpaceBloc>();
final subSpaces = subSpacesBloc.state.subSpaces;
widget.onSave?.call(subSpaces);
Navigator.of(context).pop();
}
: null,
backgroundColor: ColorsManager.secondaryColor,
borderRadius: 10,
foregroundColor: state.errorMessage.isNotEmpty
? ColorsManager.whiteColorsWithOpacity
: ColorsManager.whiteColors,
child: const Text('OK'),
),
),
],
), ),
], ],
), ),

View File

@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import '../../bloc/subspace_bloc.dart';
import '../../bloc/subspace_event.dart';
class OkCancelSubSpaceWidget extends StatelessWidget {
const OkCancelSubSpaceWidget({
super.key,
required this.subspaceNameController,
required this.onSave,
required this.errorMessage,
});
final TextEditingController subspaceNameController;
final void Function(
List<SubspaceModel>?, List<UpdateSubspaceModel> updatedSubSpaces)? onSave;
final String errorMessage;
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: CancelButton(
label: 'Cancel',
onPressed: () async {
Navigator.of(context).pop();
},
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
onPressed: errorMessage.isEmpty
? () async {
final trimmedValue = subspaceNameController.text.trim();
final subSpacesBloc = context.read<SubSpaceBloc>();
if (trimmedValue.isNotEmpty) {
subSpacesBloc.add(
AddSubSpace(
SubspaceModel(
subspaceName: trimmedValue,
disabled: false,
),
),
);
subspaceNameController.clear();
}
await Future.delayed(const Duration(milliseconds: 10));
final subSpaces = subSpacesBloc.state.subSpaces;
onSave?.call(
subSpaces, subSpacesBloc.state.updatedSubSpaceModels);
Navigator.of(context).pop();
}
: null,
backgroundColor: ColorsManager.secondaryColor,
borderRadius: 10,
foregroundColor: errorMessage.isNotEmpty
? ColorsManager.whiteColorsWithOpacity
: ColorsManager.whiteColors,
child: const Text('OK'),
),
),
],
);
}
}

View File

@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import '../../../../../utils/color_manager.dart';
import '../../../all_spaces/model/subspace_model.dart';
import '../../../create_subspace_model/widgets/subspace_chip.dart';
import '../../../space_model/models/subspace_template_model.dart';
import '../../bloc/subspace_bloc.dart';
import '../../bloc/subspace_event.dart';
class TextFieldSubSpaceDialogWidget extends StatelessWidget {
const TextFieldSubSpaceDialogWidget({
super.key,
required TextEditingController subspaceNameController,
required this.subSpaces,
}) : _subspaceNameController = subspaceNameController;
final TextEditingController _subspaceNameController;
final List<SubspaceModel> subSpaces;
@override
Widget build(BuildContext context) {
return Container(
width: context.screenWidth * 0.35,
padding: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 16,
),
decoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10),
),
child: Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
...subSpaces.asMap().entries.map(
(entry) {
final index = entry.key;
final subSpace = entry.value;
final lowerName = subSpace.subspaceName.toLowerCase();
final duplicateIndices = subSpaces
.asMap()
.entries
.where((e) => e.value.subspaceName.toLowerCase() == lowerName)
.map((e) => e.key)
.toList();
final isDuplicate = duplicateIndices.length > 1 &&
duplicateIndices.indexOf(index) != 0;
return SubspaceChip(
subSpace: SubspaceTemplateModel(
subspaceName: entry.value.subspaceName,
disabled: entry.value.disabled,
),
isDuplicate: isDuplicate,
onDeleted: () => context.read<SubSpaceBloc>().add(
RemoveSubSpace(subSpace),
),
);
},
),
SizedBox(
width: 200,
child: TextField(
controller: _subspaceNameController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: subSpaces.isEmpty ? 'Please enter the name' : null,
hintStyle: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.lightGrayColor,
),
),
onSubmitted: (value) {
final trimmedValue = value.trim();
if (trimmedValue.isNotEmpty) {
context.read<SubSpaceBloc>().add(
AddSubSpace(
SubspaceModel(
subspaceName: trimmedValue,
disabled: false,
),
),
);
_subspaceNameController.clear();
}
},
style: context.textTheme.bodyMedium,
),
),
],
),
);
}
}

View File

@ -14,11 +14,11 @@ class CenterBodyWidget extends StatelessWidget {
if (state is InitialState) { if (state is InitialState) {
context.read<CenterBodyBloc>().add(CommunityStructureSelectedEvent()); context.read<CenterBodyBloc>().add(CommunityStructureSelectedEvent());
} }
if (state is CommunityStructureState) { if (state is CommunityStructureState) {
context.read<SpaceManagementBloc>().add(BlankStateEvent(context)); context.read<SpaceManagementBloc>().add(BlankStateEvent(context));
} }
if (state is SpaceModelState) { if (state is SpaceModelState) {
context.read<SpaceManagementBloc>().add(SpaceModelLoadEvent(context)); context.read<SpaceManagementBloc>().add(SpaceModelLoadEvent(context));
} }
@ -31,15 +31,19 @@ class CenterBodyWidget extends StatelessWidget {
children: [ children: [
GestureDetector( GestureDetector(
onTap: () { onTap: () {
context.read<CenterBodyBloc>().add(CommunityStructureSelectedEvent()); context
.read<CenterBodyBloc>()
.add(CommunityStructureSelectedEvent());
}, },
child: Text( child: Text(
'Community Structure', 'Community Structure',
style: Theme.of(context).textTheme.bodyLarge!.copyWith( style: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontWeight: state is CommunityStructureState || state is CommunitySelectedState fontWeight: state is CommunityStructureState ||
state is CommunitySelectedState
? FontWeight.bold ? FontWeight.bold
: FontWeight.normal, : FontWeight.normal,
color: state is CommunityStructureState || state is CommunitySelectedState color: state is CommunityStructureState ||
state is CommunitySelectedState
? Theme.of(context).textTheme.bodyLarge!.color ? Theme.of(context).textTheme.bodyLarge!.color
: Theme.of(context) : Theme.of(context)
.textTheme .textTheme
@ -50,26 +54,26 @@ class CenterBodyWidget extends StatelessWidget {
), ),
), ),
const SizedBox(width: 20), const SizedBox(width: 20),
GestureDetector( // GestureDetector(
onTap: () { // onTap: () {
context.read<CenterBodyBloc>().add(SpaceModelSelectedEvent()); // context.read<CenterBodyBloc>().add(SpaceModelSelectedEvent());
}, // },
child: Text( // child: Text(
'Space Model', // 'Space Model',
style: Theme.of(context).textTheme.bodyLarge!.copyWith( // style: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontWeight: state is SpaceModelState // fontWeight: state is SpaceModelState
? FontWeight.bold // ? FontWeight.bold
: FontWeight.normal, // : FontWeight.normal,
color: state is SpaceModelState // color: state is SpaceModelState
? Theme.of(context).textTheme.bodyLarge!.color // ? Theme.of(context).textTheme.bodyLarge!.color
: Theme.of(context) // : Theme.of(context)
.textTheme // .textTheme
.bodyLarge! // .bodyLarge!
.color! // .color!
.withOpacity(0.5), // .withOpacity(0.5),
), // ),
), // ),
), // ),
], ],
), ),
], ],

View File

@ -24,37 +24,22 @@ class AddDeviceTypeModelBloc
if (currentState is AddDeviceModelLoaded) { if (currentState is AddDeviceModelLoaded) {
final existingProduct = currentState.selectedProducts.firstWhere( final existingProduct = currentState.selectedProducts.firstWhere(
(p) => p.productId == event.productId, (p) => p.productId == event.selectedProduct.productId,
orElse: () => SelectedProduct( orElse: () => event.selectedProduct,
productId: event.productId,
count: 0,
productName: event.productName,
product: event.product,
),
); );
List<SelectedProduct> updatedProducts; List<SelectedProduct> updatedProducts;
if (event.count > 0) { if (event.selectedProduct.count > 0) {
if (!currentState.selectedProducts.contains(existingProduct)) { if (!currentState.selectedProducts.contains(existingProduct)) {
updatedProducts = [ updatedProducts = [
...currentState.selectedProducts, ...currentState.selectedProducts,
SelectedProduct( event.selectedProduct,
productId: event.productId,
count: event.count,
productName: event.productName,
product: event.product,
),
]; ];
} else { } else {
updatedProducts = currentState.selectedProducts.map((p) { updatedProducts = currentState.selectedProducts.map((p) {
if (p.productId == event.productId) { if (p.productId == event.selectedProduct.productId) {
return SelectedProduct( return event.selectedProduct;
productId: p.productId,
count: event.count,
productName: p.productName,
product: p.product,
);
} }
return p; return p;
}).toList(); }).toList();
@ -62,7 +47,7 @@ class AddDeviceTypeModelBloc
} else { } else {
// Remove the product if the count is 0 // Remove the product if the count is 0
updatedProducts = currentState.selectedProducts updatedProducts = currentState.selectedProducts
.where((p) => p.productId != event.productId) .where((p) => p.productId != event.selectedProduct.productId)
.toList(); .toList();
} }

View File

@ -10,20 +10,15 @@ abstract class AddDeviceTypeModelEvent extends Equatable {
List<Object> get props => []; List<Object> get props => [];
} }
class UpdateProductCountEvent extends AddDeviceTypeModelEvent { class UpdateProductCountEvent extends AddDeviceTypeModelEvent {
final String productId; final SelectedProduct selectedProduct;
final int count;
final String productName;
final ProductModel product;
UpdateProductCountEvent({required this.productId, required this.count, required this.productName, required this.product}); UpdateProductCountEvent({required this.selectedProduct});
@override @override
List<Object> get props => [productId, count]; List<Object> get props => [selectedProduct];
} }
class InitializeDeviceTypeModel extends AddDeviceTypeModelEvent { class InitializeDeviceTypeModel extends AddDeviceTypeModelEvent {
final List<Tag> initialTags; final List<Tag> initialTags;
final List<SelectedProduct> addedProducts; final List<SelectedProduct> addedProducts;

View File

@ -54,10 +54,10 @@ class DeviceTypeTileWidget extends StatelessWidget {
onCountChanged: (newCount) { onCountChanged: (newCount) {
context.read<AddDeviceTypeModelBloc>().add( context.read<AddDeviceTypeModelBloc>().add(
UpdateProductCountEvent( UpdateProductCountEvent(
productId: product.uuid, selectedProduct: product.toSelectedProduct(
count: newCount, newCount,
productName: product.catName, ),
product: product), ),
); );
}, },
), ),

View File

@ -22,6 +22,18 @@ class HTTPService {
); );
client.interceptors.add(serviceLocator.get<HTTPInterceptor>()); client.interceptors.add(serviceLocator.get<HTTPInterceptor>());
// Add this interceptor for logging requests and responses
// client.interceptors.add(
// LogInterceptor(
// request: true,
// requestHeader: true,
// requestBody: true,
// responseHeader: false,
// responseBody: true,
// error: true,
// logPrint: (object) => print(object),
// ),
// );
return client; return client;
} }

View File

@ -5,12 +5,15 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_m
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart'; import 'package:syncrow_web/utils/constants/api_const.dart';
import '../pages/spaces_management/all_spaces/model/subspace_model.dart';
class CommunitySpaceManagementApi { class CommunitySpaceManagementApi {
// Community Management APIs // Community Management APIs
Future<List<CommunityModel>> fetchCommunities(String projectId, Future<List<CommunityModel>> fetchCommunities(String projectId,
@ -216,7 +219,6 @@ class CommunitySpaceManagementApi {
{required String communityId, {required String communityId,
required String name, required String name,
String? parentId, String? parentId,
String? direction,
bool isPrivate = false, bool isPrivate = false,
required Offset position, required Offset position,
String? spaceModelUuid, String? spaceModelUuid,
@ -230,7 +232,6 @@ class CommunitySpaceManagementApi {
'isPrivate': isPrivate, 'isPrivate': isPrivate,
'x': position.dx, 'x': position.dx,
'y': position.dy, 'y': position.dy,
'direction': direction,
'icon': icon, 'icon': icon,
}; };
if (parentId != null) { if (parentId != null) {
@ -265,11 +266,10 @@ class CommunitySpaceManagementApi {
required String name, required String name,
String? parentId, String? parentId,
String? icon, String? icon,
String? direction,
bool isPrivate = false, bool isPrivate = false,
required Offset position, required Offset position,
List<TagModelUpdate>? tags, List<Tag>? tags,
List<UpdateSubspaceTemplateModel>? subspaces, List<SubspaceModel>? subspaces,
String? spaceModelUuid, String? spaceModelUuid,
required String projectId}) async { required String projectId}) async {
try { try {
@ -278,9 +278,8 @@ class CommunitySpaceManagementApi {
'isPrivate': isPrivate, 'isPrivate': isPrivate,
'x': position.dx, 'x': position.dx,
'y': position.dy, 'y': position.dy,
'direction': direction,
'icon': icon, 'icon': icon,
'subspace': subspaces, 'subspaces': subspaces,
'tags': tags, 'tags': tags,
'spaceModelUuid': spaceModelUuid, 'spaceModelUuid': spaceModelUuid,
}; };

View File

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
@ -7,17 +9,23 @@ import 'package:syncrow_web/utils/constants/api_const.dart';
class SpaceModelManagementApi { class SpaceModelManagementApi {
Future<List<SpaceTemplateModel>> listSpaceModels( Future<List<SpaceTemplateModel>> listSpaceModels(
{required String projectId, int page = 1}) async { {required String projectId, int page = 1}) async {
final response = await HTTPService().get( try {
path: ApiEndpoints.listSpaceModels.replaceAll('{projectId}', projectId), // final response = await HTTPService().get(
queryParameters: {'page': page}, // path: ApiEndpoints.listSpaceModels.replaceAll('{projectId}', projectId),
expectedResponseModel: (json) { // queryParameters: {'page': page},
List<dynamic> jsonData = json['data']; // expectedResponseModel: (json) {
return jsonData.map((jsonItem) { // List<dynamic> jsonData = json['data'];
return SpaceTemplateModel.fromJson(jsonItem); // return jsonData.map((jsonItem) {
}).toList(); // return SpaceTemplateModel.fromJson(jsonItem);
}, // }).toList();
); // },
return response; // );
return [];
// response;
} catch (e) {
log(e.toString());
return [];
}
} }
Future<SpaceTemplateModel?> createSpaceModel( Future<SpaceTemplateModel?> createSpaceModel(
@ -33,8 +41,8 @@ class SpaceModelManagementApi {
return response; return response;
} }
Future<String?> updateSpaceModel( Future<String?> updateSpaceModel(CreateSpaceTemplateBodyModel spaceModel,
CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid, String projectId) async { String spaceModelUuid, String projectId) async {
final response = await HTTPService().put( final response = await HTTPService().put(
path: ApiEndpoints.updateSpaceModel path: ApiEndpoints.updateSpaceModel
.replaceAll('{projectId}', projectId) .replaceAll('{projectId}', projectId)
@ -47,7 +55,8 @@ class SpaceModelManagementApi {
return response; return response;
} }
Future<SpaceTemplateModel?> getSpaceModel(String spaceModelUuid, String projectId) async { Future<SpaceTemplateModel?> getSpaceModel(
String spaceModelUuid, String projectId) async {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getSpaceModel path: ApiEndpoints.getSpaceModel
.replaceAll('{projectId}', projectId) .replaceAll('{projectId}', projectId)