add_user_dialog

This commit is contained in:
mohammad
2024-12-16 16:37:55 +03:00
parent 56d4c34321
commit 879bf99b12
10 changed files with 369 additions and 170 deletions

View File

@ -0,0 +1,3 @@
<svg width="8" height="5" viewBox="0 0 8 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 0.740463C8 0.574789 7.93931 0.408979 7.8179 0.282701C7.57528 0.0297901 7.18168 0.0297901 6.93903 0.282701L4 3.34404L1.06095 0.282701C0.818322 0.0297898 0.424717 0.0297898 0.182084 0.282701C-0.0606947 0.535291 -0.0606947 0.945601 0.182084 1.19819L3.56055 4.71746C3.80318 4.97034 4.1968 4.97034 4.43945 4.71746L7.8179 1.19819C7.93932 1.07191 8 0.906104 8 0.740463Z" fill="#999999" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 511 B

View File

@ -0,0 +1,3 @@
<svg width="5" height="9" viewBox="0 0 5 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.647445 0.5C0.481771 0.5 0.315961 0.560695 0.189683 0.6821C-0.0632278 0.924716 -0.0632278 1.31832 0.189683 1.56097L3.25102 4.5L0.189683 7.43905C-0.0632278 7.68168 -0.0632278 8.07528 0.189683 8.31792C0.442273 8.56069 0.852583 8.56069 1.10517 8.31792L4.62444 4.93945C4.87732 4.69682 4.87732 4.3032 4.62444 4.06055L1.10517 0.6821C0.978895 0.560678 0.813086 0.5 0.647445 0.5Z" fill="#999999" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 519 B

View File

@ -3,12 +3,18 @@ import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
class UsersBloc extends Bloc<UsersEvent, UsersState> {
UsersBloc() : super(UsersInitial()) {
on<GetUsers>(_getUsers);
on<ChangeUserStatus>(_changeUserStatus);
on<CheckStepStatus>(isCompleteBasicsFun);
on<LoadCommunityAndSpacesEvent>(_onLoadCommunityAndSpaces);
on<SearchAnode>(searchTreeNode);
}
List<RolesUserModel> users = [];
@ -86,9 +92,9 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
final TextEditingController phoneController = TextEditingController();
final TextEditingController jobTitleController = TextEditingController();
bool isCompleteBasics = false;
bool isCompleteRolePermissions = false;
bool isCompleteSpaces = false;
bool? isCompleteBasics;
bool? isCompleteRolePermissions;
bool? isCompleteSpaces;
int numberBasics = 0;
int numberSpaces = 0;
@ -102,35 +108,137 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
phoneController.text.isNotEmpty &&
jobTitleController.text.isNotEmpty;
emit(ChangeStatusSteps());
return isCompleteBasics;
}
// void checkStatus(CheckStepStatus event, Emitter<UsersState> emit) {
// try {
// // Check if basic fields are completed
// isCompleteBasics = firstNameController.text.isNotEmpty &&
// lastNameController.text.isNotEmpty &&
// emailController.text.isNotEmpty &&
// phoneController.text.isNotEmpty &&
// jobTitleController.text.isNotEmpty;
// // Emit the updated state
// if (isCompleteBasics && isCompleteRolePermissions && isCompleteSpaces) {
// } else {
// // emit(IncompleteState(
// // isCompleteBasics, isCompleteRolePermissions, isCompleteSpaces));
// }
// } catch (e) {
// emit(ErrorState(e.toString()));
// }
// }
isCompleteSpacesFun(CheckStepStatus event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
isCompleteSpaces = false;
emit(ChangeStatusSteps());
print('isCompleteBasics==$isCompleteSpaces');
return isCompleteSpaces;
}
// Example placeholder methods for role permissions and spaces
bool checkRolePermissions() {
// Add logic to check if role permissions are completed
return true; // Replace with actual logic
return true;
}
bool checkSpaces() {
// Add logic to check if spaces are completed
return true; // Replace with actual logic
return true;
}
Future<List<SpaceModel>> _fetchSpacesForCommunity(
String communityUuid) async {
return await CommunitySpaceManagementApi().getSpaceHierarchy(communityUuid);
}
List<TreeNode> updatedCommunities = [];
List<TreeNode> spacesNodes = [];
_onLoadCommunityAndSpaces(
LoadCommunityAndSpacesEvent event,
Emitter<UsersState> emit,
) async {
emit(UsersLoadingState()); // Emit loading state
try {
// Fetch the list of communities
List<CommunityModel> communities =
await CommunitySpaceManagementApi().fetchCommunities();
// Fetch spaces and create TreeNodes for each community
updatedCommunities = await Future.wait(
communities.map((community) async {
// Fetch spaces for the current community
List<SpaceModel> spaces =
await _fetchSpacesForCommunity(community.uuid);
// Recursively build the tree structure
spacesNodes = _buildTreeNodes(spaces);
// Return a TreeNode for the community, with spaces as its children
return TreeNode(
uuid: community.uuid,
title: community.name,
children: spacesNodes,
isChecked: false, // Initial state; can be updated later
isHighlighted: false,
isExpanded: true, // Default to expanded for better UX
);
}).toList(),
);
// Emit the final state with the structured tree
emit(ChangeStatusSteps());
return updatedCommunities; // Return the structured data if needed
} catch (e) {
// Emit error state in case of failure
emit(ErrorState('Error loading communities and spaces: $e'));
}
}
// Helper function to recursively build tree nodes
List<TreeNode> _buildTreeNodes(List<SpaceModel> spaces) {
return spaces.map((space) {
// If the space has children, recursively build nodes for them
List<TreeNode> childNodes =
space.children != null ? _buildTreeNodes(space.children!) : [];
// Create a TreeNode for the current space
return TreeNode(
uuid: space.uuid!,
title: space.name,
isChecked: false,
isHighlighted: false,
isExpanded: childNodes.isNotEmpty, // Expand if there are children
children: childNodes,
);
}).toList();
}
void searchTreeNode(SearchAnode event, Emitter<UsersState> emit) {
emit(UsersLoadingState()); // Emit loading state
// Clear all highlights if the search term is empty
if (event.searchTerm!.isEmpty) {
_clearHighlights(updatedCommunities);
} else {
// Perform the search and update the highlights
_searchAndHighlightNodes(updatedCommunities, event.searchTerm!);
}
// Emit the updated state after processing all nodes
emit(ChangeStatusSteps());
}
// Helper function to clear all highlights in the tree
void _clearHighlights(List<TreeNode> nodes) {
for (var node in nodes) {
node.isHighlighted = false;
if (node.children.isNotEmpty) {
_clearHighlights(node.children);
}
}
}
// Helper function to search and highlight nodes recursively
bool _searchAndHighlightNodes(List<TreeNode> nodes, String searchTerm) {
bool anyMatch = false;
for (var node in nodes) {
// Check if this node matches the search term
bool isMatch =
node.title.toLowerCase().contains(searchTerm.toLowerCase());
// Recursively check children for matches
bool childMatch = _searchAndHighlightNodes(node.children, searchTerm);
// Highlight this node if it matches or any of its children match
node.isHighlighted = isMatch || childMatch;
// Update if any matches were found in this branch
anyMatch = anyMatch || node.isHighlighted;
}
return anyMatch;
}
}

View File

@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart';
sealed class UsersEvent extends Equatable {
const UsersEvent();
@ -10,6 +11,12 @@ class GetUsers extends UsersEvent {
List<Object?> get props => [];
}
class LoadCommunityAndSpacesEvent extends UsersEvent {
const LoadCommunityAndSpacesEvent();
@override
List<Object?> get props => [];
}
class GetBatchStatus extends UsersEvent {
final List<String> uuids;
const GetBatchStatus(this.uuids);
@ -17,6 +24,7 @@ class GetBatchStatus extends UsersEvent {
List<Object?> get props => [uuids];
}
//LoadCommunityAndSpacesEvent
class ChangeUserStatus extends UsersEvent {
final String userId;
final String newStatus;
@ -33,3 +41,11 @@ class CheckStepStatus extends UsersEvent {
@override
List<Object?> get props => [steps];
}
class SearchAnode extends UsersEvent {
List<TreeNode>? nodes;
String? searchTerm;
SearchAnode({this.nodes, this.searchTerm});
@override
List<Object?> get props => [nodes, searchTerm];
}

View File

@ -0,0 +1,17 @@
class TreeNode {
String uuid;
String title;
bool isChecked;
bool isHighlighted;
bool isExpanded;
List<TreeNode> children;
TreeNode({
required this.uuid,
required this.title,
this.isChecked = false,
this.isHighlighted = false,
this.isExpanded = false,
this.children = const [],
});
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/view/basics_view.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart';
@ -22,7 +23,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => UsersBloc(),
create: (BuildContext context) => UsersBloc()..add(LoadCommunityAndSpacesEvent()),
child: BlocConsumer<UsersBloc, UsersState>(
listener: (context, state) {},
builder: (context, state) {
@ -58,9 +59,9 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildStepIndicator(1, "Basics", _blocRole),
_buildStepIndicator(2, "Spaces", _blocRole),
_buildStepIndicator(
_buildStep1Indicator(1, "Basics", _blocRole),
_buildStep2Indicator(2, "Spaces", _blocRole),
_buildStep3Indicator(
3, "Role & Permissions", _blocRole),
],
),
@ -127,7 +128,6 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
}));
}
// Method to get the form content based on the current step
Widget _getFormContent() {
switch (currentStep) {
case 1:
@ -141,22 +141,70 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
}
}
// Helper method to build step indicators
Widget _buildStepIndicator(int step, String label, UsersBloc bloc) {
Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
currentStep = step;
bloc.add(const CheckStepStatus());
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteSpaces == false
? Assets.wrongProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
// if (bloc.formKey.currentState?.validate() ?? false) {
setState(() {
currentStep = step;
if (currentStep == 1) {
bloc.numberBasics = 1;
} else if (currentStep == 2) {
bloc.numberSpaces = 2;
} else if (currentStep == 3) {
bloc.numberRole = 3;
}
});
// }
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -170,12 +218,67 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteBasics == false
&& (bloc.numberBasics != 0 ||
bloc.numberRole != 0 ||
bloc.numberSpaces != 0)
? Assets.wrongProcessIcon
: bloc.isCompleteBasics == true
? Assets.completeProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
currentStep = step;
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteRolePermissions == false
? Assets.wrongProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),

View File

@ -199,6 +199,8 @@ class DeviceManagement extends StatefulWidget {
}
class _DeviceManagementState extends State<DeviceManagement> {
final List<MainRoleOption> options = [
MainRoleOption(
id: '1',

View File

@ -2,7 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -68,7 +70,12 @@ class SpacesAccessView extends StatelessWidget {
child: TextFormField(
style:
const TextStyle(color: Colors.black),
controller: _blocRole.firstNameController,
// controller: _blocRole.firstNameController,
onChanged: (value) {
_blocRole.add(SearchAnode(
nodes: _blocRole.updatedCommunities,
searchTerm: value));
},
decoration: textBoxDecoration(radios: 20)!
.copyWith(
fillColor: Colors.white,
@ -102,7 +109,9 @@ class SpacesAccessView extends StatelessWidget {
padding: const EdgeInsets.all(8.0),
child: Container(
color: ColorsManager.whiteColors,
child: TreeView())))
child: TreeView(
bloc: _blocRole,
))))
],
),
),
@ -115,90 +124,15 @@ class SpacesAccessView extends StatelessWidget {
}
}
class TreeNode {
String title;
bool isChecked;
bool isHighlighted;
bool isExpanded; // New property to manage expansion
List<TreeNode> children;
TreeNode({
required this.title,
this.isChecked = false,
this.isHighlighted = false,
this.isExpanded = false, // Default to collapsed
this.children = const [],
});
}
// ignore: must_be_immutable
class TreeView extends StatefulWidget {
UsersBloc? bloc;
TreeView({super.key, this.bloc});
@override
_TreeViewState createState() => _TreeViewState();
}
class _TreeViewState extends State<TreeView> {
List<TreeNode> treeData = [
TreeNode(
title: 'Downtown Dubai',
),
TreeNode(title: 'Dubai Creek Harbour', isHighlighted: true),
TreeNode(
title: 'Dubai Hills Estate',
isHighlighted: true,
children: [
TreeNode(title: 'North Side'),
TreeNode(
title: 'South Side',
isHighlighted: true,
children: [
TreeNode(title: 'Hills Business Park'),
TreeNode(title: 'Park Point'),
TreeNode(title: 'Acacia'),
TreeNode(
title: 'Executive Residence',
children: [
TreeNode(title: 'Residence I'),
TreeNode(
title: 'Residence II',
children: [
TreeNode(title: 'Ground Floor', isHighlighted: true),
TreeNode(title: '1st Floor', isHighlighted: true),
TreeNode(title: 'Pool', isHighlighted: true),
TreeNode(title: 'Gym', isHighlighted: true),
],
),
],
),
],
),
TreeNode(
title: 'South Side',
isHighlighted: true,
children: [
TreeNode(title: 'Hills Business Park'),
TreeNode(title: 'Park Point'),
TreeNode(title: 'Acacia'),
TreeNode(
title: 'Executive Residence',
children: [
TreeNode(title: 'Residence I'),
TreeNode(
title: 'Residence II',
children: [
TreeNode(title: 'Ground Floor', isHighlighted: true),
TreeNode(title: '1st Floor', isHighlighted: true),
TreeNode(title: 'Pool', isHighlighted: true),
TreeNode(title: 'Gym', isHighlighted: true),
],
),
],
),
],
),
],
),
];
Widget _buildTree(List<TreeNode> nodes, {int level = 0}) {
return Column(
children: nodes.map((node) => _buildNode(node, level: level)).toList(),
@ -207,31 +141,14 @@ class _TreeViewState extends State<TreeView> {
Widget _buildNode(TreeNode node, {int level = 0}) {
return Container(
color: node.isHighlighted ? Colors.blue.shade100 : Colors.transparent,
child: Padding(
padding: EdgeInsets.only(left: level * 8.0),
color: node.isHighlighted ? Colors.blue.shade50 : Colors.transparent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
GestureDetector(
onTap: () {
setState(() {
node.isExpanded = !node.isExpanded; // Toggle expansion
});
},
child: Icon(
node.children.isNotEmpty
? (node.isExpanded
? Icons.arrow_drop_down
: Icons.arrow_right)
: Icons.arrow_right,
color: node.children.isNotEmpty
? Colors.black
: Colors.transparent,
),
),
GestureDetector(
onTap: () {
setState(() {
@ -246,25 +163,50 @@ class _TreeViewState extends State<TreeView> {
height: 20,
),
),
const SizedBox(width: 15),
Expanded(
child: Text(
child: Padding(
padding: EdgeInsets.only(left: level * 10.0),
child: Row(
children: [
GestureDetector(
onTap: () {
setState(() {
node.isExpanded = !node.isExpanded;
});
},
child: SizedBox(
child: SvgPicture.asset(
node.children.isNotEmpty
? (node.isExpanded
? Assets.arrowDown
: Assets.arrowForward)
: Assets.arrowForward,
fit: BoxFit.none,
),
),
),
const SizedBox(width: 20),
Text(
node.title,
style: TextStyle(
fontSize: 16,
color: Colors.black87,
color: node.isHighlighted
? ColorsManager.blackColor
: ColorsManager.textGray,
),
),
],
),
),
),
],
),
),
if (node.isExpanded && node.children.isNotEmpty)
Padding(
padding: const EdgeInsets.only(left: 24.0),
child: _buildTree(node.children, level: level + 1),
),
_buildTree(node.children, level: level + 1),
],
),
),
);
}
@ -308,7 +250,7 @@ class _TreeViewState extends State<TreeView> {
// Update the checkbox state for parent nodes
void _updateParentCheckStatus(TreeNode node) {
TreeNode? parent = _findParent(treeData, node);
TreeNode? parent = _findParent(widget.bloc!.updatedCommunities, node);
if (parent != null) {
setState(() {
parent.isChecked = _areAllChildrenChecked(parent);
@ -332,7 +274,7 @@ class _TreeViewState extends State<TreeView> {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: _buildTree(treeData),
child: _buildTree(widget.bloc!.updatedCommunities),
);
}
}

View File

@ -252,6 +252,7 @@ class CommunitySpaceManagementApi {
path: ApiEndpoints.getSpaceHierarchy
.replaceAll('{communityId}', communityId),
expectedResponseModel: (json) {
print('=-=-=-=$json');
final spaceModels = (json['data'] as List)
.map((spaceJson) => SpaceModel.fromJson(spaceJson))
.toList();

View File

@ -391,4 +391,8 @@ class Assets {
'assets/icons/uncompleate_process_icon.svg';
static const String wrongProcessIcon =
'assets/icons/wrong_process_icon.svg';
static const String arrowForward =
'assets/icons/arrow_forward.svg';
static const String arrowDown =
'assets/icons/arrow_down.svg';
}