diff --git a/lib/pages/device_managment/all_devices/models/factory_reset_model.dart b/lib/pages/device_managment/all_devices/models/factory_reset_model.dart index aec14d16..56c6c90b 100644 --- a/lib/pages/device_managment/all_devices/models/factory_reset_model.dart +++ b/lib/pages/device_managment/all_devices/models/factory_reset_model.dart @@ -2,14 +2,17 @@ import 'package:flutter/foundation.dart'; class FactoryResetModel { final List devicesUuid; + final String operationType; FactoryResetModel({ required this.devicesUuid, + this.operationType = "RESET", }); factory FactoryResetModel.fromJson(Map json) { return FactoryResetModel( devicesUuid: List.from(json['devicesUuid']), + operationType: "RESET", ); } diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index 650c0d21..1928768b 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -12,77 +12,95 @@ class DeviceSearchFilters extends StatefulWidget { State createState() => _DeviceSearchFiltersState(); } -class _DeviceSearchFiltersState extends State with HelperResponsiveLayout { - final TextEditingController communityController = TextEditingController(); - final TextEditingController unitNameController = TextEditingController(); - final TextEditingController productNameController = TextEditingController(); +class _DeviceSearchFiltersState extends State + with HelperResponsiveLayout { + late final TextEditingController _unitNameController; + late final TextEditingController _productNameController; + + @override + void initState() { + _unitNameController = TextEditingController(); + _productNameController = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + _unitNameController.dispose(); + _productNameController.dispose(); + super.dispose(); + } + + List get _widgets => [ + _buildSearchField( + "Space Name", + _unitNameController, + 200, + ), + _buildSearchField( + "Device Name / Product Name", + _productNameController, + 300, + ), + _buildSearchResetButtons(), + ]; @override Widget build(BuildContext context) { - return isExtraLargeScreenSize(context) - ? Row( - children: [ - _buildSearchField("Community", communityController, 200), - const SizedBox(width: 20), - _buildSearchField("Space Name", unitNameController, 200), - const SizedBox(width: 20), - _buildSearchField("Device Name / Product Name", productNameController, 300), - const SizedBox(width: 20), - _buildSearchResetButtons(), - ], - ) - : Wrap( - spacing: 20, - runSpacing: 10, - children: [ - _buildSearchField( - "Community", - communityController, - 200, - ), - _buildSearchField("Space Name", unitNameController, 200), - _buildSearchField( - "Device Name / Product Name", - productNameController, - 300, - ), - _buildSearchResetButtons(), - ], - ); + if (isExtraLargeScreenSize(context)) { + return Row( + children: _widgets.map( + (e) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: e, + ); + }, + ).toList(), + ); + } + + return Wrap( + spacing: 20, + runSpacing: 10, + children: _widgets, + ); } - Widget _buildSearchField(String title, TextEditingController controller, double width) { - return Container( - child: StatefulTextField( - title: title, - width: width, - elevation: 2, - controller: controller, - onSubmitted: () { - context.read().add(SearchDevices( - productName: productNameController.text, - unitName: unitNameController.text, - community: communityController.text, - searchField: true)); - }, - onChanged: (p0) {}, - ), + Widget _buildSearchField( + String title, + TextEditingController controller, + double width, + ) { + return StatefulTextField( + title: title, + width: width, + elevation: 2, + controller: controller, + onSubmitted: () { + final searchDevicesEvent = SearchDevices( + productName: _productNameController.text, + unitName: _unitNameController.text, + searchField: true, + ); + context.read().add(searchDevicesEvent); + }, + onChanged: (p0) {}, ); } Widget _buildSearchResetButtons() { return SearchResetButtons( - onSearch: () { - context.read().add(SearchDevices( - community: communityController.text, - unitName: unitNameController.text, - productName: productNameController.text, - searchField: true)); - }, + onSearch: () => context.read().add( + SearchDevices( + unitName: _unitNameController.text, + productName: _productNameController.text, + searchField: true, + ), + ), onReset: () { - communityController.clear(); - unitNameController.clear(); - productNameController.clear(); + _unitNameController.clear(); + _productNameController.clear(); context.read() ..add(ResetFilters()) ..add(FetchDevices(context)); diff --git a/lib/pages/routines/create_new_routines/commu_dropdown.dart b/lib/pages/routines/create_new_routines/commu_dropdown.dart index 658b69a0..32ee7219 100644 --- a/lib/pages/routines/create_new_routines/commu_dropdown.dart +++ b/lib/pages/routines/create_new_routines/commu_dropdown.dart @@ -1,16 +1,16 @@ +import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/style.dart'; class CommunityDropdown extends StatelessWidget { final String? selectedValue; final Function(String?) onChanged; + final TextEditingController _searchController = TextEditingController(); - const CommunityDropdown({ + CommunityDropdown({ Key? key, required this.selectedValue, required this.onChanged, @@ -34,58 +34,119 @@ class CommunityDropdown extends StatelessWidget { const SizedBox(height: 8), BlocBuilder( builder: (context, state) { - List communities = - state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList; - return SizedBox( - child: DropdownButtonFormField( - dropdownColor: ColorsManager.whiteColors, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + ), + child: DropdownButton2( + underline: SizedBox(), value: selectedValue, - items: communities.map((community) { + items: state.communityList.map((community) { return DropdownMenuItem( value: community.uuid, - child: Text(' ${community.name}'), + child: Text( + ' ${community.name}', + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), ); }).toList(), onChanged: onChanged, - icon: const SizedBox.shrink(), - borderRadius: const BorderRadius.all(Radius.circular(10)), + style: TextStyle(color: Colors.black), hint: Padding( padding: EdgeInsets.only(left: 10), child: Text( - "Please Select", + " Please Select", style: Theme.of(context).textTheme.bodySmall!.copyWith( color: ColorsManager.textGray, ), ), ), - decoration: inputTextFormDeco().copyWith( - contentPadding: EdgeInsets.zero, - suffixIcon: Container( - padding: EdgeInsets.zero, - width: 70, - height: 45, - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: const BorderRadius.only( - bottomRight: Radius.circular(10), - topRight: Radius.circular(10), + customButton: Container( + height: 45, + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.textGray, width: 1.0), + borderRadius: BorderRadius.circular(10), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 5, + child: Text( + selectedValue != null + ? " ${state.communityList.firstWhere((element) => element.uuid == selectedValue).name}" + : ' Please Select', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: + selectedValue != null ? Colors.black : ColorsManager.textGray, + ), + overflow: TextOverflow.ellipsis, + ), ), - border: Border.all( - color: ColorsManager.textGray, - width: 1.0, + Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: const BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + ), + height: 45, + child: const Icon( + Icons.keyboard_arrow_down, + color: ColorsManager.textGray, + ), + ), ), - ), - child: const Center( - child: Icon( - Icons.keyboard_arrow_down, - color: ColorsManager.textGray, + ], + ), + ), + dropdownStyleData: DropdownStyleData( + maxHeight: MediaQuery.of(context).size.height * 0.4, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + ), + ), + dropdownSearchData: DropdownSearchData( + searchController: _searchController, + searchInnerWidgetHeight: 50, + searchInnerWidget: Container( + height: 50, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: TextFormField( + style: const TextStyle(color: Colors.black), + controller: _searchController, + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 12, + ), + hintText: 'Search for community...', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), ), ), ), + searchMatchFn: (item, searchValue) { + final communityName = (item.child as Text).data?.toLowerCase() ?? ''; + return communityName.contains(searchValue.toLowerCase().trim()); + }, + ), + onMenuStateChange: (isOpen) { + if (!isOpen) { + _searchController.clear(); + } + }, + menuItemStyleData: const MenuItemStyleData( + height: 40, ), ), - ); + )); }, ), ], diff --git a/lib/pages/routines/create_new_routines/create_new_routines.dart b/lib/pages/routines/create_new_routines/create_new_routines.dart index 5d1021f2..baf10748 100644 --- a/lib/pages/routines/create_new_routines/create_new_routines.dart +++ b/lib/pages/routines/create_new_routines/create_new_routines.dart @@ -62,28 +62,34 @@ class _CreateNewRoutinesDialogState extends State { mainAxisSize: MainAxisSize.min, children: [ const Divider(), - CommunityDropdown( - selectedValue: _selectedCommunity, - onChanged: (String? newValue) { - setState(() { - _selectedCommunity = newValue; - _selectedSpace = null; - }); - if (newValue != null) { - _fetchSpaces(newValue); - } - }, + Padding( + padding: const EdgeInsets.only(left: 15, right: 15), + child: CommunityDropdown( + selectedValue: _selectedCommunity, + onChanged: (String? newValue) { + setState(() { + _selectedCommunity = newValue; + _selectedSpace = null; + }); + if (newValue != null) { + _fetchSpaces(newValue); + } + }, + ), ), - const SizedBox(height: 16), - SpaceDropdown( - hintMessage: spaceHint, - spaces: spaces, - selectedValue: _selectedSpace, - onChanged: (String? newValue) { - setState(() { - _selectedSpace = newValue; - }); - }, + const SizedBox(height: 5), + Padding( + padding: const EdgeInsets.only(left: 15, right: 15), + child: SpaceDropdown( + hintMessage: spaceHint, + spaces: spaces, + selectedValue: _selectedSpace, + onChanged: (String? newValue) { + setState(() { + _selectedSpace = newValue; + }); + }, + ), ), const Divider(), Row( @@ -96,7 +102,6 @@ class _CreateNewRoutinesDialogState extends State { ), child: TextButton( onPressed: () { - Navigator.of(context).pop(); }, child: Text( @@ -139,6 +144,7 @@ class _CreateNewRoutinesDialogState extends State { ), ], ), + SizedBox(height: 10), ], ), ); diff --git a/lib/pages/routines/create_new_routines/space_dropdown.dart b/lib/pages/routines/create_new_routines/space_dropdown.dart index f207c736..a26ff9f4 100644 --- a/lib/pages/routines/create_new_routines/space_dropdown.dart +++ b/lib/pages/routines/create_new_routines/space_dropdown.dart @@ -1,7 +1,8 @@ +import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/style.dart'; + class SpaceDropdown extends StatelessWidget { final List spaces; @@ -33,62 +34,108 @@ class SpaceDropdown extends StatelessWidget { ), ), const SizedBox(height: 8), - DropdownButtonFormField( - value: selectedValue, - items: spaces.map((space) { - return DropdownMenuItem( - value: space.uuid, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - ' ${space.name}', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 12, - color: ColorsManager.blackColor, - ), - ), - Text( - ' ${space.lastThreeParents}', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 12, - ), - ), - ], - )); - }).toList(), - onChanged: onChanged, - icon: const SizedBox.shrink(), - borderRadius: const BorderRadius.all(Radius.circular(10)), - hint: Padding( - padding: const EdgeInsets.only(left: 10), - child: Text( - hintMessage, - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.textGray, - ), + SizedBox( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), ), - ), - decoration: inputTextFormDeco().copyWith( - contentPadding: EdgeInsets.zero, - suffixIcon: Container( - width: 70, - height: 45, - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: const BorderRadius.only( - bottomRight: Radius.circular(10), - topRight: Radius.circular(10), - ), - border: Border.all( - color: ColorsManager.textGray, - width: 1.0, + child: DropdownButton2( + underline: const SizedBox(), + value: selectedValue, + items: spaces.map((space) { + return DropdownMenuItem( + value: space.uuid, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + ' ${space.name}', + style: + Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 12, + color: ColorsManager.blackColor, + ), + ), + Text( + ' ${space.lastThreeParents}', + style: + Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 12, + ), + ), + ], + ), + ); + }).toList(), + onChanged: onChanged, + style: TextStyle(color: Colors.black), + hint: Padding( + padding: const EdgeInsets.only(left: 10), + child: Text( + hintMessage, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.textGray, + ), ), ), - child: const Icon( - Icons.keyboard_arrow_down, - color: ColorsManager.textGray, + customButton: Container( + height: 45, + decoration: BoxDecoration( + border: + Border.all(color: ColorsManager.textGray, width: 1.0), + borderRadius: BorderRadius.circular(10), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 5, + child: Padding( + padding: const EdgeInsets.only(left: 10), + child: Text( + selectedValue != null + ? spaces + .firstWhere((e) => e.uuid == selectedValue) + .name + : hintMessage, + style: + Theme.of(context).textTheme.bodySmall!.copyWith( + color: selectedValue != null + ? Colors.black + : ColorsManager.textGray, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ), + Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: const BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + ), + height: 45, + child: const Icon( + Icons.keyboard_arrow_down, + color: ColorsManager.textGray, + ), + ), + ), + ], + ), + ), + dropdownStyleData: DropdownStyleData( + maxHeight: MediaQuery.of(context).size.height * 0.4, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + ), + ), + menuItemStyleData: const MenuItemStyleData( + height: 60, ), ), ), diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index 962b3b89..83ab82b9 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -33,8 +33,6 @@ class _RoutinesViewState extends State { communityID: communityId, spaceID: spaceId)); await Future.delayed(const Duration(seconds: 1)); routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true)); - await Future.delayed(const Duration(milliseconds:500)); - _bloc.add(const ResetSelectedEvent()); } @override diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 56352432..70b86074 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -301,11 +301,18 @@ class _CommunityStructureAreaState extends State { SpaceTemplateModel? spaceModel, List? subspaces, List? tags) { setState(() { // Set the first space in the center or use passed position - Offset centerPosition = position ?? ConnectionHelper.getCenterPosition(screenSize); + Offset newPosition; + if (parentIndex != null) { + newPosition = + getBalancedChildPosition(spaces[parentIndex]); // Ensure balanced position + } else { + newPosition = position ?? ConnectionHelper.getCenterPosition(screenSize); + } + SpaceModel newSpace = SpaceModel( name: name, icon: icon, - position: centerPosition, + position: newPosition, isPrivate: false, children: [], status: SpaceStatus.newSpace, @@ -425,7 +432,7 @@ class _CommunityStructureAreaState extends State { Connection( startSpace: parent, endSpace: child, - direction: child.incomingConnection?.direction ?? "down", + direction: "down", ), ); @@ -522,6 +529,38 @@ class _CommunityStructureAreaState extends State { ); } + Offset getBalancedChildPosition(SpaceModel parent) { + int totalSiblings = parent.children.length + 1; + double totalWidth = (totalSiblings - 1) * 250; // Horizontal spacing + double startX = parent.position.dx - (totalWidth / 2); + + Offset position = Offset(startX + (parent.children.length * 250), parent.position.dy + 180); + + // Check for overlaps & adjust + while (spaces.any((s) => (s.position - position).distance < 250)) { + position = Offset(position.dx + 250, position.dy); + } + + return position; + } + + void realignTree() { + void updatePositions(SpaceModel node, double x, double y) { + node.position = Offset(x, y); + + int numChildren = node.children.length; + double childStartX = x - ((numChildren - 1) * 250) / 2; + + for (int i = 0; i < numChildren; i++) { + updatePositions(node.children[i], childStartX + (i * 250), y + 180); + } + } + + if (spaces.isNotEmpty) { + updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy); + } + } + void _onDuplicate(BuildContext parentContext) { final screenWidth = MediaQuery.of(context).size.width; @@ -604,29 +643,57 @@ class _CommunityStructureAreaState extends State { void _duplicateSpace(SpaceModel space) { final Map originalToDuplicate = {}; - const double horizontalGap = 200.0; - const double verticalGap = 100.0; + double horizontalGap = 250.0; // Increased spacing + double verticalGap = 180.0; // Adjusted for better visualization - SpaceModel duplicateRecursive( - SpaceModel original, Offset parentPosition, SpaceModel? duplicatedParent) { - Offset newPosition = Offset(parentPosition.dx + horizontalGap, original.position.dy); + print("🟢 Duplicating: ${space.name}"); - while (spaces.any((s) => - (s.position - newPosition).distance < horizontalGap && s.status != SpaceStatus.deleted)) { - newPosition += Offset(horizontalGap, 0); + /// **Find a new position ensuring no overlap** + Offset getBalancedChildPosition(SpaceModel parent) { + int totalSiblings = parent.children.length + 1; + double totalWidth = (totalSiblings - 1) * horizontalGap; + double startX = parent.position.dx - (totalWidth / 2); + Offset position = Offset( + startX + (parent.children.length * horizontalGap), parent.position.dy + verticalGap); + + // **Check for overlaps & adjust** + while (spaces.any((s) => (s.position - position).distance < horizontalGap)) { + position = Offset(position.dx + horizontalGap, position.dy); } + print("🔹 New position for ${parent.name}: (${position.dx}, ${position.dy})"); + return position; + } + + /// **Realign the entire tree after duplication** + void realignTree() { + void updatePositions(SpaceModel node, double x, double y) { + node.position = Offset(x, y); + print("✅ Adjusted ${node.name} to (${x}, ${y})"); + + int numChildren = node.children.length; + double childStartX = x - ((numChildren - 1) * horizontalGap) / 2; + + for (int i = 0; i < numChildren; i++) { + updatePositions(node.children[i], childStartX + (i * horizontalGap), y + verticalGap); + } + } + + if (spaces.isNotEmpty) { + print("🔄 Realigning tree..."); + updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy); + } + } + + /// **Recursive duplication logic** + SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) { + Offset newPosition = duplicatedParent == null + ? Offset(original.position.dx + horizontalGap, original.position.dy) + : getBalancedChildPosition(duplicatedParent); + final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces); - - final List? duplicatedSubspaces; - final List? duplicatedTags; - if (original.spaceModel != null) { - duplicatedTags = []; - duplicatedSubspaces = []; - } else { - duplicatedTags = original.tags; - duplicatedSubspaces = original.subspaces; - } + print( + "🟡 Duplicating ${original.name} → ${duplicatedName} at (${newPosition.dx}, ${newPosition.dy})"); final duplicated = SpaceModel( name: duplicatedName, @@ -637,12 +704,10 @@ class _CommunityStructureAreaState extends State { status: SpaceStatus.newSpace, parent: duplicatedParent, spaceModel: original.spaceModel, - subspaces: duplicatedSubspaces, - tags: duplicatedTags, + subspaces: original.subspaces, + tags: original.tags, ); - originalToDuplicate[original] = duplicated; - setState(() { spaces.add(duplicated); _updateNodePosition(duplicated, duplicated.position); @@ -651,60 +716,42 @@ class _CommunityStructureAreaState extends State { final newConnection = Connection( startSpace: duplicatedParent, endSpace: duplicated, - direction: original.incomingConnection?.direction ?? 'down', + direction: "down", ); connections.add(newConnection); duplicated.incomingConnection = newConnection; duplicatedParent.addOutgoingConnection(newConnection); + duplicatedParent.children.add(duplicated); + print("🔗 Created connection: ${duplicatedParent.name} → ${duplicated.name}"); } - if (original.parent != null && duplicatedParent == null) { - final originalParent = original.parent!; - final duplicatedParent = originalToDuplicate[originalParent] ?? originalParent; - - final parentConnection = Connection( - startSpace: duplicatedParent, - endSpace: duplicated, - direction: original.incomingConnection?.direction ?? "down", - ); - - connections.add(parentConnection); - duplicated.incomingConnection = parentConnection; - duplicatedParent.addOutgoingConnection(parentConnection); - } + // **Recalculate the whole tree to avoid overlaps** + realignTree(); }); - final childrenWithDownDirection = original.children - .where((child) => - child.incomingConnection?.direction == "down" && child.status != SpaceStatus.deleted) - .toList(); - - Offset childStartPosition = childrenWithDownDirection.length == 1 - ? duplicated.position - : newPosition + Offset(0, verticalGap); - - for (final child in original.children) { - final isDownDirection = child.incomingConnection?.direction == "down" ?? false; - - if (isDownDirection && childrenWithDownDirection.length == 1) { - childStartPosition = duplicated.position + Offset(0, verticalGap); - } else if (!isDownDirection) { - childStartPosition = duplicated.position + Offset(horizontalGap, 0); - } - - final duplicatedChild = duplicateRecursive(child, childStartPosition, duplicated); - duplicated.children.add(duplicatedChild); - childStartPosition += Offset(0, verticalGap); + // Recursively duplicate children + for (var child in original.children) { + duplicateRecursive(child, duplicated); } return duplicated; } + /// **Handle root duplication** if (space.parent == null) { - duplicateRecursive(space, space.position, null); + print("🟠 Duplicating root node: ${space.name}"); + SpaceModel duplicatedRoot = duplicateRecursive(space, null); + + setState(() { + spaces.add(duplicatedRoot); + realignTree(); + }); + + print("✅ Root duplication successful: ${duplicatedRoot.name}"); } else { - final duplicatedParent = originalToDuplicate[space.parent!] ?? space.parent!; - duplicateRecursive(space, space.position, duplicatedParent); + duplicateRecursive(space, space.parent); } + + print("🟢 Finished duplication process for: ${space.name}"); } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/space_card_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/space_card_widget.dart index 0a78da52..f3a476b2 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/space_card_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/space_card_widget.dart @@ -47,18 +47,6 @@ class SpaceCardWidget extends StatelessWidget { children: [ buildSpaceContainer(index), // Build the space container if (isHovered) ...[ - PlusButtonWidget( - index: index, - direction: 'left', - offset: const Offset(-21, 20), - onButtonTap: onButtonTap, - ), - PlusButtonWidget( - index: index, - direction: 'right', - offset: const Offset(140, 20), - onButtonTap: onButtonTap, - ), PlusButtonWidget( index: index, direction: 'down', diff --git a/lib/pages/spaces_management/helper/space_helper.dart b/lib/pages/spaces_management/helper/space_helper.dart index 126306cd..4049eb7e 100644 --- a/lib/pages/spaces_management/helper/space_helper.dart +++ b/lib/pages/spaces_management/helper/space_helper.dart @@ -2,8 +2,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_m import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; class SpaceHelper { - static SpaceModel? findSpaceByUuid( - String? uuid, List communities) { + static SpaceModel? findSpaceByUuid(String? uuid, List communities) { for (var community in communities) { for (var space in community.spaces) { if (space.uuid == uuid) return space; @@ -12,8 +11,7 @@ class SpaceHelper { return null; } - static SpaceModel? findSpaceByInternalId( - String? internalId, List spaces) { + static SpaceModel? findSpaceByInternalId(String? internalId, List spaces) { if (internalId != null) { for (var space in spaces) { if (space.internalId == internalId) return space; @@ -23,8 +21,7 @@ class SpaceHelper { return null; } - static String generateUniqueSpaceName( - String originalName, List spaces) { + static String generateUniqueSpaceName(String originalName, List spaces) { final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim(); int maxNumber = 0; @@ -54,13 +51,10 @@ class SpaceHelper { return space == selectedSpace || selectedSpace.parent?.internalId == space.internalId || - selectedSpace.children - ?.any((child) => child.internalId == space.internalId) == - true; + selectedSpace.children?.any((child) => child.internalId == space.internalId) == true; } - static bool isNameConflict( - String value, SpaceModel? parentSpace, SpaceModel? editSpace) { + static bool isNameConflict(String value, SpaceModel? parentSpace, SpaceModel? editSpace) { final siblings = parentSpace?.children .where((child) => child.internalId != editSpace?.internalId) .toList() ?? @@ -71,19 +65,17 @@ class SpaceHelper { .toList() ?? []; - final editSiblingConflict = - editSiblings.any((child) => child.name == value); + final editSiblingConflict = editSiblings.any((child) => child.name == value); final siblingConflict = siblings.any((child) => child.name == value); - final parentConflict = parentSpace?.name == value && - parentSpace?.internalId != editSpace?.internalId; + final parentConflict = + parentSpace?.name == value && parentSpace?.internalId != editSpace?.internalId; - final parentOfEditSpaceConflict = editSpace?.parent?.name == value && - editSpace?.parent?.internalId != editSpace?.internalId; + final parentOfEditSpaceConflict = + editSpace?.parent?.name == value && editSpace?.parent?.internalId != editSpace?.internalId; - final childConflict = - editSpace?.children.any((child) => child.name == value) ?? false; + final childConflict = editSpace?.children.any((child) => child.name == value) ?? false; return siblingConflict || parentConflict || diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index 45fb03fe..438b1abf 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -194,9 +194,10 @@ class VisitorPasswordBloc emit(DeviceLoaded()); final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - data = await AccessMangApi().fetchDevices(projectUuid); + data = await AccessMangApi().fetchDoorLockDeviceList(projectUuid); emit(TableLoaded(data)); } catch (e) { + print("error: $e"); emit(FailedState(e.toString())); } } diff --git a/lib/services/access_mang_api.dart b/lib/services/access_mang_api.dart index 6acbc395..a780a12b 100644 --- a/lib/services/access_mang_api.dart +++ b/lib/services/access_mang_api.dart @@ -6,13 +6,23 @@ import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; class AccessMangApi { + AccessMangApi() { + _validateEndpoints(); + } + + void _validateEndpoints() { + if (!ApiEndpoints.getDevices.contains('{projectId}')) { + throw Exception("Endpoint 'getDevices' must contain '{projectId}' placeholder."); + } + } + Future> fetchVisitorPassword(String projectId) async { try { final response = await HTTPService().get( - path: ApiEndpoints.visitorPassword.replaceAll('{projectId}', projectId), + path: ApiEndpoints.visitorPassword, showServerMessage: true, expectedResponseModel: (json) { - List jsonData = json; + List jsonData = json['data'] ?? []; List passwordList = jsonData.map((jsonItem) { return PasswordModel.fromJson(jsonItem); }).toList(); @@ -25,17 +35,22 @@ class AccessMangApi { } } - Future fetchDevices(String projectId) async { + Future fetchDoorLockDeviceList(String projectId) async { try { + // The endpoint structure is already validated during initialization. + final response = await HTTPService().get( path: ApiEndpoints.getDevices.replaceAll('{projectId}', projectId), + queryParameters: { + 'deviceType': 'DOOR_LOCK', + }, showServerMessage: true, expectedResponseModel: (json) { - List jsonData = json; - List passwordList = jsonData.map((jsonItem) { + List jsonData = json['data'] ?? []; + List deviceList = jsonData.map((jsonItem) { return DeviceModel.fromJson(jsonItem); }).toList(); - return passwordList; + return deviceList; }, ); return response; @@ -52,14 +67,15 @@ class AccessMangApi { String? invalidTime, List? devicesUuid}) async { final response = await HTTPService().post( - path: ApiEndpoints.sendOnlineOneTime, + path: ApiEndpoints.visitorPassword, body: jsonEncode({ "email": email, "passwordName": passwordName, "password": password, "devicesUuid": devicesUuid, "effectiveTime": effectiveTime, - "invalidTime": invalidTime + "invalidTime": invalidTime, + "operationType": "ONLINE_ONE_TIME", }), showServerMessage: true, expectedResponseModel: (json) { @@ -84,13 +100,13 @@ class AccessMangApi { "password": password, "effectiveTime": effectiveTime, "invalidTime": invalidTime, + "operationType": "ONLINE_MULTIPLE_TIME", }; if (scheduleList != null) { - body["scheduleList"] = - scheduleList.map((schedule) => schedule.toJson()).toList(); + body["scheduleList"] = scheduleList.map((schedule) => schedule.toJson()).toList(); } final response = await HTTPService().post( - path: ApiEndpoints.sendOnlineMultipleTime, + path: ApiEndpoints.visitorPassword, body: jsonEncode(body), showServerMessage: true, expectedResponseModel: (json) { @@ -105,8 +121,9 @@ class AccessMangApi { Future postOffLineOneTime( {String? email, String? passwordName, List? devicesUuid}) async { final response = await HTTPService().post( - path: ApiEndpoints.sendOffLineOneTime, + path: ApiEndpoints.visitorPassword, body: jsonEncode({ + "operationType": "OFFLINE_ONE_TIME", "email": email, "passwordName": passwordName, "devicesUuid": devicesUuid @@ -126,13 +143,14 @@ class AccessMangApi { String? invalidTime, List? devicesUuid}) async { final response = await HTTPService().post( - path: ApiEndpoints.sendOffLineMultipleTime, + path: ApiEndpoints.visitorPassword, body: jsonEncode({ "email": email, "devicesUuid": devicesUuid, "passwordName": passwordName, "effectiveTime": effectiveTime, "invalidTime": invalidTime, + "operationType": "OFFLINE_MULTIPLE_TIME", }), showServerMessage: true, expectedResponseModel: (json) { diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 9fe4a2f6..85e91759 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -32,7 +32,7 @@ class DevicesManagementApi { ); return response; } catch (e) { - debugPrint('fetchDevices Error fetching $e'); + debugPrint('Error fetching device $e'); return []; } } @@ -43,7 +43,7 @@ class DevicesManagementApi { path: ApiEndpoints.getDeviceStatus.replaceAll('{uuid}', uuid), showServerMessage: true, expectedResponseModel: (json) { - return DeviceStatus.fromJson(json); + return DeviceStatus.fromJson(json['data']); }, ); return response; @@ -60,7 +60,7 @@ class DevicesManagementApi { Future getPowerClampInfo(String deviceId) async { try { final response = await HTTPService().get( - path: ApiEndpoints.powerClamp.replaceAll('{powerClampUuid}', deviceId), + path: ApiEndpoints.getDeviceStatus.replaceAll('{uuid}', deviceId), showServerMessage: true, expectedResponseModel: (json) { return json; @@ -97,6 +97,7 @@ class DevicesManagementApi { 'devicesUuid': uuids, 'code': code, 'value': value, + 'operationType': 'COMMAND', }; final response = await HTTPService().post( @@ -104,7 +105,7 @@ class DevicesManagementApi { body: body, showServerMessage: true, expectedResponseModel: (json) { - return (json['successResults'] as List).isNotEmpty; + return json['success'] ?? false; }, ); @@ -124,7 +125,7 @@ class DevicesManagementApi { if (json == null || json.isEmpty || json == []) { return devices; } - for (var device in json['devices']) { + for (var device in json['data']['devices']) { devices.add(DeviceModel.fromJson(device)); } return devices; @@ -152,7 +153,7 @@ class DevicesManagementApi { path: ApiEndpoints.getDeviceLogs.replaceAll('{uuid}', uuid).replaceAll('{code}', code), showServerMessage: false, expectedResponseModel: (json) { - return DeviceReport.fromJson(json); + return DeviceReport.fromJson(json['data']); }, ); return response; @@ -168,7 +169,7 @@ class DevicesManagementApi { .replaceAll('{endTime}', to ?? ''), showServerMessage: false, expectedResponseModel: (json) { - return DeviceReport.fromJson(json); + return DeviceReport.fromJson(json['data']); }, ); return response; @@ -184,7 +185,7 @@ class DevicesManagementApi { queryParameters: queryParameters, showServerMessage: true, expectedResponseModel: (json) { - return DeviceStatus.fromJson(json['status']); + return DeviceStatus.fromJson(json['data']['status']); }, ); return response; diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index be972392..454ec46d 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -9,21 +9,8 @@ abstract class ApiEndpoints { static const String sendOtp = '/authentication/user/send-otp'; static const String verifyOtp = '/authentication/user/verify-otp'; static const String getRegion = '/region'; - static const String visitorPassword = - '/projects/{projectId}/visitor-password'; - static const String getDevices = - '/projects/{projectId}/visitor-password/devices'; - - static const String sendOnlineOneTime = - '/visitor-password/temporary-password/online/one-time'; - static const String sendOnlineMultipleTime = - '/visitor-password/temporary-password/online/multiple-time'; - -//offline Password - static const String sendOffLineOneTime = - '/visitor-password/temporary-password/offline/one-time'; - static const String sendOffLineMultipleTime = - '/visitor-password/temporary-password/offline/multiple-time'; + static const String visitorPassword = '/visitor-passwords'; + static const String getDevices = '/projects/{projectId}/devices'; static const String getUser = '/user/{userUuid}'; @@ -32,15 +19,15 @@ abstract class ApiEndpoints { static const String getAllDevices = '/projects/{projectId}/devices'; static const String getSpaceDevices = '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/devices'; - static const String getDeviceStatus = '/device/{uuid}/functions/status'; - static const String getBatchStatus = '/device/status/batch'; + static const String getDeviceStatus = '/devices/{uuid}/functions/status'; + static const String getBatchStatus = '/devices/batch'; - static const String deviceControl = '/device/{uuid}/control'; - static const String deviceBatchControl = '/device/control/batch'; - static const String gatewayApi = '/device/gateway/{gatewayUuid}/devices'; + static const String deviceControl = '/devices/{uuid}/command'; + static const String deviceBatchControl = '/devices/batch'; + static const String gatewayApi = '/devices/gateway/{gatewayUuid}/devices'; static const String openDoorLock = '/door-lock/open/{doorLockUuid}'; - static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}'; + static const String getDeviceLogs = '/devices/{uuid}/report-logs?code={code}'; // Space Module static const String createSpace = @@ -70,18 +57,13 @@ abstract class ApiEndpoints { static const String createUserCommunity = '/projects/{projectId}/communities/user'; static const String getDeviceLogsByDate = - '/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; + '/devices/{uuid}/report-logs?code={code}&startTime={startTime}&endTime={endTime}'; static const String scheduleByDeviceId = '/schedule/{deviceUuid}'; - static const String getScheduleByDeviceId = - '/schedule/{deviceUuid}?category={category}'; - static const String deleteScheduleByDeviceId = - '/schedule/{deviceUuid}/{scheduleUuid}'; - static const String updateScheduleByDeviceId = - '/schedule/enable/{deviceUuid}'; - static const String factoryReset = '/device/factory/reset/{deviceUuid}'; - static const String powerClamp = - '/device/{powerClampUuid}/power-clamp/status'; + static const String getScheduleByDeviceId = '/schedule/{deviceUuid}?category={category}'; + static const String deleteScheduleByDeviceId = '/schedule/{deviceUuid}/{scheduleUuid}'; + static const String updateScheduleByDeviceId = '/schedule/enable/{deviceUuid}'; + static const String factoryReset = '/devices/batch'; //product static const String listProducts = '/products';