From 56d4c34321f0f56f3b988a1f37e5dcc41026f161 Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 12 Dec 2024 19:00:51 +0300 Subject: [PATCH 01/17] roles_and_permission --- assets/icons/active_user.svg | 18 + assets/icons/box_checked.png | Bin 0 -> 754 bytes assets/icons/compleate_process_icon.svg | 9 + assets/icons/current_process_icon.svg | 3 + assets/icons/deactive_user.svg | 18 + assets/icons/empty_box.png | Bin 0 -> 425 bytes assets/icons/invited_icon.svg | 7 + assets/icons/rectangle_check_box.png | Bin 0 -> 523 bytes assets/icons/search_icon_user.svg | 4 + assets/icons/uncompleate_process_icon.svg | 3 + assets/icons/wrong_process_icon.svg | 10 + lib/pages/home/bloc/home_bloc.dart | 9 +- .../bloc/roles_permission_bloc.dart | 42 ++ .../bloc/roles_permission_event.dart | 41 ++ .../bloc/roles_permission_state.dart | 77 +++ .../model/role_model.dart | 6 + .../model/roles_user_model.dart | 22 + .../users_page/bloc/users_bloc.dart | 136 +++++ .../users_page/bloc/users_event.dart | 35 ++ .../users_page/bloc/users_status.dart | 57 ++ .../users_page/view/add_user_dialog.dart | 214 ++++++++ .../users_page/view/basics_view.dart | 279 ++++++++++ .../users_page/view/roles_and_permission.dart | 498 ++++++++++++++++++ .../users_page/view/spaces_access_view.dart | 338 ++++++++++++ .../users_page/view/user_table.dart | 256 +++++++++ .../users_page/view/users_page.dart | 241 +++++++++ .../view/create_role_card.dart | 0 .../roles_and_permission/view/role_card.dart | 57 ++ .../view/roles_and_permission_page.dart | 96 ++++ .../roles_and_permission/view/roles_page.dart | 69 +++ lib/utils/app_routes.dart | 6 +- lib/utils/color_manager.dart | 13 +- lib/utils/constants/assets.dart | 158 ++++-- lib/utils/constants/routes_const.dart | 1 + lib/utils/style.dart | 79 ++- pubspec.lock | 24 +- 36 files changed, 2739 insertions(+), 87 deletions(-) create mode 100644 assets/icons/active_user.svg create mode 100644 assets/icons/box_checked.png create mode 100644 assets/icons/compleate_process_icon.svg create mode 100644 assets/icons/current_process_icon.svg create mode 100644 assets/icons/deactive_user.svg create mode 100644 assets/icons/empty_box.png create mode 100644 assets/icons/invited_icon.svg create mode 100644 assets/icons/rectangle_check_box.png create mode 100644 assets/icons/search_icon_user.svg create mode 100644 assets/icons/uncompleate_process_icon.svg create mode 100644 assets/icons/wrong_process_icon.svg create mode 100644 lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart create mode 100644 lib/pages/roles_and_permission/bloc/roles_permission_event.dart create mode 100644 lib/pages/roles_and_permission/bloc/roles_permission_state.dart create mode 100644 lib/pages/roles_and_permission/model/role_model.dart create mode 100644 lib/pages/roles_and_permission/model/roles_user_model.dart create mode 100644 lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart create mode 100644 lib/pages/roles_and_permission/users_page/bloc/users_event.dart create mode 100644 lib/pages/roles_and_permission/users_page/bloc/users_status.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/basics_view.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/user_table.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/users_page.dart create mode 100644 lib/pages/roles_and_permission/view/create_role_card.dart create mode 100644 lib/pages/roles_and_permission/view/role_card.dart create mode 100644 lib/pages/roles_and_permission/view/roles_and_permission_page.dart create mode 100644 lib/pages/roles_and_permission/view/roles_page.dart diff --git a/assets/icons/active_user.svg b/assets/icons/active_user.svg new file mode 100644 index 00000000..5e0806e0 --- /dev/null +++ b/assets/icons/active_user.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/box_checked.png b/assets/icons/box_checked.png new file mode 100644 index 0000000000000000000000000000000000000000..d93b9d76d9df6c22695ef154f7b27f151dc63a79 GIT binary patch literal 754 zcmV#?`b=x}-yKsn#)Vx;QBaZaH++eUHK6j0|-3R)(Zx zVRaOSP9)Yi6PfZm?+51;vwnwUA1{kLszV?46N#Mg@dwDToF(Sb$}CBbACq`IS;Fa) zz~Mszx>#7^n0n&J>Q83N3p~j@QF&2Fag(Hr4@+8w+u7Iz1}AzfIb-srM+h-Y3qE(N zDt$EXMf#No`2s^H1OpPWNEvtIrWza8YK}~GgPtRgl7^_$$OT)PdQMU=bU+uLe(goD z7f6hn>K1OTW|-G^e(}}=-8d7WSv&AXSE-wPVdpsx&Ga9YczXXYLaJ?rRxwGRO_y+P zK)_IKE4enYBkez8KS0#Zs5fO_#r0AmzsWIY_~BZ7{*>Iabp02{SkjN|Dsv}J#7v~8 zi&m>s+EPri#C2Op?P#P{K5(MBbnSQhSF)*?s?By%w@pjtNR1ApEyXFri~0ApRm@P! zK76)fNrM6R(w1Uk*6`u;_iC45Ky4tColDz_sno@j-PYiZa4T&qcH}qI;PrA^iEH=V zNp~eC*45x7cZxkp5GDeMsxbN};$bw$fpRqDH9$J=5} zF4D<{+9n4l>})L{(!BDgE2ENTj!sbSU0Bi^_DHz6T$NIwhx(K`*iV=()K>+!1AYj4 kSrLMNK@o58iCJanAKq9oum;g}8UO$Q07*qoM6N<$g1q%wMF0Q* literal 0 HcmV?d00001 diff --git a/assets/icons/compleate_process_icon.svg b/assets/icons/compleate_process_icon.svg new file mode 100644 index 00000000..a4159de2 --- /dev/null +++ b/assets/icons/compleate_process_icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/current_process_icon.svg b/assets/icons/current_process_icon.svg new file mode 100644 index 00000000..967928e3 --- /dev/null +++ b/assets/icons/current_process_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/deactive_user.svg b/assets/icons/deactive_user.svg new file mode 100644 index 00000000..7011f5fb --- /dev/null +++ b/assets/icons/deactive_user.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/empty_box.png b/assets/icons/empty_box.png new file mode 100644 index 0000000000000000000000000000000000000000..71e798759d2913e747487d2b5ffe9cbe32b5831f GIT binary patch literal 425 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3oCO|{#S9E$svykh8Km+7D9BhG zZ8s34L5V* z+7c~3fABHB^!;!DB8~9lg?8_Yx$F`pq*5c}DlRTyedxRN_uqeqx4jMNsJ&m`KBe4e zS>~m@?aFeOV?{OS3ZJUzxNeel6?|IhSIndsuBl`uQ?PK# zPwm84yY_wX@1ABbA#eNb|3c6BR&^O3w7*x(yh*?BujG-RKY$_2 N;OXk;vd$@?2>^L{tuFuo literal 0 HcmV?d00001 diff --git a/assets/icons/invited_icon.svg b/assets/icons/invited_icon.svg new file mode 100644 index 00000000..5563de14 --- /dev/null +++ b/assets/icons/invited_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/rectangle_check_box.png b/assets/icons/rectangle_check_box.png new file mode 100644 index 0000000000000000000000000000000000000000..3404c79cabb9ea8574f45e1698a32f58dc98cafc GIT binary patch literal 523 zcmV+m0`&cfP)%D|6&E zzQc@&oE1{6ku-PM&?@{)xeCl}4BK+X%q8mQD5dcp6Lh=(m~ zubS%Fuun6V8UnpXhLQ$pQf$VN?wyj<3mwqH(^p>jM}g#uss6(DX@>as$!9kPbko`h z?Yaf;wo2VpYCp#~`h+WX#je;DyJA=T55$x!y7Bw)yO?5(NGw{=O+0L(o480{3Xjej zsErZ}rs|~LcTUep#!Wl+2+G_Ko|z|NEvxEVa`LwRrHgb@u5y*PPFo9zG;h9nvZ^#o zG(g>($?J_h66OjWDFr>$Ri?jJm^SKG!JWVlLN7%j_yg*=#yzu}^BwrGqlvs0I6VLW N002ovPDHLkV1k%v=!XCR literal 0 HcmV?d00001 diff --git a/assets/icons/search_icon_user.svg b/assets/icons/search_icon_user.svg new file mode 100644 index 00000000..61eca62d --- /dev/null +++ b/assets/icons/search_icon_user.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/uncompleate_process_icon.svg b/assets/icons/uncompleate_process_icon.svg new file mode 100644 index 00000000..4ede6757 --- /dev/null +++ b/assets/icons/uncompleate_process_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/wrong_process_icon.svg b/assets/icons/wrong_process_icon.svg new file mode 100644 index 00000000..de5b475c --- /dev/null +++ b/assets/icons/wrong_process_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index c837e40a..04c35295 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -42,7 +42,8 @@ class HomeBloc extends Bloc { Future _fetchUserInfo(FetchUserInfo event, Emitter emit) async { try { - var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); + var uuid = + await const FlutterSecureStorage().read(key: UserModel.userUuidKey); user = await HomeApi().fetchUserInfo(uuid); emit(HomeInitial()); } catch (e) { @@ -93,8 +94,10 @@ class HomeBloc extends Bloc { HomeItemModel( title: 'Move in', icon: Assets.moveinIcon, - active: false, - onPress: (context) {}, + active: true, + onPress: (context) { + context.go(RoutesConst.rolesAndPermissions); + }, color: ColorsManager.primaryColor, ), HomeItemModel( diff --git a/lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart b/lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart new file mode 100644 index 00000000..4f4988b3 --- /dev/null +++ b/lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart @@ -0,0 +1,42 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_state.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/role_model.dart'; + +class RolesPermissionBloc + extends Bloc { + RolesPermissionBloc() : super(RolesInitial()) { + on(_getRoles); + on(changeTapSelected); + } + List roleModel = []; + + FutureOr _getRoles( + GetRoles event, Emitter emit) async { + emit(UsersLoadingState()); + try { + roleModel = [ + RoleModel(roleId: '1', roleImage: '', roleName: 'Admin'), + RoleModel(roleId: '2', roleImage: '', roleName: 'Security'), + RoleModel(roleId: '2', roleImage: '', roleName: 'Reception'), + ]; + emit(UsersLoadedState()); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + bool tapSelect = true; + + changeTapSelected( + ChangeTapSelected event, Emitter emit) { + try { + emit(RolesLoadingState()); + tapSelect = event.selected; + emit(ChangeTapStatus(select: !tapSelect)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } +} diff --git a/lib/pages/roles_and_permission/bloc/roles_permission_event.dart b/lib/pages/roles_and_permission/bloc/roles_permission_event.dart new file mode 100644 index 00000000..d5dce346 --- /dev/null +++ b/lib/pages/roles_and_permission/bloc/roles_permission_event.dart @@ -0,0 +1,41 @@ +import 'package:equatable/equatable.dart'; + +sealed class RolesPermissionEvent extends Equatable { + const RolesPermissionEvent(); +} + +class GetRoles extends RolesPermissionEvent { + const GetRoles(); + @override + List get props => []; +} + +class GetBatchStatus extends RolesPermissionEvent { + final List uuids; + const GetBatchStatus(this.uuids); + @override + List get props => [uuids]; +} + +class GetDeviceRecords extends RolesPermissionEvent { + final String uuid; + + const GetDeviceRecords(this.uuid); + @override + List get props => [uuid]; +} + +class GetDeviceAutomationRecords extends RolesPermissionEvent { + final String uuid; + const GetDeviceAutomationRecords(this.uuid); + @override + List get props => [uuid]; +} + +class ChangeTapSelected extends RolesPermissionEvent { + final bool selected; + const ChangeTapSelected(this.selected); + + @override + List get props => [selected]; +} diff --git a/lib/pages/roles_and_permission/bloc/roles_permission_state.dart b/lib/pages/roles_and_permission/bloc/roles_permission_state.dart new file mode 100644 index 00000000..55c2a8cb --- /dev/null +++ b/lib/pages/roles_and_permission/bloc/roles_permission_state.dart @@ -0,0 +1,77 @@ +import 'package:equatable/equatable.dart'; + +sealed class RolesPermissionState extends Equatable { + const RolesPermissionState(); +} + +final class RolesInitial extends RolesPermissionState { + @override + List get props => []; +} + +final class RolesLoadingState extends RolesPermissionState { + @override + List get props => []; +} +final class UsersLoadingState extends RolesPermissionState { + @override + List get props => []; +} + +final class RolesLoadedState extends RolesPermissionState { + @override + List get props => []; +} +final class UsersLoadedState extends RolesPermissionState { + @override + List get props => []; +} + +final class ErrorState extends RolesPermissionState { + final String message; + + const ErrorState(this.message); + + @override + List get props => [message]; +} + +/// report state +final class SosReportLoadingState extends RolesPermissionState { + @override + List get props => []; +} + +final class RolesErrorState extends RolesPermissionState { + final String message; + + const RolesErrorState(this.message); + + @override + List get props => [message]; +} + +/// automation reports + +final class SosAutomationReportLoadingState extends RolesPermissionState { + @override + List get props => []; +} + +final class SosAutomationReportErrorState extends RolesPermissionState { + final String message; + + const SosAutomationReportErrorState(this.message); + + @override + List get props => [message]; +} + +final class ChangeTapStatus extends RolesPermissionState { + bool select = true; + + ChangeTapStatus({required this.select}); + + @override + List get props => [select]; +} diff --git a/lib/pages/roles_and_permission/model/role_model.dart b/lib/pages/roles_and_permission/model/role_model.dart new file mode 100644 index 00000000..3d139904 --- /dev/null +++ b/lib/pages/roles_and_permission/model/role_model.dart @@ -0,0 +1,6 @@ +class RoleModel { + String? roleId; + String? roleName; + String? roleImage; + RoleModel({this.roleId, this.roleName, this.roleImage}); +} diff --git a/lib/pages/roles_and_permission/model/roles_user_model.dart b/lib/pages/roles_and_permission/model/roles_user_model.dart new file mode 100644 index 00000000..244c58de --- /dev/null +++ b/lib/pages/roles_and_permission/model/roles_user_model.dart @@ -0,0 +1,22 @@ +class RolesUserModel { + String? id; + String? userName; + String? userEmail; + String? userRole; + String? creationDate; + String? creationTime; + String? createdBy; + String? status; + String? action; + RolesUserModel( + {this.id, + this.userName, + this.userEmail, + this.userRole, + this.creationDate, + this.creationTime, + this.status, + this.action, + this.createdBy, + }); +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart new file mode 100644 index 00000000..5de74e4b --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -0,0 +1,136 @@ +import 'package:bloc/bloc.dart'; +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'; + +class UsersBloc extends Bloc { + UsersBloc() : super(UsersInitial()) { + on(_getUsers); + on(_changeUserStatus); + on(isCompleteBasicsFun); + } + + List users = []; + + Future _getUsers(GetUsers event, Emitter emit) async { + emit(UsersLoadingState()); + try { + users = [ + RolesUserModel( + id: '1', + userName: 'user 1', + userEmail: 'test1@test.com', + action: '', + createdBy: 'Admin', + creationDate: '25/10/2024', + creationTime: '10:30 AM', + status: 'Invited', + ), + RolesUserModel( + id: '2', + userName: 'user 2', + userEmail: 'test2@test.com', + action: '', + createdBy: 'Admin', + creationDate: '25/10/2024', + creationTime: '10:30 AM', + status: 'Active', + ), + RolesUserModel( + id: '3', + userName: 'user 3', + userEmail: 'test3@test.com', + action: '', + createdBy: 'Admin', + creationDate: '25/10/2024', + creationTime: '10:30 AM', + status: 'Disabled', + ), + ]; + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + void _changeUserStatus(ChangeUserStatus event, Emitter emit) { + try { + // Update the user's status + users = users.map((user) { + if (user.id == event.userId) { + return RolesUserModel( + id: user.id, + userName: user.userName, + userEmail: user.userEmail, + createdBy: user.createdBy, + creationDate: user.creationDate, + creationTime: user.creationTime, + status: event.newStatus, + action: user.action, + ); + } + return user; + }).toList(); + + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + final formKey = GlobalKey(); + final TextEditingController firstNameController = TextEditingController(); + final TextEditingController lastNameController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + final TextEditingController phoneController = TextEditingController(); + final TextEditingController jobTitleController = TextEditingController(); + + bool isCompleteBasics = false; + bool isCompleteRolePermissions = false; + bool isCompleteSpaces = false; + + int numberBasics = 0; + int numberSpaces = 0; + int numberRole = 0; + + isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { + emit(UsersLoadingState()); + isCompleteBasics = firstNameController.text.isNotEmpty && + lastNameController.text.isNotEmpty && + emailController.text.isNotEmpty && + phoneController.text.isNotEmpty && + jobTitleController.text.isNotEmpty; + emit(ChangeStatusSteps()); + } + + // void checkStatus(CheckStepStatus event, Emitter 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())); + // } + // } + +// 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 + } + + bool checkSpaces() { + // Add logic to check if spaces are completed + return true; // Replace with actual logic + } +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart new file mode 100644 index 00000000..3b679a7e --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -0,0 +1,35 @@ +import 'package:equatable/equatable.dart'; + +sealed class UsersEvent extends Equatable { + const UsersEvent(); +} + +class GetUsers extends UsersEvent { + const GetUsers(); + @override + List get props => []; +} + +class GetBatchStatus extends UsersEvent { + final List uuids; + const GetBatchStatus(this.uuids); + @override + List get props => [uuids]; +} + +class ChangeUserStatus extends UsersEvent { + final String userId; + final String newStatus; + + const ChangeUserStatus({required this.userId, required this.newStatus}); + + @override + List get props => [userId, newStatus]; +} + +class CheckStepStatus extends UsersEvent { + final int? steps; + const CheckStepStatus({this.steps}); + @override + List get props => [steps]; +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart new file mode 100644 index 00000000..b9937f77 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; + +sealed class UsersState extends Equatable { + const UsersState(); +} + +final class UsersInitial extends UsersState { + @override + List get props => []; +} + +final class ChangeStatusSteps extends UsersState { + @override + List get props => []; +} + +final class UsersLoadingState extends UsersState { + @override + List get props => []; +} + +final class UsersLoadedState extends UsersState { + List users = []; + UsersLoadedState({required this.users}); + @override + List get props => [users]; +} + +final class ErrorState extends UsersState { + final String message; + + const ErrorState(this.message); + + @override + List get props => [message]; +} + +final class RolesErrorState extends UsersState { + final String message; + + const RolesErrorState(this.message); + + @override + List get props => [message]; +} + +/// automation reports + +final class ChangeTapStatus extends UsersState { + bool select = true; + + ChangeTapStatus({required this.select}); + + @override + List get props => [select]; +} diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart new file mode 100644 index 00000000..b2ee8cf5 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -0,0 +1,214 @@ +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_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'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/spaces_access_view.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class AddNewUserDialog extends StatefulWidget { + const AddNewUserDialog({super.key}); + + @override + _AddNewUserDialogState createState() => _AddNewUserDialogState(); +} + +class _AddNewUserDialogState extends State { + int currentStep = 1; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => UsersBloc(), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + final _blocRole = BlocProvider.of(context); + + return Dialog( + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + width: 900, + child: Column( + children: [ + // Title + const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Add New User", + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), + ), + ), + ), + const Divider(), + Expanded( + child: Row( + children: [ + // Sidebar for Steps + Expanded( + child: Container( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildStepIndicator(1, "Basics", _blocRole), + _buildStepIndicator(2, "Spaces", _blocRole), + _buildStepIndicator( + 3, "Role & Permissions", _blocRole), + ], + ), + ), + ), + Container( + width: 1, + color: ColorsManager.grayBorder, + ), + // Main content (Form) + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + Expanded( + child: _getFormContent(), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + ElevatedButton( + onPressed: () { + if (_blocRole.formKey.currentState + ?.validate() ?? + false) { + // Proceed to next step or finish + setState(() { + if (currentStep < 3) { + currentStep++; + } else { + Navigator.of(context).pop(); + } + }); + } + }, + child: Text(currentStep < 3 + ? "Next" + : "Finish"), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + )); + })); + } + + // Method to get the form content based on the current step + Widget _getFormContent() { + switch (currentStep) { + case 1: + return const BasicsView(); + case 2: + return const SpacesAccessView(); + case 3: + return const RolesAndPermission(); + default: + return Container(); + } + } + + // Helper method to build step indicators + Widget _buildStepIndicator(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, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + SvgPicture.asset( + currentStep == step + ? Assets.currentProcessIcon + : bloc.isCompleteBasics == false + && (bloc.numberBasics != 0 || + bloc.numberRole != 0 || + bloc.numberSpaces != 0) + ? 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, + ), + ), + ) + ], + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/view/basics_view.dart new file mode 100644 index 00000000..d64d837d --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/basics_view.dart @@ -0,0 +1,279 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +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_status.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class BasicsView extends StatelessWidget { + const BasicsView({super.key}); + @override + Widget build(BuildContext context) { + return BlocBuilder(builder: (context, state) { + final _blocRole = BlocProvider.of(context); + return Form( + key: _blocRole.formKey, + child: ListView( + shrinkWrap: true, + children: [ + Text( + 'Set up the basics', + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 20, + color: Colors.black), + ), + const SizedBox( + height: 80, + ), + Text( + 'To get started, fill out some basic information about who you’re adding as a user.', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ), + const SizedBox( + height: 25, + ), + Row( + children: [ + // First Name + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'First Name', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + style: TextStyle(color: Colors.black), + controller: _blocRole.firstNameController, + decoration: inputTextFormDeco( + hintText: "Enter first name", + ).copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter first name'; + } + return null; + }, + ), + ), + ], + ), + ), + + SizedBox(width: 10), + + // Last Name + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + const Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text('Last Name', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + )), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _blocRole.lastNameController, + style: TextStyle(color: Colors.black), + decoration: + inputTextFormDeco(hintText: "Enter last name") + .copyWith( + hintStyle: context + .textTheme.bodyMedium + ?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray)), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter last name'; + } + return null; + }, + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'Email Address', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _blocRole.emailController, + style: TextStyle(color: Colors.black), + decoration: inputTextFormDeco(hintText: "name@example.com") + .copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter last name'; + } + return null; + }, + ), + ), + ], + ), + SizedBox(height: 10), + Row( + children: [ + // Phone Number + + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'Mobile Number', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + style: const TextStyle(color: Colors.black), + controller: _blocRole.phoneController, + decoration: inputTextFormDeco( + hintText: "05x xxx xxxx", + ).copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a phone number'; + } + return null; + }, + keyboardType: TextInputType.phone, + ), + ), + ], + ), + ), + + SizedBox(width: 10), + + // Job Title + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'Job Title', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _blocRole.jobTitleController, + style: TextStyle(color: Colors.black), + decoration: inputTextFormDeco( + hintText: "Job Title (Optional)") + .copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + keyboardType: TextInputType.phone, + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: 20), + ], + ), + ); + }); + } +} diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart new file mode 100644 index 00000000..c9b58581 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart @@ -0,0 +1,498 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +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_status.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'; +import 'package:syncrow_web/utils/style.dart'; + +class RolesAndPermission extends StatelessWidget { + const RolesAndPermission({super.key}); + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + + return BlocBuilder(builder: (context, state) { + final _blocRole = BlocProvider.of(context); + return Container( + color: Colors.white, + child: Form( + key: _blocRole.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Role & Permissions', + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 20, + color: Colors.black), + ), + const SizedBox( + height: 15, + ), + const SizedBox(width: 300, height: 110, child: DropdownExample()), + const SizedBox(height: 10), + Expanded( + child: SizedBox( + child: Column( + children: [ + Expanded( + flex: 2, + child: Container( + decoration: const BoxDecoration( + color: ColorsManager.CircleRolesBackground, + borderRadius: BorderRadius.only( + topRight: Radius.circular(20), + topLeft: Radius.circular(20)), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(20)), + border: Border.all( + color: ColorsManager.grayBorder)), + child: TextFormField( + style: + const TextStyle(color: Colors.black), + controller: _blocRole.firstNameController, + decoration: textBoxDecoration(radios: 20)! + .copyWith( + fillColor: Colors.white, + suffixIcon: Padding( + padding: + const EdgeInsets.only(right: 16), + child: SvgPicture.asset( + Assets.textFieldSearch, + width: 24, + height: 24, + ), + ), + hintStyle: context.textTheme.bodyMedium + ?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + ), + ), + ), + ], + ), + ), + ), + ), + Expanded( + flex: 7, + child: Container( + color: ColorsManager.CircleRolesBackground, + padding: const EdgeInsets.all(8.0), + child: Container( + color: ColorsManager.whiteColors, + child: const DeviceManagement()))) + ], + ), + ), + ), + ], + ), + ), + ); + }); + } +} + +class DropdownExample extends StatefulWidget { + const DropdownExample({super.key}); + + @override + _DropdownExampleState createState() => _DropdownExampleState(); +} + +class _DropdownExampleState extends State { + String? selectedRole; + List roles = ['Admin', 'User', 'Guest', 'Moderator']; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Role", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: Colors.black, + ), + ), + const SizedBox(height: 8), + SizedBox( + child: DropdownButtonFormField( + alignment: Alignment.center, + focusColor: ColorsManager.whiteColors, + autofocus: true, + value: selectedRole, + items: roles.map((role) { + return DropdownMenuItem( + value: role, + child: Text(role), + ); + }).toList(), + onChanged: (value) { + setState(() { + selectedRole = value; + }); + }, + padding: EdgeInsets.zero, + icon: const SizedBox.shrink(), + borderRadius: const BorderRadius.all(Radius.circular(10)), + hint: const Padding( + padding: EdgeInsets.only(left: 20), + child: Text("Please Select"), + ), + decoration: inputTextFormDeco().copyWith( + contentPadding: EdgeInsets.zero, + suffixIcon: Container( + padding: EdgeInsets.zero, + width: 70, + height: 50, + decoration: BoxDecoration( + color: ColorsManager.graysColor, + borderRadius: const BorderRadius.only( + bottomRight: Radius.circular(10), + topRight: Radius.circular(10), + ), + border: Border.all( + color: ColorsManager.grayBorder, + width: 1.0, + ), + ), + child: const Center( + child: Icon(Icons.keyboard_arrow_down), + ), + ), + ), + ), + ), + ], + ), + ); + } +} + +class DeviceManagement extends StatefulWidget { + const DeviceManagement({Key? key}) : super(key: key); + + @override + _DeviceManagementState createState() => _DeviceManagementState(); +} + +class _DeviceManagementState extends State { + final List options = [ + MainRoleOption( + id: '1', + title: "Device Management", + subOptions: [ + SubRoleOption( + id: '11', + title: "Manage devices in private spaces", + children: [ + ChildRoleOption(id: '111', title: "Control"), + ChildRoleOption(id: '112', title: "Assign device"), + ChildRoleOption(id: '113', title: "View"), + ], + ), + SubRoleOption( + id: '12', + title: "Manage", + children: [ + ChildRoleOption(id: '121', title: "cc"), + ChildRoleOption(id: '122', title: "Assign"), + ChildRoleOption(id: '123', title: "s"), + ], + ), + ], + ), + MainRoleOption( + id: '2', + title: "Device Management", + subOptions: [ + SubRoleOption( + id: '22', + title: "Manage devices in private spaces", + children: [ + ChildRoleOption(id: '211', title: "Control"), + ChildRoleOption(id: '212', title: "Assign device"), + ChildRoleOption(id: '213', title: "View"), + ], + ), + ], + ), + ]; + + void toggleOptionById(String id) { + setState(() { + for (var mainOption in options) { + if (mainOption.id == id) { + final isChecked = + checkifOneOfthemChecked(mainOption) == CheckState.all; + mainOption.isChecked = !isChecked; + + for (var subOption in mainOption.subOptions) { + subOption.isChecked = !isChecked; + for (var child in subOption.children) { + child.isChecked = !isChecked; + } + } + return; + } + + for (var subOption in mainOption.subOptions) { + if (subOption.id == id) { + subOption.isChecked = !subOption.isChecked; + for (var child in subOption.children) { + child.isChecked = subOption.isChecked; + } + mainOption.isChecked = + mainOption.subOptions.every((sub) => sub.isChecked); + return; + } + + for (var child in subOption.children) { + if (child.id == id) { + child.isChecked = !child.isChecked; + subOption.isChecked = + subOption.children.every((child) => child.isChecked); + mainOption.isChecked = + mainOption.subOptions.every((sub) => sub.isChecked); + return; + } + } + } + } + }); + } + + CheckState checkifOneOfthemChecked(MainRoleOption mainOption) { + bool allSelected = true; + bool someSelected = false; + + for (var subOption in mainOption.subOptions) { + if (subOption.isChecked) { + someSelected = true; + } else { + allSelected = false; + } + + for (var child in subOption.children) { + if (child.isChecked) { + someSelected = true; + } else { + allSelected = false; + } + } + } + + if (allSelected) { + return CheckState.all; + } else if (someSelected) { + return CheckState.some; + } else { + return CheckState.none; + } + } + + @override + Widget build(BuildContext context) { + return ListView.builder( + padding: const EdgeInsets.all(8), + itemCount: options.length, + itemBuilder: (context, index) { + final option = options[index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + InkWell( + onTap: () => toggleOptionById(option.id), + child: Builder( + builder: (context) { + final checkState = checkifOneOfthemChecked(option); + + if (checkState == CheckState.all) { + return Image.asset( + Assets.CheckBoxChecked, + width: 20, + height: 20, + ); + } else if (checkState == CheckState.some) { + return Image.asset( + Assets.rectangleCheckBox, + width: 20, + height: 20, + ); + } else { + return Image.asset( + Assets.emptyBox, + width: 20, + height: 20, + ); + } + }, + ), + ), + const SizedBox(width: 8), + Text( + option.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 12, + color: ColorsManager.blackColor), + ), + ], + ), + const SizedBox( + height: 10, + ), + ...option.subOptions.map((subOption) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + InkWell( + onTap: () => toggleOptionById(subOption.id), + child: Builder( + builder: (context) { + final checkState = + checkifOneOfthemChecked(MainRoleOption( + id: subOption.id, + title: subOption.title, + subOptions: [subOption], + )); + + if (checkState == CheckState.all) { + return Image.asset( + Assets.CheckBoxChecked, + width: 20, + height: 20, + ); + } else if (checkState == CheckState.some) { + return Image.asset( + Assets.rectangleCheckBox, + width: 20, + height: 20, + ); + } else { + return Image.asset( + Assets.emptyBox, + width: 20, + height: 20, + ); + } + }, + ), + ), + const SizedBox(width: 8), + Text( + subOption.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 12, + color: ColorsManager.lightGreyColor), + ), + ], + ), + Padding( + padding: const EdgeInsets.only(left: 50.0), + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // 2 items per row + mainAxisSpacing: 2.0, // Space between rows + crossAxisSpacing: 0.2, // Space between columns + childAspectRatio: 5, // Adjust aspect ratio as needed + ), + itemCount: subOption.children.length, + itemBuilder: (context, index) { + final child = subOption.children[index]; + return CheckboxListTile( + dense: true, + controlAffinity: ListTileControlAffinity.leading, + title: Text( + child.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.lightGreyColor), + ), + value: child.isChecked, + onChanged: (value) => toggleOptionById(child.id), + ); + }, + ), + ) + ], + ); + }).toList(), + ], + ); + }, + ); + } +} + +class MainRoleOption { + String id; + String title; + bool isChecked; + List subOptions; + MainRoleOption({ + required this.id, + required this.title, + this.isChecked = false, + this.subOptions = const [], + }); +} + +class SubRoleOption { + String id; + String title; + bool isChecked; + List children; + + SubRoleOption({ + required this.id, + required this.title, + this.isChecked = false, + this.children = const [], + }); +} + +class ChildRoleOption { + String id; + String title; + bool isChecked; + + ChildRoleOption({ + required this.id, + required this.title, + this.isChecked = false, + }); +} + +enum CheckState { none, some, all } diff --git a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart new file mode 100644 index 00000000..9dc35a89 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart @@ -0,0 +1,338 @@ +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_status.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'; +import 'package:syncrow_web/utils/style.dart'; + +class SpacesAccessView extends StatelessWidget { + const SpacesAccessView({super.key}); + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + + return BlocBuilder(builder: (context, state) { + final _blocRole = BlocProvider.of(context); + return Container( + color: Colors.white, + child: Form( + key: _blocRole.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Spaces access', + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 20, + color: Colors.black), + ), + const SizedBox( + height: 35, + ), + const SizedBox( + child: Text( + 'Select the spaces you would like to grant access to for the user you are adding'), + ), + const SizedBox( + height: 25, + ), + Expanded( + child: SizedBox( + child: Column( + children: [ + Expanded( + flex: 2, + child: Container( + decoration: const BoxDecoration( + color: ColorsManager.CircleRolesBackground, + borderRadius: BorderRadius.only( + topRight: Radius.circular(20), + topLeft: Radius.circular(20)), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(20)), + border: Border.all( + color: ColorsManager.grayBorder)), + child: TextFormField( + style: + const TextStyle(color: Colors.black), + controller: _blocRole.firstNameController, + decoration: textBoxDecoration(radios: 20)! + .copyWith( + fillColor: Colors.white, + suffixIcon: Padding( + padding: + const EdgeInsets.only(right: 16), + child: SvgPicture.asset( + Assets.textFieldSearch, + width: 24, + height: 24, + ), + ), + hintStyle: context.textTheme.bodyMedium + ?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + ), + ), + ), + ], + ), + ), + ), + ), + Expanded( + flex: 7, + child: Container( + color: ColorsManager.CircleRolesBackground, + padding: const EdgeInsets.all(8.0), + child: Container( + color: ColorsManager.whiteColors, + child: TreeView()))) + ], + ), + ), + ), + ], + ), + ), + ); + }); + } +} + +class TreeNode { + String title; + bool isChecked; + bool isHighlighted; + bool isExpanded; // New property to manage expansion + List children; + + TreeNode({ + required this.title, + this.isChecked = false, + this.isHighlighted = false, + this.isExpanded = false, // Default to collapsed + this.children = const [], + }); +} + +class TreeView extends StatefulWidget { + @override + _TreeViewState createState() => _TreeViewState(); +} + +class _TreeViewState extends State { + List 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 nodes, {int level = 0}) { + return Column( + children: nodes.map((node) => _buildNode(node, level: level)).toList(), + ); + } + + 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), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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(() { + node.isChecked = !node.isChecked; + _updateChildrenCheckStatus(node, node.isChecked); + _updateParentCheckStatus(node); + }); + }, + child: Image.asset( + _getCheckBoxImage(node), + width: 20, + height: 20, + ), + ), + Expanded( + child: Text( + node.title, + style: TextStyle( + fontSize: 16, + color: Colors.black87, + ), + ), + ), + ], + ), + if (node.isExpanded && node.children.isNotEmpty) + Padding( + padding: const EdgeInsets.only(left: 24.0), + child: _buildTree(node.children, level: level + 1), + ), + ], + ), + ), + ); + } + + // Determine the appropriate image based on the check state + String _getCheckBoxImage(TreeNode node) { + if (node.children.isEmpty) { + return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox; + } + if (_areAllChildrenChecked(node)) { + return Assets.CheckBoxChecked; + } else if (_areSomeChildrenChecked(node)) { + return Assets.rectangleCheckBox; + } else { + return Assets.emptyBox; + } + } + + // Helper to determine if all children are checked + bool _areAllChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.every((child) => + child.isChecked && + (child.children.isEmpty || _areAllChildrenChecked(child))); + } + + // Helper to determine if some children are checked + bool _areSomeChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.any((child) => + child.isChecked || + (child.children.isNotEmpty && _areSomeChildrenChecked(child))); + } + + // Update the checkbox state for all children + void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { + for (var child in node.children) { + child.isChecked = isChecked; + _updateChildrenCheckStatus(child, isChecked); + } + } + + // Update the checkbox state for parent nodes + void _updateParentCheckStatus(TreeNode node) { + TreeNode? parent = _findParent(treeData, node); + if (parent != null) { + setState(() { + parent.isChecked = _areAllChildrenChecked(parent); + _updateParentCheckStatus(parent); // Recursively update ancestors + }); + } + } + + // Helper to find a node's parent + TreeNode? _findParent(List nodes, TreeNode target) { + for (var node in nodes) { + if (node.children.contains(target)) { + return node; + } + var parent = _findParent(node.children, target); + if (parent != null) return parent; + } + return null; + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: _buildTree(treeData), + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/view/user_table.dart b/lib/pages/roles_and_permission/users_page/view/user_table.dart new file mode 100644 index 00000000..f951f06e --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/user_table.dart @@ -0,0 +1,256 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DynamicTableScreen extends StatefulWidget { + final List titles; + final List> rows; + + DynamicTableScreen({required this.titles, required this.rows}); + + @override + _DynamicTableScreenState createState() => _DynamicTableScreenState(); +} + +class _DynamicTableScreenState extends State + with WidgetsBindingObserver { + late List columnWidths; + + // @override + // void initState() { + // super.initState(); + // // Initialize column widths with default sizes proportional to the screen width + // // Assigning placeholder values here. The actual sizes will be updated in `build`. + // } + @override + void initState() { + super.initState(); + columnWidths = List.filled(widget.titles.length, 150.0); + + WidgetsBinding.instance.addObserver(this); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeMetrics() { + super.didChangeMetrics(); + // Screen size might have changed + final newScreenWidth = MediaQuery.of(context).size.width; + setState(() { + columnWidths = List.generate(widget.titles.length, (index) { + if (index == 1) { + return newScreenWidth * + 0.12; // 20% of screen width for the second column + } else if (index == 9) { + return newScreenWidth * + 0.2; // 25% of screen width for the tenth column + } + return newScreenWidth * + 0.09; // Default to 10% of screen width for other columns + }); + }); + } + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + // Initialize column widths if they are still set to placeholder values + if (columnWidths.every((width) => width == 150.0)) { + columnWidths = List.generate(widget.titles.length, (index) { + if (index == 1) { + return screenWidth * + 0.12; // 20% of screen width for the second column + } else if (index == 9) { + return screenWidth * 0.2; // 25% of screen width for the tenth column + } + return screenWidth * + 0.09; // Default to 10% of screen width for other columns + }); + setState(() {}); + } + return SizedBox( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: FittedBox( + child: Column( + children: [ + // Header Row with Resizable Columns + Container( + color: ColorsManager.CircleRolesBackground, + child: Row( + children: List.generate(widget.titles.length, (index) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + FittedBox( + child: Container( + padding: const EdgeInsets.only(left: 10, right: 10), + width: columnWidths[index], + child: Text( + widget.titles[index], + style: const TextStyle( + fontWeight: FontWeight.w400, + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + ), + ), + GestureDetector( + onHorizontalDragUpdate: (details) { + setState(() { + columnWidths[index] = (columnWidths[index] + + details.delta.dx) + .clamp( + 150.0, 300.0); // Minimum & Maximum size + }); + }, + child: MouseRegion( + cursor: SystemMouseCursors + .resizeColumn, // Set the cursor to resize + child: Container( + color: Colors.green, + child: Container( + color: ColorsManager.boxDivider, + width: 1, + height: 50, // Height of the header cell + ), + ), + ), + ), + ], + ); + }), + ), + ), + // Data Rows with Dividers + Container( + color: ColorsManager.whiteColors, + child: Column( + children: widget.rows.map((row) { + int rowIndex = widget.rows.indexOf(row); + return Column( + children: [ + Container( + child: Padding( + padding: const EdgeInsets.only( + left: 5, top: 10, right: 5, bottom: 10), + child: Row( + children: List.generate(row.length, (index) { + return SizedBox( + width: columnWidths[index], + child: SizedBox( + child: Padding( + padding: const EdgeInsets.only( + left: 15, right: 10), + child: row[index], + ), + ), + ); + }), + ), + ), + ), + if (rowIndex < widget.rows.length - 1) + Row( + children: + List.generate(widget.titles.length, (index) { + return SizedBox( + width: columnWidths[index], + child: const Divider( + color: ColorsManager.boxDivider, + thickness: 1, + height: 1, + ), + ); + })) + // Add a Divider below each row except the last one + ], + ); + }).toList(), + ), + ), + ], + ), + ), + ), + ); + } +} + + + + // Widget build(BuildContext context) { + // return Scaffold( + // body: SingleChildScrollView( + // scrollDirection: Axis.horizontal, + // child: SingleChildScrollView( + // scrollDirection: Axis.vertical, + // child: Column( + // children: [ + // // Header Row with Resizable Columns + // Container( + // color: Colors.green, + // child: Row( + // children: List.generate(widget.titles.length, (index) { + // return Row( + // children: [ + // Container( + // width: columnWidths[index], + // decoration: const BoxDecoration( + // color: Colors.green, + // ), + // child: Text( + // widget.titles[index], + // style: TextStyle(fontWeight: FontWeight.bold), + // textAlign: TextAlign.center, + // ), + // ), + // GestureDetector( + // onHorizontalDragUpdate: (details) { + // setState(() { + // columnWidths[index] = (columnWidths[index] + + // details.delta.dx) + // .clamp(50.0, 300.0); // Minimum & Maximum size + // }); + // }, + // child: MouseRegion( + // cursor: SystemMouseCursors + // .resizeColumn, // Set the cursor to resize + // child: Container( + // color: Colors.green, + // child: Container( + // color: Colors.black, + // width: 1, + // height: 50, // Height of the header cell + // ), + // ), + // ), + // ), + // ], + // ); + // }), + // ), + // ), + // // Data Rows + // ...widget.rows.map((row) { + // return Row( + // children: List.generate(row.length, (index) { + // return Container( + // width: columnWidths[index], + // child: row[index], + // ); + // }), + // ); + // }).toList(), + // ], + // ), + // ), + // ), + // ); + // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/view/users_page.dart b/lib/pages/roles_and_permission/users_page/view/users_page.dart new file mode 100644 index 00000000..273b8a34 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/users_page.dart @@ -0,0 +1,241 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +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/add_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/user_table.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'; +import 'package:syncrow_web/utils/style.dart'; + +class UsersPage extends StatelessWidget { + const UsersPage({super.key}); + @override + Widget build(BuildContext context) { + final TextEditingController searchController = TextEditingController(); + + Widget actionButton({required String title, required Function()? onTap}) { + return InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.only(left: 8, right: 8), + child: Text( + title, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: title == "Delete" + ? ColorsManager.red + : ColorsManager.spaceColor, + fontWeight: FontWeight.w400, + ), + ), + ), + ); + } + + Widget status({required String status}) { + return Center( + child: Container( + padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(20)), + color: status == "Invited" + ? ColorsManager.invitedOrange.withOpacity(0.5) + : status == "Active" + ? ColorsManager.activeGreen.withOpacity(0.5) + : ColorsManager.disabledPink.withOpacity(0.5), + ), + child: Padding( + padding: + const EdgeInsets.only(left: 10, right: 10, bottom: 5, top: 5), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + status, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: status == "Invited" + ? ColorsManager.invitedOrangeText + : status == "Active" + ? ColorsManager.activeGreenText + : ColorsManager.disabledRedText, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ), + ); + } + + Widget changeIconStatus( + {required String userId, + required String status, + required Function()? onTap}) { + return Center( + child: InkWell( + onTap: () { + final newStatus = status == 'Active' + ? 'Disabled' + : status == 'Disabled' + ? 'Invited' + : 'Active'; + context + .read() + .add(ChangeUserStatus(userId: userId, newStatus: newStatus)); + }, + child: Padding( + padding: + const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), + child: SvgPicture.asset( + status == "Invited" + ? Assets.invitedIcon + : status == "Active" + ? Assets.activeUser + : Assets.deActiveUser, + height: 35, + ), + ), + ), + ); + } + +// return RolesAndPermission(); +// } +// } + return BlocBuilder( + builder: (context, state) { + final screenSize = MediaQuery.of(context).size; + + if (state is UsersLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is UsersLoadedState) { + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + decoration: containerDecoration.copyWith( + borderRadius: const BorderRadius.all( + Radius.circular(20), + ), + ), + width: screenSize.width * 0.4, + child: TextFormField( + controller: searchController, + style: const TextStyle(color: Colors.black), + decoration: textBoxDecoration(radios: 15)!.copyWith( + fillColor: ColorsManager.whiteColors, + errorStyle: const TextStyle(height: 0), + hintStyle: context.textTheme.titleSmall?.copyWith( + color: Colors.grey, + fontSize: 12, + ), + hintText: 'Search', + suffixIcon: SizedBox( + child: SvgPicture.asset( + Assets.searchIconUser, + fit: BoxFit.none, + ), + ), + ), + ), + ), + const SizedBox(width: 20), + InkWell( + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return const AddNewUserDialog(); + }, + ).then((listDevice) { + if (listDevice != null) {} + }); + }, + child: Container( + decoration: containerWhiteDecoration, + width: screenSize.width * 0.18, + height: 50, + child: const Center( + child: Text( + 'Add New User', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: ColorsManager.blueColor, + ), + ), + ), + ), + ), + ], + ), + const SizedBox(height: 25), + DynamicTableScreen( + titles: const [ + "Full Name", + "Email Address", + "Job Title", + "Role", + "Creation Date", + "Creation Time", + "Created By", + "Status", + "De/Activate", + "Action" + ], + rows: state.users.map((user) { + return [ + Text(user.userName!), + Text(user.userEmail!), + const Text("Test"), + const Text("Member"), + Text(user.creationDate!), + Text(user.creationTime!), + Text(user.createdBy!), + changeIconStatus( + status: user.status!, + userId: user.id!, + onTap: () {}, + ), + status(status: user.status!), + Row( + children: [ + actionButton( + title: "Activity Log", + onTap: () {}, + ), + actionButton( + title: "Edit", + onTap: () {}, + ), + actionButton( + title: "Delete", + onTap: () {}, + ), + ], + ), + ]; + }).toList(), + ), + ], + ), + ); + } else if (state is ErrorState) { + return Center(child: Text(state.message)); + } else { + return const Center(child: Text('No data available.')); + } + }, + ); + } +} diff --git a/lib/pages/roles_and_permission/view/create_role_card.dart b/lib/pages/roles_and_permission/view/create_role_card.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/pages/roles_and_permission/view/role_card.dart b/lib/pages/roles_and_permission/view/role_card.dart new file mode 100644 index 00000000..b3f59ee9 --- /dev/null +++ b/lib/pages/roles_and_permission/view/role_card.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class RoleCard extends StatelessWidget { + final String name; + const RoleCard({super.key, required this.name}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, // Card background color + borderRadius: BorderRadius.circular(20), // Rounded corners + boxShadow: [ + BoxShadow( + color: ColorsManager.blackColor.withOpacity(0.2), // Shadow color + blurRadius: 20, // Spread of the shadow + offset: const Offset(2, 2), // No directional bias + spreadRadius: 1, // Ensures the shadow is more noticeable + ), + ], + ), + child: Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const CircleAvatar( + backgroundColor: ColorsManager.neutralGray, + radius: 65, + child: CircleAvatar( + backgroundColor: ColorsManager.CircleRolesBackground, + radius: 62, + child: Icon( + Icons.admin_panel_settings, + size: 40, + color: Colors.blue, + ), + ), + ), + Text( + name, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart new file mode 100644 index 00000000..05f924ca --- /dev/null +++ b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_state.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/view/users_page.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/web_layout/web_scaffold.dart'; + +class RolesAndPermissionPage extends StatelessWidget { + const RolesAndPermissionPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => + RolesPermissionBloc()..add(const GetRoles()), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + final _blocRole = BlocProvider.of(context); + + return state is RolesLoadingState + ? const Center(child: CircularProgressIndicator()) + : WebScaffold( + enableMenuSidebar: false, + appBarTitle: FittedBox( + child: Text( + 'Roles & Permissions', + style: Theme.of(context).textTheme.headlineLarge, + ), + ), + rightBody: const NavigateHomeGridView(), + centerBody: Row( + children: [ + // TextButton( + // style: TextButton.styleFrom( + // backgroundColor: null, + // ), + // onPressed: () { + // _blocRole.add(const ChangeTapSelected(true)); + // }, + // child: Text( + // 'Roles', + // style: context.textTheme.titleMedium?.copyWith( + // color: (_blocRole.tapSelect == true) + // ? ColorsManager.whiteColors + // : ColorsManager.grayColor, + // fontWeight: (_blocRole.tapSelect == true) + // ? FontWeight.w700 + // : FontWeight.w400, + // ), + // ), + // ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: null, + ), + onPressed: () { + // _blocRole.add(const ChangeTapSelected(false)); + }, + child: Text( + 'Users', + style: context.textTheme.titleMedium?.copyWith( + color: (_blocRole.tapSelect == true) + ? ColorsManager.whiteColors + : ColorsManager.grayColor, + fontWeight: (_blocRole.tapSelect == true) + ? FontWeight.w700 + : FontWeight.w400, + ), + ), + ), + ], + ), + scaffoldBody: BlocProvider( + create: (context) => UsersBloc()..add(const GetUsers()), + child: const UsersPage(), + ) + // _blocRole.tapSelect == false + // ? UsersPage( + // blocRole: _blocRole, + // ) + // : RolesPage( + // blocRole: _blocRole, + // ) + ); + }, + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/view/roles_page.dart b/lib/pages/roles_and_permission/view/roles_page.dart new file mode 100644 index 00000000..9c8ef0cd --- /dev/null +++ b/lib/pages/roles_and_permission/view/roles_page.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/view/role_card.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'; +import 'package:syncrow_web/utils/style.dart'; + +class RolesPage extends StatelessWidget { + final RolesPermissionBloc blocRole; + const RolesPage({super.key, required this.blocRole}); + + @override + Widget build(BuildContext context) { + final TextEditingController searchController = TextEditingController(); + double screenWidth = MediaQuery.of(context).size.width; + + int crossAxisCount = (screenWidth ~/ 200).clamp(1, 6); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: containerDecoration.copyWith( + borderRadius: const BorderRadius.all( + Radius.circular(20), + ), + ), + width: 250, + child: TextFormField( + controller: searchController, + style: const TextStyle(color: Colors.black), + decoration: textBoxDecoration(radios: 15)!.copyWith( + fillColor: ColorsManager.whiteColors, + errorStyle: const TextStyle(height: 0), + hintStyle: context.textTheme.titleSmall?.copyWith( + color: Colors.grey, + fontSize: 12, + ), + hintText: 'Search', + suffixIcon: SvgPicture.asset(Assets.searchIconUser)), + ), + ), + Expanded( + child: GridView.builder( + padding: const EdgeInsets.all(10), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + childAspectRatio: 2 / 2.5, + ), + itemCount: blocRole.roleModel.length ?? 0, + itemBuilder: (context, index) { + final role = blocRole.roleModel[index]; + if (role == null) { + return const SizedBox.shrink(); + } + return RoleCard( + name: role.roleName ?? 'Unknown', + ); + }, + ), + ), + ], + ); + } +} diff --git a/lib/utils/app_routes.dart b/lib/utils/app_routes.dart index 20a89e21..246e269e 100644 --- a/lib/utils/app_routes.dart +++ b/lib/utils/app_routes.dart @@ -3,6 +3,7 @@ import 'package:syncrow_web/pages/access_management/view/access_management.dart' import 'package:syncrow_web/pages/auth/view/login_page.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart'; +import 'package:syncrow_web/pages/roles_and_permission/view/roles_and_permission_page.dart'; import 'package:syncrow_web/pages/spaces_management/view/spaces_management_page.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart'; @@ -32,7 +33,10 @@ class AppRoutes { ), GoRoute( path: RoutesConst.spacesManagementPage, - builder: (context, state) => SpaceManagementPage()), + builder: (context, state) => const SpaceManagementPage()), + GoRoute( + path: RoutesConst.rolesAndPermissions, + builder: (context, state) => const RolesAndPermissionPage()), ]; } } diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 95d0f214..5549b566 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -5,7 +5,8 @@ abstract class ColorsManager { static const Color switchOffColor = Color(0x7F8D99AE); static const Color primaryColor = Color(0xFF0030CB); //023DFE static const Color secondaryTextColor = Color(0xFF848484); - static Color primaryColorWithOpacity = const Color(0xFF023DFE).withOpacity(0.6); + static Color primaryColorWithOpacity = + const Color(0xFF023DFE).withOpacity(0.6); static const Color whiteColors = Colors.white; static const Color secondaryColor = Color(0xFF023DFE); static const Color onSecondaryColor = Color(0xFF023DFE); @@ -54,5 +55,13 @@ abstract class ColorsManager { static const Color warningRed = Color(0xFFFF6465); static const Color borderColor = Color(0xFFE5E5E5); static const Color CircleImageBackground = Color(0xFFF4F4F4); + static const Color CircleRolesBackground = Color(0xFFF8F8F8); + static const Color activeGreen = Color(0xFF99FF93); + static const Color activeGreenText = Color(0xFF008905); + static const Color disabledPink = Color(0xFFFF9395); + static const Color disabledRedText = Color(0xFF890002); + static const Color invitedOrange = Color(0xFFFFE193); + static const Color invitedOrangeText = Color(0xFFFFBF00); + //background: #F8F8F8; + } -//background: #background: #5D5D5D; diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 958c2c1c..4e35e84f 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -13,10 +13,12 @@ class Assets { static const String rightLine = "assets/images/right_line.png"; static const String google = "assets/images/google.svg"; static const String facebook = "assets/images/facebook.svg"; - static const String invisiblePassword = "assets/images/Password_invisible.svg"; + static const String invisiblePassword = + "assets/images/Password_invisible.svg"; static const String visiblePassword = "assets/images/password_visible.svg"; static const String accessIcon = "assets/images/access_icon.svg"; - static const String spaseManagementIcon = "assets/images/spase_management_icon.svg"; + static const String spaseManagementIcon = + "assets/images/spase_management_icon.svg"; static const String devicesIcon = "assets/images/devices_icon.svg"; static const String moveinIcon = "assets/images/movein_icon.svg"; static const String constructionIcon = "assets/images/construction_icon.svg"; @@ -29,13 +31,15 @@ class Assets { static const String emptyTable = "assets/images/empty_table.svg"; // General assets - static const String motionlessDetection = "assets/icons/motionless_detection.svg"; + static const String motionlessDetection = + "assets/icons/motionless_detection.svg"; static const String acHeating = "assets/icons/ac_heating.svg"; static const String acPowerOff = "assets/icons/ac_power_off.svg"; static const String acFanMiddle = "assets/icons/ac_fan_middle.svg"; static const String switchAlarmSound = "assets/icons/switch_alarm_sound.svg"; static const String resetOff = "assets/icons/reset_off.svg"; - static const String sensitivityOperationIcon = "assets/icons/sesitivity_operation_icon.svg"; + static const String sensitivityOperationIcon = + "assets/icons/sesitivity_operation_icon.svg"; static const String motionDetection = "assets/icons/motion_detection.svg"; static const String freezing = "assets/icons/freezing.svg"; static const String indicator = "assets/icons/indicator.svg"; @@ -56,7 +60,8 @@ class Assets { static const String celsiusDegrees = "assets/icons/celsius_degrees.svg"; static const String masterState = "assets/icons/master_state.svg"; static const String acPower = "assets/icons/ac_power.svg"; - static const String farDetectionFunction = "assets/icons/far_detection_function.svg"; + static const String farDetectionFunction = + "assets/icons/far_detection_function.svg"; static const String nobodyTime = "assets/icons/nobody_time.svg"; // Automation functions @@ -64,33 +69,47 @@ class Assets { "assets/icons/automation_functions/temp_password_unlock.svg"; static const String doorlockNormalOpen = "assets/icons/automation_functions/doorlock_normal_open.svg"; - static const String doorbell = "assets/icons/automation_functions/doorbell.svg"; + static const String doorbell = + "assets/icons/automation_functions/doorbell.svg"; static const String remoteUnlockViaApp = "assets/icons/automation_functions/remote_unlock_via_app.svg"; - static const String doubleLock = "assets/icons/automation_functions/double_lock.svg"; - static const String selfTestResult = "assets/icons/automation_functions/self_test_result.svg"; - static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg"; - static const String presenceState = "assets/icons/automation_functions/presence_state.svg"; - static const String currentTemp = "assets/icons/automation_functions/current_temp.svg"; - static const String presence = "assets/icons/automation_functions/presence.svg"; + static const String doubleLock = + "assets/icons/automation_functions/double_lock.svg"; + static const String selfTestResult = + "assets/icons/automation_functions/self_test_result.svg"; + static const String lockAlarm = + "assets/icons/automation_functions/lock_alarm.svg"; + static const String presenceState = + "assets/icons/automation_functions/presence_state.svg"; + static const String currentTemp = + "assets/icons/automation_functions/current_temp.svg"; + static const String presence = + "assets/icons/automation_functions/presence.svg"; static const String residualElectricity = "assets/icons/automation_functions/residual_electricity.svg"; - static const String hijackAlarm = "assets/icons/automation_functions/hijack_alarm.svg"; - static const String passwordUnlock = "assets/icons/automation_functions/password_unlock.svg"; + static const String hijackAlarm = + "assets/icons/automation_functions/hijack_alarm.svg"; + static const String passwordUnlock = + "assets/icons/automation_functions/password_unlock.svg"; static const String remoteUnlockRequest = "assets/icons/automation_functions/remote_unlock_req.svg"; - static const String cardUnlock = "assets/icons/automation_functions/card_unlock.svg"; + static const String cardUnlock = + "assets/icons/automation_functions/card_unlock.svg"; static const String motion = "assets/icons/automation_functions/motion.svg"; static const String fingerprintUnlock = "assets/icons/automation_functions/fingerprint_unlock.svg"; // Presence Sensor Assets static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg"; - static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg"; + static const String sensorPresenceIcon = + "assets/icons/sensor_presence_ic.svg"; static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg"; - static const String illuminanceRecordIcon = "assets/icons/illuminance_record_ic.svg"; - static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg"; - static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg"; + static const String illuminanceRecordIcon = + "assets/icons/illuminance_record_ic.svg"; + static const String presenceRecordIcon = + "assets/icons/presence_record_ic.svg"; + static const String helpDescriptionIcon = + "assets/icons/help_description_ic.svg"; static const String lightPulp = "assets/icons/light_pulb.svg"; static const String acDevice = "assets/icons/ac_device.svg"; @@ -140,10 +159,12 @@ class Assets { static const String unit = 'assets/icons/unit_icon.svg'; static const String villa = 'assets/icons/villa_icon.svg'; static const String iconEdit = 'assets/icons/icon_edit_icon.svg'; - static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg'; + static const String textFieldSearch = + 'assets/icons/textfield_search_icon.svg'; static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg'; static const String addIcon = 'assets/icons/add_icon.svg'; - static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg'; + static const String smartThermostatIcon = + 'assets/icons/smart_thermostat_icon.svg'; static const String smartLightIcon = 'assets/icons/smart_light_icon.svg'; static const String presenceSensor = 'assets/icons/presence_sensor.svg'; static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg'; @@ -191,7 +212,8 @@ class Assets { //assets/icons/water_leak_normal.svg static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg'; //assets/icons/water_leak_detected.svg - static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg'; + static const String waterLeakDetected = + 'assets/icons/water_leak_detected.svg'; //assets/icons/automation_records.svg static const String automationRecords = 'assets/icons/automation_records.svg'; @@ -256,40 +278,64 @@ class Assets { static const String delay = 'assets/icons/routine/delay.svg'; // Assets for functions_icons - static const String assetsSensitivityFunction = "assets/icons/functions_icons/sensitivity.svg"; + static const String assetsSensitivityFunction = + "assets/icons/functions_icons/sensitivity.svg"; static const String assetsSensitivityOperationIcon = "assets/icons/functions_icons/sesitivity_operation_icon.svg"; - static const String assetsAcPower = "assets/icons/functions_icons/ac_power.svg"; - static const String assetsAcPowerOFF = "assets/icons/functions_icons/ac_power_off.svg"; - static const String assetsChildLock = "assets/icons/functions_icons/child_lock.svg"; - static const String assetsFreezing = "assets/icons/functions_icons/freezing.svg"; - static const String assetsFanSpeed = "assets/icons/functions_icons/fan_speed.svg"; - static const String assetsAcCooling = "assets/icons/functions_icons/ac_cooling.svg"; - static const String assetsAcHeating = "assets/icons/functions_icons/ac_heating.svg"; - static const String assetsCelsiusDegrees = "assets/icons/functions_icons/celsius_degrees.svg"; - static const String assetsTempreture = "assets/icons/functions_icons/tempreture.svg"; - static const String assetsAcFanLow = "assets/icons/functions_icons/ac_fan_low.svg"; - static const String assetsAcFanMiddle = "assets/icons/functions_icons/ac_fan_middle.svg"; - static const String assetsAcFanHigh = "assets/icons/functions_icons/ac_fan_high.svg"; - static const String assetsAcFanAuto = "assets/icons/functions_icons/ac_fan_auto.svg"; - static const String assetsSceneChildLock = "assets/icons/functions_icons/scene_child_lock.svg"; + static const String assetsAcPower = + "assets/icons/functions_icons/ac_power.svg"; + static const String assetsAcPowerOFF = + "assets/icons/functions_icons/ac_power_off.svg"; + static const String assetsChildLock = + "assets/icons/functions_icons/child_lock.svg"; + static const String assetsFreezing = + "assets/icons/functions_icons/freezing.svg"; + static const String assetsFanSpeed = + "assets/icons/functions_icons/fan_speed.svg"; + static const String assetsAcCooling = + "assets/icons/functions_icons/ac_cooling.svg"; + static const String assetsAcHeating = + "assets/icons/functions_icons/ac_heating.svg"; + static const String assetsCelsiusDegrees = + "assets/icons/functions_icons/celsius_degrees.svg"; + static const String assetsTempreture = + "assets/icons/functions_icons/tempreture.svg"; + static const String assetsAcFanLow = + "assets/icons/functions_icons/ac_fan_low.svg"; + static const String assetsAcFanMiddle = + "assets/icons/functions_icons/ac_fan_middle.svg"; + static const String assetsAcFanHigh = + "assets/icons/functions_icons/ac_fan_high.svg"; + static const String assetsAcFanAuto = + "assets/icons/functions_icons/ac_fan_auto.svg"; + static const String assetsSceneChildLock = + "assets/icons/functions_icons/scene_child_lock.svg"; static const String assetsSceneChildUnlock = "assets/icons/functions_icons/scene_child_unlock.svg"; - static const String assetsSceneRefresh = "assets/icons/functions_icons/scene_refresh.svg"; - static const String assetsLightCountdown = "assets/icons/functions_icons/light_countdown.svg"; - static const String assetsFarDetection = "assets/icons/functions_icons/far_detection.svg"; + static const String assetsSceneRefresh = + "assets/icons/functions_icons/scene_refresh.svg"; + static const String assetsLightCountdown = + "assets/icons/functions_icons/light_countdown.svg"; + static const String assetsFarDetection = + "assets/icons/functions_icons/far_detection.svg"; static const String assetsFarDetectionFunction = "assets/icons/functions_icons/far_detection_function.svg"; - static const String assetsIndicator = "assets/icons/functions_icons/indicator.svg"; - static const String assetsMotionDetection = "assets/icons/functions_icons/motion_detection.svg"; + static const String assetsIndicator = + "assets/icons/functions_icons/indicator.svg"; + static const String assetsMotionDetection = + "assets/icons/functions_icons/motion_detection.svg"; static const String assetsMotionlessDetection = "assets/icons/functions_icons/motionless_detection.svg"; - static const String assetsNobodyTime = "assets/icons/functions_icons/nobody_time.svg"; - static const String assetsFactoryReset = "assets/icons/functions_icons/factory_reset.svg"; - static const String assetsMasterState = "assets/icons/functions_icons/master_state.svg"; + static const String assetsNobodyTime = + "assets/icons/functions_icons/nobody_time.svg"; + static const String assetsFactoryReset = + "assets/icons/functions_icons/factory_reset.svg"; + static const String assetsMasterState = + "assets/icons/functions_icons/master_state.svg"; static const String assetsSwitchAlarmSound = "assets/icons/functions_icons/switch_alarm_sound.svg"; - static const String assetsResetOff = "assets/icons/functions_icons/reset_off.svg"; + static const String assetsResetOff = + "assets/icons/functions_icons/reset_off.svg"; // Assets for automation_functions static const String assetsCardUnlock = @@ -320,11 +366,29 @@ class Assets { "assets/icons/functions_icons/automation_functions/self_test_result.svg"; static const String assetsPresence = "assets/icons/functions_icons/automation_functions/presence.svg"; - static const String assetsMotion = "assets/icons/functions_icons/automation_functions/motion.svg"; + static const String assetsMotion = + "assets/icons/functions_icons/automation_functions/motion.svg"; static const String assetsCurrentTemp = "assets/icons/functions_icons/automation_functions/current_temp.svg"; static const String assetsPresenceState = "assets/icons/functions_icons/automation_functions/presence_state.svg"; //assets/icons/routine/automation.svg static const String automation = 'assets/icons/routine/automation.svg'; + static const String searchIconUser = 'assets/icons/search_icon_user.svg'; + static const String searchIcoUser = 'assets/icons/search_icon_user.svg'; + static const String activeUser = 'assets/icons/active_user.svg'; + static const String deActiveUser = 'assets/icons/deactive_user.svg'; + static const String invitedIcon = 'assets/icons/invited_icon.svg'; + static const String rectangleCheckBox = + 'assets/icons/rectangle_check_box.png'; + static const String CheckBoxChecked = 'assets/icons/box_checked.png'; + static const String emptyBox = 'assets/icons/empty_box.png'; + static const String completeProcessIcon = + 'assets/icons/compleate_process_icon.svg'; + static const String currentProcessIcon = + 'assets/icons/current_process_icon.svg'; + static const String uncomplete_ProcessIcon = + 'assets/icons/uncompleate_process_icon.svg'; + static const String wrongProcessIcon = + 'assets/icons/wrong_process_icon.svg'; } diff --git a/lib/utils/constants/routes_const.dart b/lib/utils/constants/routes_const.dart index 094787d4..8a65e9ae 100644 --- a/lib/utils/constants/routes_const.dart +++ b/lib/utils/constants/routes_const.dart @@ -5,4 +5,5 @@ class RoutesConst { static const String accessManagementPage = '/access-management-page'; static const String deviceManagementPage = '/device-management-page'; static const String spacesManagementPage = '/spaces_management-page'; + static const String rolesAndPermissions = '/roles_and_Permissions-page'; } diff --git a/lib/utils/style.dart b/lib/utils/style.dart index a80c68d6..b5ea59ee 100644 --- a/lib/utils/style.dart +++ b/lib/utils/style.dart @@ -2,51 +2,59 @@ import 'package:flutter/material.dart'; import 'color_manager.dart'; -InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration( +InputDecoration? textBoxDecoration( + {bool suffixIcon = false, double radios = 8}) => + InputDecoration( focusColor: ColorsManager.grayColor, suffixIcon: suffixIcon ? const Icon(Icons.search) : null, hintText: 'Search', filled: true, // Enable background filling fillColor: const Color(0xffF5F6F7), // Set the background color border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Add border radius + borderRadius: BorderRadius.circular(radios), // Add border radius borderSide: BorderSide.none, // Remove the underline ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Add border radius + borderRadius: BorderRadius.circular(radios), // Add border radius borderSide: BorderSide.none, // Remove the underline ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Add border radius + borderRadius: BorderRadius.circular(radios), // Add border radius borderSide: BorderSide.none, // Remove the underline ), errorBorder: OutlineInputBorder( borderSide: const BorderSide(color: Colors.red, width: 2), - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(radios), ), focusedErrorBorder: OutlineInputBorder( borderSide: const BorderSide(color: Colors.red, width: 2), - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(radios), ), ); -BoxDecoration containerDecoration = BoxDecoration(boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.3), - spreadRadius: 2, - blurRadius: 4, - offset: const Offset(0, 5), // changes position of shadow - ), -], color: ColorsManager.boxColor, borderRadius: const BorderRadius.all(Radius.circular(10))); +BoxDecoration containerDecoration = BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + spreadRadius: 2, + blurRadius: 4, + offset: const Offset(0, 5), // changes position of shadow + ), + ], + color: ColorsManager.boxColor, + borderRadius: const BorderRadius.all(Radius.circular(10))); -BoxDecoration containerWhiteDecoration = BoxDecoration(boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.3), - spreadRadius: 2, - blurRadius: 4, - offset: const Offset(0, 5), // changes position of shadow - ), -], color: ColorsManager.whiteColors, borderRadius: const BorderRadius.all(Radius.circular(15))); +BoxDecoration containerWhiteDecoration = BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + spreadRadius: 2, + blurRadius: 4, + offset: const Offset(0, 5), // changes position of shadow + ), + ], + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.all(Radius.circular(15))); BoxDecoration subSectionContainerDecoration = BoxDecoration( color: ColorsManager.whiteColors, @@ -59,3 +67,30 @@ BoxDecoration subSectionContainerDecoration = BoxDecoration( ), ], ); + +InputDecoration inputTextFormDeco({hintText}) => InputDecoration( + hintText: hintText, + border: const OutlineInputBorder( + + borderSide: BorderSide( + width: 1, + color: ColorsManager.textGray, // Border color for unfocused state + ), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide( + width: 2, + color: ColorsManager.textGray, // Border color when focused + ), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide( + width: 1, + color: ColorsManager + .textGray // Border color for enabled (but unfocused) state + ), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + ); diff --git a/pubspec.lock b/pubspec.lock index 192106d7..ea2315d7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -316,18 +316,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: @@ -364,18 +364,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.12.0" nested: dependency: transitive description: @@ -593,10 +593,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.0" time_picker_spinner: dependency: "direct main" description: @@ -657,10 +657,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.1" web: dependency: transitive description: From 879bf99b12243232606a9c9869222793cfbfc5cf Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 16 Dec 2024 16:37:55 +0300 Subject: [PATCH 02/17] add_user_dialog --- assets/icons/arrow_down.svg | 3 + assets/icons/arrow_forward.svg | 3 + .../users_page/bloc/users_bloc.dart | 160 +++++++++++++--- .../users_page/bloc/users_event.dart | 16 ++ .../users_page/model/tree_node_model.dart | 17 ++ .../users_page/view/add_user_dialog.dart | 155 ++++++++++++--- .../users_page/view/roles_and_permission.dart | 2 + .../users_page/view/spaces_access_view.dart | 178 ++++++------------ lib/services/space_mana_api.dart | 1 + lib/utils/constants/assets.dart | 4 + 10 files changed, 369 insertions(+), 170 deletions(-) create mode 100644 assets/icons/arrow_down.svg create mode 100644 assets/icons/arrow_forward.svg create mode 100644 lib/pages/roles_and_permission/users_page/model/tree_node_model.dart diff --git a/assets/icons/arrow_down.svg b/assets/icons/arrow_down.svg new file mode 100644 index 00000000..2b4be77b --- /dev/null +++ b/assets/icons/arrow_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/arrow_forward.svg b/assets/icons/arrow_forward.svg new file mode 100644 index 00000000..e5866360 --- /dev/null +++ b/assets/icons/arrow_forward.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index 5de74e4b..0d153804 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -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 { UsersBloc() : super(UsersInitial()) { on(_getUsers); on(_changeUserStatus); on(isCompleteBasicsFun); + on(_onLoadCommunityAndSpaces); + on(searchTreeNode); } List users = []; @@ -86,9 +92,9 @@ class UsersBloc extends Bloc { 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 { phoneController.text.isNotEmpty && jobTitleController.text.isNotEmpty; emit(ChangeStatusSteps()); + return isCompleteBasics; } - // void checkStatus(CheckStepStatus event, Emitter 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 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> _fetchSpacesForCommunity( + String communityUuid) async { + return await CommunitySpaceManagementApi().getSpaceHierarchy(communityUuid); + } + + List updatedCommunities = []; + List spacesNodes = []; + _onLoadCommunityAndSpaces( + LoadCommunityAndSpacesEvent event, + Emitter emit, + ) async { + emit(UsersLoadingState()); // Emit loading state + try { + // Fetch the list of communities + List 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 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 _buildTreeNodes(List spaces) { + return spaces.map((space) { + // If the space has children, recursively build nodes for them + List 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 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 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 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; } } diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart index 3b679a7e..77890673 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -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 get props => []; } +class LoadCommunityAndSpacesEvent extends UsersEvent { + const LoadCommunityAndSpacesEvent(); + @override + List get props => []; +} + class GetBatchStatus extends UsersEvent { final List uuids; const GetBatchStatus(this.uuids); @@ -17,6 +24,7 @@ class GetBatchStatus extends UsersEvent { List get props => [uuids]; } +//LoadCommunityAndSpacesEvent class ChangeUserStatus extends UsersEvent { final String userId; final String newStatus; @@ -33,3 +41,11 @@ class CheckStepStatus extends UsersEvent { @override List get props => [steps]; } + +class SearchAnode extends UsersEvent { + List? nodes; + String? searchTerm; + SearchAnode({this.nodes, this.searchTerm}); + @override + List get props => [nodes, searchTerm]; +} diff --git a/lib/pages/roles_and_permission/users_page/model/tree_node_model.dart b/lib/pages/roles_and_permission/users_page/model/tree_node_model.dart new file mode 100644 index 00000000..a5e622dc --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/model/tree_node_model.dart @@ -0,0 +1,17 @@ +class TreeNode { + String uuid; + String title; + bool isChecked; + bool isHighlighted; + bool isExpanded; + List children; + + TreeNode({ + required this.uuid, + required this.title, + this.isChecked = false, + this.isHighlighted = false, + this.isExpanded = false, + this.children = const [], + }); +} \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart index b2ee8cf5..21e92727 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -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 { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => UsersBloc(), + create: (BuildContext context) => UsersBloc()..add(LoadCommunityAndSpacesEvent()), child: BlocConsumer( listener: (context, state) {}, builder: (context, state) { @@ -58,9 +59,9 @@ class _AddNewUserDialogState extends State { 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), ], ), @@ -120,14 +121,13 @@ class _AddNewUserDialogState extends State { ), ], ), - ), - ], - ), - )); - })); + ), + ], + ), + )); + })); } - // 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 { } } - // 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 { currentStep == step ? Assets.currentProcessIcon : bloc.isCompleteBasics == false - && (bloc.numberBasics != 0 || - bloc.numberRole != 0 || - bloc.numberSpaces != 0) - ? Assets.wrongProcessIcon + ? 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, ), diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart index c9b58581..812a3170 100644 --- a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart @@ -199,6 +199,8 @@ class DeviceManagement extends StatefulWidget { } class _DeviceManagementState extends State { + + final List options = [ MainRoleOption( id: '1', diff --git a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart index 9dc35a89..5dff9b0a 100644 --- a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart @@ -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 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 { - List 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 nodes, {int level = 0}) { return Column( children: nodes.map((node) => _buildNode(node, level: level)).toList(), @@ -207,31 +141,14 @@ class _TreeViewState extends State { 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), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + color: node.isHighlighted ? Colors.blue.shade50 : Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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,24 +163,49 @@ class _TreeViewState extends State { height: 20, ), ), + const SizedBox(width: 15), Expanded( - child: Text( - node.title, - style: TextStyle( - fontSize: 16, - color: Colors.black87, + 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: 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), - ), - ], - ), + ), + if (node.isExpanded && node.children.isNotEmpty) + _buildTree(node.children, level: level + 1), + ], ), ); } @@ -308,7 +250,7 @@ class _TreeViewState extends State { // 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 { @override Widget build(BuildContext context) { return SingleChildScrollView( - child: _buildTree(treeData), + child: _buildTree(widget.bloc!.updatedCommunities), ); } } diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 5d2464e6..58dc0d9d 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -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(); diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 4e35e84f..acb314d9 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -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'; } From a46f69636d1dd213943e3017c621a3492a7c22fc Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 16 Dec 2024 18:19:22 +0300 Subject: [PATCH 03/17] add_user_dialog --- .../users_page/bloc/users_bloc.dart | 87 ++++++++----------- .../users_page/bloc/users_event.dart | 11 +++ .../users_page/view/add_user_dialog.dart | 18 ++-- .../users_page/view/spaces_access_view.dart | 11 +-- 4 files changed, 64 insertions(+), 63 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index 0d153804..72c85bad 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -7,7 +7,6 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_nod 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 { UsersBloc() : super(UsersInitial()) { on(_getUsers); @@ -15,6 +14,7 @@ class UsersBloc extends Bloc { on(isCompleteBasicsFun); on(_onLoadCommunityAndSpaces); on(searchTreeNode); + on(isCompleteSpacesFun); } List users = []; @@ -62,7 +62,6 @@ class UsersBloc extends Bloc { void _changeUserStatus(ChangeUserStatus event, Emitter emit) { try { - // Update the user's status users = users.map((user) { if (user.id == event.userId) { return RolesUserModel( @@ -111,21 +110,27 @@ class UsersBloc extends Bloc { return isCompleteBasics; } - isCompleteSpacesFun(CheckStepStatus event, Emitter emit) { + void isCompleteSpacesFun( + CheckSpacesStepStatus event, Emitter emit) { emit(UsersLoadingState()); - isCompleteSpaces = false; + + try { + List selectedIds = + getSelectedIds(updatedCommunities); + isCompleteSpaces = selectedIds.isNotEmpty; + } catch (e) { + emit(ErrorState('Error while retrieving selected IDs: $e')); + return; + } + emit(ChangeStatusSteps()); - print('isCompleteBasics==$isCompleteSpaces'); - return isCompleteSpaces; } bool checkRolePermissions() { return true; } - bool checkSpaces() { - return true; - } + Future> _fetchSpacesForCommunity( String communityUuid) async { @@ -134,83 +139,60 @@ class UsersBloc extends Bloc { List updatedCommunities = []; List spacesNodes = []; + _onLoadCommunityAndSpaces( - LoadCommunityAndSpacesEvent event, - Emitter emit, - ) async { - emit(UsersLoadingState()); // Emit loading state + LoadCommunityAndSpacesEvent event, Emitter emit) async { try { - // Fetch the list of communities + emit(UsersLoadingState()); List 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 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 + isChecked: false, isHighlighted: false, - isExpanded: true, // Default to expanded for better UX + isExpanded: true, ); }).toList(), ); - - // Emit the final state with the structured tree emit(ChangeStatusSteps()); - - return updatedCommunities; // Return the structured data if needed + return updatedCommunities; } 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 _buildTreeNodes(List spaces) { return spaces.map((space) { - // If the space has children, recursively build nodes for them List childNodes = - space.children != null ? _buildTreeNodes(space.children!) : []; - - // Create a TreeNode for the current space + space.children != null ? _buildTreeNodes(space.children) : []; return TreeNode( uuid: space.uuid!, title: space.name, isChecked: false, isHighlighted: false, - isExpanded: childNodes.isNotEmpty, // Expand if there are children + isExpanded: childNodes.isNotEmpty, children: childNodes, ); }).toList(); } void searchTreeNode(SearchAnode event, Emitter emit) { - emit(UsersLoadingState()); // Emit loading state - - // Clear all highlights if the search term is empty + emit(UsersLoadingState()); 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 nodes) { for (var node in nodes) { node.isHighlighted = false; @@ -220,25 +202,32 @@ class UsersBloc extends Bloc { } } -// Helper function to search and highlight nodes recursively bool _searchAndHighlightNodes(List 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; } + + List selectedIds = []; + List getSelectedIds(List nodes) { + List selectedIds = []; + for (var node in nodes) { + if (node.isChecked) { + selectedIds.add(node.uuid); + } + if (node.children.isNotEmpty) { + selectedIds.addAll(getSelectedIds(node.children)); + } + } + return selectedIds; + } + } diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart index 77890673..f1675b08 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -10,6 +10,11 @@ class GetUsers extends UsersEvent { @override List get props => []; } +class CheckSpacesStepStatus extends UsersEvent { + const CheckSpacesStepStatus(); + @override + List get props => []; +} class LoadCommunityAndSpacesEvent extends UsersEvent { const LoadCommunityAndSpacesEvent(); @@ -49,3 +54,9 @@ class SearchAnode extends UsersEvent { @override List get props => [nodes, searchTerm]; } +class SelecteId extends UsersEvent { + List? nodes; + SelecteId({this.nodes,}); + @override + List get props => [nodes]; +} diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart index 21e92727..895f4825 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -23,7 +23,8 @@ class _AddNewUserDialogState extends State { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => UsersBloc()..add(LoadCommunityAndSpacesEvent()), + create: (BuildContext context) => + UsersBloc()..add(LoadCommunityAndSpacesEvent()), child: BlocConsumer( listener: (context, state) {}, builder: (context, state) { @@ -121,11 +122,11 @@ class _AddNewUserDialogState extends State { ), ], ), - ), - ], - ), - )); - })); + ), + ], + ), + )); + })); } Widget _getFormContent() { @@ -162,7 +163,9 @@ class _AddNewUserDialogState extends State { ? Assets.currentProcessIcon : bloc.isCompleteSpaces == false ? Assets.wrongProcessIcon - : Assets.uncomplete_ProcessIcon, + : bloc.isCompleteSpaces == true + ? Assets.completeProcessIcon + : Assets.uncomplete_ProcessIcon, width: 25, height: 25, ), @@ -263,6 +266,7 @@ class _AddNewUserDialogState extends State { onTap: () { setState(() { currentStep = step; + bloc.add(const CheckSpacesStepStatus()); }); }, child: Column( diff --git a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart index 5dff9b0a..4bc330b2 100644 --- a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart @@ -125,6 +125,7 @@ class SpacesAccessView extends StatelessWidget { } // ignore: must_be_immutable + class TreeView extends StatefulWidget { UsersBloc? bloc; TreeView({super.key, this.bloc}); @@ -155,6 +156,8 @@ class _TreeViewState extends State { node.isChecked = !node.isChecked; _updateChildrenCheckStatus(node, node.isChecked); _updateParentCheckStatus(node); + // widget.bloc!.add( + // SelecteId(nodes: widget.bloc!.updatedCommunities)); }); }, child: Image.asset( @@ -210,7 +213,6 @@ class _TreeViewState extends State { ); } - // Determine the appropriate image based on the check state String _getCheckBoxImage(TreeNode node) { if (node.children.isEmpty) { return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox; @@ -224,7 +226,6 @@ class _TreeViewState extends State { } } - // Helper to determine if all children are checked bool _areAllChildrenChecked(TreeNode node) { return node.children.isNotEmpty && node.children.every((child) => @@ -232,7 +233,6 @@ class _TreeViewState extends State { (child.children.isEmpty || _areAllChildrenChecked(child))); } - // Helper to determine if some children are checked bool _areSomeChildrenChecked(TreeNode node) { return node.children.isNotEmpty && node.children.any((child) => @@ -240,7 +240,6 @@ class _TreeViewState extends State { (child.children.isNotEmpty && _areSomeChildrenChecked(child))); } - // Update the checkbox state for all children void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { for (var child in node.children) { child.isChecked = isChecked; @@ -248,18 +247,16 @@ class _TreeViewState extends State { } } - // Update the checkbox state for parent nodes void _updateParentCheckStatus(TreeNode node) { TreeNode? parent = _findParent(widget.bloc!.updatedCommunities, node); if (parent != null) { setState(() { parent.isChecked = _areAllChildrenChecked(parent); - _updateParentCheckStatus(parent); // Recursively update ancestors + _updateParentCheckStatus(parent); }); } } - // Helper to find a node's parent TreeNode? _findParent(List nodes, TreeNode target) { for (var node in nodes) { if (node.children.contains(target)) { From c31f1262a2ee6e51ec7b4c15f49d4c5f9c441969 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 16 Dec 2024 18:59:56 +0300 Subject: [PATCH 04/17] delete_dialog --- .../users_page/bloc/users_bloc.dart | 18 ++---- .../users_page/view/delete_user_dialog.dart | 63 +++++++++++++++++++ 2 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index 72c85bad..ad5e49ce 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_nod 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 { UsersBloc() : super(UsersInitial()) { on(_getUsers); @@ -103,9 +104,7 @@ class UsersBloc extends Bloc { emit(UsersLoadingState()); isCompleteBasics = firstNameController.text.isNotEmpty && lastNameController.text.isNotEmpty && - emailController.text.isNotEmpty && - phoneController.text.isNotEmpty && - jobTitleController.text.isNotEmpty; + emailController.text.isNotEmpty; emit(ChangeStatusSteps()); return isCompleteBasics; } @@ -115,8 +114,7 @@ class UsersBloc extends Bloc { emit(UsersLoadingState()); try { - List selectedIds = - getSelectedIds(updatedCommunities); + List selectedIds = getSelectedIds(updatedCommunities); isCompleteSpaces = selectedIds.isNotEmpty; } catch (e) { emit(ErrorState('Error while retrieving selected IDs: $e')); @@ -130,8 +128,6 @@ class UsersBloc extends Bloc { return true; } - - Future> _fetchSpacesForCommunity( String communityUuid) async { return await CommunitySpaceManagementApi().getSpaceHierarchy(communityUuid); @@ -204,7 +200,6 @@ class UsersBloc extends Bloc { bool _searchAndHighlightNodes(List nodes, String searchTerm) { bool anyMatch = false; - for (var node in nodes) { bool isMatch = node.title.toLowerCase().contains(searchTerm.toLowerCase()); @@ -218,16 +213,15 @@ class UsersBloc extends Bloc { List selectedIds = []; List getSelectedIds(List nodes) { - List selectedIds = []; + List selectedIds = []; for (var node in nodes) { if (node.isChecked) { - selectedIds.add(node.uuid); + selectedIds.add(node.uuid); } if (node.children.isNotEmpty) { selectedIds.addAll(getSelectedIds(node.children)); } } - return selectedIds; + return selectedIds; } - } diff --git a/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart new file mode 100644 index 00000000..090d4fe3 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.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_status.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class AddNewUserDialog extends StatefulWidget { + const AddNewUserDialog({super.key}); + + @override + _AddNewUserDialogState createState() => _AddNewUserDialogState(); +} + +class _AddNewUserDialogState extends State { + int currentStep = 1; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => UsersBloc(), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + final _blocRole = BlocProvider.of(context); + + return Dialog( + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + child: const Column( + children: [ + Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Delete User", + style: TextStyle( + color: ColorsManager.red, + fontSize: 18, + fontWeight: FontWeight.bold), + ), + ), + ), + Divider(), + Expanded( + child: Text( + "Are you sure you want to delete this user?", + textAlign: TextAlign.center, + )), + Row( + children: [ + Expanded(child: Text('Cancel')), + Expanded(child: Text('Delete')), + ], + ) + ], + ), + )); + })); + } +} From bd1204c03aa41672a6434037ae118ee91e411785 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 17 Dec 2024 16:51:32 +0300 Subject: [PATCH 05/17] add_user_dialog --- .../model/role_type_model.dart | 22 ++ .../users_page/bloc/users_bloc.dart | 64 +++- .../users_page/bloc/users_event.dart | 29 +- .../users_page/bloc/users_status.dart | 4 + .../users_page/view/add_user_dialog.dart | 9 +- .../users_page/view/basics_view.dart | 150 ++++++--- .../users_page/view/delete_user_dialog.dart | 8 +- .../users_page/view/roles_and_permission.dart | 294 +++++++++--------- lib/services/user_permission.dart | 36 +++ lib/utils/constants/api_const.dart | 35 ++- pubspec.lock | 40 +++ pubspec.yaml | 2 + 12 files changed, 481 insertions(+), 212 deletions(-) create mode 100644 lib/pages/roles_and_permission/model/role_type_model.dart create mode 100644 lib/services/user_permission.dart diff --git a/lib/pages/roles_and_permission/model/role_type_model.dart b/lib/pages/roles_and_permission/model/role_type_model.dart new file mode 100644 index 00000000..1705e25c --- /dev/null +++ b/lib/pages/roles_and_permission/model/role_type_model.dart @@ -0,0 +1,22 @@ +class RoleTypeModel { + final String uuid; + final String createdAt; + final String updatedAt; + final String type; + + RoleTypeModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.type, + }); + + factory RoleTypeModel.fromJson(Map json) { + return RoleTypeModel( + uuid: json['uuid'], + createdAt: json['createdAt'], + updatedAt: json['updatedAt'], + type: json['type'], + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index ad5e49ce..5b846ecf 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -1,12 +1,15 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.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/roles_and_permission/users_page/view/roles_and_permission.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'; +import 'package:syncrow_web/services/user_permission.dart'; class UsersBloc extends Bloc { UsersBloc() : super(UsersInitial()) { @@ -16,6 +19,9 @@ class UsersBloc extends Bloc { on(_onLoadCommunityAndSpaces); on(searchTreeNode); on(isCompleteSpacesFun); + on(_getRolePermission); + on(_getPermissions); + on(searchRolePermission); } List users = []; @@ -112,7 +118,6 @@ class UsersBloc extends Bloc { void isCompleteSpacesFun( CheckSpacesStepStatus event, Emitter emit) { emit(UsersLoadingState()); - try { List selectedIds = getSelectedIds(updatedCommunities); isCompleteSpaces = selectedIds.isNotEmpty; @@ -224,4 +229,61 @@ class UsersBloc extends Bloc { } return selectedIds; } + + List roles = []; + List permissions = []; + + _getRolePermission(RoleEvent event, Emitter emit) async { + try { + emit(UsersLoadingState()); + roles = await UserPermissionApi().fetchRoles(); + add(PermissionEvent(roleUuid: roles.first.uuid)); + emit(RolePermissionInitial()); + } catch (e) { + emit(ErrorState('Error loading communities and spaces: $e')); + } + } + + _getPermissions(PermissionEvent event, Emitter emit) async { + try { + emit(UsersLoadingState()); + permissions = await UserPermissionApi().fetchPermission( + event.roleUuid == "" ? roles.first.uuid : event.roleUuid); + emit(RolePermissionInitial()); + } catch (e) { + emit(ErrorState('Error loading communities and spaces: $e')); + } + } + + bool _searchRolePermission(List nodes, String searchTerm) { + bool anyMatch = false; + for (var node in nodes) { + bool isMatch = + node.title.toLowerCase().contains(searchTerm.toLowerCase()); + bool childMatch = _searchRolePermission(node.subOptions, searchTerm); + node.isHighlighted = isMatch || childMatch; + + anyMatch = anyMatch || node.isHighlighted; + } + return anyMatch; + } + + void searchRolePermission(SearchPermission event, Emitter emit) { + emit(UsersLoadingState()); + if (event.searchTerm!.isEmpty) { + _clearHighlightsRolePermission(permissions); + } else { + _searchRolePermission(permissions, event.searchTerm!); + } + emit(ChangeStatusSteps()); + } + + void _clearHighlightsRolePermission(List nodes) { + for (var node in nodes) { + node.isHighlighted = false; + if (node.subOptions.isNotEmpty) { + _clearHighlightsRolePermission(node.subOptions); + } + } + } } diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart index f1675b08..6f59b495 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; sealed class UsersEvent extends Equatable { const UsersEvent(); @@ -10,6 +11,7 @@ class GetUsers extends UsersEvent { @override List get props => []; } + class CheckSpacesStepStatus extends UsersEvent { const CheckSpacesStepStatus(); @override @@ -22,6 +24,19 @@ class LoadCommunityAndSpacesEvent extends UsersEvent { List get props => []; } +class RoleEvent extends UsersEvent { + const RoleEvent(); + @override + List get props => []; +} + +class PermissionEvent extends UsersEvent { + final String? roleUuid; + const PermissionEvent({this.roleUuid = ""}); + @override + List get props => [roleUuid]; +} + class GetBatchStatus extends UsersEvent { final List uuids; const GetBatchStatus(this.uuids); @@ -29,7 +44,6 @@ class GetBatchStatus extends UsersEvent { List get props => [uuids]; } -//LoadCommunityAndSpacesEvent class ChangeUserStatus extends UsersEvent { final String userId; final String newStatus; @@ -54,9 +68,20 @@ class SearchAnode extends UsersEvent { @override List get props => [nodes, searchTerm]; } + +class SearchPermission extends UsersEvent { + List? nodes; + String? searchTerm; + SearchPermission({this.nodes, this.searchTerm}); + @override + List get props => [nodes, searchTerm]; +} + class SelecteId extends UsersEvent { List? nodes; - SelecteId({this.nodes,}); + SelecteId({ + this.nodes, + }); @override List get props => [nodes]; } diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart index b9937f77..5d1a68f6 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart @@ -9,6 +9,10 @@ final class UsersInitial extends UsersState { @override List get props => []; } +final class RolePermissionInitial extends UsersState { + @override + List get props => []; +} final class ChangeStatusSteps extends UsersState { @override diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart index 895f4825..dc45cfdc 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -23,8 +23,9 @@ class _AddNewUserDialogState extends State { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => - UsersBloc()..add(LoadCommunityAndSpacesEvent()), + create: (BuildContext context) => UsersBloc() + ..add(const LoadCommunityAndSpacesEvent()) + ..add(const RoleEvent()), child: BlocConsumer( listener: (context, state) {}, builder: (context, state) { @@ -45,7 +46,9 @@ class _AddNewUserDialogState extends State { child: Text( "Add New User", style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold), + fontSize: 20, + fontWeight: FontWeight.w700, + color: ColorsManager.secondaryColor), ), ), ), diff --git a/lib/pages/roles_and_permission/users_page/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/view/basics_view.dart index d64d837d..abf6642c 100644 --- a/lib/pages/roles_and_permission/users_page/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/view/basics_view.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl_phone_field/country_picker_dialog.dart'; +import 'package:intl_phone_field/intl_phone_field.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_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -47,9 +49,16 @@ class BasicsView extends StatelessWidget { SizedBox( child: Row( children: [ - Text( - "*", - style: TextStyle(color: ColorsManager.red), + // SizedBox( + // width: 15, + // ), + const Text( + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), ), Text( 'First Name', @@ -96,8 +105,12 @@ class BasicsView extends StatelessWidget { child: Row( children: [ const Text( - "*", - style: TextStyle(color: ColorsManager.red), + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), ), Text('Last Name', style: context.textTheme.bodyMedium?.copyWith( @@ -140,9 +153,13 @@ class BasicsView extends StatelessWidget { SizedBox( child: Row( children: [ - Text( - "*", - style: TextStyle(color: ColorsManager.red), + const Text( + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), ), Text( 'Email Address', @@ -187,10 +204,6 @@ class BasicsView extends StatelessWidget { SizedBox( child: Row( children: [ - Text( - "*", - style: TextStyle(color: ColorsManager.red), - ), Text( 'Mobile Number', style: context.textTheme.bodyMedium?.copyWith( @@ -200,28 +213,93 @@ class BasicsView extends StatelessWidget { ), ], )), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - style: const TextStyle(color: Colors.black), - controller: _blocRole.phoneController, - decoration: inputTextFormDeco( - hintText: "05x xxx xxxx", - ).copyWith( - hintStyle: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w400, - fontSize: 12, - color: ColorsManager.textGray), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a phone number'; - } - return null; - }, - keyboardType: TextInputType.phone, + // InternationalPhoneNumberInput( + // spaceBetweenSelectorAndTextField: 50, + // initialValue: PhoneNumber(isoCode: 'AE'), + // inputDecoration: inputTextFormDeco( + // hintText: "x xxx xxxx", + // ).copyWith( + // hintStyle: context.textTheme.bodyMedium?.copyWith( + // fontWeight: FontWeight.w400, + // fontSize: 12, + // color: ColorsManager.textGray), + // ), + // onInputChanged: (PhoneNumber number) { + // print(number.phoneNumber); + // }, + // onInputValidated: (bool value) { + // print(value); + // }, + // selectorConfig: const SelectorConfig( + // selectorType: PhoneInputSelectorType.BOTTOM_SHEET, + // useBottomSheetSafeArea: true, + // leadingPadding: 15, + // trailingSpace: false, + // setSelectorButtonAsPrefixIcon: true), + // ignoreBlank: true, + // autoValidateMode: AutovalidateMode.disabled, + + // selectorTextStyle: + // TextStyle(color: ColorsManager.blackColor), + // // initialValue: number, + // // textFieldController: controller, + // formatInput: true, + // keyboardType: const TextInputType.numberWithOptions( + // signed: true, decimal: true), + // // inputBorder: OutlineInputBorder( + // // borderSide: + // // BorderSide(color: Colors.black,)), + // onSaved: (PhoneNumber number) { + // print('On Saved: $number'); + // }, + // textStyle: + // const TextStyle(color: ColorsManager.blackColor), + // ), + + IntlPhoneField( + pickerDialogStyle: PickerDialogStyle(), + dropdownIconPosition: IconPosition.leading, + disableLengthCheck: true, + dropdownTextStyle: TextStyle(color: Colors.black), + textInputAction: TextInputAction.done, + decoration: inputTextFormDeco( + hintText: "05x xxx xxxx", + ).copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), ), - ), + + initialCountryCode: 'AE', + style: TextStyle(color: Colors.black), + onChanged: (phone) { + print(phone.completeNumber); + }, + ) + + // Padding( + // padding: const EdgeInsets.all(8.0), + // child: TextFormField( + // style: const TextStyle(color: Colors.black), + // controller: _blocRole.phoneController, + // decoration: inputTextFormDeco( + // hintText: "05x xxx xxxx", + // ).copyWith( + // hintStyle: context.textTheme.bodyMedium?.copyWith( + // fontWeight: FontWeight.w400, + // fontSize: 12, + // color: ColorsManager.textGray), + // ), + // validator: (value) { + // if (value == null || value.isEmpty) { + // return 'Please enter a phone number'; + // } + // return null; + // }, + // keyboardType: TextInputType.phone, + // ), + // ), ], ), ), @@ -236,10 +314,6 @@ class BasicsView extends StatelessWidget { SizedBox( child: Row( children: [ - Text( - "*", - style: TextStyle(color: ColorsManager.red), - ), Text( 'Job Title', style: context.textTheme.bodyMedium?.copyWith( diff --git a/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart index 090d4fe3..f10fd4ed 100644 --- a/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart @@ -5,14 +5,14 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_blo import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class AddNewUserDialog extends StatefulWidget { - const AddNewUserDialog({super.key}); +class DeleteUserDialog extends StatefulWidget { + const DeleteUserDialog({super.key}); @override - _AddNewUserDialogState createState() => _AddNewUserDialogState(); + _DeleteUserDialogState createState() => _DeleteUserDialogState(); } -class _AddNewUserDialogState extends State { +class _DeleteUserDialogState extends State { int currentStep = 1; @override diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart index 812a3170..a8c186e8 100644 --- a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +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/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -34,7 +35,12 @@ class RolesAndPermission extends StatelessWidget { const SizedBox( height: 15, ), - const SizedBox(width: 300, height: 110, child: DropdownExample()), + SizedBox( + width: 300, + height: 110, + child: DropdownExample( + bloc: _blocRole, + )), const SizedBox(height: 10), Expanded( child: SizedBox( @@ -64,6 +70,11 @@ class RolesAndPermission extends StatelessWidget { style: const TextStyle(color: Colors.black), controller: _blocRole.firstNameController, + onChanged: (value) { + _blocRole.add(SearchPermission( + nodes: _blocRole.permissions, + searchTerm: value)); + }, decoration: textBoxDecoration(radios: 20)! .copyWith( fillColor: Colors.white, @@ -97,7 +108,9 @@ class RolesAndPermission extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: Container( color: ColorsManager.whiteColors, - child: const DeviceManagement()))) + child: DeviceManagement( + bloc: _blocRole, + )))) ], ), ), @@ -111,7 +124,8 @@ class RolesAndPermission extends StatelessWidget { } class DropdownExample extends StatefulWidget { - const DropdownExample({super.key}); + final UsersBloc? bloc; + const DropdownExample({super.key, this.bloc}); @override _DropdownExampleState createState() => _DropdownExampleState(); @@ -119,7 +133,13 @@ class DropdownExample extends StatefulWidget { class _DropdownExampleState extends State { String? selectedRole; - List roles = ['Admin', 'User', 'Guest', 'Moderator']; + @override + void initState() { + super.initState(); + if (widget.bloc != null && widget.bloc!.roles.isNotEmpty) { + selectedRole = widget.bloc!.roles.first.uuid; + } + } @override Widget build(BuildContext context) { @@ -140,19 +160,20 @@ class _DropdownExampleState extends State { SizedBox( child: DropdownButtonFormField( alignment: Alignment.center, - focusColor: ColorsManager.whiteColors, + focusColor: Colors.white, autofocus: true, value: selectedRole, - items: roles.map((role) { - return DropdownMenuItem( - value: role, - child: Text(role), + items: widget.bloc!.roles.map((role) { + return DropdownMenuItem( + value: role.uuid, + child: Text(role.type), ); }).toList(), onChanged: (value) { setState(() { selectedRole = value; }); + widget.bloc!.add(PermissionEvent(roleUuid: selectedRole)); }, padding: EdgeInsets.zero, icon: const SizedBox.shrink(), @@ -168,13 +189,13 @@ class _DropdownExampleState extends State { width: 70, height: 50, decoration: BoxDecoration( - color: ColorsManager.graysColor, + color: Colors.grey[200], borderRadius: const BorderRadius.only( bottomRight: Radius.circular(10), topRight: Radius.circular(10), ), border: Border.all( - color: ColorsManager.grayBorder, + color: Colors.grey, width: 1.0, ), ), @@ -192,60 +213,17 @@ class _DropdownExampleState extends State { } class DeviceManagement extends StatefulWidget { - const DeviceManagement({Key? key}) : super(key: key); + final UsersBloc? bloc; + const DeviceManagement({Key? key, this.bloc}) : super(key: key); @override _DeviceManagementState createState() => _DeviceManagementState(); } class _DeviceManagementState extends State { - - - final List options = [ - MainRoleOption( - id: '1', - title: "Device Management", - subOptions: [ - SubRoleOption( - id: '11', - title: "Manage devices in private spaces", - children: [ - ChildRoleOption(id: '111', title: "Control"), - ChildRoleOption(id: '112', title: "Assign device"), - ChildRoleOption(id: '113', title: "View"), - ], - ), - SubRoleOption( - id: '12', - title: "Manage", - children: [ - ChildRoleOption(id: '121', title: "cc"), - ChildRoleOption(id: '122', title: "Assign"), - ChildRoleOption(id: '123', title: "s"), - ], - ), - ], - ), - MainRoleOption( - id: '2', - title: "Device Management", - subOptions: [ - SubRoleOption( - id: '22', - title: "Manage devices in private spaces", - children: [ - ChildRoleOption(id: '211', title: "Control"), - ChildRoleOption(id: '212', title: "Assign device"), - ChildRoleOption(id: '213', title: "View"), - ], - ), - ], - ), - ]; - void toggleOptionById(String id) { setState(() { - for (var mainOption in options) { + for (var mainOption in widget.bloc!.permissions) { if (mainOption.id == id) { final isChecked = checkifOneOfthemChecked(mainOption) == CheckState.all; @@ -253,7 +231,7 @@ class _DeviceManagementState extends State { for (var subOption in mainOption.subOptions) { subOption.isChecked = !isChecked; - for (var child in subOption.children) { + for (var child in subOption.subOptions) { child.isChecked = !isChecked; } } @@ -263,7 +241,7 @@ class _DeviceManagementState extends State { for (var subOption in mainOption.subOptions) { if (subOption.id == id) { subOption.isChecked = !subOption.isChecked; - for (var child in subOption.children) { + for (var child in subOption.subOptions) { child.isChecked = subOption.isChecked; } mainOption.isChecked = @@ -271,11 +249,11 @@ class _DeviceManagementState extends State { return; } - for (var child in subOption.children) { + for (var child in subOption.subOptions) { if (child.id == id) { child.isChecked = !child.isChecked; subOption.isChecked = - subOption.children.every((child) => child.isChecked); + subOption.subOptions.every((child) => child.isChecked); mainOption.isChecked = mainOption.subOptions.every((sub) => sub.isChecked); return; @@ -286,7 +264,7 @@ class _DeviceManagementState extends State { }); } - CheckState checkifOneOfthemChecked(MainRoleOption mainOption) { + CheckState checkifOneOfthemChecked(PermissionOption mainOption) { bool allSelected = true; bool someSelected = false; @@ -297,7 +275,7 @@ class _DeviceManagementState extends State { allSelected = false; } - for (var child in subOption.children) { + for (var child in subOption.subOptions) { if (child.isChecked) { someSelected = true; } else { @@ -319,16 +297,16 @@ class _DeviceManagementState extends State { Widget build(BuildContext context) { return ListView.builder( padding: const EdgeInsets.all(8), - itemCount: options.length, + itemCount: widget.bloc!.permissions.length, itemBuilder: (context, index) { - final option = options[index]; + final option = widget.bloc!.permissions[index]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ InkWell( - onTap: () => toggleOptionById(option.id), + // onTap: () => toggleOptionById(option.id), child: Builder( builder: (context) { final checkState = checkifOneOfthemChecked(option); @@ -372,50 +350,55 @@ class _DeviceManagementState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - InkWell( - onTap: () => toggleOptionById(subOption.id), - child: Builder( - builder: (context) { - final checkState = - checkifOneOfthemChecked(MainRoleOption( - id: subOption.id, - title: subOption.title, - subOptions: [subOption], - )); + Container( + color: option.isHighlighted + ? Colors.blue.shade50 + : Colors.white, + child: Row( + children: [ + InkWell( + // onTap: () => toggleOptionById(subOption.id), + child: Builder( + builder: (context) { + final checkState = + checkifOneOfthemChecked(PermissionOption( + id: subOption.id, + title: subOption.title, + subOptions: [subOption], + )); - if (checkState == CheckState.all) { - return Image.asset( - Assets.CheckBoxChecked, - width: 20, - height: 20, - ); - } else if (checkState == CheckState.some) { - return Image.asset( - Assets.rectangleCheckBox, - width: 20, - height: 20, - ); - } else { - return Image.asset( - Assets.emptyBox, - width: 20, - height: 20, - ); - } - }, + if (checkState == CheckState.all) { + return Image.asset( + Assets.CheckBoxChecked, + width: 20, + height: 20, + ); + } else if (checkState == CheckState.some) { + return Image.asset( + Assets.rectangleCheckBox, + width: 20, + height: 20, + ); + } else { + return Image.asset( + Assets.emptyBox, + width: 20, + height: 20, + ); + } + }, + ), ), - ), - const SizedBox(width: 8), - Text( - subOption.title, - style: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w700, - fontSize: 12, - color: ColorsManager.lightGreyColor), - ), - ], + const SizedBox(width: 8), + Text( + subOption.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 12, + color: ColorsManager.lightGreyColor), + ), + ], + ), ), Padding( padding: const EdgeInsets.only(left: 50.0), @@ -424,15 +407,18 @@ class _DeviceManagementState extends State { physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, // 2 items per row - mainAxisSpacing: 2.0, // Space between rows - crossAxisSpacing: 0.2, // Space between columns - childAspectRatio: 5, // Adjust aspect ratio as needed + crossAxisCount: 2, + mainAxisSpacing: 2.0, + crossAxisSpacing: 0.2, + childAspectRatio: 5, ), - itemCount: subOption.children.length, + itemCount: subOption.subOptions.length, itemBuilder: (context, index) { - final child = subOption.children[index]; + final child = subOption.subOptions[index]; return CheckboxListTile( + selectedTileColor: child.isHighlighted + ? Colors.blue.shade50 + : Colors.white, dense: true, controlAffinity: ListTileControlAffinity.leading, title: Text( @@ -444,6 +430,7 @@ class _DeviceManagementState extends State { ), value: child.isChecked, onChanged: (value) => toggleOptionById(child.id), + enabled: false, ); }, ), @@ -458,43 +445,44 @@ class _DeviceManagementState extends State { } } -class MainRoleOption { - String id; - String title; - bool isChecked; - List subOptions; - MainRoleOption({ - required this.id, - required this.title, - this.isChecked = false, - this.subOptions = const [], - }); -} - -class SubRoleOption { - String id; - String title; - bool isChecked; - List children; - - SubRoleOption({ - required this.id, - required this.title, - this.isChecked = false, - this.children = const [], - }); -} - -class ChildRoleOption { - String id; - String title; - bool isChecked; - - ChildRoleOption({ - required this.id, - required this.title, - this.isChecked = false, - }); -} enum CheckState { none, some, all } + +class PermissionOption { + String id; + String title; + bool isChecked; + bool isHighlighted; + List subOptions; + + PermissionOption({ + required this.id, + required this.title, + this.isChecked = false, + this.isHighlighted = false, + this.subOptions = const [], + }); + + factory PermissionOption.fromJson(Map json) { + return PermissionOption( + id: json['id'] ?? '', + title: json['title'] ?? '', + isChecked: json['isChecked'] ?? false, + isHighlighted: json['isHighlighted'] ?? false, + subOptions: (json['subOptions'] as List?) + ?.map((sub) => PermissionOption.fromJson(sub)) + .toList() ?? + [], + ); + } + + Map toJson() { + return { + 'id': id, + 'title': title, + 'isChecked': isChecked, + 'isHighlighted': isHighlighted, + 'subOptions': subOptions.map((sub) => sub.toJson()).toList(), + }; + } +} diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart new file mode 100644 index 00000000..91091dbe --- /dev/null +++ b/lib/services/user_permission.dart @@ -0,0 +1,36 @@ + +import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; + +class UserPermissionApi { + static final HTTPService _httpService = HTTPService(); + + fetchRoles() async { + final response = await _httpService.get( + path: ApiEndpoints.roleTypes, + showServerMessage: true, + expectedResponseModel: (json) { + final List fetchedRoles = (json['data'] as List) + .map((item) => RoleTypeModel.fromJson(item)) + .toList(); + return fetchedRoles; + }, + ); + return response; + } + + Future> fetchPermission(roleUuid) async { + final response = await _httpService.get( + path: ApiEndpoints.permission.replaceAll("roleUuid", roleUuid), + showServerMessage: true, + expectedResponseModel: (json) { + return (json as List) + .map((data) => PermissionOption.fromJson(data)) + .toList(); + }, + ); + return response ?? []; + } +} diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 007b488d..752c5bea 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -11,12 +11,14 @@ abstract class ApiEndpoints { static const String visitorPassword = '/visitor-password'; static const String getDevices = '/visitor-password/devices'; - static const String sendOnlineOneTime = '/visitor-password/temporary-password/online/one-time'; + 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 sendOffLineOneTime = + '/visitor-password/temporary-password/offline/one-time'; static const String sendOffLineMultipleTime = '/visitor-password/temporary-password/offline/multiple-time'; @@ -38,8 +40,10 @@ abstract class ApiEndpoints { // Space Module static const String createSpace = '/communities/{communityId}/spaces'; static const String listSpaces = '/communities/{communityId}/spaces'; - static const String deleteSpace = '/communities/{communityId}/spaces/{spaceId}'; - static const String updateSpace = '/communities/{communityId}/spaces/{spaceId}'; + static const String deleteSpace = + '/communities/{communityId}/spaces/{spaceId}'; + static const String updateSpace = + '/communities/{communityId}/spaces/{spaceId}'; static const String getSpace = '/communities/{communityId}/spaces/{spaceId}'; static const String getSpaceHierarchy = '/communities/{communityId}/spaces'; @@ -55,11 +59,15 @@ abstract class ApiEndpoints { '/device/report-logs/{uuid}?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 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 powerClamp = + '/device/{powerClampUuid}/power-clamp/status'; //product static const String listProducts = '/products'; @@ -68,13 +76,18 @@ abstract class ApiEndpoints { static const String getIconScene = '/scene/icon'; static const String createScene = '/scene/tap-to-run'; static const String createAutomation = '/automation'; - static const String getUnitScenes = '/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; - static const String getAutomationDetails = '/automation/details/{automationId}'; + static const String getUnitScenes = + '/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; + static const String getAutomationDetails = + '/automation/details/{automationId}'; static const String getScene = '/scene/tap-to-run/{sceneId}'; static const String deleteScene = '/scene/tap-to-run/{sceneId}'; static const String deleteAutomation = '/automation/{automationId}'; - static const String updateScene = '/scene/tap-to-run/{sceneId}'; + static const String updateScene = '/scene/tap-to-run/{sceneId}'; static const String updateAutomation = '/automation/{automationId}'; + static const String roleTypes = '/role/types'; + static const String permission = '/permission/{roleUuid}'; + // static const String updateAutomation = '/automation/{automationId}'; } diff --git a/pubspec.lock b/pubspec.lock index ea2315d7..98c62a94 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -304,6 +304,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.19.0" + intl_phone_field: + dependency: "direct main" + description: + name: intl_phone_field + sha256: "73819d3dfcb68d2c85663606f6842597c3ddf6688ac777f051b17814fe767bbf" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + intl_phone_number_input: + dependency: "direct main" + description: + name: intl_phone_number_input + sha256: "1c4328713a9503ab26a1fdbb6b00b4cada68c18aac922b35bedbc72eff1297c3" + url: "https://pub.dev" + source: hosted + version: "0.7.4" js: dependency: transitive description: @@ -336,6 +352,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + libphonenumber_platform_interface: + dependency: transitive + description: + name: libphonenumber_platform_interface + sha256: f801f6c65523f56504b83f0890e6dad584ab3a7507dca65fec0eed640afea40f + url: "https://pub.dev" + source: hosted + version: "0.4.2" + libphonenumber_plugin: + dependency: transitive + description: + name: libphonenumber_plugin + sha256: c615021d9816fbda2b2587881019ed595ecdf54d999652d7e4cce0e1f026368c + url: "https://pub.dev" + source: hosted + version: "0.3.3" + libphonenumber_web: + dependency: transitive + description: + name: libphonenumber_web + sha256: "8186f420dbe97c3132283e52819daff1e55d60d6db46f7ea5ac42f42a28cc2ef" + url: "https://pub.dev" + source: hosted + version: "0.3.2" lints: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ffff62ac..c99481d1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,6 +52,8 @@ dependencies: fl_chart: ^0.69.0 uuid: ^4.4.2 time_picker_spinner: ^1.0.0 + intl_phone_field: ^3.2.0 + intl_phone_number_input: ^0.7.4 dev_dependencies: flutter_test: From 298aab5116cb6eca92cdf4f846ccb834c0af678a Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 17 Dec 2024 17:07:14 +0300 Subject: [PATCH 06/17] send_invite_user --- .../users_page/bloc/users_bloc.dart | 13 +++++++++ lib/services/user_permission.dart | 29 ++++++++++++++++++- lib/utils/constants/api_const.dart | 1 + 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index 5b846ecf..ec3139b7 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -268,6 +268,19 @@ class UsersBloc extends Bloc { return anyMatch; } + _sendInvitUser(List nodes, String searchTerm) async { + emit(UsersLoadingState()); + await UserPermissionApi().sendInviteUser( + email: emailController.text, + firstName: firstNameController.text, + jobTitle: jobTitleController.text, + lastName: lastNameController.text, + phoneNumber: phoneController.text, + roleUuid: '', + spaceUuids: selectedIds); + emit(RolePermissionInitial()); + } + void searchRolePermission(SearchPermission event, Emitter emit) { emit(UsersLoadingState()); if (event.searchTerm!.isEmpty) { diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 91091dbe..22083191 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -1,4 +1,3 @@ - import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; import 'package:syncrow_web/services/api/http_service.dart'; @@ -33,4 +32,32 @@ class UserPermissionApi { ); return response ?? []; } + + Future sendInviteUser({ + String? firstName, + String? lastName, + String? email, + String? jobTitle, + String? phoneNumber, + String? roleUuid, + List? spaceUuids, + }) async { + final response = await _httpService.post( + path: ApiEndpoints.permission, + showServerMessage: true, + body: { + "firstName": firstName, + "lastName": lastName, + "email": email, + "jobTitle": jobTitle, + "phoneNumber": phoneNumber, + "roleUuid": roleUuid, + "spaceUuids": spaceUuids + }, + expectedResponseModel: (json) { + print(json); + }, + ); + return response ?? []; + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 752c5bea..28923538 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -89,5 +89,6 @@ abstract class ApiEndpoints { static const String updateAutomation = '/automation/{automationId}'; static const String roleTypes = '/role/types'; static const String permission = '/permission/{roleUuid}'; + static const String inviteUser = '/invite-user'; // static const String updateAutomation = '/automation/{automationId}'; } From 573852b4b4c72f538b50e2bc6ef7726fdce0ef96 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 18 Dec 2024 17:11:37 +0300 Subject: [PATCH 07/17] user_invite --- assets/icons/user_management.svg | 7 ++ lib/pages/home/bloc/home_bloc.dart | 4 +- .../users_page/bloc/users_bloc.dart | 76 +++++++++++------ .../users_page/bloc/users_event.dart | 19 +++++ .../users_page/bloc/users_status.dart | 18 ++++ .../users_page/view/add_user_dialog.dart | 83 ++++++++++++------- .../users_page/view/basics_view.dart | 79 +++--------------- .../users_page/view/roles_and_permission.dart | 1 + lib/utils/constants/assets.dart | 12 +-- lib/utils/user_drop_down_menu.dart | 43 +++++++--- 10 files changed, 198 insertions(+), 144 deletions(-) create mode 100644 assets/icons/user_management.svg diff --git a/assets/icons/user_management.svg b/assets/icons/user_management.svg new file mode 100644 index 00000000..3255117a --- /dev/null +++ b/assets/icons/user_management.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 04c35295..022354fa 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -95,9 +95,7 @@ class HomeBloc extends Bloc { title: 'Move in', icon: Assets.moveinIcon, active: true, - onPress: (context) { - context.go(RoutesConst.rolesAndPermissions); - }, + onPress: (context) {}, color: ColorsManager.primaryColor, ), HomeItemModel( diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index ec3139b7..f0e3c9ad 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -22,9 +22,20 @@ class UsersBloc extends Bloc { on(_getRolePermission); on(_getPermissions); on(searchRolePermission); + on(_sendInvitUser); + on(_validateBasicsStep); + on(isCompleteRoleFun); + } + void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { + if (formKey.currentState?.validate() ?? false) { + emit(const BasicsStepValidState()); + } else { + emit(const BasicsStepInvalidState()); + } } List users = []; + String roleSelected = ''; Future _getUsers(GetUsers event, Emitter emit) async { emit(UsersLoadingState()); @@ -106,31 +117,42 @@ class UsersBloc extends Bloc { int numberSpaces = 0; int numberRole = 0; - isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { + // isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { + // emit(UsersLoadingState()); + // isCompleteBasics = firstNameController.text.isNotEmpty && + // lastNameController.text.isNotEmpty && + // emailController.text.isNotEmpty; + // emit(ChangeStatusSteps()); + // return isCompleteBasics; + // } + + bool isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { emit(UsersLoadingState()); + + final emailRegex = RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + ); isCompleteBasics = firstNameController.text.isNotEmpty && lastNameController.text.isNotEmpty && - emailController.text.isNotEmpty; + emailController.text.isNotEmpty && + emailRegex.hasMatch(emailController.text); + emit(ChangeStatusSteps()); - return isCompleteBasics; + return isCompleteBasics!; } void isCompleteSpacesFun( CheckSpacesStepStatus event, Emitter emit) { emit(UsersLoadingState()); - try { - List selectedIds = getSelectedIds(updatedCommunities); - isCompleteSpaces = selectedIds.isNotEmpty; - } catch (e) { - emit(ErrorState('Error while retrieving selected IDs: $e')); - return; - } - + List selectedIds = getSelectedIds(updatedCommunities); + isCompleteSpaces = selectedIds.isNotEmpty; emit(ChangeStatusSteps()); } - bool checkRolePermissions() { - return true; + void isCompleteRoleFun(CheckRoleStepStatus event, Emitter emit) { + emit(UsersLoadingState()); + isCompleteRolePermissions = roleSelected != ''; + emit(ChangeStatusSteps()); } Future> _fetchSpacesForCommunity( @@ -249,6 +271,7 @@ class UsersBloc extends Bloc { emit(UsersLoadingState()); permissions = await UserPermissionApi().fetchPermission( event.roleUuid == "" ? roles.first.uuid : event.roleUuid); + roleSelected = event.roleUuid!; emit(RolePermissionInitial()); } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); @@ -268,17 +291,22 @@ class UsersBloc extends Bloc { return anyMatch; } - _sendInvitUser(List nodes, String searchTerm) async { - emit(UsersLoadingState()); - await UserPermissionApi().sendInviteUser( - email: emailController.text, - firstName: firstNameController.text, - jobTitle: jobTitleController.text, - lastName: lastNameController.text, - phoneNumber: phoneController.text, - roleUuid: '', - spaceUuids: selectedIds); - emit(RolePermissionInitial()); + _sendInvitUser(SendInviteUsers event, Emitter emit) async { + try { + emit(UsersLoadingState()); + List selectedIds = getSelectedIds(updatedCommunities); + await UserPermissionApi().sendInviteUser( + email: emailController.text, + firstName: firstNameController.text, + jobTitle: jobTitleController.text, + lastName: lastNameController.text, + phoneNumber: phoneController.text, + roleUuid: roleSelected, + spaceUuids: selectedIds); + emit(SaveState()); + } catch (e) { + emit(ErrorState('Error: $e')); + } } void searchRolePermission(SearchPermission event, Emitter emit) { diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart index 6f59b495..633c1ea5 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -12,12 +12,25 @@ class GetUsers extends UsersEvent { List get props => []; } +class SendInviteUsers extends UsersEvent { + const SendInviteUsers(); + @override + List get props => []; +} + class CheckSpacesStepStatus extends UsersEvent { const CheckSpacesStepStatus(); @override List get props => []; } +class CheckRoleStepStatus extends UsersEvent { + const CheckRoleStepStatus(); + @override + List get props => []; +} + + class LoadCommunityAndSpacesEvent extends UsersEvent { const LoadCommunityAndSpacesEvent(); @override @@ -85,3 +98,9 @@ class SelecteId extends UsersEvent { @override List get props => [nodes]; } + +class ValidateBasicsStep extends UsersEvent { + const ValidateBasicsStep(); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart index 5d1a68f6..b5fc01d7 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart @@ -9,6 +9,7 @@ final class UsersInitial extends UsersState { @override List get props => []; } + final class RolePermissionInitial extends UsersState { @override List get props => []; @@ -24,6 +25,11 @@ final class UsersLoadingState extends UsersState { List get props => []; } +final class SaveState extends UsersState { + @override + List get props => []; +} + final class UsersLoadedState extends UsersState { List users = []; UsersLoadedState({required this.users}); @@ -59,3 +65,15 @@ final class ChangeTapStatus extends UsersState { @override List get props => [select]; } + +final class BasicsStepValidState extends UsersState { + const BasicsStepValidState(); + @override + List get props => []; +} + +class BasicsStepInvalidState extends UsersState { + const BasicsStepInvalidState(); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart index dc45cfdc..cae89058 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -88,37 +88,6 @@ class _AddNewUserDialogState extends State { child: _getFormContent(), ), const SizedBox(height: 20), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("Cancel"), - ), - ElevatedButton( - onPressed: () { - if (_blocRole.formKey.currentState - ?.validate() ?? - false) { - // Proceed to next step or finish - setState(() { - if (currentStep < 3) { - currentStep++; - } else { - Navigator.of(context).pop(); - } - }); - } - }, - child: Text(currentStep < 3 - ? "Next" - : "Finish"), - ), - ], - ), ], ), ), @@ -126,6 +95,53 @@ class _AddNewUserDialogState extends State { ], ), ), + const Divider(), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + InkWell( + onTap: () { + setState(() { + if (currentStep < 3) { + currentStep++; + if (currentStep == 2) { + _blocRole.add(const CheckStepStatus()); + } else if (currentStep == 3) { + _blocRole + .add(const CheckSpacesStepStatus()); + _blocRole + .add(const CheckSpacesStepStatus()); + } else { + _blocRole.add(const SendInviteUsers()); + } + } + }); + }, + child: Text( + currentStep < 3 ? "Next" : "Save", + style: TextStyle( + color: (_blocRole.isCompleteSpaces == false || + _blocRole.isCompleteBasics == + false || + _blocRole + .isCompleteRolePermissions == + false) && + currentStep == 3 + ? ColorsManager.grayColor + : ColorsManager.secondaryColor), + ), + ), + ], + ), + ), ], ), )); @@ -209,8 +225,12 @@ class _AddNewUserDialogState extends State { return GestureDetector( onTap: () { setState(() { + bloc.add(const CheckSpacesStepStatus()); currentStep = step; }); + Future.delayed(const Duration(milliseconds: 500), () { + bloc.add(const ValidateBasicsStep()); + }); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -270,6 +290,7 @@ class _AddNewUserDialogState extends State { setState(() { currentStep = step; bloc.add(const CheckSpacesStepStatus()); + bloc.add(const CheckStepStatus()); }); }, child: Column( diff --git a/lib/pages/roles_and_permission/users_page/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/view/basics_view.dart index abf6642c..22398ed3 100644 --- a/lib/pages/roles_and_permission/users_page/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/view/basics_view.dart @@ -14,6 +14,9 @@ class BasicsView extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { final _blocRole = BlocProvider.of(context); + if (state is BasicsStepInvalidState) { + _blocRole.formKey.currentState?.validate(); + } return Form( key: _blocRole.formKey, child: ListView( @@ -184,7 +187,14 @@ class BasicsView extends StatelessWidget { ), validator: (value) { if (value == null || value.isEmpty) { - return 'Enter last name'; + return 'Enter Email Address'; + } + // Regular expression for email validation + final emailRegex = RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + ); + if (!emailRegex.hasMatch(value)) { + return 'Enter a valid Email Address'; } return null; }, @@ -213,49 +223,6 @@ class BasicsView extends StatelessWidget { ), ], )), - // InternationalPhoneNumberInput( - // spaceBetweenSelectorAndTextField: 50, - // initialValue: PhoneNumber(isoCode: 'AE'), - // inputDecoration: inputTextFormDeco( - // hintText: "x xxx xxxx", - // ).copyWith( - // hintStyle: context.textTheme.bodyMedium?.copyWith( - // fontWeight: FontWeight.w400, - // fontSize: 12, - // color: ColorsManager.textGray), - // ), - // onInputChanged: (PhoneNumber number) { - // print(number.phoneNumber); - // }, - // onInputValidated: (bool value) { - // print(value); - // }, - // selectorConfig: const SelectorConfig( - // selectorType: PhoneInputSelectorType.BOTTOM_SHEET, - // useBottomSheetSafeArea: true, - // leadingPadding: 15, - // trailingSpace: false, - // setSelectorButtonAsPrefixIcon: true), - // ignoreBlank: true, - // autoValidateMode: AutovalidateMode.disabled, - - // selectorTextStyle: - // TextStyle(color: ColorsManager.blackColor), - // // initialValue: number, - // // textFieldController: controller, - // formatInput: true, - // keyboardType: const TextInputType.numberWithOptions( - // signed: true, decimal: true), - // // inputBorder: OutlineInputBorder( - // // borderSide: - // // BorderSide(color: Colors.black,)), - // onSaved: (PhoneNumber number) { - // print('On Saved: $number'); - // }, - // textStyle: - // const TextStyle(color: ColorsManager.blackColor), - // ), - IntlPhoneField( pickerDialogStyle: PickerDialogStyle(), dropdownIconPosition: IconPosition.leading, @@ -270,36 +237,12 @@ class BasicsView extends StatelessWidget { fontSize: 12, color: ColorsManager.textGray), ), - initialCountryCode: 'AE', style: TextStyle(color: Colors.black), onChanged: (phone) { print(phone.completeNumber); }, ) - - // Padding( - // padding: const EdgeInsets.all(8.0), - // child: TextFormField( - // style: const TextStyle(color: Colors.black), - // controller: _blocRole.phoneController, - // decoration: inputTextFormDeco( - // hintText: "05x xxx xxxx", - // ).copyWith( - // hintStyle: context.textTheme.bodyMedium?.copyWith( - // fontWeight: FontWeight.w400, - // fontSize: 12, - // color: ColorsManager.textGray), - // ), - // validator: (value) { - // if (value == null || value.isEmpty) { - // return 'Please enter a phone number'; - // } - // return null; - // }, - // keyboardType: TextInputType.phone, - // ), - // ), ], ), ), diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart index a8c186e8..9b7ab542 100644 --- a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart @@ -71,6 +71,7 @@ class RolesAndPermission extends StatelessWidget { const TextStyle(color: Colors.black), controller: _blocRole.firstNameController, onChanged: (value) { + _blocRole.add(SearchPermission( nodes: _blocRole.permissions, searchTerm: value)); diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index acb314d9..8b271d5e 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -389,10 +389,10 @@ class Assets { 'assets/icons/current_process_icon.svg'; static const String uncomplete_ProcessIcon = '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'; + 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'; + + static const String userManagement = 'assets/icons/user_management.svg'; } +//user_management.svg diff --git a/lib/utils/user_drop_down_menu.dart b/lib/utils/user_drop_down_menu.dart index 3a0c4194..5bdc18fd 100644 --- a/lib/utils/user_drop_down_menu.dart +++ b/lib/utils/user_drop_down_menu.dart @@ -53,7 +53,8 @@ class _UserDropdownMenuState extends State { } Future _showPopupMenu(BuildContext context) async { - final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox; + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; final RelativeRect position = RelativeRect.fromRect( Rect.fromLTRB( overlay.size.width, @@ -86,11 +87,13 @@ class _UserDropdownMenuState extends State { ), ), PopupMenuItem( - onTap: () {}, + onTap: () { + context.go(RoutesConst.rolesAndPermissions); + }, child: ListTile( - leading: SvgPicture.asset(Assets.settings), + leading: SvgPicture.asset(Assets.userManagement), title: Text( - "Settings", + "User Management", style: context.textTheme.bodyMedium, ), ), @@ -107,7 +110,8 @@ class _UserDropdownMenuState extends State { height: 200, width: 400, child: Padding( - padding: const EdgeInsets.only(top: 24, left: 24, right: 24), + padding: + const EdgeInsets.only(top: 24, left: 24, right: 24), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -120,7 +124,10 @@ class _UserDropdownMenuState extends State { padding: const EdgeInsets.only(top: 16), child: Text( 'Log out of your Syncrow account', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black, @@ -151,11 +158,15 @@ class _UserDropdownMenuState extends State { ), Expanded( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Text( '${widget.user?.firstName ?? ''} ${widget.user?.lastName}', - style: Theme.of(context).textTheme.titleMedium!.copyWith( + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith( color: Colors.black, fontWeight: FontWeight.bold, fontSize: 20, @@ -163,7 +174,10 @@ class _UserDropdownMenuState extends State { ), Text( ' ${widget.user?.email}', - style: Theme.of(context).textTheme.bodySmall!.copyWith( + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( color: Colors.black, ), ), @@ -189,7 +203,10 @@ class _UserDropdownMenuState extends State { elevation: 1, child: Text( 'Cancel', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( fontSize: 12, color: Colors.black, ), @@ -211,8 +228,10 @@ class _UserDropdownMenuState extends State { elevation: 1, child: Text( 'Logout', - style: - Theme.of(context).textTheme.bodyMedium!.copyWith(fontSize: 12, color: Colors.white), + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(fontSize: 12, color: Colors.white), ), ), ), From ed2187b7ff4db67579fa7a68c838d0c8c9e4278e Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 26 Dec 2024 16:32:18 +0300 Subject: [PATCH 08/17] add_user_dialog --- assets/icons/atoz_icon.png | Bin 0 -> 1540 bytes assets/icons/filter_table_icon.svg | 3 + assets/icons/ztoa_icon.png | Bin 0 -> 1513 bytes lib/pages/common/custom_dialog.dart | 2 +- .../model/role_type_model.dart | 2 +- .../bloc/users_bloc.dart | 185 +++---- .../bloc/users_event.dart | 33 +- .../bloc/users_status.dart | 11 +- .../model/permission_option_model.dart | 40 ++ .../model/tree_node_model.dart | 0 .../view/add_user_dialog.dart | 167 +++--- .../view/basics_view.dart | 99 ++-- .../view/delete_user_dialog.dart | 4 +- .../view/permission_management.dart | 239 +++++++++ .../add_user_dialog/view/role_dropdown.dart | 115 ++++ .../view/roles_and_permission.dart | 127 +++++ .../view/spaces_access_view.dart | 8 +- .../users_table/bloc/user_table_bloc.dart | 185 +++++++ .../users_table/bloc/user_table_event.dart | 63 +++ .../users_table/bloc/user_table_state.dart | 82 +++ .../view/creation_date_filter.dart | 73 +++ .../users_table/view/de_activate_filter.dart | 73 +++ .../users_table/view/name_filter.dart | 69 +++ .../users_table/view/user_table.dart | 299 +++++++++++ .../{ => users_table}/view/users_page.dart | 83 ++- .../users_page/view/roles_and_permission.dart | 489 ------------------ .../users_page/view/user_table.dart | 256 --------- .../view/roles_and_permission_page.dart | 14 +- lib/services/space_mana_api.dart | 1 - lib/services/user_permission.dart | 68 ++- lib/utils/constants/api_const.dart | 38 +- lib/utils/constants/assets.dart | 3 + 32 files changed, 1783 insertions(+), 1048 deletions(-) create mode 100644 assets/icons/atoz_icon.png create mode 100644 assets/icons/filter_table_icon.svg create mode 100644 assets/icons/ztoa_icon.png rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/bloc/users_bloc.dart (73%) rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/bloc/users_event.dart (82%) rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/bloc/users_status.dart (88%) create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/model/tree_node_model.dart (100%) rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/view/add_user_dialog.dart (92%) rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/view/basics_view.dart (76%) rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/view/delete_user_dialog.dart (96%) create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/view/spaces_access_view.dart (98%) create mode 100644 lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart rename lib/pages/roles_and_permission/users_page/{ => users_table}/view/users_page.dart (73%) delete mode 100644 lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart delete mode 100644 lib/pages/roles_and_permission/users_page/view/user_table.dart diff --git a/assets/icons/atoz_icon.png b/assets/icons/atoz_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..33a9c351375b827aba0c385af50da41414fec209 GIT binary patch literal 1540 zcmV+f2K)JmP)5XvcS(j2Vzz()WZZ2Y}5eP`EeFL=L94RP~Gqn(}E9Y22O{hD_H{)ZsN zOs0=87KRgIdk-LAG@g6$G7}pf?oPl75Dq^K$eHx$?%rRiCu_4Y{hWwx0b3AG3Q}nX zU@{qb^k`%7B*cY6ae@*OVZh)<|9E0$buM0=Ln#O0^bkbikCAu~8U)krK4CDSpA+Ep z(J=Aw(EU-+?>>W2s4o)GZqI@=M?=H~B7Av#tSFQt)p@fQi)GcotXf%yrV~dg2Q}3| z^6Pj>@jfH)nI-9D8enr5JwwvE@nR1q}DkSS?O!4WuNr_btO zNt^(gsB}_DE`g3Ism%!#lpP?Ybeb8F;oHw_2)SGU2Z!c<`uU%SR<9?Biyd)?-wfRk z$v_@!_p)4qSG&*k@1(p`ylNpNu@||n`Xd|zPW@I|r!4bBTaOOCxFn%K3`uuL>)9d4P zX|tiE74@)mu{CUN6(OIuHvrL1ptI8hPQjBVDwn6qxHKk9SL5r`iRxNLFYFyS*VA;G zLN@Cx32F0-AbFi_En87l*WXlAW{#FCTyBAB?R3EqWj|AD0>Y$%t z{~&9=#q}}+Jdi<(%+dZ|decLBPv7&Irwlbsz^KI{znizp9 zup!N$zpGR?{y|4=?5$khu^~1KZ=QOTD1qfI)wlMd8ua>AiLSo#C`QWhVnc*<)?7U< zOC|W|Vh)-K8`1zfIdwC-q*^++lNYMqV>3_SAaA}#o7kA@e0}q$J5)WPB(yO+)Q(`5 z7sBq$^3%CR+kD#RR;B3H8Jkb!Rwz_RQD7FUcr4EWN5QK~G+&tJef{1so^SI3qNJ|Y zq}OSebyyfS}$90fhOymn97 z1zSUWxv%bTr$nLaoa&tU7@t%6NeMDp^Am7~SB73+V+l7WjuPkH5smQ5+FVqdw>88+ zl5VBX!+4eSfx8yQQ=)AcZb-;n?Mwjh#oAN5sasJ<>useHs?^D4+Eec#R4=F`7z zc$L0BpI+Yf)C_Uc;~<}QMjGW1!&P~8G^o);1S$=Il3xFDy`>BhJw^gAgW5fhkg!XB zNAjw=P?s`in@{(5sqX56r>3_bUMf}9<5I4O#T|2FYc%)JL(ZhIf?EB&GP6=S+}h^T zJ^uP1?LJ={BmNR(UCqy@zrA^{uTY|u>OV<%sVl0@8CsC!g{H%eB?R7_qD);2TAvAQ z3`+^M1S2e=^$DYWcF*J2+rYmD_;@3;{F~?i0000 + + diff --git a/assets/icons/ztoa_icon.png b/assets/icons/ztoa_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..003e572526ff10c21a3b27befd50e025ed515491 GIT binary patch literal 1513 zcmVv%6j|<|8J68Y(pyRXMbkT9R;R4yBINN)Rck+x`L2V)$kF|HEZ+2OG4ZGf1aHyM~wBpUo?(*B2-+S}s z8Q^~iqUUl$gs}u15gRW6in{yQlc%|bv9bOX1i)uw-%SG}QEpeL4qID)IYvq9-s(3C zob=yFItE9?#*R1X$B*_>L}r&oi6=(CpCCYP+ik-r!{+wmx_bzv=cVHi7C?*{E}#^IK$-Zx4*1SEk0SW$39Cg?E35Of z&T-dy>h1z))9?#@2+1-)W-qWzI6P;Z%L2n*RThlOmk&PdI@5gFBVmB{w#JvHLPJC% zG6)cl)e|xan|&BP>SST#rA7}SFu4Baxz1T`9^PE^)aJHhMUz|u3#wBPD3<7LITrGGTYm#tXz2ilni$D2YE2L=?TB7z zIMX5gq_dsY?4D9r{&#z7eXkXY!j9N1 zyn6B|Qv%D+t=Kw%G8pKzX7v81YC0*$lQjiTIo|R(qm|)8cLACSAJPC@1#2<-v|8q} zRg|jHW4%ZqQ}ljEhZ0OWpI`Z53Qt3P7fjiGx>Omzba66{=FQ~?^NYSGyhzBJ#gN0< zA`16HX`d7Y*5EB_!*f7A5{cVUS}e_R+qrgV&zc4?H$^o_&QIMQF+F5h9z zbHv5PFVeVu-@${xxa$p!-Qj;)&JnR-gNIs+oD=*+zP$RaxeC6H_;lA=-(HPEUz-)2 zc^@A$?HvtrdG8Z&PuPHgNU(&P6UQ0n=D1DRc_R09#6NdJrp>qUD(Ot9wvDG1-)%f7 zX`A@fk8dTJ1gVw#^GWAeuu{tj5a|pM`_iNzrsU6+8*;IZEr4o)@`!8CCSt~$9h45y@GcT zQe_4`YVf3q7i}txCKzK-lFGkh*PIY(zE%IW7B+2XKE1r5)*NwN709Q9kv3XaVcwLJ@{Qcp{YM*aDJ=|x7t1h*s+xJeFtMWKq(6PAV?QB7F4-P`4Dct?) z`XnnKNdgGu(`P#m|I_~VS|8~wLtbuPKApJo;}F)5hfH#EXn}X(6~#{&$5Z(%cSu)x zXklnNLRdoJmmefbr)(QjG0a2Y=6*9;R4UfTxINevu`Us4s&>4x*a0wyo+;Ji*pAw5 z!hn~7lP$F)@&XO)*QQM{X{PQ5bV!s)w56hlXG-b-9tfsehBS}OP?JW|{vDC2%3wvN z{HHi`M`Q#(RQ4ND_>aJ_x^Ehi9JPOSBItlWEnQ1F@4IwilIPeM3Q6o=ZwhN-4#k|R P00000NkvXXu0mjfgwxI} literal 0 HcmV?d00001 diff --git a/lib/pages/common/custom_dialog.dart b/lib/pages/common/custom_dialog.dart index a40ef10f..9899bda4 100644 --- a/lib/pages/common/custom_dialog.dart +++ b/lib/pages/common/custom_dialog.dart @@ -12,7 +12,7 @@ Future showCustomDialog({ double? iconWidth, VoidCallback? onOkPressed, bool barrierDismissible = false, - required List actions, + List? actions, }) { return showDialog( context: context, diff --git a/lib/pages/roles_and_permission/model/role_type_model.dart b/lib/pages/roles_and_permission/model/role_type_model.dart index 1705e25c..16b24ec5 100644 --- a/lib/pages/roles_and_permission/model/role_type_model.dart +++ b/lib/pages/roles_and_permission/model/role_type_model.dart @@ -16,7 +16,7 @@ class RoleTypeModel { uuid: json['uuid'], createdAt: json['createdAt'], updatedAt: json['updatedAt'], - type: json['type'], + type: json['type'].toString().toLowerCase().replaceAll("_", " "), ); } } diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart similarity index 73% rename from lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index f0e3c9ad..f0ddf01e 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -1,20 +1,19 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/common/custom_dialog.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.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/roles_and_permission/users_page/view/roles_and_permission.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/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'; import 'package:syncrow_web/services/user_permission.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; class UsersBloc extends Bloc { UsersBloc() : super(UsersInitial()) { - on(_getUsers); - on(_changeUserStatus); on(isCompleteBasicsFun); on(_onLoadCommunityAndSpaces); on(searchTreeNode); @@ -25,6 +24,7 @@ class UsersBloc extends Bloc { on(_sendInvitUser); on(_validateBasicsStep); on(isCompleteRoleFun); + on(checkEmail); } void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { if (formKey.currentState?.validate() ?? false) { @@ -34,74 +34,8 @@ class UsersBloc extends Bloc { } } - List users = []; String roleSelected = ''; - Future _getUsers(GetUsers event, Emitter emit) async { - emit(UsersLoadingState()); - try { - users = [ - RolesUserModel( - id: '1', - userName: 'user 1', - userEmail: 'test1@test.com', - action: '', - createdBy: 'Admin', - creationDate: '25/10/2024', - creationTime: '10:30 AM', - status: 'Invited', - ), - RolesUserModel( - id: '2', - userName: 'user 2', - userEmail: 'test2@test.com', - action: '', - createdBy: 'Admin', - creationDate: '25/10/2024', - creationTime: '10:30 AM', - status: 'Active', - ), - RolesUserModel( - id: '3', - userName: 'user 3', - userEmail: 'test3@test.com', - action: '', - createdBy: 'Admin', - creationDate: '25/10/2024', - creationTime: '10:30 AM', - status: 'Disabled', - ), - ]; - emit(UsersLoadedState(users: users)); - } catch (e) { - emit(ErrorState(e.toString())); - } - } - - void _changeUserStatus(ChangeUserStatus event, Emitter emit) { - try { - users = users.map((user) { - if (user.id == event.userId) { - return RolesUserModel( - id: user.id, - userName: user.userName, - userEmail: user.userEmail, - createdBy: user.createdBy, - creationDate: user.creationDate, - creationTime: user.creationTime, - status: event.newStatus, - action: user.action, - ); - } - return user; - }).toList(); - - emit(UsersLoadedState(users: users)); - } catch (e) { - emit(ErrorState(e.toString())); - } - } - final formKey = GlobalKey(); final TextEditingController firstNameController = TextEditingController(); final TextEditingController lastNameController = TextEditingController(); @@ -109,6 +43,9 @@ class UsersBloc extends Bloc { final TextEditingController phoneController = TextEditingController(); final TextEditingController jobTitleController = TextEditingController(); + final TextEditingController roleSearchController = TextEditingController(); + // final TextEditingController jobTitleController = TextEditingController(); + bool? isCompleteBasics; bool? isCompleteRolePermissions; bool? isCompleteSpaces; @@ -117,30 +54,6 @@ class UsersBloc extends Bloc { int numberSpaces = 0; int numberRole = 0; - // isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { - // emit(UsersLoadingState()); - // isCompleteBasics = firstNameController.text.isNotEmpty && - // lastNameController.text.isNotEmpty && - // emailController.text.isNotEmpty; - // emit(ChangeStatusSteps()); - // return isCompleteBasics; - // } - - bool isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { - emit(UsersLoadingState()); - - final emailRegex = RegExp( - r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', - ); - isCompleteBasics = firstNameController.text.isNotEmpty && - lastNameController.text.isNotEmpty && - emailController.text.isNotEmpty && - emailRegex.hasMatch(emailController.text); - - emit(ChangeStatusSteps()); - return isCompleteBasics!; - } - void isCompleteSpacesFun( CheckSpacesStepStatus event, Emitter emit) { emit(UsersLoadingState()); @@ -184,7 +97,7 @@ class UsersBloc extends Bloc { ); }).toList(), ); - emit(ChangeStatusSteps()); + emit(SpacesLoadedState()); return updatedCommunities; } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); @@ -239,6 +152,7 @@ class UsersBloc extends Bloc { } List selectedIds = []; + List getSelectedIds(List nodes) { List selectedIds = []; for (var node in nodes) { @@ -259,7 +173,7 @@ class UsersBloc extends Bloc { try { emit(UsersLoadingState()); roles = await UserPermissionApi().fetchRoles(); - add(PermissionEvent(roleUuid: roles.first.uuid)); + // add(PermissionEvent(roleUuid: roles.first.uuid)); emit(RolePermissionInitial()); } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); @@ -294,18 +208,40 @@ class UsersBloc extends Bloc { _sendInvitUser(SendInviteUsers event, Emitter emit) async { try { emit(UsersLoadingState()); - List selectedIds = getSelectedIds(updatedCommunities); - await UserPermissionApi().sendInviteUser( - email: emailController.text, - firstName: firstNameController.text, - jobTitle: jobTitleController.text, - lastName: lastNameController.text, - phoneNumber: phoneController.text, - roleUuid: roleSelected, - spaceUuids: selectedIds); + List selectedIds = getSelectedIds(updatedCommunities) ?? []; + bool res = await UserPermissionApi().sendInviteUser( + email: emailController.text, + firstName: firstNameController.text, + jobTitle: jobTitleController.text, + lastName: lastNameController.text, + phoneNumber: phoneController.text, + roleUuid: roleSelected, + spaceUuids: selectedIds, + ); + if (res == true) { + showCustomDialog( + barrierDismissible: false, + context: event.context, + message: "The invite was sent successfully.", + iconPath: Assets.deviceNoteIcon, + title: "Invite Success", + dialogHeight: MediaQuery.of(event.context).size.height * 0.3, + actions: [ + TextButton( + onPressed: () { + Navigator.of(event.context).pop(); + Navigator.of(event.context).pop(); + }, + child: const Text('OK'), + ), + ], + ); + } else { + emit(const ErrorState('Failed to send invite.')); + } emit(SaveState()); } catch (e) { - emit(ErrorState('Error: $e')); + emit(ErrorState('Failed to send invite: ${e.toString()}')); } } @@ -319,6 +255,37 @@ class UsersBloc extends Bloc { emit(ChangeStatusSteps()); } + String checkEmailValid = ''; + + Future checkEmail( + CheckEmailEvent event, Emitter emit) async { + emit(UsersLoadingState()); + String? res = await UserPermissionApi().checkEmail( + emailController.text, + ); + checkEmailValid = res!; + emit(ChangeStatusSteps()); + } + + bool isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { + emit(UsersLoadingState()); + add(const CheckEmailEvent()); + final emailRegex = RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + ); + bool isEmailValid = emailRegex.hasMatch(emailController.text); + bool isEmailServerValid = checkEmailValid == 'Valid email'; + isCompleteBasics = firstNameController.text.isNotEmpty && + lastNameController.text.isNotEmpty && + emailController.text.isNotEmpty && + isEmailValid && + isEmailServerValid; + + emit(ChangeStatusSteps()); + emit(ValidateBasics()); + return isCompleteBasics!; + } + void _clearHighlightsRolePermission(List nodes) { for (var node in nodes) { node.isHighlighted = false; diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart similarity index 82% rename from lib/pages/roles_and_permission/users_page/bloc/users_event.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart index 633c1ea5..950726d4 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart @@ -1,21 +1,17 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; sealed class UsersEvent extends Equatable { const UsersEvent(); } -class GetUsers extends UsersEvent { - const GetUsers(); - @override - List get props => []; -} - class SendInviteUsers extends UsersEvent { - const SendInviteUsers(); + final BuildContext context; + const SendInviteUsers({required this.context}); @override - List get props => []; + List get props => [context]; } class CheckSpacesStepStatus extends UsersEvent { @@ -30,7 +26,6 @@ class CheckRoleStepStatus extends UsersEvent { List get props => []; } - class LoadCommunityAndSpacesEvent extends UsersEvent { const LoadCommunityAndSpacesEvent(); @override @@ -57,16 +52,6 @@ class GetBatchStatus extends UsersEvent { List get props => [uuids]; } -class ChangeUserStatus extends UsersEvent { - final String userId; - final String newStatus; - - const ChangeUserStatus({required this.userId, required this.newStatus}); - - @override - List get props => [userId, newStatus]; -} - class CheckStepStatus extends UsersEvent { final int? steps; const CheckStepStatus({this.steps}); @@ -104,3 +89,9 @@ class ValidateBasicsStep extends UsersEvent { @override List get props => []; } + +class CheckEmailEvent extends UsersEvent { + const CheckEmailEvent(); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart similarity index 88% rename from lib/pages/roles_and_permission/users_page/bloc/users_status.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart index b5fc01d7..c1bf3512 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart @@ -30,11 +30,10 @@ final class SaveState extends UsersState { List get props => []; } -final class UsersLoadedState extends UsersState { - List users = []; - UsersLoadedState({required this.users}); +final class SpacesLoadedState extends UsersState { + SpacesLoadedState(); @override - List get props => [users]; + List get props => []; } final class ErrorState extends UsersState { @@ -77,3 +76,7 @@ class BasicsStepInvalidState extends UsersState { @override List get props => []; } +final class ValidateBasics extends UsersState { + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart new file mode 100644 index 00000000..c476ebb4 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart @@ -0,0 +1,40 @@ +class PermissionOption { + String id; + String title; + bool isChecked; + bool isHighlighted; + List subOptions; + + PermissionOption({ + required this.id, + required this.title, + this.isChecked = false, + this.isHighlighted = false, + this.subOptions = const [], + }); + + factory PermissionOption.fromJson(Map json) { + return PermissionOption( + id: json['id'] ?? '', + title: json['title'].toString().toLowerCase().replaceAll("_", " ") ?? '', + isChecked: json['isChecked'] ?? false, + isHighlighted: json['isHighlighted'] ?? false, + subOptions: (json['subOptions'] as List?) + ?.map((sub) => PermissionOption.fromJson(sub)) + .toList() ?? + [], + ); + } + + Map toJson() { + return { + 'id': id, + 'title': title, + 'isChecked': isChecked, + 'isHighlighted': isHighlighted, + 'subOptions': subOptions.map((sub) => sub.toJson()).toList(), + }; + } +} + +enum CheckState { none, some, all } diff --git a/lib/pages/roles_and_permission/users_page/model/tree_node_model.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart similarity index 100% rename from lib/pages/roles_and_permission/users_page/model/tree_node_model.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart similarity index 92% rename from lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart index cae89058..14f7a0fb 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -1,12 +1,12 @@ 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'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/view/spaces_access_view.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -56,7 +56,6 @@ class _AddNewUserDialogState extends State { Expanded( child: Row( children: [ - // Sidebar for Steps Expanded( child: Container( padding: const EdgeInsets.all(20), @@ -75,7 +74,6 @@ class _AddNewUserDialogState extends State { width: 1, color: ColorsManager.grayBorder, ), - // Main content (Form) Expanded( flex: 2, child: Padding( @@ -109,6 +107,8 @@ class _AddNewUserDialogState extends State { ), InkWell( onTap: () { + _blocRole.add(const CheckEmailEvent()); + setState(() { if (currentStep < 3) { currentStep++; @@ -117,11 +117,10 @@ class _AddNewUserDialogState extends State { } else if (currentStep == 3) { _blocRole .add(const CheckSpacesStepStatus()); - _blocRole - .add(const CheckSpacesStepStatus()); - } else { - _blocRole.add(const SendInviteUsers()); } + } else { + _blocRole + .add(SendInviteUsers(context: context)); } }); }, @@ -161,12 +160,84 @@ class _AddNewUserDialogState extends State { } } + int step3 = 0; + + Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + bloc.add(const CheckSpacesStepStatus()); + currentStep = step; + Future.delayed(const Duration(milliseconds: 500), () { + bloc.add(const ValidateBasicsStep()); + }); + }); + + if (step3 == 3) { + bloc.add(const CheckRoleStepStatus()); + } + }, + 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.isCompleteBasics == false + ? 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 _buildStep2Indicator(int step, String label, UsersBloc bloc) { return GestureDetector( onTap: () { setState(() { currentStep = step; bloc.add(const CheckStepStatus()); + if (step3 == 3) { + bloc.add(const CheckRoleStepStatus()); + } }); }, child: Column( @@ -221,15 +292,14 @@ class _AddNewUserDialogState extends State { ); } - Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) { + Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) { return GestureDetector( onTap: () { setState(() { - bloc.add(const CheckSpacesStepStatus()); currentStep = step; - }); - Future.delayed(const Duration(milliseconds: 500), () { - bloc.add(const ValidateBasicsStep()); + step3 = step; + bloc.add(const CheckSpacesStepStatus()); + bloc.add(const CheckStepStatus()); }); }, child: Column( @@ -243,9 +313,9 @@ class _AddNewUserDialogState extends State { SvgPicture.asset( currentStep == step ? Assets.currentProcessIcon - : bloc.isCompleteBasics == false + : bloc.isCompleteRolePermissions == false ? Assets.wrongProcessIcon - : bloc.isCompleteBasics == true + : bloc.isCompleteRolePermissions == true ? Assets.completeProcessIcon : Assets.uncomplete_ProcessIcon, width: 25, @@ -283,63 +353,4 @@ class _AddNewUserDialogState extends State { ), ); } - - Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) { - return GestureDetector( - onTap: () { - setState(() { - currentStep = step; - bloc.add(const CheckSpacesStepStatus()); - 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.isCompleteRolePermissions == 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, - ), - ), - ) - ], - ), - ); - } } diff --git a/lib/pages/roles_and_permission/users_page/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart similarity index 76% rename from lib/pages/roles_and_permission/users_page/view/basics_view.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index 22398ed3..a6ec686b 100644 --- a/lib/pages/roles_and_permission/users_page/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl_phone_field/countries.dart'; import 'package:intl_phone_field/country_picker_dialog.dart'; import 'package:intl_phone_field/intl_phone_field.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_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -30,7 +32,7 @@ class BasicsView extends StatelessWidget { color: Colors.black), ), const SizedBox( - height: 80, + height: 50, ), Text( 'To get started, fill out some basic information about who you’re adding as a user.', @@ -40,11 +42,10 @@ class BasicsView extends StatelessWidget { ), ), const SizedBox( - height: 25, + height: 35, ), Row( children: [ - // First Name Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -52,9 +53,6 @@ class BasicsView extends StatelessWidget { SizedBox( child: Row( children: [ - // SizedBox( - // width: 15, - // ), const Text( " * ", style: TextStyle( @@ -75,7 +73,14 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( - style: TextStyle(color: Colors.black), + style: + const TextStyle(color: ColorsManager.blackColor), + onChanged: (value) { + Future.delayed(const Duration(milliseconds: 200), + () { + _blocRole.add(ValidateBasicsStep()); + }); + }, controller: _blocRole.firstNameController, decoration: inputTextFormDeco( hintText: "Enter first name", @@ -96,10 +101,7 @@ class BasicsView extends StatelessWidget { ], ), ), - - SizedBox(width: 10), - - // Last Name + const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -125,8 +127,14 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( + onChanged: (value) { + Future.delayed(const Duration(milliseconds: 200), + () { + _blocRole.add(ValidateBasicsStep()); + }); + }, controller: _blocRole.lastNameController, - style: TextStyle(color: Colors.black), + style: const TextStyle(color: Colors.black), decoration: inputTextFormDeco(hintText: "Enter last name") .copyWith( @@ -149,7 +157,7 @@ class BasicsView extends StatelessWidget { ), ], ), - SizedBox(height: 10), + const SizedBox(height: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -176,8 +184,14 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( + onChanged: (value) { + Future.delayed(const Duration(milliseconds: 200), () { + _blocRole.add(const CheckStepStatus()); + _blocRole.add(ValidateBasicsStep()); + }); + }, controller: _blocRole.emailController, - style: TextStyle(color: Colors.black), + style: const TextStyle(color: ColorsManager.blackColor), decoration: inputTextFormDeco(hintText: "name@example.com") .copyWith( hintStyle: context.textTheme.bodyMedium?.copyWith( @@ -189,24 +203,24 @@ class BasicsView extends StatelessWidget { if (value == null || value.isEmpty) { return 'Enter Email Address'; } - // Regular expression for email validation final emailRegex = RegExp( r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', ); if (!emailRegex.hasMatch(value)) { return 'Enter a valid Email Address'; } + if (_blocRole.checkEmailValid != "Valid email") { + return _blocRole.checkEmailValid; + } return null; }, ), ), ], ), - SizedBox(height: 10), + const SizedBox(height: 10), Row( children: [ - // Phone Number - Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -227,7 +241,8 @@ class BasicsView extends StatelessWidget { pickerDialogStyle: PickerDialogStyle(), dropdownIconPosition: IconPosition.leading, disableLengthCheck: true, - dropdownTextStyle: TextStyle(color: Colors.black), + dropdownTextStyle: + const TextStyle(color: ColorsManager.blackColor), textInputAction: TextInputAction.done, decoration: inputTextFormDeco( hintText: "05x xxx xxxx", @@ -238,18 +253,39 @@ class BasicsView extends StatelessWidget { color: ColorsManager.textGray), ), initialCountryCode: 'AE', - style: TextStyle(color: Colors.black), - onChanged: (phone) { - print(phone.completeNumber); - }, + countries: const [ + Country( + name: "United Arab Emirates", + nameTranslations: { + "en": "United Arab Emirates", + "ar": "الإمارات العربية المتحدة", + }, + flag: "🇦🇪", + code: "AE", + dialCode: "971", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Saudi Arabia", + nameTranslations: { + "en": "Saudi Arabia", + "ar": "السعودية", + }, + flag: "🇸🇦", + code: "SA", + dialCode: "966", + minLength: 9, + maxLength: 9, + ), + ], + style: const TextStyle(color: Colors.black), + controller: _blocRole.phoneController, ) ], ), ), - - SizedBox(width: 10), - - // Job Title + const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -270,7 +306,8 @@ class BasicsView extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: TextFormField( controller: _blocRole.jobTitleController, - style: TextStyle(color: Colors.black), + style: + const TextStyle(color: ColorsManager.blackColor), decoration: inputTextFormDeco( hintText: "Job Title (Optional)") .copyWith( @@ -287,7 +324,7 @@ class BasicsView extends StatelessWidget { ), ], ), - SizedBox(height: 20), + const SizedBox(height: 20), ], ), ); diff --git a/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart similarity index 96% rename from lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart index f10fd4ed..002b0171 100644 --- a/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.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_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class DeleteUserDialog extends StatefulWidget { diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart new file mode 100644 index 00000000..b16cd8d2 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart @@ -0,0 +1,239 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_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'; + +class PermissionManagement extends StatefulWidget { + final UsersBloc? bloc; + const PermissionManagement({Key? key, this.bloc}) : super(key: key); + + @override + _PermissionManagementState createState() => _PermissionManagementState(); +} + +class _PermissionManagementState extends State { + void toggleOptionById(String id) { + setState(() { + for (var mainOption in widget.bloc!.permissions) { + if (mainOption.id == id) { + final isChecked = + checkifOneOfthemChecked(mainOption) == CheckState.all; + mainOption.isChecked = !isChecked; + + for (var subOption in mainOption.subOptions) { + subOption.isChecked = !isChecked; + for (var child in subOption.subOptions) { + child.isChecked = !isChecked; + } + } + return; + } + + for (var subOption in mainOption.subOptions) { + if (subOption.id == id) { + subOption.isChecked = !subOption.isChecked; + for (var child in subOption.subOptions) { + child.isChecked = subOption.isChecked; + } + mainOption.isChecked = + mainOption.subOptions.every((sub) => sub.isChecked); + return; + } + + for (var child in subOption.subOptions) { + if (child.id == id) { + child.isChecked = !child.isChecked; + subOption.isChecked = + subOption.subOptions.every((child) => child.isChecked); + mainOption.isChecked = + mainOption.subOptions.every((sub) => sub.isChecked); + return; + } + } + } + } + }); + } + + CheckState checkifOneOfthemChecked(PermissionOption mainOption) { + bool allSelected = true; + bool someSelected = false; + + for (var subOption in mainOption.subOptions) { + if (subOption.isChecked) { + someSelected = true; + } else { + allSelected = false; + } + + for (var child in subOption.subOptions) { + if (child.isChecked) { + someSelected = true; + } else { + allSelected = false; + } + } + } + + if (allSelected) { + return CheckState.all; + } else if (someSelected) { + return CheckState.some; + } else { + return CheckState.none; + } + } + + @override + Widget build(BuildContext context) { + return ListView.builder( + padding: const EdgeInsets.all(8), + itemCount: widget.bloc!.permissions.length, + itemBuilder: (context, index) { + final option = widget.bloc!.permissions[index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + InkWell( + // onTap: () => toggleOptionById(option.id), + child: Builder( + builder: (context) { + final checkState = checkifOneOfthemChecked(option); + + if (checkState == CheckState.all) { + return Image.asset( + Assets.CheckBoxChecked, + width: 20, + height: 20, + ); + } else if (checkState == CheckState.some) { + return Image.asset( + Assets.rectangleCheckBox, + width: 20, + height: 20, + ); + } else { + return Image.asset( + Assets.emptyBox, + width: 20, + height: 20, + ); + } + }, + ), + ), + const SizedBox(width: 8), + Text( + option.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 12, + color: ColorsManager.blackColor), + ), + ], + ), + const SizedBox( + height: 10, + ), + ...option.subOptions.map((subOption) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + color: option.isHighlighted + ? Colors.blue.shade50 + : Colors.white, + child: Row( + children: [ + InkWell( + // onTap: () => toggleOptionById(subOption.id), + child: Builder( + builder: (context) { + final checkState = + checkifOneOfthemChecked(PermissionOption( + id: subOption.id, + title: subOption.title, + subOptions: [subOption], + )); + + if (checkState == CheckState.all) { + return Image.asset( + Assets.CheckBoxChecked, + width: 20, + height: 20, + ); + } else if (checkState == CheckState.some) { + return Image.asset( + Assets.rectangleCheckBox, + width: 20, + height: 20, + ); + } else { + return Image.asset( + Assets.emptyBox, + width: 20, + height: 20, + ); + } + }, + ), + ), + const SizedBox(width: 8), + Text( + subOption.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 12, + color: ColorsManager.lightGreyColor), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(left: 50.0), + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 2.0, + crossAxisSpacing: 0.2, + childAspectRatio: 5, + ), + itemCount: subOption.subOptions.length, + itemBuilder: (context, index) { + final child = subOption.subOptions[index]; + return CheckboxListTile( + selectedTileColor: child.isHighlighted + ? Colors.blue.shade50 + : Colors.white, + dense: true, + controlAffinity: ListTileControlAffinity.leading, + title: Text( + child.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.lightGreyColor), + ), + value: child.isChecked, + onChanged: (value) => toggleOptionById(child.id), + enabled: false, + ); + }, + ), + ) + ], + ); + }).toList(), + ], + ); + }, + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart new file mode 100644 index 00000000..1bc9331e --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class RoleDropdown extends StatefulWidget { + final UsersBloc? bloc; + const RoleDropdown({super.key, this.bloc}); + + @override + _RoleDropdownState createState() => _RoleDropdownState(); +} + +class _RoleDropdownState extends State { + late String selectedRole; + + @override + void initState() { + super.initState(); + selectedRole = widget.bloc!.roleSelected; + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Row( + children: [ + Text( + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), + ), + Text( + "Role", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: Colors.black, + ), + ), + ], + ), + const SizedBox(height: 8), + SizedBox( + child: DropdownButtonFormField( + dropdownColor: ColorsManager.whiteColors, + alignment: Alignment.center, + focusColor: Colors.white, + autofocus: true, + value: selectedRole.isNotEmpty ? selectedRole : null, + items: widget.bloc!.roles.map((role) { + return DropdownMenuItem( + value: role.uuid, + child: Text(role.type), + ); + }).toList(), + onChanged: (value) { + setState(() { + selectedRole = value!; + }); + widget.bloc!.roleSelected = selectedRole; + widget.bloc! + .add(PermissionEvent(roleUuid: widget.bloc!.roleSelected)); + }, + icon: const SizedBox.shrink(), + borderRadius: const BorderRadius.all(Radius.circular(10)), + hint: const Padding( + padding: EdgeInsets.only(left: 10), + child: Text( + "Please Select", + style: TextStyle( + color: ColorsManager.textGray, + ), + ), + ), + decoration: inputTextFormDeco().copyWith( + contentPadding: EdgeInsets.zero, + suffixIcon: Container( + padding: EdgeInsets.zero, + 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: const Center( + child: Icon( + Icons.keyboard_arrow_down, + color: ColorsManager.textGray, + ), + ), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart new file mode 100644 index 00000000..b054a88c --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart @@ -0,0 +1,127 @@ +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/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.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'; +import 'package:syncrow_web/utils/style.dart'; + +class RolesAndPermission extends StatelessWidget { + const RolesAndPermission({super.key}); + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + + return BlocBuilder(builder: (context, state) { + final _blocRole = BlocProvider.of(context); + return Container( + color: Colors.white, + child: Form( + key: _blocRole.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Role & Permissions', + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 20, + color: Colors.black), + ), + const SizedBox( + height: 15, + ), + SizedBox( + width: 350, + height: 100, + child: RoleDropdown( + bloc: _blocRole, + )), + const SizedBox(height: 10), + Expanded( + child: SizedBox( + child: Column( + children: [ + Expanded( + flex: 2, + child: Container( + decoration: const BoxDecoration( + color: ColorsManager.CircleRolesBackground, + borderRadius: BorderRadius.only( + topRight: Radius.circular(20), + topLeft: Radius.circular(20)), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(20)), + border: Border.all( + color: ColorsManager.grayBorder)), + child: TextFormField( + style: + const TextStyle(color: Colors.black), + controller: + _blocRole.roleSearchController, + onChanged: (value) { + _blocRole.add(SearchPermission( + nodes: _blocRole.permissions, + searchTerm: value)); + }, + decoration: textBoxDecoration(radios: 20)! + .copyWith( + fillColor: Colors.white, + suffixIcon: Padding( + padding: + const EdgeInsets.only(right: 16), + child: SvgPicture.asset( + Assets.textFieldSearch, + width: 24, + height: 24, + ), + ), + hintStyle: context.textTheme.bodyMedium + ?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + ), + ), + ), + ], + ), + ), + ), + ), + Expanded( + flex: 7, + child: Container( + color: ColorsManager.CircleRolesBackground, + padding: const EdgeInsets.all(8.0), + child: Container( + color: ColorsManager.whiteColors, + child: PermissionManagement( + bloc: _blocRole, + )))) + ], + ), + ), + ), + ], + ), + ), + ); + }); + } +} diff --git a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart similarity index 98% rename from lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart index 4bc330b2..f942d1d5 100644 --- a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart @@ -1,10 +1,10 @@ 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/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/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'; diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart new file mode 100644 index 00000000..2a31a91f --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -0,0 +1,185 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart'; + +class UserTableBloc extends Bloc { + UserTableBloc() : super(TableInitial()) { + on(_getUsers); + on(_changeUserStatus); + on(_toggleSortUsersByNameAsc); + on(_toggleSortUsersByNameDesc); + on(_toggleSortUsersByDateOldestToNewest); + on(_toggleSortUsersByDateNewestToOldest); + } + + List users = []; + List initialUsers = []; // Save the initial state + String currentSortOrder = ''; // Keeps track of the current sorting order + String currentSortOrderDate = ''; // Keeps track of the current sorting order + + Future _getUsers(GetUsers event, Emitter emit) async { + emit(UsersLoadingState()); + try { + users = [ + RolesUserModel( + id: '1', + userName: 'b 1', + userEmail: 'test1@test.com', + action: '', + createdBy: 'Admin', + creationDate: '25/10/2024', + creationTime: '10:30 AM', + status: 'Invited', + ), + RolesUserModel( + id: '2', + userName: 'a 2', + userEmail: 'test2@test.com', + action: '', + createdBy: 'Admin', + creationDate: '24/10/2024', + creationTime: '2:30 PM', + status: 'Active', + ), + RolesUserModel( + id: '3', + userName: 'c 3', + userEmail: 'test3@test.com', + action: '', + createdBy: 'Admin', + creationDate: '23/10/2024', + creationTime: '9:00 AM', + status: 'Disabled', + ), + ]; + // Sort users by newest to oldest as default + users.sort((a, b) { + final dateA = _parseDateTime(a.creationDate!); + final dateB = _parseDateTime(b.creationDate!); + return dateB.compareTo(dateA); // Newest to oldest + }); + initialUsers = List.from(users); // Save the initial state + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + void _changeUserStatus(ChangeUserStatus event, Emitter emit) { + try { + users = users.map((user) { + if (user.id == event.userId) { + return RolesUserModel( + id: user.id, + userName: user.userName, + userEmail: user.userEmail, + createdBy: user.createdBy, + creationDate: user.creationDate, + creationTime: user.creationTime, + status: event.newStatus, + action: user.action, + ); + } + return user; + }).toList(); + + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + void _toggleSortUsersByNameAsc( + SortUsersByNameAsc event, Emitter emit) { + if (currentSortOrder == "Asc") { + // If already sorted ascending, reset to the initial state + emit(UsersLoadingState()); + currentSortOrder = ""; + users = List.from(initialUsers); // Reset to saved initial state + emit(UsersLoadedState(users: users)); + } else { + // Sort ascending + emit(UsersLoadingState()); + currentSortOrder = "Asc"; + users.sort((a, b) => a.userName!.compareTo(b.userName!)); + emit(UsersLoadedState(users: users)); + } + } + + void _toggleSortUsersByNameDesc( + SortUsersByNameDesc event, Emitter emit) { + if (currentSortOrder == "Desc") { + // If already sorted descending, reset to the initial state + emit(UsersLoadingState()); + currentSortOrder = ""; + users = List.from(initialUsers); // Reset to saved initial state + emit(UsersLoadedState(users: users)); + } else { + // Sort descending + emit(UsersLoadingState()); + currentSortOrder = "Desc"; + users.sort((a, b) => b.userName!.compareTo(a.userName!)); + emit(UsersLoadedState(users: users)); + } + } + + void _toggleSortUsersByDateNewestToOldest( + DateNewestToOldestEvent event, Emitter emit) { + if (currentSortOrderDate == "NewestToOldest") { + // If already sorted ascending, reset to the initial state + emit(UsersLoadingState()); + currentSortOrder = ""; + currentSortOrderDate = ""; + users = List.from(initialUsers); // Reset to saved initial state + emit(UsersLoadedState(users: users)); + } else { + // Sort ascending + emit(UsersLoadingState()); + users.sort((a, b) { + final dateA = _parseDateTime(a.creationDate!); + final dateB = _parseDateTime(b.creationDate!); + return dateB.compareTo(dateA); // Newest to oldest + }); + emit(UsersLoadedState(users: users)); + } + } + + void _toggleSortUsersByDateOldestToNewest( + DateOldestToNewestEvent event, Emitter emit) { + if (currentSortOrderDate == "OldestToNewest") { + // If already sorted ascending, reset to the initial state + emit(UsersLoadingState()); + currentSortOrder = ""; + currentSortOrderDate = ""; + users = List.from(initialUsers); // Reset to saved initial state + emit(UsersLoadedState(users: users)); + } else { + // Sort ascending + emit(UsersLoadingState()); + users.sort((a, b) { + final dateA = _parseDateTime(a.creationDate!); + final dateB = _parseDateTime(b.creationDate!); + return dateA.compareTo(dateB); // Newest to oldest + }); + emit(UsersLoadedState(users: users)); + } + } + + DateTime _parseDateTime(String date) { + try { + // Split the date into day, month, and year + final dateParts = date.split('/'); + final day = int.parse(dateParts[0]); + final month = int.parse(dateParts[1]); + final year = int.parse(dateParts[2]); + + // Split the time into hours and minutes + + return DateTime(year, month, day); + } catch (e) { + throw FormatException('Invalid date or time format: $date '); + } + } +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart new file mode 100644 index 00000000..a6c77bd3 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart @@ -0,0 +1,63 @@ +import 'package:equatable/equatable.dart'; + +sealed class UserTableEvent extends Equatable { + const UserTableEvent(); +} + +class GetRoles extends UserTableEvent { + const GetRoles(); + @override + List get props => []; +} + +class GetUsers extends UserTableEvent { + const GetUsers(); + @override + List get props => []; +} + +class ChangeUserStatus extends UserTableEvent { + final String userId; + final String newStatus; + + const ChangeUserStatus({required this.userId, required this.newStatus}); + + @override + List get props => [userId, newStatus]; +} + +class SortUsersByNameAsc extends UserTableEvent { + const SortUsersByNameAsc(); + + @override + List get props => []; +} + +class SortUsersByNameDesc extends UserTableEvent { + const SortUsersByNameDesc(); + + @override + List get props => []; +} + +class StoreUsersEvent extends UserTableEvent { + const StoreUsersEvent(); + + @override + List get props => []; +} + + +class DateNewestToOldestEvent extends UserTableEvent { + const DateNewestToOldestEvent(); + + @override + List get props => []; +} + +class DateOldestToNewestEvent extends UserTableEvent { + const DateOldestToNewestEvent(); + + @override + List get props => []; +} \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart new file mode 100644 index 00000000..2a132947 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart @@ -0,0 +1,82 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; + +sealed class UserTableState extends Equatable { + const UserTableState(); +} + +final class TableInitial extends UserTableState { + @override + List get props => []; +} + +final class RolesLoadingState extends UserTableState { + @override + List get props => []; +} + +final class UsersLoadingState extends UserTableState { + @override + List get props => []; +} + +final class RolesLoadedState extends UserTableState { + @override + List get props => []; +} + +final class UsersLoadedState extends UserTableState { + List users = []; + UsersLoadedState({required this.users}); + @override + List get props => [users]; +} + +final class ErrorState extends UserTableState { + final String message; + + const ErrorState(this.message); + + @override + List get props => [message]; +} + +/// report state +final class SosReportLoadingState extends UserTableState { + @override + List get props => []; +} + +final class RolesErrorState extends UserTableState { + final String message; + + const RolesErrorState(this.message); + + @override + List get props => [message]; +} + +/// automation reports + +final class SosAutomationReportLoadingState extends UserTableState { + @override + List get props => []; +} + +final class SosAutomationReportErrorState extends UserTableState { + final String message; + + const SosAutomationReportErrorState(this.message); + + @override + List get props => [message]; +} + +final class ChangeTapStatus extends UserTableState { + bool select = true; + + ChangeTapStatus({required this.select}); + + @override + List get props => [select]; +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart b/lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart new file mode 100644 index 00000000..45ebc3ae --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +Future showDateFilterMenu({ + required BuildContext context, + Function()? aToZTap, + Function()? zToaTap, + String? isSelected, +}) async { + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; + final RelativeRect position = RelativeRect.fromRect( + Rect.fromLTRB( + overlay.size.width / 2, + 240, + 0, + overlay.size.height, + ), + Offset.zero & overlay.size, + ); + + await showMenu( + context: context, + position: position, + color: ColorsManager.whiteColors, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(10), + bottomLeft: Radius.circular(10), + ), + ), + items: [ + PopupMenuItem( + onTap: aToZTap, + child: ListTile( + leading: Image.asset( + Assets.AtoZIcon, + width: 25, + ), + title: Text( + "Sort from newest to oldest", + // style: context.textTheme.bodyMedium, + style: TextStyle( + color: isSelected == "NewestToOldest" + ? Colors.black + : Colors.blueGrey), + ), + ), + ), + PopupMenuItem( + onTap: zToaTap, + child: ListTile( + leading: Image.asset( + Assets.ZtoAIcon, + width: 25, + ), + title: Text( + "Sort from oldest to newest", + style: TextStyle( + color: isSelected == "OldestToNewest" + ? Colors.black + : Colors.blueGrey), + ), + ), + ), + ], + ).then((value) { + // setState(() { + // _isDropdownOpen = false; + // }); + }); +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart b/lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart new file mode 100644 index 00000000..e78eae6b --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +Future showDeActivateFilterMenu({ + required BuildContext context, + Function()? aToZTap, + Function()? zToaTap, + String? isSelected, +}) async { + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; + final RelativeRect position = RelativeRect.fromRect( + Rect.fromLTRB( + overlay.size.width / 2, + 240, + 0, + overlay.size.height, + ), + Offset.zero & overlay.size, + ); + + await showMenu( + context: context, + position: position, + color: ColorsManager.whiteColors, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(10), + bottomLeft: Radius.circular(10), + ), + ), + items: [ + PopupMenuItem( + onTap: aToZTap, + child: ListTile( + leading: Image.asset( + Assets.AtoZIcon, + width: 25, + ), + title: Text( + "Sort A to Z", + // style: context.textTheme.bodyMedium, + style: TextStyle( + color: isSelected == "NewestToOldest" + ? Colors.black + : Colors.blueGrey), + ), + ), + ), + PopupMenuItem( + onTap: zToaTap, + child: ListTile( + leading: Image.asset( + Assets.ZtoAIcon, + width: 25, + ), + title: Text( + "Sort Z to A", + style: TextStyle( + color: isSelected == "OldestToNewest" + ? Colors.black + : Colors.blueGrey), + ), + ), + ), + ], + ).then((value) { + // setState(() { + // _isDropdownOpen = false; + // }); + }); +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart b/lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart new file mode 100644 index 00000000..e869e10b --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +Future showNameMenu({ + required BuildContext context, + Function()? aToZTap, + Function()? zToaTap, + String? isSelected, +}) async { + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; + final RelativeRect position = RelativeRect.fromRect( + Rect.fromLTRB( + overlay.size.width / 25, + 240, + 0, + overlay.size.height, + ), + Offset.zero & overlay.size, + ); + + await showMenu( + context: context, + position: position, + color: ColorsManager.whiteColors, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(10), + bottomLeft: Radius.circular(10), + ), + ), + items: [ + PopupMenuItem( + onTap: aToZTap, + child: ListTile( + leading: Image.asset( + Assets.AtoZIcon, + width: 25, + ), + title: Text( + "Sort A to Z", + // style: context.textTheme.bodyMedium, + style: TextStyle( + color: isSelected == "Asc" ? Colors.black : Colors.blueGrey), + ), + ), + ), + PopupMenuItem( + onTap: zToaTap, + child: ListTile( + leading: Image.asset( + Assets.ZtoAIcon, + width: 25, + ), + title: Text( + "Sort Z to A", + style: TextStyle( + color: isSelected == "Desc" ? Colors.black : Colors.blueGrey), + ), + ), + ), + ], + ).then((value) { + // setState(() { + // _isDropdownOpen = false; + // }); + }); +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart new file mode 100644 index 00000000..926ac92a --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -0,0 +1,299 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class DynamicTableScreen extends StatefulWidget { + final List titles; + final List> rows; + final void Function(int columnIndex)? onFilter; + + DynamicTableScreen( + {required this.titles, required this.rows, required this.onFilter}); + + @override + _DynamicTableScreenState createState() => _DynamicTableScreenState(); +} + +class _DynamicTableScreenState extends State + with WidgetsBindingObserver { + late List columnWidths; + + // @override + // void initState() { + // super.initState(); + // // Initialize column widths with default sizes proportional to the screen width + // // Assigning placeholder values here. The actual sizes will be updated in `build`. + // } + @override + void initState() { + super.initState(); + columnWidths = List.filled(widget.titles.length, 150.0); + WidgetsBinding.instance.addObserver(this); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeMetrics() { + super.didChangeMetrics(); + // Screen size might have changed + final newScreenWidth = MediaQuery.of(context).size.width; + setState(() { + columnWidths = List.generate(widget.titles.length, (index) { + if (index == 1) { + return newScreenWidth * + 0.12; // 20% of screen width for the second column + } else if (index == 9) { + return newScreenWidth * + 0.2; // 25% of screen width for the tenth column + } + return newScreenWidth * + 0.09; // Default to 10% of screen width for other columns + }); + }); + } + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + // Initialize column widths if they are still set to placeholder values + if (columnWidths.every((width) => width == 120.0)) { + columnWidths = List.generate(widget.titles.length, (index) { + if (index == 1) { + return screenWidth * 0.11; + } else if (index == 9) { + return screenWidth * 0.2; + } + return screenWidth * 0.11; + }); + setState(() {}); + } + return Container( + child: SingleChildScrollView( + clipBehavior: Clip.none, + scrollDirection: Axis.horizontal, + child: Container( + decoration: containerDecoration.copyWith( + borderRadius: const BorderRadius.all(Radius.circular(20))), + child: FittedBox( + child: Column( + children: [ + // Header Row with Resizable Columns + Container( + decoration: containerDecoration.copyWith( + color: ColorsManager.CircleRolesBackground, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(15), + topRight: Radius.circular(15))), + child: Row( + children: List.generate(widget.titles.length, (index) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + FittedBox( + child: Container( + padding: const EdgeInsets.only(left: 5, right: 5), + width: columnWidths[index], + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + child: Text( + widget.titles[index], + maxLines: 2, + style: const TextStyle( + overflow: TextOverflow.ellipsis, + fontWeight: FontWeight.w400, + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + ), + if (index != 1 && + index != 9 && + index != 7 && + index != 5) + FittedBox( + child: IconButton( + icon: SvgPicture.asset( + Assets.filterTableIcon, + fit: BoxFit.none, + ), + onPressed: () { + if (widget.onFilter != null) { + widget.onFilter!(index); + } + }, + ), + ) + ], + ), + ), + ), + GestureDetector( + onHorizontalDragUpdate: (details) { + setState(() { + columnWidths[index] = (columnWidths[index] + + details.delta.dx) + .clamp( + 150.0, 300.0); // Minimum & Maximum size + }); + }, + child: MouseRegion( + cursor: SystemMouseCursors + .resizeColumn, // Set the cursor to resize + child: Container( + color: Colors.green, + child: Container( + color: ColorsManager.boxDivider, + width: 1, + height: 50, // Height of the header cell + ), + ), + ), + ), + ], + ); + }), + ), + ), + // Data Rows with Dividers + Container( + decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(15), + bottomRight: Radius.circular(15))), + child: Column( + children: widget.rows.map((row) { + int rowIndex = widget.rows.indexOf(row); + return Column( + children: [ + Container( + child: Padding( + padding: const EdgeInsets.only( + left: 5, top: 10, right: 5, bottom: 10), + child: Row( + children: List.generate(row.length, (index) { + return SizedBox( + width: columnWidths[index], + child: SizedBox( + child: Padding( + padding: const EdgeInsets.only( + left: 15, right: 10), + child: row[index], + ), + ), + ); + }), + ), + ), + ), + if (rowIndex < widget.rows.length - 1) + Row( + children: List.generate(widget.titles.length, + (index) { + return SizedBox( + width: columnWidths[index], + child: const Divider( + color: ColorsManager.boxDivider, + thickness: 1, + height: 1, + ), + ); + })) + // Add a Divider below each row except the last one + ], + ); + }).toList(), + ), + ), + ], + ), + ), + ), + ), + ); + } +} + + + + // Widget build(BuildContext context) { + // return Scaffold( + // body: SingleChildScrollView( + // scrollDirection: Axis.horizontal, + // child: SingleChildScrollView( + // scrollDirection: Axis.vertical, + // child: Column( + // children: [ + // // Header Row with Resizable Columns + // Container( + // color: Colors.green, + // child: Row( + // children: List.generate(widget.titles.length, (index) { + // return Row( + // children: [ + // Container( + // width: columnWidths[index], + // decoration: const BoxDecoration( + // color: Colors.green, + // ), + // child: Text( + // widget.titles[index], + // style: TextStyle(fontWeight: FontWeight.bold), + // textAlign: TextAlign.center, + // ), + // ), + // GestureDetector( + // onHorizontalDragUpdate: (details) { + // setState(() { + // columnWidths[index] = (columnWidths[index] + + // details.delta.dx) + // .clamp(50.0, 300.0); // Minimum & Maximum size + // }); + // }, + // child: MouseRegion( + // cursor: SystemMouseCursors + // .resizeColumn, // Set the cursor to resize + // child: Container( + // color: Colors.green, + // child: Container( + // color: Colors.black, + // width: 1, + // height: 50, // Height of the header cell + // ), + // ), + // ), + // ), + // ], + // ); + // }), + // ), + // ), + // // Data Rows + // ...widget.rows.map((row) { + // return Row( + // children: List.generate(row.length, (index) { + // return Container( + // width: columnWidths[index], + // child: row[index], + // ); + // }), + // ); + // }).toList(), + // ], + // ), + // ), + // ), + // ); + // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart similarity index 73% rename from lib/pages/roles_and_permission/users_page/view/users_page.dart rename to lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index 273b8a34..758f062b 100644 --- a/lib/pages/roles_and_permission/users_page/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -1,11 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package 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/add_user_dialog.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/view/user_table.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/name_filter.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/user_table.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'; @@ -85,7 +88,7 @@ class UsersPage extends StatelessWidget { ? 'Invited' : 'Active'; context - .read() + .read() .add(ChangeUserStatus(userId: userId, newStatus: newStatus)); }, child: Padding( @@ -104,12 +107,10 @@ class UsersPage extends StatelessWidget { ); } -// return RolesAndPermission(); -// } -// } - return BlocBuilder( + return BlocBuilder( builder: (context, state) { final screenSize = MediaQuery.of(context).size; + final _blocRole = BlocProvider.of(context); if (state is UsersLoadingState) { return const Center(child: CircularProgressIndicator()); @@ -158,7 +159,9 @@ class UsersPage extends StatelessWidget { return const AddNewUserDialog(); }, ).then((listDevice) { - if (listDevice != null) {} + if (listDevice != null) { + + } }); }, child: Container( @@ -181,6 +184,56 @@ class UsersPage extends StatelessWidget { ), const SizedBox(height: 25), DynamicTableScreen( + onFilter: (columnIndex) { + if (columnIndex == 0) { + showNameMenu( + context: context, + isSelected: _blocRole.currentSortOrder, + aToZTap: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + zToaTap: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 4) { + showDateFilterMenu( + context: context, + isSelected: _blocRole.currentSortOrderDate, + aToZTap: () { + context + .read() + .add(const DateNewestToOldestEvent()); + }, + zToaTap: () { + context + .read() + .add(const DateOldestToNewestEvent()); + }, + ); + } + if (columnIndex == 8) { + showDeActivateFilterMenu( + context: context, + isSelected: _blocRole.currentSortOrderDate, + aToZTap: () { + context + .read() + .add(const DateNewestToOldestEvent()); + }, + zToaTap: () { + context + .read() + .add(const DateOldestToNewestEvent()); + }, + ); + } + }, titles: const [ "Full Name", "Email Address", @@ -210,10 +263,10 @@ class UsersPage extends StatelessWidget { status(status: user.status!), Row( children: [ - actionButton( - title: "Activity Log", - onTap: () {}, - ), + // actionButton( + // title: "Activity Log", + // onTap: () {}, + // ), actionButton( title: "Edit", onTap: () {}, diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart deleted file mode 100644 index 9b7ab542..00000000 --- a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart +++ /dev/null @@ -1,489 +0,0 @@ -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/utils/color_manager.dart'; -import 'package:syncrow_web/utils/constants/assets.dart'; -import 'package:syncrow_web/utils/extension/build_context_x.dart'; -import 'package:syncrow_web/utils/style.dart'; - -class RolesAndPermission extends StatelessWidget { - const RolesAndPermission({super.key}); - @override - Widget build(BuildContext context) { - final size = MediaQuery.of(context).size; - - return BlocBuilder(builder: (context, state) { - final _blocRole = BlocProvider.of(context); - return Container( - color: Colors.white, - child: Form( - key: _blocRole.formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - 'Role & Permissions', - style: context.textTheme.bodyLarge?.copyWith( - fontWeight: FontWeight.w700, - fontSize: 20, - color: Colors.black), - ), - const SizedBox( - height: 15, - ), - SizedBox( - width: 300, - height: 110, - child: DropdownExample( - bloc: _blocRole, - )), - const SizedBox(height: 10), - Expanded( - child: SizedBox( - child: Column( - children: [ - Expanded( - flex: 2, - child: Container( - decoration: const BoxDecoration( - color: ColorsManager.CircleRolesBackground, - borderRadius: BorderRadius.only( - topRight: Radius.circular(20), - topLeft: Radius.circular(20)), - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Expanded( - child: Container( - decoration: BoxDecoration( - borderRadius: const BorderRadius.all( - Radius.circular(20)), - border: Border.all( - color: ColorsManager.grayBorder)), - child: TextFormField( - style: - const TextStyle(color: Colors.black), - controller: _blocRole.firstNameController, - onChanged: (value) { - - _blocRole.add(SearchPermission( - nodes: _blocRole.permissions, - searchTerm: value)); - }, - decoration: textBoxDecoration(radios: 20)! - .copyWith( - fillColor: Colors.white, - suffixIcon: Padding( - padding: - const EdgeInsets.only(right: 16), - child: SvgPicture.asset( - Assets.textFieldSearch, - width: 24, - height: 24, - ), - ), - hintStyle: context.textTheme.bodyMedium - ?.copyWith( - fontWeight: FontWeight.w400, - fontSize: 12, - color: ColorsManager.textGray), - ), - ), - ), - ), - ], - ), - ), - ), - ), - Expanded( - flex: 7, - child: Container( - color: ColorsManager.CircleRolesBackground, - padding: const EdgeInsets.all(8.0), - child: Container( - color: ColorsManager.whiteColors, - child: DeviceManagement( - bloc: _blocRole, - )))) - ], - ), - ), - ), - ], - ), - ), - ); - }); - } -} - -class DropdownExample extends StatefulWidget { - final UsersBloc? bloc; - const DropdownExample({super.key, this.bloc}); - - @override - _DropdownExampleState createState() => _DropdownExampleState(); -} - -class _DropdownExampleState extends State { - String? selectedRole; - @override - void initState() { - super.initState(); - if (widget.bloc != null && widget.bloc!.roles.isNotEmpty) { - selectedRole = widget.bloc!.roles.first.uuid; - } - } - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "Role", - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: Colors.black, - ), - ), - const SizedBox(height: 8), - SizedBox( - child: DropdownButtonFormField( - alignment: Alignment.center, - focusColor: Colors.white, - autofocus: true, - value: selectedRole, - items: widget.bloc!.roles.map((role) { - return DropdownMenuItem( - value: role.uuid, - child: Text(role.type), - ); - }).toList(), - onChanged: (value) { - setState(() { - selectedRole = value; - }); - widget.bloc!.add(PermissionEvent(roleUuid: selectedRole)); - }, - padding: EdgeInsets.zero, - icon: const SizedBox.shrink(), - borderRadius: const BorderRadius.all(Radius.circular(10)), - hint: const Padding( - padding: EdgeInsets.only(left: 20), - child: Text("Please Select"), - ), - decoration: inputTextFormDeco().copyWith( - contentPadding: EdgeInsets.zero, - suffixIcon: Container( - padding: EdgeInsets.zero, - width: 70, - height: 50, - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: const BorderRadius.only( - bottomRight: Radius.circular(10), - topRight: Radius.circular(10), - ), - border: Border.all( - color: Colors.grey, - width: 1.0, - ), - ), - child: const Center( - child: Icon(Icons.keyboard_arrow_down), - ), - ), - ), - ), - ), - ], - ), - ); - } -} - -class DeviceManagement extends StatefulWidget { - final UsersBloc? bloc; - const DeviceManagement({Key? key, this.bloc}) : super(key: key); - - @override - _DeviceManagementState createState() => _DeviceManagementState(); -} - -class _DeviceManagementState extends State { - void toggleOptionById(String id) { - setState(() { - for (var mainOption in widget.bloc!.permissions) { - if (mainOption.id == id) { - final isChecked = - checkifOneOfthemChecked(mainOption) == CheckState.all; - mainOption.isChecked = !isChecked; - - for (var subOption in mainOption.subOptions) { - subOption.isChecked = !isChecked; - for (var child in subOption.subOptions) { - child.isChecked = !isChecked; - } - } - return; - } - - for (var subOption in mainOption.subOptions) { - if (subOption.id == id) { - subOption.isChecked = !subOption.isChecked; - for (var child in subOption.subOptions) { - child.isChecked = subOption.isChecked; - } - mainOption.isChecked = - mainOption.subOptions.every((sub) => sub.isChecked); - return; - } - - for (var child in subOption.subOptions) { - if (child.id == id) { - child.isChecked = !child.isChecked; - subOption.isChecked = - subOption.subOptions.every((child) => child.isChecked); - mainOption.isChecked = - mainOption.subOptions.every((sub) => sub.isChecked); - return; - } - } - } - } - }); - } - - CheckState checkifOneOfthemChecked(PermissionOption mainOption) { - bool allSelected = true; - bool someSelected = false; - - for (var subOption in mainOption.subOptions) { - if (subOption.isChecked) { - someSelected = true; - } else { - allSelected = false; - } - - for (var child in subOption.subOptions) { - if (child.isChecked) { - someSelected = true; - } else { - allSelected = false; - } - } - } - - if (allSelected) { - return CheckState.all; - } else if (someSelected) { - return CheckState.some; - } else { - return CheckState.none; - } - } - - @override - Widget build(BuildContext context) { - return ListView.builder( - padding: const EdgeInsets.all(8), - itemCount: widget.bloc!.permissions.length, - itemBuilder: (context, index) { - final option = widget.bloc!.permissions[index]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - InkWell( - // onTap: () => toggleOptionById(option.id), - child: Builder( - builder: (context) { - final checkState = checkifOneOfthemChecked(option); - - if (checkState == CheckState.all) { - return Image.asset( - Assets.CheckBoxChecked, - width: 20, - height: 20, - ); - } else if (checkState == CheckState.some) { - return Image.asset( - Assets.rectangleCheckBox, - width: 20, - height: 20, - ); - } else { - return Image.asset( - Assets.emptyBox, - width: 20, - height: 20, - ); - } - }, - ), - ), - const SizedBox(width: 8), - Text( - option.title, - style: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w700, - fontSize: 12, - color: ColorsManager.blackColor), - ), - ], - ), - const SizedBox( - height: 10, - ), - ...option.subOptions.map((subOption) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - color: option.isHighlighted - ? Colors.blue.shade50 - : Colors.white, - child: Row( - children: [ - InkWell( - // onTap: () => toggleOptionById(subOption.id), - child: Builder( - builder: (context) { - final checkState = - checkifOneOfthemChecked(PermissionOption( - id: subOption.id, - title: subOption.title, - subOptions: [subOption], - )); - - if (checkState == CheckState.all) { - return Image.asset( - Assets.CheckBoxChecked, - width: 20, - height: 20, - ); - } else if (checkState == CheckState.some) { - return Image.asset( - Assets.rectangleCheckBox, - width: 20, - height: 20, - ); - } else { - return Image.asset( - Assets.emptyBox, - width: 20, - height: 20, - ); - } - }, - ), - ), - const SizedBox(width: 8), - Text( - subOption.title, - style: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w700, - fontSize: 12, - color: ColorsManager.lightGreyColor), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.only(left: 50.0), - child: GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - mainAxisSpacing: 2.0, - crossAxisSpacing: 0.2, - childAspectRatio: 5, - ), - itemCount: subOption.subOptions.length, - itemBuilder: (context, index) { - final child = subOption.subOptions[index]; - return CheckboxListTile( - selectedTileColor: child.isHighlighted - ? Colors.blue.shade50 - : Colors.white, - dense: true, - controlAffinity: ListTileControlAffinity.leading, - title: Text( - child.title, - style: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w400, - fontSize: 12, - color: ColorsManager.lightGreyColor), - ), - value: child.isChecked, - onChanged: (value) => toggleOptionById(child.id), - enabled: false, - ); - }, - ), - ) - ], - ); - }).toList(), - ], - ); - }, - ); - } -} - - -enum CheckState { none, some, all } - -class PermissionOption { - String id; - String title; - bool isChecked; - bool isHighlighted; - List subOptions; - - PermissionOption({ - required this.id, - required this.title, - this.isChecked = false, - this.isHighlighted = false, - this.subOptions = const [], - }); - - factory PermissionOption.fromJson(Map json) { - return PermissionOption( - id: json['id'] ?? '', - title: json['title'] ?? '', - isChecked: json['isChecked'] ?? false, - isHighlighted: json['isHighlighted'] ?? false, - subOptions: (json['subOptions'] as List?) - ?.map((sub) => PermissionOption.fromJson(sub)) - .toList() ?? - [], - ); - } - - Map toJson() { - return { - 'id': id, - 'title': title, - 'isChecked': isChecked, - 'isHighlighted': isHighlighted, - 'subOptions': subOptions.map((sub) => sub.toJson()).toList(), - }; - } -} diff --git a/lib/pages/roles_and_permission/users_page/view/user_table.dart b/lib/pages/roles_and_permission/users_page/view/user_table.dart deleted file mode 100644 index f951f06e..00000000 --- a/lib/pages/roles_and_permission/users_page/view/user_table.dart +++ /dev/null @@ -1,256 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; - -class DynamicTableScreen extends StatefulWidget { - final List titles; - final List> rows; - - DynamicTableScreen({required this.titles, required this.rows}); - - @override - _DynamicTableScreenState createState() => _DynamicTableScreenState(); -} - -class _DynamicTableScreenState extends State - with WidgetsBindingObserver { - late List columnWidths; - - // @override - // void initState() { - // super.initState(); - // // Initialize column widths with default sizes proportional to the screen width - // // Assigning placeholder values here. The actual sizes will be updated in `build`. - // } - @override - void initState() { - super.initState(); - columnWidths = List.filled(widget.titles.length, 150.0); - - WidgetsBinding.instance.addObserver(this); - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - - @override - void didChangeMetrics() { - super.didChangeMetrics(); - // Screen size might have changed - final newScreenWidth = MediaQuery.of(context).size.width; - setState(() { - columnWidths = List.generate(widget.titles.length, (index) { - if (index == 1) { - return newScreenWidth * - 0.12; // 20% of screen width for the second column - } else if (index == 9) { - return newScreenWidth * - 0.2; // 25% of screen width for the tenth column - } - return newScreenWidth * - 0.09; // Default to 10% of screen width for other columns - }); - }); - } - - @override - Widget build(BuildContext context) { - final screenWidth = MediaQuery.of(context).size.width; - - // Initialize column widths if they are still set to placeholder values - if (columnWidths.every((width) => width == 150.0)) { - columnWidths = List.generate(widget.titles.length, (index) { - if (index == 1) { - return screenWidth * - 0.12; // 20% of screen width for the second column - } else if (index == 9) { - return screenWidth * 0.2; // 25% of screen width for the tenth column - } - return screenWidth * - 0.09; // Default to 10% of screen width for other columns - }); - setState(() {}); - } - return SizedBox( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: FittedBox( - child: Column( - children: [ - // Header Row with Resizable Columns - Container( - color: ColorsManager.CircleRolesBackground, - child: Row( - children: List.generate(widget.titles.length, (index) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - FittedBox( - child: Container( - padding: const EdgeInsets.only(left: 10, right: 10), - width: columnWidths[index], - child: Text( - widget.titles[index], - style: const TextStyle( - fontWeight: FontWeight.w400, - fontSize: 13, - color: ColorsManager.grayColor, - ), - ), - ), - ), - GestureDetector( - onHorizontalDragUpdate: (details) { - setState(() { - columnWidths[index] = (columnWidths[index] + - details.delta.dx) - .clamp( - 150.0, 300.0); // Minimum & Maximum size - }); - }, - child: MouseRegion( - cursor: SystemMouseCursors - .resizeColumn, // Set the cursor to resize - child: Container( - color: Colors.green, - child: Container( - color: ColorsManager.boxDivider, - width: 1, - height: 50, // Height of the header cell - ), - ), - ), - ), - ], - ); - }), - ), - ), - // Data Rows with Dividers - Container( - color: ColorsManager.whiteColors, - child: Column( - children: widget.rows.map((row) { - int rowIndex = widget.rows.indexOf(row); - return Column( - children: [ - Container( - child: Padding( - padding: const EdgeInsets.only( - left: 5, top: 10, right: 5, bottom: 10), - child: Row( - children: List.generate(row.length, (index) { - return SizedBox( - width: columnWidths[index], - child: SizedBox( - child: Padding( - padding: const EdgeInsets.only( - left: 15, right: 10), - child: row[index], - ), - ), - ); - }), - ), - ), - ), - if (rowIndex < widget.rows.length - 1) - Row( - children: - List.generate(widget.titles.length, (index) { - return SizedBox( - width: columnWidths[index], - child: const Divider( - color: ColorsManager.boxDivider, - thickness: 1, - height: 1, - ), - ); - })) - // Add a Divider below each row except the last one - ], - ); - }).toList(), - ), - ), - ], - ), - ), - ), - ); - } -} - - - - // Widget build(BuildContext context) { - // return Scaffold( - // body: SingleChildScrollView( - // scrollDirection: Axis.horizontal, - // child: SingleChildScrollView( - // scrollDirection: Axis.vertical, - // child: Column( - // children: [ - // // Header Row with Resizable Columns - // Container( - // color: Colors.green, - // child: Row( - // children: List.generate(widget.titles.length, (index) { - // return Row( - // children: [ - // Container( - // width: columnWidths[index], - // decoration: const BoxDecoration( - // color: Colors.green, - // ), - // child: Text( - // widget.titles[index], - // style: TextStyle(fontWeight: FontWeight.bold), - // textAlign: TextAlign.center, - // ), - // ), - // GestureDetector( - // onHorizontalDragUpdate: (details) { - // setState(() { - // columnWidths[index] = (columnWidths[index] + - // details.delta.dx) - // .clamp(50.0, 300.0); // Minimum & Maximum size - // }); - // }, - // child: MouseRegion( - // cursor: SystemMouseCursors - // .resizeColumn, // Set the cursor to resize - // child: Container( - // color: Colors.green, - // child: Container( - // color: Colors.black, - // width: 1, - // height: 50, // Height of the header cell - // ), - // ), - // ), - // ), - // ], - // ); - // }), - // ), - // ), - // // Data Rows - // ...widget.rows.map((row) { - // return Row( - // children: List.generate(row.length, (index) { - // return Container( - // width: columnWidths[index], - // child: row[index], - // ); - // }), - // ); - // }).toList(), - // ], - // ), - // ), - // ), - // ); - // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart index 05f924ca..da003536 100644 --- a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart +++ b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart @@ -2,11 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_state.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/view/users_page.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/users_page.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; @@ -17,8 +16,7 @@ class RolesAndPermissionPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => - RolesPermissionBloc()..add(const GetRoles()), + create: (BuildContext context) => RolesPermissionBloc(), child: BlocConsumer( listener: (context, state) {}, builder: (context, state) { @@ -77,8 +75,8 @@ class RolesAndPermissionPage extends StatelessWidget { ), ], ), - scaffoldBody: BlocProvider( - create: (context) => UsersBloc()..add(const GetUsers()), + scaffoldBody: BlocProvider( + create: (context) => UserTableBloc()..add(const GetUsers()), child: const UsersPage(), ) // _blocRole.tapSelect == false diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 58dc0d9d..5d2464e6 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -252,7 +252,6 @@ class CommunitySpaceManagementApi { path: ApiEndpoints.getSpaceHierarchy .replaceAll('{communityId}', communityId), expectedResponseModel: (json) { - print('=-=-=-=$json'); final spaceModels = (json['data'] as List) .map((spaceJson) => SpaceModel.fromJson(spaceJson)) .toList(); diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 22083191..d5cb8243 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -1,5 +1,7 @@ +import 'dart:convert'; +import 'package:dio/dio.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -33,7 +35,7 @@ class UserPermissionApi { return response ?? []; } - Future sendInviteUser({ + Future sendInviteUser({ String? firstName, String? lastName, String? email, @@ -42,22 +44,58 @@ class UserPermissionApi { String? roleUuid, List? spaceUuids, }) async { - final response = await _httpService.post( - path: ApiEndpoints.permission, - showServerMessage: true, - body: { + try { + final body = { "firstName": firstName, "lastName": lastName, "email": email, - "jobTitle": jobTitle, - "phoneNumber": phoneNumber, + "jobTitle": jobTitle != '' ? jobTitle : " ", + "phoneNumber": phoneNumber != '' ? phoneNumber : " ", "roleUuid": roleUuid, - "spaceUuids": spaceUuids - }, - expectedResponseModel: (json) { - print(json); - }, - ); - return response ?? []; + "projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c", + "spaceUuids": spaceUuids, + }; + final response = await _httpService.post( + path: ApiEndpoints.inviteUser, + showServerMessage: true, + body: jsonEncode(body), + expectedResponseModel: (json) { + if (json['statusCode'] != 400) { + return json["success"]; + } else { + return false; + } + }, + ); + + return response ?? []; + } on DioException catch (e) { + return false; + } catch (e) { + return false; + } + } + + Future checkEmail(email) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.checkEmail, + showServerMessage: true, + body: {"email": email}, + expectedResponseModel: (json) { + if (json['statusCode'] != 400) { + return json["message"]; + } + }, + ); + return response ?? []; + } on DioException catch (e) { + if (e.response != null) { + final errorMessage = e.response?.data['error']; + return errorMessage; + } + } catch (e) { + return e.toString(); + } } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 28923538..da746fbd 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -1,6 +1,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; abstract class ApiEndpoints { + static const String projectUuid = "0e62577c-06fa-41b9-8a92-99a21fbaf51c"; static String baseUrl = dotenv.env['BASE_URL'] ?? ''; static const String signUp = '/authentication/user/signup'; static const String login = '/authentication/user/login'; @@ -38,23 +39,32 @@ abstract class ApiEndpoints { static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}'; // Space Module - static const String createSpace = '/communities/{communityId}/spaces'; - static const String listSpaces = '/communities/{communityId}/spaces'; + static const String createSpace = + '/projects/${projectUuid}/communities/{communityId}/spaces'; + static const String listSpaces = + '/projects/${projectUuid}/communities/{communityId}/spaces'; static const String deleteSpace = - '/communities/{communityId}/spaces/{spaceId}'; + '/projects/${projectUuid}/communities/{communityId}/spaces/{spaceId}'; static const String updateSpace = - '/communities/{communityId}/spaces/{spaceId}'; - static const String getSpace = '/communities/{communityId}/spaces/{spaceId}'; - static const String getSpaceHierarchy = '/communities/{communityId}/spaces'; + '/projects/${projectUuid}/communities/{communityId}/spaces/{spaceId}'; + static const String getSpace = + '/projects/${projectUuid}/communities/{communityId}/spaces/{spaceId}'; + static const String getSpaceHierarchy = + '/projects/${projectUuid}/communities/{communityId}/spaces'; // Community Module - static const String createCommunity = '/communities'; - static const String getCommunityList = '/communities'; - static const String getCommunityById = '/communities/{communityId}'; - static const String updateCommunity = '/communities/{communityId}'; - static const String deleteCommunity = '/communities/{communityId}'; - static const String getUserCommunities = '/communities/user/{userUuid}'; - static const String createUserCommunity = '/communities/user'; + static const String createCommunity = '/projects/${projectUuid}/communities'; + static const String getCommunityList = '/projects/${projectUuid}/communities'; + static const String getCommunityById = + '/projects/${projectUuid}/communities/{communityId}'; + static const String updateCommunity = + '/projects/${projectUuid}/communities/{communityId}'; + static const String deleteCommunity = + '/projects/${projectUuid}/communities/{communityId}'; + static const String getUserCommunities = + '/projects/${projectUuid}/communities/user/{userUuid}'; + static const String createUserCommunity = + '/projects/${projectUuid}/communities/user'; static const String getDeviceLogsByDate = '/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; @@ -90,5 +100,7 @@ abstract class ApiEndpoints { static const String roleTypes = '/role/types'; static const String permission = '/permission/{roleUuid}'; static const String inviteUser = '/invite-user'; + static const String checkEmail = '/invite-user/check-email'; // static const String updateAutomation = '/automation/{automationId}'; + // https://syncrow-dev.azurewebsites.net/invite-user/check-email } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 8b271d5e..1f4074c4 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -394,5 +394,8 @@ class Assets { static const String arrowDown = 'assets/icons/arrow_down.svg'; static const String userManagement = 'assets/icons/user_management.svg'; + static const String filterTableIcon = 'assets/icons/filter_table_icon.svg'; + static const String ZtoAIcon = 'assets/icons/ztoa_icon.png'; + static const String AtoZIcon = 'assets/icons/atoz_icon.png'; } //user_management.svg From c834ad0670875d3d23b817498fbe0e8e89825614 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 29 Dec 2024 09:52:13 +0300 Subject: [PATCH 09/17] add_new_user_dialog --- .../add_user_dialog/bloc/users_bloc.dart | 1 - .../view/permission_management.dart | 82 ++++++++++++++++--- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index f0ddf01e..dbace8ad 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -199,7 +199,6 @@ class UsersBloc extends Bloc { node.title.toLowerCase().contains(searchTerm.toLowerCase()); bool childMatch = _searchRolePermission(node.subOptions, searchTerm); node.isHighlighted = isMatch || childMatch; - anyMatch = anyMatch || node.isHighlighted; } return anyMatch; diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart index b16cd8d2..266d431e 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart @@ -208,22 +208,52 @@ class _PermissionManagementState extends State { itemCount: subOption.subOptions.length, itemBuilder: (context, index) { final child = subOption.subOptions[index]; - return CheckboxListTile( - selectedTileColor: child.isHighlighted + return Container( + color: option.isHighlighted ? Colors.blue.shade50 : Colors.white, - dense: true, - controlAffinity: ListTileControlAffinity.leading, - title: Text( - child.title, - style: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w400, - fontSize: 12, - color: ColorsManager.lightGreyColor), + child: Row( + children: [ + Builder( + builder: (context) { + final checkState = + checkifOneOfthemChecked(PermissionOption( + id: child.id, + title: child.title, + subOptions: [child], + )); + + if (checkState == CheckState.all) { + return Image.asset( + Assets.CheckBoxChecked, + width: 20, + height: 20, + ); + } else if (checkState == CheckState.some) { + return Image.asset( + Assets.rectangleCheckBox, + width: 20, + height: 20, + ); + } else { + return Image.asset( + Assets.emptyBox, + width: 20, + height: 20, + ); + } + }, + ), + const SizedBox(width: 8), + Text( + child.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.lightGreyColor), + ), + ], ), - value: child.isChecked, - onChanged: (value) => toggleOptionById(child.id), - enabled: false, ); }, ), @@ -237,3 +267,29 @@ class _PermissionManagementState extends State { ); } } + + + // Container( + // height: 50, + // width: 120, + // child: CheckboxListTile( + // activeColor: ColorsManager.dialogBlueTitle, + // selectedTileColor: child.isHighlighted + // ? Colors.blue.shade50 + // : Colors.white, + // dense: true, + // controlAffinity: + // ListTileControlAffinity.leading, + // title: Text( + // child.title, + // style: context.textTheme.bodyMedium?.copyWith( + // fontWeight: FontWeight.w400, + // fontSize: 12, + // color: ColorsManager.lightGreyColor), + // ), + // value: child.isChecked, + // onChanged: (value) => + // toggleOptionById(child.id), + // enabled: false, + // ), + // ), \ No newline at end of file From 8f7b46be251c160b6bf99914d23d7255ba60754e Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 29 Dec 2024 11:22:11 +0300 Subject: [PATCH 10/17] fixes checkEmail error --- lib/services/user_permission.dart | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index d5cb8243..91e45073 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -76,7 +76,9 @@ class UserPermissionApi { } } - Future checkEmail(email) async { + + + Future checkEmail(String email) async { try { final response = await _httpService.post( path: ApiEndpoints.checkEmail, @@ -84,16 +86,26 @@ class UserPermissionApi { body: {"email": email}, expectedResponseModel: (json) { if (json['statusCode'] != 400) { - return json["message"]; + var message = json["message"]; + if (message is String) { + return message; + } else { + return 'Unexpected message format'; + } } + return null; }, ); - return response ?? []; + return response ?? 'Unknown error occurred'; } on DioException catch (e) { if (e.response != null) { + print(e.response?.data['error']); final errorMessage = e.response?.data['error']; - return errorMessage; + return errorMessage is String + ? errorMessage + : 'Error occurred while checking email'; } + return 'Error occurred while checking email'; } catch (e) { return e.toString(); } From 9e5cbc285ebd5804382aa5134bbe1c80ea8fb674 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 29 Dec 2024 11:22:45 +0300 Subject: [PATCH 11/17] checkEmail --- lib/services/user_permission.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 91e45073..0e31795a 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -76,8 +76,6 @@ class UserPermissionApi { } } - - Future checkEmail(String email) async { try { final response = await _httpService.post( @@ -99,7 +97,6 @@ class UserPermissionApi { return response ?? 'Unknown error occurred'; } on DioException catch (e) { if (e.response != null) { - print(e.response?.data['error']); final errorMessage = e.response?.data['error']; return errorMessage is String ? errorMessage From b264f6042fb1b2feaff785d9cb0ba5124727727f Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 31 Dec 2024 11:20:32 +0300 Subject: [PATCH 12/17] remove package and change color name --- .../view/roles_and_permission.dart | 4 +-- .../view/spaces_access_view.dart | 4 +-- .../users_table/view/user_table.dart | 2 +- .../roles_and_permission/view/role_card.dart | 2 +- lib/pages/routiens/widgets/dragable_card.dart | 2 +- lib/utils/color_manager.dart | 4 +-- pubspec.lock | 32 ------------------- pubspec.yaml | 1 - 8 files changed, 9 insertions(+), 42 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart index b054a88c..0bc16a94 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart @@ -52,7 +52,7 @@ class RolesAndPermission extends StatelessWidget { flex: 2, child: Container( decoration: const BoxDecoration( - color: ColorsManager.CircleRolesBackground, + color: ColorsManager.circleRolesBackground, borderRadius: BorderRadius.only( topRight: Radius.circular(20), topLeft: Radius.circular(20)), @@ -107,7 +107,7 @@ class RolesAndPermission extends StatelessWidget { Expanded( flex: 7, child: Container( - color: ColorsManager.CircleRolesBackground, + color: ColorsManager.circleRolesBackground, padding: const EdgeInsets.all(8.0), child: Container( color: ColorsManager.whiteColors, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart index f942d1d5..081fab40 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart @@ -51,7 +51,7 @@ class SpacesAccessView extends StatelessWidget { flex: 2, child: Container( decoration: const BoxDecoration( - color: ColorsManager.CircleRolesBackground, + color: ColorsManager.circleRolesBackground, borderRadius: BorderRadius.only( topRight: Radius.circular(20), topLeft: Radius.circular(20)), @@ -105,7 +105,7 @@ class SpacesAccessView extends StatelessWidget { Expanded( flex: 7, child: Container( - color: ColorsManager.CircleRolesBackground, + color: ColorsManager.circleRolesBackground, padding: const EdgeInsets.all(8.0), child: Container( color: ColorsManager.whiteColors, diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart index 926ac92a..f42c0c03 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -88,7 +88,7 @@ class _DynamicTableScreenState extends State // Header Row with Resizable Columns Container( decoration: containerDecoration.copyWith( - color: ColorsManager.CircleRolesBackground, + color: ColorsManager.circleRolesBackground, borderRadius: BorderRadius.only( topLeft: Radius.circular(15), topRight: Radius.circular(15))), diff --git a/lib/pages/roles_and_permission/view/role_card.dart b/lib/pages/roles_and_permission/view/role_card.dart index b3f59ee9..b08b14ea 100644 --- a/lib/pages/roles_and_permission/view/role_card.dart +++ b/lib/pages/roles_and_permission/view/role_card.dart @@ -32,7 +32,7 @@ class RoleCard extends StatelessWidget { backgroundColor: ColorsManager.neutralGray, radius: 65, child: CircleAvatar( - backgroundColor: ColorsManager.CircleRolesBackground, + backgroundColor: ColorsManager.circleRolesBackground, radius: 62, child: Icon( Icons.admin_panel_settings, diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index e26d3d12..dd41e1a2 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -86,7 +86,7 @@ class DraggableCard extends StatelessWidget { height: 50, width: 50, decoration: BoxDecoration( - color: ColorsManager.CircleImageBackground, + color: ColorsManager.circleImageBackground, borderRadius: BorderRadius.circular(90), border: Border.all( color: ColorsManager.graysColor, diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 5549b566..91bfe98c 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -54,8 +54,8 @@ abstract class ColorsManager { static const Color neutralGray = Color(0xFFE5E5E5); static const Color warningRed = Color(0xFFFF6465); static const Color borderColor = Color(0xFFE5E5E5); - static const Color CircleImageBackground = Color(0xFFF4F4F4); - static const Color CircleRolesBackground = Color(0xFFF8F8F8); + static const Color circleImageBackground = Color(0xFFF4F4F4); + static const Color circleRolesBackground = Color(0xFFF8F8F8); static const Color activeGreen = Color(0xFF99FF93); static const Color activeGreenText = Color(0xFF008905); static const Color disabledPink = Color(0xFFFF9395); diff --git a/pubspec.lock b/pubspec.lock index 98c62a94..86d132c3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -312,14 +312,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" - intl_phone_number_input: - dependency: "direct main" - description: - name: intl_phone_number_input - sha256: "1c4328713a9503ab26a1fdbb6b00b4cada68c18aac922b35bedbc72eff1297c3" - url: "https://pub.dev" - source: hosted - version: "0.7.4" js: dependency: transitive description: @@ -352,30 +344,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" - libphonenumber_platform_interface: - dependency: transitive - description: - name: libphonenumber_platform_interface - sha256: f801f6c65523f56504b83f0890e6dad584ab3a7507dca65fec0eed640afea40f - url: "https://pub.dev" - source: hosted - version: "0.4.2" - libphonenumber_plugin: - dependency: transitive - description: - name: libphonenumber_plugin - sha256: c615021d9816fbda2b2587881019ed595ecdf54d999652d7e4cce0e1f026368c - url: "https://pub.dev" - source: hosted - version: "0.3.3" - libphonenumber_web: - dependency: transitive - description: - name: libphonenumber_web - sha256: "8186f420dbe97c3132283e52819daff1e55d60d6db46f7ea5ac42f42a28cc2ef" - url: "https://pub.dev" - source: hosted - version: "0.3.2" lints: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c99481d1..6951987c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,7 +53,6 @@ dependencies: uuid: ^4.4.2 time_picker_spinner: ^1.0.0 intl_phone_field: ^3.2.0 - intl_phone_number_input: ^0.7.4 dev_dependencies: flutter_test: From 3876909beade9c20ef85842367554c1e8547256b Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 5 Jan 2025 17:27:26 +0300 Subject: [PATCH 13/17] edit_user and pagination and search and filter --- .../model/edit_user_model.dart | 267 +++++++++++++ .../model/roles_user_model.dart | 68 +++- .../add_user_dialog/bloc/users_bloc.dart | 232 ++++++++++- .../add_user_dialog/bloc/users_event.dart | 90 ++++- .../add_user_dialog/bloc/users_status.dart | 8 + .../add_user_dialog/view/add_user_dialog.dart | 15 +- .../add_user_dialog/view/basics_view.dart | 8 +- .../add_user_dialog/view/build_tree_view.dart | 146 +++++++ .../view/delete_user_dialog.dart | 129 +++++-- .../view/edit_user_dialog.dart | 364 ++++++++++++++++++ .../view/popup_menu_filter.dart | 108 ++++++ .../view/spaces_access_view.dart | 161 +------- .../users_table/bloc/user_table_bloc.dart | 259 +++++++++---- .../users_table/bloc/user_table_event.dart | 62 ++- .../users_table/view/user_table.dart | 136 ++++--- .../users_table/view/users_page.dart | 245 ++++++++++-- .../view/roles_and_permission_page.dart | 2 +- lib/services/space_mana_api.dart | 1 + lib/services/user_permission.dart | 114 ++++++ lib/utils/constants/api_const.dart | 8 +- macos/Podfile.lock | 36 ++ macos/Runner.xcodeproj/project.pbxproj | 98 ++++- .../contents.xcworkspacedata | 3 + pubspec.lock | 8 + pubspec.yaml | 1 + 25 files changed, 2156 insertions(+), 413 deletions(-) create mode 100644 lib/pages/roles_and_permission/model/edit_user_model.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart create mode 100644 macos/Podfile.lock diff --git a/lib/pages/roles_and_permission/model/edit_user_model.dart b/lib/pages/roles_and_permission/model/edit_user_model.dart new file mode 100644 index 00000000..17fba1a4 --- /dev/null +++ b/lib/pages/roles_and_permission/model/edit_user_model.dart @@ -0,0 +1,267 @@ +// import 'dart:convert'; + +// // Model for Space +// class UserSpaceModel { +// final String uuid; +// final DateTime createdAt; +// final DateTime updatedAt; + +// UserSpaceModel({ +// required this.uuid, +// required this.createdAt, +// required this.updatedAt, +// }); + +// factory UserSpaceModel.fromJson(Map json) { +// return UserSpaceModel( +// uuid: json['uuid'], +// createdAt: DateTime.parse(json['createdAt']), +// updatedAt: DateTime.parse(json['updatedAt']), +// ); +// } + +// Map toJson() { +// return { +// 'uuid': uuid, +// 'createdAt': createdAt.toIso8601String(), +// 'updatedAt': updatedAt.toIso8601String(), +// }; +// } +// } + +// // Model for User +// class EditUserModel { +// final String uuid; +// final DateTime createdAt; +// final dynamic email; +// final dynamic? jobTitle; +// final dynamic status; +// final String firstName; +// final String lastName; +// final String? phoneNumber; +// final bool isEnabled; +// final dynamic invitedBy; +// final dynamic roleType; +// final List spaces; +// final String createdDate; +// final String createdTime; + +// EditUserModel({ +// required this.uuid, +// required this.createdAt, +// required this.email, +// this.jobTitle, +// required this.status, +// required this.firstName, +// required this.lastName, +// this.phoneNumber, +// required this.isEnabled, +// required this.invitedBy, +// required this.roleType, +// required this.spaces, +// required this.createdDate, +// required this.createdTime, +// }); + +// factory EditUserModel.fromJson(Map json) { +// var spacesList = (json['spaces'] as List) +// .map((spaceJson) => UserSpaceModel.fromJson(spaceJson)) +// .toList(); + +// return EditUserModel( +// uuid: json['uuid'], +// createdAt: DateTime.parse(json['createdAt']), +// email: json['email'], +// jobTitle: json['jobTitle'], +// status: json['status'], +// firstName: json['firstName'], +// lastName: json['lastName'], +// phoneNumber: json['phoneNumber'], +// isEnabled: json['isEnabled'], +// invitedBy: json['invitedBy'], +// roleType: json['roleType'], +// spaces: spacesList, +// createdDate: json['createdDate'], +// createdTime: json['createdTime'], +// ); +// } + +// Map toJson() { +// return { +// 'uuid': uuid, +// 'createdAt': createdAt.toIso8601String(), +// 'email': email, +// 'jobTitle': jobTitle, +// 'status': status, +// 'firstName': firstName, +// 'lastName': lastName, +// 'phoneNumber': phoneNumber, +// 'isEnabled': isEnabled, +// 'invitedBy': invitedBy, +// 'roleType': roleType, +// 'spaces': spaces.map((space) => space.toJson()).toList(), +// 'createdDate': createdDate, +// 'createdTime': createdTime, +// }; +// } +// } + +class UserProjectResponse { + final int statusCode; + final String message; + final EditUserModel data; + final bool success; + + UserProjectResponse({ + required this.statusCode, + required this.message, + required this.data, + required this.success, + }); + + /// Create a [UserProjectResponse] from JSON data + factory UserProjectResponse.fromJson(Map json) { + return UserProjectResponse( + statusCode: json['statusCode'] as int, + message: json['message'] as String, + data: EditUserModel.fromJson(json['data'] as Map), + success: json['success'] as bool, + ); + } + + /// Convert the [UserProjectResponse] to JSON + Map toJson() { + return { + 'statusCode': statusCode, + 'message': message, + 'data': data.toJson(), + 'success': success, + }; + } +} + +class EditUserModel { + final String uuid; + final String firstName; + final String lastName; + final String email; + final String createdDate; // e.g. "1/3/2025" + final String createdTime; // e.g. "8:41:43 AM" + final String status; // e.g. "invited" + final String invitedBy; // e.g. "SUPER_ADMIN" + final String phoneNumber; // can be empty + final String jobTitle; // can be empty + final String roleType; // e.g. "ADMIN" + final List spaces; + + EditUserModel({ + required this.uuid, + required this.firstName, + required this.lastName, + required this.email, + required this.createdDate, + required this.createdTime, + required this.status, + required this.invitedBy, + required this.phoneNumber, + required this.jobTitle, + required this.roleType, + required this.spaces, + }); + + /// Create a [UserData] from JSON data + factory EditUserModel.fromJson(Map json) { + return EditUserModel( + uuid: json['uuid'] as String, + firstName: json['firstName'] as String, + lastName: json['lastName'] as String, + email: json['email'] as String, + createdDate: json['createdDate'] as String, + createdTime: json['createdTime'] as String, + status: json['status'] as String, + invitedBy: json['invitedBy'] as String, + phoneNumber: json['phoneNumber'] ?? '', + jobTitle: json['jobTitle'] ?? '', + roleType: json['roleType'] as String, + spaces: (json['spaces'] as List) + .map((e) => UserSpaceModel.fromJson(e as Map)) + .toList(), + ); + } + + /// Convert the [UserData] to JSON + Map toJson() { + return { + 'uuid': uuid, + 'firstName': firstName, + 'lastName': lastName, + 'email': email, + 'createdDate': createdDate, + 'createdTime': createdTime, + 'status': status, + 'invitedBy': invitedBy, + 'phoneNumber': phoneNumber, + 'jobTitle': jobTitle, + 'roleType': roleType, + 'spaces': spaces.map((e) => e.toJson()).toList(), + }; + } +} + +class UserSpaceModel { + final String uuid; + final String createdAt; // e.g. "2024-11-04T07:20:35.940Z" + final String updatedAt; // e.g. "2024-11-28T18:47:29.736Z" + final dynamic spaceTuyaUuid; + final dynamic spaceName; + final dynamic invitationCode; + final bool disabled; + final double x; + final double y; + final String icon; + + UserSpaceModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.spaceTuyaUuid, + required this.spaceName, + required this.invitationCode, + required this.disabled, + required this.x, + required this.y, + required this.icon, + }); + + /// Create a [UserSpaceModel] from JSON data + factory UserSpaceModel.fromJson(Map json) { + return UserSpaceModel( + uuid: json['uuid'] as String, + createdAt: json['createdAt'] as String, + updatedAt: json['updatedAt'] as String, + spaceTuyaUuid: json['spaceTuyaUuid'] as String?, + spaceName: json['spaceName'] as String, + invitationCode: json['invitationCode'] as String?, + disabled: json['disabled'] as bool, + x: (json['x'] as num).toDouble(), + y: (json['y'] as num).toDouble(), + icon: json['icon'] as String, + ); + } + + /// Convert the [UserSpaceModel] to JSON + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt, + 'updatedAt': updatedAt, + 'spaceTuyaUuid': spaceTuyaUuid, + 'spaceName': spaceName, + 'invitationCode': invitationCode, + 'disabled': disabled, + 'x': x, + 'y': y, + 'icon': icon, + }; + } +} diff --git a/lib/pages/roles_and_permission/model/roles_user_model.dart b/lib/pages/roles_and_permission/model/roles_user_model.dart index 244c58de..6298dbe6 100644 --- a/lib/pages/roles_and_permission/model/roles_user_model.dart +++ b/lib/pages/roles_and_permission/model/roles_user_model.dart @@ -1,22 +1,50 @@ class RolesUserModel { - String? id; - String? userName; - String? userEmail; - String? userRole; - String? creationDate; - String? creationTime; - String? createdBy; - String? status; - String? action; - RolesUserModel( - {this.id, - this.userName, - this.userEmail, - this.userRole, - this.creationDate, - this.creationTime, - this.status, - this.action, - this.createdBy, - }); + final String uuid; + final DateTime createdAt; + final String email; + final dynamic firstName; + final dynamic lastName; + final dynamic roleType; + final dynamic status; + final bool isEnabled; + final String invitedBy; + final dynamic phoneNumber; + final dynamic jobTitle; + final dynamic createdDate; + final dynamic createdTime; + + RolesUserModel({ + required this.uuid, + required this.createdAt, + required this.email, + required this.firstName, + required this.lastName, + required this.roleType, + required this.status, + required this.isEnabled, + required this.invitedBy, + this.phoneNumber, + this.jobTitle, + required this.createdDate, + required this.createdTime, + }); + + factory RolesUserModel.fromJson(Map json) { + return RolesUserModel( + uuid: json['uuid'], + createdAt: DateTime.parse(json['createdAt']), + email: json['email'], + firstName: json['firstName'], + lastName: json['lastName'], + roleType: json['roleType'].toString().toLowerCase().replaceAll("_", " "), + status: json['status'], + isEnabled: json['isEnabled'], + invitedBy: + json['invitedBy'].toString().toLowerCase().replaceAll("_", " "), + phoneNumber: json['phoneNumber'], + jobTitle: json['jobTitle'].toString(), + createdDate: json['createdDate'], + createdTime: json['createdTime'], + ); + } } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index dbace8ad..730825cb 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -1,6 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/common/custom_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/edit_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; @@ -25,6 +26,11 @@ class UsersBloc extends Bloc { on(_validateBasicsStep); on(isCompleteRoleFun); on(checkEmail); + on(getUserById); + on(_onToggleNodeExpansion); + on(_onToggleNodeCheck); + on(_editInvitUser); + } void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { if (formKey.currentState?.validate() ?? false) { @@ -42,9 +48,7 @@ class UsersBloc extends Bloc { final TextEditingController emailController = TextEditingController(); final TextEditingController phoneController = TextEditingController(); final TextEditingController jobTitleController = TextEditingController(); - final TextEditingController roleSearchController = TextEditingController(); - // final TextEditingController jobTitleController = TextEditingController(); bool? isCompleteBasics; bool? isCompleteRolePermissions; @@ -84,6 +88,7 @@ class UsersBloc extends Bloc { await CommunitySpaceManagementApi().fetchCommunities(); updatedCommunities = await Future.wait( communities.map((community) async { + print(community.uuid); List spaces = await _fetchSpacesForCommunity(community.uuid); spacesNodes = _buildTreeNodes(spaces); @@ -228,8 +233,8 @@ class UsersBloc extends Bloc { actions: [ TextButton( onPressed: () { - Navigator.of(event.context).pop(); - Navigator.of(event.context).pop(); + Navigator.of(event.context).pop(true); + Navigator.of(event.context).pop(true); }, child: const Text('OK'), ), @@ -244,6 +249,48 @@ class UsersBloc extends Bloc { } } + _editInvitUser(EditInviteUsers event, Emitter emit) async { + try { + emit(UsersLoadingState()); + List selectedIds = getSelectedIds(updatedCommunities) ?? []; + bool res = await UserPermissionApi().editInviteUser( + userId: event.userId, + firstName: firstNameController.text, + jobTitle: jobTitleController.text, + lastName: lastNameController.text, + phoneNumber: phoneController.text, + roleUuid: roleSelected, + spaceUuids: selectedIds, + ); + if (res == true) { + showCustomDialog( + barrierDismissible: false, + context: event.context, + message: "The invite was sent successfully.", + iconPath: Assets.deviceNoteIcon, + title: "Invite Success", + dialogHeight: MediaQuery.of(event.context).size.height * 0.3, + actions: [ + TextButton( + onPressed: () { + Navigator.of(event.context).pop(true); + Navigator.of(event.context).pop(true); + }, + child: const Text('OK'), + ), + ], + ).then( + (value) {}, + ); + } else { + emit(const ErrorState('Failed to send invite.')); + } + emit(SaveState()); + } catch (e) { + emit(ErrorState('Failed to send invite: ${e.toString()}')); + } + } + void searchRolePermission(SearchPermission event, Emitter emit) { emit(UsersLoadingState()); if (event.searchTerm!.isEmpty) { @@ -268,18 +315,22 @@ class UsersBloc extends Bloc { bool isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { emit(UsersLoadingState()); - add(const CheckEmailEvent()); - final emailRegex = RegExp( - r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', - ); - bool isEmailValid = emailRegex.hasMatch(emailController.text); - bool isEmailServerValid = checkEmailValid == 'Valid email'; - isCompleteBasics = firstNameController.text.isNotEmpty && - lastNameController.text.isNotEmpty && - emailController.text.isNotEmpty && - isEmailValid && - isEmailServerValid; - + if (event.isEditUser == false) { + add(const CheckEmailEvent()); + final emailRegex = RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + ); + bool isEmailValid = emailRegex.hasMatch(emailController.text); + bool isEmailServerValid = checkEmailValid == 'Valid email'; + isCompleteBasics = firstNameController.text.isNotEmpty && + lastNameController.text.isNotEmpty && + emailController.text.isNotEmpty && + isEmailValid && + isEmailServerValid; + } else { + isCompleteBasics = firstNameController.text.isNotEmpty && + lastNameController.text.isNotEmpty; + } emit(ChangeStatusSteps()); emit(ValidateBasics()); return isCompleteBasics!; @@ -293,4 +344,153 @@ class UsersBloc extends Bloc { } } } + + EditUserModel? res = EditUserModel( + spaces: [], + jobTitle: '', + phoneNumber: '', + uuid: '', + email: '', + firstName: '', + lastName: '', + roleType: '', + status: '', + invitedBy: '', + createdDate: '', + createdTime: ''); + + Future getUserById( + GetUserByIdEvent event, + Emitter emit, + ) async { + emit(UsersLoadingState()); + + try { + if (event.uuid?.isNotEmpty ?? false) { + final res = await UserPermissionApi().fetchUserById(event.uuid); + + if (res != null) { + // Populate the text controllers + firstNameController.text = res.firstName; + lastNameController.text = res.lastName; + emailController.text = res.email; + phoneController.text = res.phoneNumber ?? ''; + jobTitleController.text = res.jobTitle ?? ''; + res.roleType; + if (updatedCommunities.isNotEmpty) { + // Create a list of UUIDs to mark + final uuidsToMark = res.spaces.map((space) => space.uuid).toList(); + // Print all IDs and mark nodes in updatedCommunities + print('Printing and marking nodes in updatedCommunities:'); + _printAndMarkNodes(updatedCommunities, uuidsToMark); + } else { + print('updatedCommunities is empty!'); + } + final roleId = roles + .firstWhere((element) => + element.type == + res.roleType.toString().toLowerCase().replaceAll("_", " ")) + .uuid; + print('Role ID: $roleId'); + roleSelected = roleId; + add(PermissionEvent(roleUuid: roleSelected)); + emit(ChangeStatusSteps()); + } else { + // emit(UsersErrorState("User not found")); + } + } else { + // emit(UsersErrorState("Invalid user ID")); + } + } catch (e) { + print("Failed to fetch user data: $e"); + // emit(UsersErrorState("Failed to fetch user data: $e")); + } + } + + /// Recursively print all the node IDs, including nested children. + /// Recursively print all node IDs and mark nodes as `isChecked` if their UUID exists in the list. + void _printAndMarkNodes(List nodes, List uuidsToMark, + [int level = 0]) { + for (final node in nodes) { + // Check if the current node's UUID exists in the list of UUIDs to mark. + if (uuidsToMark.contains(node.uuid)) { + node.isChecked = true; // Mark the node as checked. + print( + '${' ' * level}MATCH FOUND: Node ID: ${node.uuid}, Title: ${node.title} is marked as checked.'); + } else { + print('${' ' * level}Node ID: ${node.uuid}, Title: ${node.title}'); + } + if (node.children.isNotEmpty) { + _printAndMarkNodes(node.children, uuidsToMark, level + 1); + } + } + } + + void _onToggleNodeExpansion( + ToggleNodeExpansion event, + Emitter emit, + ) { + emit(UsersLoadingState()); + event.node.isExpanded = !event.node.isExpanded; + emit(ChangeStatusSteps()); + } + + void _onToggleNodeCheck( + ToggleNodeCheck event, + Emitter emit, + ) { + emit(UsersLoadingState()); + //Toggle node's checked state + event.node.isChecked = !event.node.isChecked; + debugPrint( + 'Node toggled. ID: ${event.node.uuid}, isChecked: ${event.node.isChecked}', + ); + // Update children and parent + _updateChildrenCheckStatus(event.node, event.node.isChecked); + _updateParentCheckStatus(event.node); + + // Finally, emit a new state + emit(ChangeStatusSteps()); + } + +// Existing methods that remain in the BLoC: + + void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { + for (var child in node.children) { + child.isChecked = isChecked; + _updateChildrenCheckStatus(child, isChecked); + } + } + + void _updateParentCheckStatus(TreeNode node) { + TreeNode? parent = _findParent(updatedCommunities, node); + if (parent != null) { + parent.isChecked = _areAllChildrenChecked(parent); + _updateParentCheckStatus(parent); + } + } + + bool _areAllChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.every((child) => + child.isChecked && + (child.children.isEmpty || _areAllChildrenChecked(child))); + } + +// Private helper method to find the parent of a given node. + TreeNode? _findParent(List nodes, TreeNode target) { + for (var node in nodes) { + if (node.children.contains(target)) { + return node; + } + final parent = _findParent(node.children, target); + if (parent != null) { + return parent; + } + } + return null; + } + + + } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart index 950726d4..0f4631ff 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart @@ -14,6 +14,14 @@ class SendInviteUsers extends UsersEvent { List get props => [context]; } +class EditInviteUsers extends UsersEvent { + final BuildContext context; + final String userId; + const EditInviteUsers({required this.context, required this.userId}); + @override + List get props => [context, userId]; +} + class CheckSpacesStepStatus extends UsersEvent { const CheckSpacesStepStatus(); @override @@ -52,9 +60,11 @@ class GetBatchStatus extends UsersEvent { List get props => [uuids]; } +//isEditUser:widget.userId!=''? false:true class CheckStepStatus extends UsersEvent { final int? steps; - const CheckStepStatus({this.steps}); + bool? isEditUser = false; + CheckStepStatus({this.steps, required this.isEditUser}); @override List get props => [steps]; } @@ -85,7 +95,7 @@ class SelecteId extends UsersEvent { } class ValidateBasicsStep extends UsersEvent { - const ValidateBasicsStep(); + ValidateBasicsStep(); @override List get props => []; } @@ -95,3 +105,79 @@ class CheckEmailEvent extends UsersEvent { @override List get props => []; } + +class GetUserByIdEvent extends UsersEvent { + final String? uuid; + const GetUserByIdEvent({this.uuid}); + @override + List get props => [uuid]; +} + +class ToggleNodeExpansion extends UsersEvent { + final TreeNode node; + + ToggleNodeExpansion({required this.node}); + + @override + List get props => [node]; +} + +class UpdateNodeCheckStatus extends UsersEvent { + final TreeNode node; + + UpdateNodeCheckStatus({required this.node}); + @override + List get props => [node]; +} + +// Define new events +class ToggleNodeHighlightEvent extends UsersEvent { + final TreeNode node; + + ToggleNodeHighlightEvent(this.node); + @override + List get props => [node]; +} + +class ExpandAllNodesEvent extends UsersEvent { + @override + List get props => []; +} + +class CollapseAllNodesEvent extends UsersEvent { + @override + List get props => []; +} + +class ClearSelectionsEvent extends UsersEvent { + @override + List get props => []; +} + +class ToggleNodeCheckEvent extends UsersEvent { + final TreeNode node; + + ToggleNodeCheckEvent(this.node); + @override + List get props => []; +} + +// users_event.dart + +// 1. Extend UsersEvent +class ToggleNodeCheck extends UsersEvent { + final TreeNode node; + + // 2. Add a constructor that takes the node to toggle + ToggleNodeCheck(this.node); + @override + List get props => []; +} + +class EditUserEvent extends UsersEvent { + const EditUserEvent(); + @override + List get props => []; +} + + diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart index c1bf3512..646dccfd 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; sealed class UsersState extends Equatable { const UsersState(); @@ -80,3 +81,10 @@ final class ValidateBasics extends UsersState { @override List get props => []; } +class UsersLoadedState extends UsersState { + final List updatedCommunities; + + UsersLoadedState({required this.updatedCommunities}); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart index 14f7a0fb..48df801a 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -113,7 +113,8 @@ class _AddNewUserDialogState extends State { if (currentStep < 3) { currentStep++; if (currentStep == 2) { - _blocRole.add(const CheckStepStatus()); + _blocRole.add( + CheckStepStatus(isEditUser: false)); } else if (currentStep == 3) { _blocRole .add(const CheckSpacesStepStatus()); @@ -150,9 +151,11 @@ class _AddNewUserDialogState extends State { Widget _getFormContent() { switch (currentStep) { case 1: - return const BasicsView(); + return BasicsView( + userId: '', + ); case 2: - return const SpacesAccessView(); + return SpacesAccessView(); case 3: return const RolesAndPermission(); default: @@ -169,7 +172,7 @@ class _AddNewUserDialogState extends State { bloc.add(const CheckSpacesStepStatus()); currentStep = step; Future.delayed(const Duration(milliseconds: 500), () { - bloc.add(const ValidateBasicsStep()); + bloc.add(ValidateBasicsStep()); }); }); @@ -234,7 +237,7 @@ class _AddNewUserDialogState extends State { onTap: () { setState(() { currentStep = step; - bloc.add(const CheckStepStatus()); + bloc.add(CheckStepStatus(isEditUser: false)); if (step3 == 3) { bloc.add(const CheckRoleStepStatus()); } @@ -299,7 +302,7 @@ class _AddNewUserDialogState extends State { currentStep = step; step3 = step; bloc.add(const CheckSpacesStepStatus()); - bloc.add(const CheckStepStatus()); + bloc.add(CheckStepStatus(isEditUser: false)); }); }, child: Column( diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index a6ec686b..bbca9aaa 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -11,7 +11,8 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class BasicsView extends StatelessWidget { - const BasicsView({super.key}); + String? userId = ''; + BasicsView({super.key, this.userId}); @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { @@ -184,10 +185,11 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( + enabled: userId!=''? false:true, onChanged: (value) { Future.delayed(const Duration(milliseconds: 200), () { - _blocRole.add(const CheckStepStatus()); - _blocRole.add(ValidateBasicsStep()); + _blocRole.add(CheckStepStatus(isEditUser:userId!=''? false:true)); + _blocRole.add( ValidateBasicsStep()); }); }, controller: _blocRole.emailController, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart new file mode 100644 index 00000000..b7fc1085 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart @@ -0,0 +1,146 @@ +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/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class TreeView extends StatelessWidget { + final String? userId; + + const TreeView({ + super.key, + this.userId, + }); + + @override + Widget build(BuildContext context) { + final _blocRole = BlocProvider.of(context); + debugPrint('TreeView constructed with userId = $userId'); + return BlocProvider( + create: (_) => UsersBloc(), + // ..add(const LoadCommunityAndSpacesEvent()), + child: BlocConsumer( + listener: (context, state) { + // if (state is SpacesLoadedState) { + // _blocRole.add(GetUserByIdEvent(uuid: userId)); + // } + }, + builder: (context, state) { + if (state is UsersLoadingState) { + return const Center(child: CircularProgressIndicator()); + } + return SingleChildScrollView( + child: _buildTree(_blocRole.updatedCommunities, _blocRole), + ); + }, + ), + ); + } + + Widget _buildTree( + List nodes, + UsersBloc bloc, { + int level = 0, + }) { + return Column( + children: nodes.map((node) { + return Container( + color: node.isHighlighted ? Colors.blue.shade50 : Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + /// Checkbox (GestureDetector) + GestureDetector( + onTap: () { + bloc.add(ToggleNodeCheck(node)); + }, + child: Image.asset( + _getCheckBoxImage(node), + width: 20, + height: 20, + ), + ), + const SizedBox(width: 15), + Expanded( + child: Padding( + padding: EdgeInsets.only(left: level * 10.0), + child: Row( + children: [ + GestureDetector( + onTap: () { + bloc.add(ToggleNodeExpansion(node: node)); + }, + child: node.children.isNotEmpty + ? SvgPicture.asset( + node.isExpanded + ? Assets.arrowDown + : Assets.arrowForward, + fit: BoxFit.none, + ) + : const SizedBox(width: 16), + ), + const SizedBox(width: 20), + Text( + node.title, + style: TextStyle( + fontSize: 16, + color: node.isHighlighted + ? ColorsManager.blackColor + : ColorsManager.textGray, + ), + ), + ], + ), + ), + ), + ], + ), + ), + if (node.isExpanded) + _buildTree( + node.children, + bloc, + level: level + 1, + ), + ], + ), + ); + }).toList(), + ); + } + + String _getCheckBoxImage(TreeNode node) { + if (node.children.isEmpty) { + return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox; + } + if (_areAllChildrenChecked(node)) { + return Assets.CheckBoxChecked; + } else if (_areSomeChildrenChecked(node)) { + return Assets.rectangleCheckBox; + } else { + return Assets.emptyBox; + } + } + + bool _areAllChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.every((child) => + child.isChecked && + (child.children.isEmpty || _areAllChildrenChecked(child))); + } + + bool _areSomeChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.any((child) => + child.isChecked || + (child.children.isNotEmpty && _areSomeChildrenChecked(child))); + } +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart index 002b0171..837ee62c 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class DeleteUserDialog extends StatefulWidget { - const DeleteUserDialog({super.key}); + final Function()? onTapDelete; + DeleteUserDialog({super.key, this.onTapDelete}); @override _DeleteUserDialogState createState() => _DeleteUserDialogState(); @@ -17,47 +15,94 @@ class _DeleteUserDialogState extends State { @override Widget build(BuildContext context) { - return BlocProvider( - create: (BuildContext context) => UsersBloc(), - child: BlocConsumer( - listener: (context, state) {}, - builder: (context, state) { - final _blocRole = BlocProvider.of(context); - - return Dialog( - child: Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(20))), - child: const Column( - children: [ - Padding( - padding: EdgeInsets.all(8.0), - child: SizedBox( - child: Text( - "Delete User", - style: TextStyle( - color: ColorsManager.red, - fontSize: 18, - fontWeight: FontWeight.bold), + return Dialog( + child: Container( + height: 160, + width: 200, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + child: Column( + children: [ + const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Delete User", + style: TextStyle( + color: ColorsManager.red, + fontSize: 18, + fontWeight: FontWeight.bold), + ), + ), + ), + const Padding( + padding: EdgeInsets.only( + left: 25, + right: 25, + ), + child: Divider(), + ), + const Expanded( + child: Padding( + padding: EdgeInsets.only(left: 25, right: 25, top: 10, bottom: 10), + child: Text( + "Are you sure you want to delete this user?", + textAlign: TextAlign.center, + ), + )), + Row( + children: [ + Expanded( + child: InkWell( + onTap: () { + Navigator.of(context).pop(true); + }, + child: Container( + padding: const EdgeInsets.all(10), + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.grayBorder, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.grayBorder, + width: 1, ), ), ), - Divider(), - Expanded( + child: const Center(child: Text('Cancel'))), + )), + Expanded( + child: InkWell( + onTap: widget.onTapDelete, + child: Container( + padding: const EdgeInsets.all(10), + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.grayBorder, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.grayBorder, + width: 1, + ), + ), + ), + child: const Center( child: Text( - "Are you sure you want to delete this user?", - textAlign: TextAlign.center, - )), - Row( - children: [ - Expanded(child: Text('Cancel')), - Expanded(child: Text('Delete')), - ], - ) - ], - ), - )); - })); + 'Delete', + style: TextStyle( + color: ColorsManager.red, + ), + ))), + )), + ], + ) + ], + ), + )); } } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart new file mode 100644 index 00000000..8b6600e0 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart @@ -0,0 +1,364 @@ +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/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class EditUserDialog extends StatefulWidget { + final String? userId; + const EditUserDialog({super.key, this.userId}); + + @override + _EditUserDialogState createState() => _EditUserDialogState(); +} + +class _EditUserDialogState extends State { + int currentStep = 1; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => UsersBloc() + ..add(const LoadCommunityAndSpacesEvent()) + ..add(const RoleEvent()) + ..add(GetUserByIdEvent(uuid: widget.userId)), + child: BlocConsumer(listener: (context, state) { + if (state is SpacesLoadedState) { + BlocProvider.of(context) + .add(GetUserByIdEvent(uuid: widget.userId)); + } + }, builder: (context, state) { + final _blocRole = BlocProvider.of(context); + + return Dialog( + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + width: 900, + child: Column( + children: [ + // Title + const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Edit User", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: ColorsManager.secondaryColor), + ), + ), + ), + const Divider(), + Expanded( + child: Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildStep1Indicator(1, "Basics", _blocRole), + _buildStep2Indicator(2, "Spaces", _blocRole), + _buildStep3Indicator( + 3, "Role & Permissions", _blocRole), + ], + ), + ), + ), + Container( + width: 1, + color: ColorsManager.grayBorder, + ), + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + Expanded( + child: _getFormContent(widget.userId), + ), + const SizedBox(height: 20), + ], + ), + ), + ), + ], + ), + ), + const Divider(), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + InkWell( + onTap: () { + // _blocRole.add(const CheckEmailEvent()); + + setState(() { + if (currentStep < 3) { + currentStep++; + if (currentStep == 2) { + _blocRole + .add(CheckStepStatus(isEditUser: true)); + } else if (currentStep == 3) { + _blocRole.add(const CheckSpacesStepStatus()); + } + } else { + _blocRole.add(EditInviteUsers( + context: context, + userId: widget.userId!)); + } + }); + }, + child: Text( + currentStep < 3 ? "Next" : "Save", + style: TextStyle( + color: (_blocRole.isCompleteSpaces == false || + _blocRole.isCompleteBasics == false || + _blocRole.isCompleteRolePermissions == + false) && + currentStep == 3 + ? ColorsManager.grayColor + : ColorsManager.secondaryColor), + ), + ), + ], + ), + ), + ], + ), + )); + })); + } + + Widget _getFormContent(userid) { + switch (currentStep) { + case 1: + return BasicsView( + userId: userid, + ); + case 2: + return SpacesAccessView( + userId: userid, + ); + case 3: + return const RolesAndPermission(); + default: + return Container(); + } + } + + int step3 = 0; + + Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + bloc.add(const CheckSpacesStepStatus()); + currentStep = step; + Future.delayed(const Duration(milliseconds: 500), () { + bloc.add(ValidateBasicsStep()); + }); + }); + + if (step3 == 3) { + bloc.add(const CheckRoleStepStatus()); + } + }, + 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.isCompleteBasics == false + ? 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 _buildStep2Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + currentStep = step; + bloc.add(CheckStepStatus(isEditUser: true)); + if (step3 == 3) { + bloc.add(const CheckRoleStepStatus()); + } + }); + }, + 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 + : bloc.isCompleteSpaces == 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; + step3 = step; + bloc.add(const CheckSpacesStepStatus()); + bloc.add(CheckStepStatus(isEditUser: true)); + }); + }, + 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 + : bloc.isCompleteRolePermissions == 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, + ), + ), + ) + ], + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart new file mode 100644 index 00000000..02eac036 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +Future showPopUpFilterMenu({ + required BuildContext context, + Function()? onSortAtoZ, + Function()? onSortZtoA, + Function()? cancelButton, + required Map checkboxStates, + Function()? onOkPressed, + List? list, +}) async { + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; + + await showMenu( + context: context, + position: RelativeRect.fromLTRB( + overlay.size.width / 4, + 240, + overlay.size.width / 4, + 0, + ), + color: ColorsManager.whiteColors, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + items: [ + PopupMenuItem( + onTap: onSortAtoZ, + child: ListTile( + leading: Image.asset( + Assets.AtoZIcon, + width: 25, + ), + title: const Text( + "Sort A to Z", + style: TextStyle(color: Colors.blueGrey), + ), + ), + ), + PopupMenuItem( + onTap: onSortZtoA, + child: ListTile( + leading: Image.asset( + Assets.ZtoAIcon, + width: 25, + ), + title: const Text( + "Sort Z to A", + style: TextStyle(color: Colors.blueGrey), + ), + ), + ), + const PopupMenuDivider(), + const PopupMenuItem( + child: Text( + "Filter by Status", + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + PopupMenuItem( + child: SizedBox( + height: 200, + width: 400, + child: ListView.builder( + itemCount: list?.length ?? 0, + itemBuilder: (context, index) { + final item = list![index]; + return CheckboxListTile( + title: Text(item), + value: checkboxStates[item], + onChanged: (bool? newValue) { + checkboxStates[item] = newValue ?? false; + (context as Element).markNeedsBuild(); + }, + ); + }, + ), + ), + ), + PopupMenuItem( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); // Close the menu + }, + child: const Text("Cancel"), + ), + GestureDetector( + onTap: onOkPressed, + child: const Text( + "OK", + style: TextStyle( + color: ColorsManager.spaceColor, + ), + ), + ), + ], + ), + ), + ], + ); +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart index 081fab40..dc2eefc3 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart @@ -4,14 +4,15 @@ import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.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'; import 'package:syncrow_web/utils/style.dart'; class SpacesAccessView extends StatelessWidget { - const SpacesAccessView({super.key}); + String? userId = ''; + SpacesAccessView({super.key, this.userId}); @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; @@ -109,9 +110,7 @@ class SpacesAccessView extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: Container( color: ColorsManager.whiteColors, - child: TreeView( - bloc: _blocRole, - )))) + child: TreeView(userId: userId)))) ], ), ), @@ -123,155 +122,3 @@ class SpacesAccessView extends StatelessWidget { }); } } - -// ignore: must_be_immutable - -class TreeView extends StatefulWidget { - UsersBloc? bloc; - TreeView({super.key, this.bloc}); - @override - _TreeViewState createState() => _TreeViewState(); -} - -class _TreeViewState extends State { - Widget _buildTree(List nodes, {int level = 0}) { - return Column( - children: nodes.map((node) => _buildNode(node, level: level)).toList(), - ); - } - - Widget _buildNode(TreeNode node, {int level = 0}) { - return Container( - color: node.isHighlighted ? Colors.blue.shade50 : Colors.transparent, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(5.0), - child: Row( - children: [ - GestureDetector( - onTap: () { - setState(() { - node.isChecked = !node.isChecked; - _updateChildrenCheckStatus(node, node.isChecked); - _updateParentCheckStatus(node); - // widget.bloc!.add( - // SelecteId(nodes: widget.bloc!.updatedCommunities)); - }); - }, - child: Image.asset( - _getCheckBoxImage(node), - width: 20, - height: 20, - ), - ), - const SizedBox(width: 15), - Expanded( - 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: node.isHighlighted - ? ColorsManager.blackColor - : ColorsManager.textGray, - ), - ), - ], - ), - ), - ), - ], - ), - ), - if (node.isExpanded && node.children.isNotEmpty) - _buildTree(node.children, level: level + 1), - ], - ), - ); - } - - String _getCheckBoxImage(TreeNode node) { - if (node.children.isEmpty) { - return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox; - } - if (_areAllChildrenChecked(node)) { - return Assets.CheckBoxChecked; - } else if (_areSomeChildrenChecked(node)) { - return Assets.rectangleCheckBox; - } else { - return Assets.emptyBox; - } - } - - bool _areAllChildrenChecked(TreeNode node) { - return node.children.isNotEmpty && - node.children.every((child) => - child.isChecked && - (child.children.isEmpty || _areAllChildrenChecked(child))); - } - - bool _areSomeChildrenChecked(TreeNode node) { - return node.children.isNotEmpty && - node.children.any((child) => - child.isChecked || - (child.children.isNotEmpty && _areSomeChildrenChecked(child))); - } - - void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { - for (var child in node.children) { - child.isChecked = isChecked; - _updateChildrenCheckStatus(child, isChecked); - } - } - - void _updateParentCheckStatus(TreeNode node) { - TreeNode? parent = _findParent(widget.bloc!.updatedCommunities, node); - if (parent != null) { - setState(() { - parent.isChecked = _areAllChildrenChecked(parent); - _updateParentCheckStatus(parent); - }); - } - } - - TreeNode? _findParent(List nodes, TreeNode target) { - for (var node in nodes) { - if (node.children.contains(target)) { - return node; - } - var parent = _findParent(node.children, target); - if (parent != null) return parent; - } - return null; - } - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: _buildTree(widget.bloc!.updatedCommunities), - ); - } -} diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart index 2a31a91f..82212103 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; +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/users_table/bloc/user_table_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart'; +import 'package:syncrow_web/services/user_permission.dart'; class UserTableBloc extends Bloc { UserTableBloc() : super(TableInitial()) { @@ -12,79 +14,108 @@ class UserTableBloc extends Bloc { on(_toggleSortUsersByNameDesc); on(_toggleSortUsersByDateOldestToNewest); on(_toggleSortUsersByDateNewestToOldest); + on(_searchUsers); + on(_handlePageChange); + on(_filterUsersByRole); + on(_filterUsersByJobTitle); + on(_filterUsersByCreated); + on(_filterUserActevate); + on(_deleteUser); } - + int itemsPerPage = 10; + int currentPage = 1; List users = []; - List initialUsers = []; // Save the initial state - String currentSortOrder = ''; // Keeps track of the current sorting order - String currentSortOrderDate = ''; // Keeps track of the current sorting order + List initialUsers = []; + String currentSortOrder = ''; + String currentSortOrderDate = ''; + List roleTypes = []; + List jobTitle = []; + List createdBy = []; + List deActivate = []; Future _getUsers(GetUsers event, Emitter emit) async { emit(UsersLoadingState()); try { - users = [ - RolesUserModel( - id: '1', - userName: 'b 1', - userEmail: 'test1@test.com', - action: '', - createdBy: 'Admin', - creationDate: '25/10/2024', - creationTime: '10:30 AM', - status: 'Invited', - ), - RolesUserModel( - id: '2', - userName: 'a 2', - userEmail: 'test2@test.com', - action: '', - createdBy: 'Admin', - creationDate: '24/10/2024', - creationTime: '2:30 PM', - status: 'Active', - ), - RolesUserModel( - id: '3', - userName: 'c 3', - userEmail: 'test3@test.com', - action: '', - createdBy: 'Admin', - creationDate: '23/10/2024', - creationTime: '9:00 AM', - status: 'Disabled', - ), - ]; - // Sort users by newest to oldest as default + roleTypes.clear(); + jobTitle.clear(); + createdBy.clear(); + deActivate.clear(); + users = await UserPermissionApi().fetchUsers(); + for (var user in users) { + roleTypes.add(user.roleType.toString()); + } + for (var user in users) { + jobTitle.add(user.jobTitle.toString()); + } + for (var user in users) { + createdBy.add(user.invitedBy.toString()); + } + for (var user in users) { + deActivate.add(user.status.toString()); + } + roleTypes = roleTypes.toSet().toList(); + jobTitle = jobTitle.toSet().toList(); + createdBy = createdBy.toSet().toList(); + deActivate = deActivate.toSet().toList(); + users.sort((a, b) { - final dateA = _parseDateTime(a.creationDate!); - final dateB = _parseDateTime(b.creationDate!); - return dateB.compareTo(dateA); // Newest to oldest + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateB.compareTo(dateA); }); - initialUsers = List.from(users); // Save the initial state + initialUsers = List.from(users); + _handlePageChange(ChangePage(1), emit); emit(UsersLoadedState(users: users)); } catch (e) { emit(ErrorState(e.toString())); } } - void _changeUserStatus(ChangeUserStatus event, Emitter emit) { + Future _deleteUser( + DeleteUserEvent event, Emitter emit) async { + emit(UsersLoadingState()); try { - users = users.map((user) { - if (user.id == event.userId) { - return RolesUserModel( - id: user.id, - userName: user.userName, - userEmail: user.userEmail, - createdBy: user.createdBy, - creationDate: user.creationDate, - creationTime: user.creationTime, - status: event.newStatus, - action: user.action, - ); - } - return user; - }).toList(); + bool res = await UserPermissionApi().deleteUserById(event.userId); + if (res == true) { + Navigator.of(event.context).pop(true); + } else { + emit(const ErrorState('Something error')); + } + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + Future _changeUserStatus( + ChangeUserStatus event, Emitter emit) async { + try { + emit(UsersLoadingState()); + bool res = await UserPermissionApi().changeUserStatusById( + event.userId, event.newStatus == "disabled" ? true : false); + if (res == true) { + add(const GetUsers()); + // users = users.map((user) { + // if (user.uuid == event.userId) { + // return RolesUserModel( + // uuid: user.uuid, + // createdAt: user.createdAt, + // email: user.email, + // firstName: user.firstName, + // lastName: user.lastName, + // roleType: user.roleType, + // status: event.newStatus, + // isEnabled: event.newStatus == "disabled" ? false : true, + // invitedBy: user.invitedBy, + // phoneNumber: user.phoneNumber, + // jobTitle: user.jobTitle, + // createdDate: user.createdDate, + // createdTime: user.createdTime, + // ); + // } + // return user; + // }).toList(); + } emit(UsersLoadedState(users: users)); } catch (e) { emit(ErrorState(e.toString())); @@ -94,16 +125,14 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByNameAsc( SortUsersByNameAsc event, Emitter emit) { if (currentSortOrder == "Asc") { - // If already sorted ascending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; - users = List.from(initialUsers); // Reset to saved initial state + users = List.from(initialUsers); emit(UsersLoadedState(users: users)); } else { - // Sort ascending emit(UsersLoadingState()); currentSortOrder = "Asc"; - users.sort((a, b) => a.userName!.compareTo(b.userName!)); + users.sort((a, b) => a.firstName!.compareTo(b.firstName!)); emit(UsersLoadedState(users: users)); } } @@ -111,7 +140,6 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByNameDesc( SortUsersByNameDesc event, Emitter emit) { if (currentSortOrder == "Desc") { - // If already sorted descending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; users = List.from(initialUsers); // Reset to saved initial state @@ -120,7 +148,7 @@ class UserTableBloc extends Bloc { // Sort descending emit(UsersLoadingState()); currentSortOrder = "Desc"; - users.sort((a, b) => b.userName!.compareTo(a.userName!)); + users.sort((a, b) => b.firstName!.compareTo(a.firstName!)); emit(UsersLoadedState(users: users)); } } @@ -128,19 +156,17 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByDateNewestToOldest( DateNewestToOldestEvent event, Emitter emit) { if (currentSortOrderDate == "NewestToOldest") { - // If already sorted ascending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; currentSortOrderDate = ""; - users = List.from(initialUsers); // Reset to saved initial state + users = List.from(initialUsers); emit(UsersLoadedState(users: users)); } else { - // Sort ascending emit(UsersLoadingState()); users.sort((a, b) { - final dateA = _parseDateTime(a.creationDate!); - final dateB = _parseDateTime(b.creationDate!); - return dateB.compareTo(dateA); // Newest to oldest + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateB.compareTo(dateA); }); emit(UsersLoadedState(users: users)); } @@ -149,19 +175,17 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByDateOldestToNewest( DateOldestToNewestEvent event, Emitter emit) { if (currentSortOrderDate == "OldestToNewest") { - // If already sorted ascending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; currentSortOrderDate = ""; - users = List.from(initialUsers); // Reset to saved initial state + users = List.from(initialUsers); emit(UsersLoadedState(users: users)); } else { - // Sort ascending emit(UsersLoadingState()); users.sort((a, b) { - final dateA = _parseDateTime(a.creationDate!); - final dateB = _parseDateTime(b.creationDate!); - return dateA.compareTo(dateB); // Newest to oldest + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateA.compareTo(dateB); }); emit(UsersLoadedState(users: users)); } @@ -169,17 +193,94 @@ class UserTableBloc extends Bloc { DateTime _parseDateTime(String date) { try { - // Split the date into day, month, and year final dateParts = date.split('/'); final day = int.parse(dateParts[0]); final month = int.parse(dateParts[1]); final year = int.parse(dateParts[2]); - - // Split the time into hours and minutes - return DateTime(year, month, day); } catch (e) { throw FormatException('Invalid date or time format: $date '); } } + + Future _searchUsers( + SearchUsers event, Emitter emit) async { + try { + final query = event.query.toLowerCase(); + final filteredUsers = initialUsers.where((user) { + final fullName = "${user.firstName} ${user.lastName}".toLowerCase(); + final email = user.email?.toLowerCase() ?? ""; + return fullName.contains(query) || email.contains(query); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + void _paginateUsers( + int pageNumber, int itemsPerPage, Emitter emit) { + final startIndex = (pageNumber - 1) * itemsPerPage; + final endIndex = startIndex + itemsPerPage; + if (startIndex >= users.length) { + emit(UsersLoadedState(users: const [])); + return; + } + final paginatedUsers = users.sublist( + startIndex, + endIndex > users.length ? users.length : endIndex, + ); + emit(UsersLoadedState(users: paginatedUsers)); + } + + void _handlePageChange(ChangePage event, Emitter emit) { + final itemsPerPage = 10; + final startIndex = (event.pageNumber - 1) * itemsPerPage; + final endIndex = startIndex + itemsPerPage; + if (startIndex >= users.length) { + emit(UsersLoadedState(users: [])); + return; + } + final paginatedUsers = users.sublist( + startIndex, + endIndex > users.length ? users.length : endIndex, + ); + emit(UsersLoadedState(users: paginatedUsers)); + } + + void _filterUsersByRole( + FilterUsersByRoleEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = initialUsers.where((user) { + return event.selectedRoles.contains(user.roleType); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } + + void _filterUsersByJobTitle( + FilterUsersByJobEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = users.where((user) { + return event.selectedJob.contains(user.jobTitle); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } + + void _filterUsersByCreated( + FilterUsersByCreatedEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = initialUsers.where((user) { + return event.selectedCreatedBy.contains(user.invitedBy); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } + + void _filterUserActevate( + FilterUsersByDeActevateEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = initialUsers.where((user) { + return event.selectedActivate.contains(user.status); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } } diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart index a6c77bd3..dbcd9a26 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; sealed class UserTableEvent extends Equatable { const UserTableEvent(); @@ -47,7 +48,6 @@ class StoreUsersEvent extends UserTableEvent { List get props => []; } - class DateNewestToOldestEvent extends UserTableEvent { const DateNewestToOldestEvent(); @@ -60,4 +60,62 @@ class DateOldestToNewestEvent extends UserTableEvent { @override List get props => []; -} \ No newline at end of file +} + +class SearchUsers extends UserTableEvent { + final String query; + SearchUsers(this.query); + @override + List get props => []; +} + +class ChangePage extends UserTableEvent { + final int pageNumber; + + ChangePage(this.pageNumber); + + @override + List get props => [pageNumber]; +} + +class DeleteUserEvent extends UserTableEvent { + final String userId; + final BuildContext context; + + const DeleteUserEvent(this.userId, this.context); + + @override + List get props => [userId, context]; +} + +class FilterUsersByRoleEvent extends UserTableEvent { + final List selectedRoles; + + FilterUsersByRoleEvent(this.selectedRoles); + @override + List get props => [selectedRoles]; +} + +class FilterUsersByJobEvent extends UserTableEvent { + final List selectedJob; + + FilterUsersByJobEvent(this.selectedJob); + @override + List get props => [selectedJob]; +} + +class FilterUsersByCreatedEvent extends UserTableEvent { + final List selectedCreatedBy; + + FilterUsersByCreatedEvent(this.selectedCreatedBy); + @override + List get props => [selectedCreatedBy]; +} + +class FilterUsersByDeActevateEvent extends UserTableEvent { + final List selectedActivate; + + FilterUsersByDeActevateEvent(this.selectedActivate); + @override + List get props => [selectedActivate]; +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart index f42c0c03..4f777ee3 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -81,15 +81,17 @@ class _DynamicTableScreenState extends State scrollDirection: Axis.horizontal, child: Container( decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, borderRadius: const BorderRadius.all(Radius.circular(20))), child: FittedBox( child: Column( children: [ // Header Row with Resizable Columns Container( + width: MediaQuery.of(context).size.width, decoration: containerDecoration.copyWith( color: ColorsManager.circleRolesBackground, - borderRadius: BorderRadius.only( + borderRadius: const BorderRadius.only( topLeft: Radius.circular(15), topRight: Radius.circular(15))), child: Row( @@ -167,56 +169,96 @@ class _DynamicTableScreenState extends State ), ), // Data Rows with Dividers - Container( - decoration: containerDecoration.copyWith( - color: ColorsManager.whiteColors, - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(15), - bottomRight: Radius.circular(15))), - child: Column( - children: widget.rows.map((row) { - int rowIndex = widget.rows.indexOf(row); - return Column( - children: [ - Container( - child: Padding( - padding: const EdgeInsets.only( - left: 5, top: 10, right: 5, bottom: 10), - child: Row( - children: List.generate(row.length, (index) { - return SizedBox( - width: columnWidths[index], - child: SizedBox( - child: Padding( - padding: const EdgeInsets.only( - left: 15, right: 10), - child: row[index], - ), + widget.rows.isEmpty + ? Container( + child: SizedBox( + height: MediaQuery.of(context).size.height / 2, + child: Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + SvgPicture.asset(Assets.emptyTable), + const SizedBox( + height: 15, ), - ); - }), - ), + const Text( + 'No Users', + style: TextStyle( + color: ColorsManager.lightGrayColor, + fontSize: 16, + fontWeight: FontWeight.w700), + ) + ], + ), + ], ), ), - if (rowIndex < widget.rows.length - 1) - Row( - children: List.generate(widget.titles.length, - (index) { - return SizedBox( - width: columnWidths[index], - child: const Divider( - color: ColorsManager.boxDivider, - thickness: 1, - height: 1, - ), + ), + ) + : Center( + child: Container( + // height: MediaQuery.of(context).size.height * 0.59, + width: MediaQuery.of(context).size.width, + decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(15), + bottomRight: Radius.circular(15))), + child: ListView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: widget.rows.length, + itemBuilder: (context, rowIndex) { + final row = widget.rows[rowIndex]; + return Column( + children: [ + Container( + child: Padding( + padding: const EdgeInsets.only( + left: 5, + top: 10, + right: 5, + bottom: 10), + child: Row( + children: + List.generate(row.length, (index) { + return SizedBox( + width: columnWidths[index], + child: SizedBox( + child: Padding( + padding: const EdgeInsets.only( + left: 15, right: 10), + child: row[index], + ), + ), + ); + }), + ), + ), + ), + if (rowIndex < widget.rows.length - 1) + Row( + children: List.generate( + widget.titles.length, (index) { + return SizedBox( + width: columnWidths[index], + child: const Divider( + color: ColorsManager.boxDivider, + thickness: 1, + height: 1, + ), + ); + }), + ), + ], ); - })) - // Add a Divider below each row except the last one - ], - ); - }).toList(), - ), - ), + }, + ), + ), + ), ], ), ), diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index 758f062b..8d838ba2 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:number_pagination/number_pagination.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart'; @@ -15,7 +19,8 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class UsersPage extends StatelessWidget { - const UsersPage({super.key}); + UsersPage({super.key}); + @override Widget build(BuildContext context) { final TextEditingController searchController = TextEditingController(); @@ -44,9 +49,9 @@ class UsersPage extends StatelessWidget { padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(20)), - color: status == "Invited" + color: status == "invited" ? ColorsManager.invitedOrange.withOpacity(0.5) - : status == "Active" + : status == "active" ? ColorsManager.activeGreen.withOpacity(0.5) : ColorsManager.disabledPink.withOpacity(0.5), ), @@ -60,9 +65,9 @@ class UsersPage extends StatelessWidget { Text( status, style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: status == "Invited" + color: status == "invited" ? ColorsManager.invitedOrangeText - : status == "Active" + : status == "active" ? ColorsManager.activeGreenText : ColorsManager.disabledRedText, fontWeight: FontWeight.w400, @@ -81,23 +86,14 @@ class UsersPage extends StatelessWidget { required Function()? onTap}) { return Center( child: InkWell( - onTap: () { - final newStatus = status == 'Active' - ? 'Disabled' - : status == 'Disabled' - ? 'Invited' - : 'Active'; - context - .read() - .add(ChangeUserStatus(userId: userId, newStatus: newStatus)); - }, + onTap: onTap, child: Padding( padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), child: SvgPicture.asset( - status == "Invited" + status == "invited" ? Assets.invitedIcon - : status == "Active" + : status == "active" ? Assets.activeUser : Assets.deActiveUser, height: 35, @@ -113,12 +109,14 @@ class UsersPage extends StatelessWidget { final _blocRole = BlocProvider.of(context); if (state is UsersLoadingState) { + _blocRole.add(ChangePage(_blocRole.currentPage)); + return const Center(child: CircularProgressIndicator()); } else if (state is UsersLoadedState) { return Padding( padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: ListView( + shrinkWrap: true, children: [ Row( children: [ @@ -131,6 +129,9 @@ class UsersPage extends StatelessWidget { width: screenSize.width * 0.4, child: TextFormField( controller: searchController, + onChanged: (value) { + context.read().add(SearchUsers(value)); + }, style: const TextStyle(color: Colors.black), decoration: textBoxDecoration(radios: 15)!.copyWith( fillColor: ColorsManager.whiteColors, @@ -158,9 +159,9 @@ class UsersPage extends StatelessWidget { builder: (BuildContext context) { return const AddNewUserDialog(); }, - ).then((listDevice) { - if (listDevice != null) { - + ).then((v) { + if (v != null) { + _blocRole.add(const GetUsers()); } }); }, @@ -201,6 +202,66 @@ class UsersPage extends StatelessWidget { }, ); } + if (columnIndex == 2) { + final Map checkboxStates = { + for (var item in _blocRole.jobTitle) + item: false, // Initialize with false + }; + + showPopUpFilterMenu( + list: _blocRole.jobTitle, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole.add(FilterUsersByJobEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 3) { + final Map checkboxStates = { + for (var item in _blocRole.roleTypes) + item: false, // Initialize with false + }; + + showPopUpFilterMenu( + list: _blocRole.roleTypes, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole.add(FilterUsersByRoleEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } if (columnIndex == 4) { showDateFilterMenu( context: context, @@ -217,6 +278,37 @@ class UsersPage extends StatelessWidget { }, ); } + if (columnIndex == 6) { + final Map checkboxStates = { + for (var item in _blocRole.createdBy) + item: false, // Initialize with false + }; + + showPopUpFilterMenu( + list: _blocRole.createdBy, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole + .add(FilterUsersByCreatedEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } if (columnIndex == 8) { showDeActivateFilterMenu( context: context, @@ -248,19 +340,37 @@ class UsersPage extends StatelessWidget { ], rows: state.users.map((user) { return [ - Text(user.userName!), - Text(user.userEmail!), - const Text("Test"), - const Text("Member"), - Text(user.creationDate!), - Text(user.creationTime!), - Text(user.createdBy!), + Text('${user.firstName} ${user.lastName}'), + Text(user.email ?? ''), + Text(user.jobTitle ?? ''), + Text(user.roleType ?? ''), + Text(user.createdDate ?? ''), + Text(user.createdTime ?? ''), + Text(user.invitedBy), changeIconStatus( - status: user.status!, - userId: user.id!, - onTap: () {}, + status: + user.isEnabled == false ? 'disabled' : user.status, + userId: user.uuid, + onTap: user.status != "invited" + ? () { + final newStatus = user.status == 'active' + ? 'disabled' + : user.status == 'disabled' + ? 'invited' + : 'active'; + context.read().add( + ChangeUserStatus( + userId: user.uuid, + newStatus: user.isEnabled == false + ? 'disabled' + : user.status)); + } + : null, + ), + status( + status: + user.isEnabled == false ? 'disabled' : user.status, ), - status(status: user.status!), Row( children: [ // actionButton( @@ -269,17 +379,80 @@ class UsersPage extends StatelessWidget { // ), actionButton( title: "Edit", - onTap: () {}, + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return EditUserDialog(userId: user.uuid); + }, + ).then((v) { + if (v != null) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + } + }); + }, ), actionButton( title: "Delete", - onTap: () {}, + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return DeleteUserDialog( + onTapDelete: () { + _blocRole.add( + DeleteUserEvent(user.uuid, context)); + }, + ); + }, + ).then((v) { + if (v != null) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + } + }); + }, ), ], ), ]; }).toList(), ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + width: 500, + child: NumberPagination( + buttonRadius: 10, + selectedButtonColor: ColorsManager.secondaryColor, + buttonUnSelectedBorderColor: ColorsManager.grayBorder, + lastPageIcon: + const Icon(Icons.keyboard_double_arrow_right), + firstPageIcon: + const Icon(Icons.keyboard_double_arrow_left), + totalPages: + (_blocRole.users.length / _blocRole.itemsPerPage) + .ceil(), + currentPage: _blocRole.currentPage, + onPageChanged: (int pageNumber) { + _blocRole.currentPage = pageNumber; + context + .read() + .add(ChangePage(pageNumber)); + }, + ), + ), + ], + ), + ), ], ), ); diff --git a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart index da003536..44944ed3 100644 --- a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart +++ b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart @@ -77,7 +77,7 @@ class RolesAndPermissionPage extends StatelessWidget { ), scaffoldBody: BlocProvider( create: (context) => UserTableBloc()..add(const GetUsers()), - child: const UsersPage(), + child: UsersPage(), ) // _blocRole.tapSelect == false // ? UsersPage( diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 5d2464e6..abf151fb 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -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(); diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 0e31795a..f62437ac 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -1,6 +1,9 @@ import 'dart:convert'; import 'package:dio/dio.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/edit_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -8,6 +11,25 @@ import 'package:syncrow_web/utils/constants/api_const.dart'; class UserPermissionApi { static final HTTPService _httpService = HTTPService(); + Future> fetchUsers() async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getUsers, + showServerMessage: true, + expectedResponseModel: (json) { + debugPrint('fetchUsers Response: $json'); + final List data = + json['data'] ?? []; // Default to an empty list if no data + return data.map((item) => RolesUserModel.fromJson(item)).toList(); + }, + ); + return response; + } catch (e, stackTrace) { + debugPrint('Error in fetchUsers: $e'); + rethrow; + } + } + fetchRoles() async { final response = await _httpService.get( path: ApiEndpoints.roleTypes, @@ -67,6 +89,7 @@ class UserPermissionApi { } }, ); + print('sendInviteUser=$body'); return response ?? []; } on DioException catch (e) { @@ -107,4 +130,95 @@ class UserPermissionApi { return e.toString(); } } + + Future fetchUserById(userUuid) async { + final response = await _httpService.get( + path: ApiEndpoints.getUserById.replaceAll("{userUuid}", userUuid), + showServerMessage: true, + expectedResponseModel: (json) { + EditUserModel res = EditUserModel.fromJson(json['data']); + return res; + }, + ); + return response; + } + + Future editInviteUser({ + String? firstName, + String? userId, + String? lastName, + String? jobTitle, + String? phoneNumber, + String? roleUuid, + List? spaceUuids, + }) async { + try { + final body = { + "firstName": firstName, + "lastName": lastName, + "jobTitle": jobTitle != '' ? jobTitle : " ", + "phoneNumber": phoneNumber != '' ? phoneNumber : " ", + "roleUuid": roleUuid, + "projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c", + "spaceUuids": spaceUuids, + }; + final response = await _httpService.put( + path: ApiEndpoints.editUser.replaceAll('{inviteUserUuid}', userId!), + body: jsonEncode(body), + expectedResponseModel: (json) { + if (json['statusCode'] != 400) { + return json["success"]; + } else { + return false; + } + }, + ); + return response; + } on DioException catch (e) { + return false; + } catch (e) { + return false; + } + } + + Future deleteUserById(userUuid) async { + try { + final response = await _httpService.delete( + path: ApiEndpoints.deleteUser.replaceAll("{inviteUserUuid}", userUuid), + showServerMessage: true, + expectedResponseModel: (json) { + return json['success']; + }, + ); + return response; + } catch (e) { + return false; + } + } + + Future changeUserStatusById(userUuid, status) async { + try { + Map bodya = { + "disable": status, + "projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c" + }; + print('changeUserStatusById==$bodya'); + print('changeUserStatusById==$userUuid'); + + final response = await _httpService.put( + path: ApiEndpoints.changeUserStatus + .replaceAll("{invitedUserUuid}", userUuid), + body: bodya, + expectedResponseModel: (json) { + print('changeUserStatusById==${json['success']}'); + return json['success']; + }, + ); + + return response; + } catch (e) { + return false; + print(e); + } + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index da746fbd..a229c5f6 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -100,7 +100,13 @@ abstract class ApiEndpoints { static const String roleTypes = '/role/types'; static const String permission = '/permission/{roleUuid}'; static const String inviteUser = '/invite-user'; + static const String checkEmail = '/invite-user/check-email'; + static const String getUsers = '/projects/${projectUuid}/user'; + static const String getUserById = '/projects/${projectUuid}/user/{userUuid}'; + static const String editUser = '/invite-user/{inviteUserUuid}'; + static const String deleteUser = '/invite-user/{inviteUserUuid}'; + static const String changeUserStatus = '/invite-user/{invitedUserUuid}/disable'; + // static const String updateAutomation = '/automation/{automationId}'; - // https://syncrow-dev.azurewebsites.net/invite-user/check-email } diff --git a/macos/Podfile.lock b/macos/Podfile.lock new file mode 100644 index 00000000..0639648b --- /dev/null +++ b/macos/Podfile.lock @@ -0,0 +1,36 @@ +PODS: + - flutter_secure_storage_macos (6.1.1): + - FlutterMacOS + - FlutterMacOS (1.0.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + +EXTERNAL SOURCES: + flutter_secure_storage_macos: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos + FlutterMacOS: + :path: Flutter/ephemeral + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + +SPEC CHECKSUMS: + flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9 + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + +PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 + +COCOAPODS: 1.16.2 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index e1af4c91..eec16af6 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -21,6 +21,8 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 108157F896CD9F637B06D7C0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DAF1C60594A51D692304366 /* Pods_Runner.framework */; }; + 2D0F1F294F673EF0DB5E4CA1 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */; }; 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; @@ -60,11 +62,12 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 24D7BEF98D33245EFB9F6A1B /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* syncrow_web.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "syncrow_web.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* syncrow_web.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = syncrow_web.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -76,8 +79,15 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 5DAF1C60594A51D692304366 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + AB949539E0D0A8E2BDAB9ADF /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F244F079A053D959E1C5C362 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,6 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 2D0F1F294F673EF0DB5E4CA1 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -92,6 +103,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 108157F896CD9F637B06D7C0 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -125,6 +137,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 75DCDFECC7757C5159E8F0C5 /* Pods */, ); sourceTree = ""; }; @@ -172,9 +185,25 @@ path = Runner; sourceTree = ""; }; + 75DCDFECC7757C5159E8F0C5 /* Pods */ = { + isa = PBXGroup; + children = ( + 24D7BEF98D33245EFB9F6A1B /* Pods-Runner.debug.xcconfig */, + F244F079A053D959E1C5C362 /* Pods-Runner.release.xcconfig */, + AB949539E0D0A8E2BDAB9ADF /* Pods-Runner.profile.xcconfig */, + 96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */, + A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */, + 81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 5DAF1C60594A51D692304366 /* Pods_Runner.framework */, + E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -186,6 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + A1935203066F42991FF0ED43 /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -204,11 +234,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 8ECFD939A4D371A145DBA191 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 92D754792F50A5D35F6D5AEE /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -329,6 +361,67 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 8ECFD939A4D371A145DBA191 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 92D754792F50A5D35F6D5AEE /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + A1935203066F42991FF0ED43 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -380,6 +473,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -394,6 +488,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -408,6 +503,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16..21a3cc14 100644 --- a/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/pubspec.lock b/pubspec.lock index 86d132c3..d1c39c65 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -392,6 +392,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + number_pagination: + dependency: "direct main" + description: + name: number_pagination + sha256: "75d3a28616196e7c8df431d0fb7c48e811e462155f4cf3b5b4167b3408421327" + url: "https://pub.dev" + source: hosted + version: "1.1.6" path: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6951987c..786a39c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,7 @@ dependencies: uuid: ^4.4.2 time_picker_spinner: ^1.0.0 intl_phone_field: ^3.2.0 + number_pagination: ^1.1.6 dev_dependencies: flutter_test: From fe8f8160ec676c91e1d50ae1fe226154c5aedcfb Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 6 Jan 2025 14:20:57 +0300 Subject: [PATCH 14/17] filter --- .../add_user_dialog/bloc/users_bloc.dart | 31 +- .../add_user_dialog/bloc/users_event.dart | 7 - .../view/popup_menu_filter.dart | 96 ++- .../users_table/bloc/user_table_bloc.dart | 95 ++- .../users_table/bloc/user_table_event.dart | 16 + .../users_table/bloc/user_table_state.dart | 9 + .../users_table/view/user_table.dart | 2 +- .../users_table/view/users_page.dart | 737 ++++++++++-------- 8 files changed, 580 insertions(+), 413 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index 730825cb..c06c0969 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -30,8 +30,8 @@ class UsersBloc extends Bloc { on(_onToggleNodeExpansion); on(_onToggleNodeCheck); on(_editInvitUser); - } + void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { if (formKey.currentState?.validate() ?? false) { emit(const BasicsStepValidState()); @@ -381,7 +381,7 @@ class UsersBloc extends Bloc { // Create a list of UUIDs to mark final uuidsToMark = res.spaces.map((space) => space.uuid).toList(); // Print all IDs and mark nodes in updatedCommunities - print('Printing and marking nodes in updatedCommunities:'); + debugPrint('Printing and marking nodes in updatedCommunities:'); _printAndMarkNodes(updatedCommunities, uuidsToMark); } else { print('updatedCommunities is empty!'); @@ -391,34 +391,27 @@ class UsersBloc extends Bloc { element.type == res.roleType.toString().toLowerCase().replaceAll("_", " ")) .uuid; - print('Role ID: $roleId'); + debugPrint('Role ID: $roleId'); roleSelected = roleId; add(PermissionEvent(roleUuid: roleSelected)); emit(ChangeStatusSteps()); - } else { - // emit(UsersErrorState("User not found")); - } - } else { - // emit(UsersErrorState("Invalid user ID")); - } + } else {} + } else {} } catch (e) { print("Failed to fetch user data: $e"); - // emit(UsersErrorState("Failed to fetch user data: $e")); } } - /// Recursively print all the node IDs, including nested children. - /// Recursively print all node IDs and mark nodes as `isChecked` if their UUID exists in the list. void _printAndMarkNodes(List nodes, List uuidsToMark, [int level = 0]) { for (final node in nodes) { - // Check if the current node's UUID exists in the list of UUIDs to mark. if (uuidsToMark.contains(node.uuid)) { - node.isChecked = true; // Mark the node as checked. - print( + node.isChecked = true; + debugPrint( '${' ' * level}MATCH FOUND: Node ID: ${node.uuid}, Title: ${node.title} is marked as checked.'); } else { - print('${' ' * level}Node ID: ${node.uuid}, Title: ${node.title}'); + debugPrint( + '${' ' * level}Node ID: ${node.uuid}, Title: ${node.title}'); } if (node.children.isNotEmpty) { _printAndMarkNodes(node.children, uuidsToMark, level + 1); @@ -453,8 +446,6 @@ class UsersBloc extends Bloc { emit(ChangeStatusSteps()); } -// Existing methods that remain in the BLoC: - void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { for (var child in node.children) { child.isChecked = isChecked; @@ -477,7 +468,6 @@ class UsersBloc extends Bloc { (child.children.isEmpty || _areAllChildrenChecked(child))); } -// Private helper method to find the parent of a given node. TreeNode? _findParent(List nodes, TreeNode target) { for (var node in nodes) { if (node.children.contains(target)) { @@ -490,7 +480,4 @@ class UsersBloc extends Bloc { } return null; } - - - } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart index 0f4631ff..c193bbf3 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart @@ -129,8 +129,6 @@ class UpdateNodeCheckStatus extends UsersEvent { @override List get props => [node]; } - -// Define new events class ToggleNodeHighlightEvent extends UsersEvent { final TreeNode node; @@ -161,14 +159,9 @@ class ToggleNodeCheckEvent extends UsersEvent { @override List get props => []; } - -// users_event.dart - -// 1. Extend UsersEvent class ToggleNodeCheck extends UsersEvent { final TreeNode node; - // 2. Add a constructor that takes the node to toggle ToggleNodeCheck(this.node); @override List get props => []; diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart index 02eac036..fbadd07e 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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'; +import 'package:syncrow_web/utils/style.dart'; Future showPopUpFilterMenu({ required BuildContext context, @@ -8,20 +11,18 @@ Future showPopUpFilterMenu({ Function()? onSortZtoA, Function()? cancelButton, required Map checkboxStates, + required RelativeRect position, + // Function(String)? onTextFieldChanged, Function()? onOkPressed, List? list, }) async { - final RenderBox overlay = - Overlay.of(context).context.findRenderObject() as RenderBox; + await showMenu( context: context, - position: RelativeRect.fromLTRB( - overlay.size.width / 4, - 240, - overlay.size.width / 4, - 0, - ), + position:position, + + color: ColorsManager.whiteColors, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10)), @@ -55,28 +56,69 @@ Future showPopUpFilterMenu({ ), const PopupMenuDivider(), const PopupMenuItem( - child: Text( - "Filter by Status", - style: TextStyle(fontWeight: FontWeight.bold), - ), - ), + child: Text( + "Filter by Status", + style: TextStyle(fontWeight: FontWeight.bold), + ) + // Container( + // decoration: containerDecoration.copyWith( + // boxShadow: [], + // borderRadius: const BorderRadius.only( + // topLeft: Radius.circular(10), topRight: Radius.circular(10))), + // child: Padding( + // padding: const EdgeInsets.all(8.0), + // child: TextFormField( + // onChanged: onTextFieldChanged, + // style: const TextStyle(color: Colors.black), + // decoration: textBoxDecoration(radios: 15)!.copyWith( + // fillColor: ColorsManager.whiteColors, + // errorStyle: const TextStyle(height: 0), + // hintStyle: context.textTheme.titleSmall?.copyWith( + // color: Colors.grey, + // fontSize: 12, + // ), + // hintText: 'Search', + // suffixIcon: SizedBox( + // child: SvgPicture.asset( + // Assets.searchIconUser, + // fit: BoxFit.none, + // ), + // ), + // ), + // // "Filter by Status", + // // style: TextStyle(fontWeight: FontWeight.bold), + // ), + // ), + // ), + ), PopupMenuItem( - child: SizedBox( + child: Container( + decoration: containerDecoration.copyWith( + boxShadow: [], + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10))), + padding: const EdgeInsets.all(10), height: 200, width: 400, - child: ListView.builder( - itemCount: list?.length ?? 0, - itemBuilder: (context, index) { - final item = list![index]; - return CheckboxListTile( - title: Text(item), - value: checkboxStates[item], - onChanged: (bool? newValue) { - checkboxStates[item] = newValue ?? false; - (context as Element).markNeedsBuild(); - }, - ); - }, + child: Container( + padding: const EdgeInsets.all(10), + color: Colors.white, + child: ListView.builder( + itemCount: list?.length ?? 0, + itemBuilder: (context, index) { + final item = list![index]; + return CheckboxListTile( + dense: true, + title: Text(item), + value: checkboxStates[item], + onChanged: (bool? newValue) { + checkboxStates[item] = newValue ?? false; + (context as Element).markNeedsBuild(); + }, + ); + }, + ), ), ), ), diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart index 82212103..cdfef350 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -19,8 +19,9 @@ class UserTableBloc extends Bloc { on(_filterUsersByRole); on(_filterUsersByJobTitle); on(_filterUsersByCreated); - on(_filterUserActevate); + on(_filterUserStatus); on(_deleteUser); + on(_filterClear); } int itemsPerPage = 10; int currentPage = 1; @@ -31,7 +32,7 @@ class UserTableBloc extends Bloc { List roleTypes = []; List jobTitle = []; List createdBy = []; - List deActivate = []; + List status = ['active', 'invited', 'disabled']; Future _getUsers(GetUsers event, Emitter emit) async { emit(UsersLoadingState()); @@ -39,8 +40,14 @@ class UserTableBloc extends Bloc { roleTypes.clear(); jobTitle.clear(); createdBy.clear(); - deActivate.clear(); + // deActivate.clear(); users = await UserPermissionApi().fetchUsers(); + + users.sort((a, b) { + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateB.compareTo(dateA); + }); for (var user in users) { roleTypes.add(user.roleType.toString()); } @@ -50,20 +57,14 @@ class UserTableBloc extends Bloc { for (var user in users) { createdBy.add(user.invitedBy.toString()); } - for (var user in users) { - deActivate.add(user.status.toString()); - } + // for (var user in users) { + // deActivate.add(user.status.toString()); + // } + initialUsers = List.from(users); roleTypes = roleTypes.toSet().toList(); jobTitle = jobTitle.toSet().toList(); createdBy = createdBy.toSet().toList(); - deActivate = deActivate.toSet().toList(); - - users.sort((a, b) { - final dateA = _parseDateTime(a.createdDate); - final dateB = _parseDateTime(b.createdDate); - return dateB.compareTo(dateA); - }); - initialUsers = List.from(users); + // deActivate = deActivate.toSet().toList(); _handlePageChange(ChangePage(1), emit); emit(UsersLoadedState(users: users)); } catch (e) { @@ -127,7 +128,7 @@ class UserTableBloc extends Bloc { if (currentSortOrder == "Asc") { emit(UsersLoadingState()); currentSortOrder = ""; - users = List.from(initialUsers); + users = List.from(users); emit(UsersLoadedState(users: users)); } else { emit(UsersLoadingState()); @@ -248,39 +249,83 @@ class UserTableBloc extends Bloc { emit(UsersLoadedState(users: paginatedUsers)); } + Set selectedRoles = {}; + Set selectedJobTitles = {}; + Set selectedCreatedBy = {}; + Set selectedStatuses = {}; + void _filterUsersByRole( FilterUsersByRoleEvent event, Emitter emit) { - emit(UsersLoadingState()); + selectedRoles = event.selectedRoles.toSet(); + final filteredUsers = initialUsers.where((user) { - return event.selectedRoles.contains(user.roleType); + if (selectedRoles.isEmpty) return true; + return selectedRoles.contains(user.roleType); }).toList(); + emit(UsersLoadedState(users: filteredUsers)); } void _filterUsersByJobTitle( FilterUsersByJobEvent event, Emitter emit) { - emit(UsersLoadingState()); - final filteredUsers = users.where((user) { - return event.selectedJob.contains(user.jobTitle); + selectedJobTitles = event.selectedJob.toSet(); + + final filteredUsers = initialUsers.where((user) { + if (selectedJobTitles.isEmpty) return true; + return selectedJobTitles.contains(user.jobTitle); }).toList(); + emit(UsersLoadedState(users: filteredUsers)); } void _filterUsersByCreated( FilterUsersByCreatedEvent event, Emitter emit) { - emit(UsersLoadingState()); + selectedCreatedBy = event.selectedCreatedBy.toSet(); + final filteredUsers = initialUsers.where((user) { - return event.selectedCreatedBy.contains(user.invitedBy); + if (selectedCreatedBy.isEmpty) return true; + return selectedCreatedBy.contains(user.invitedBy); }).toList(); + emit(UsersLoadedState(users: filteredUsers)); } - void _filterUserActevate( + void _filterUserStatus( FilterUsersByDeActevateEvent event, Emitter emit) { - emit(UsersLoadingState()); + selectedStatuses = event.selectedActivate.toSet(); + final filteredUsers = initialUsers.where((user) { - return event.selectedActivate.contains(user.status); + if (selectedStatuses.isEmpty) return true; + return selectedStatuses.contains(user.status); }).toList(); + emit(UsersLoadedState(users: filteredUsers)); } + + void _resetAllFilters(Emitter emit) { + selectedRoles.clear(); + selectedJobTitles.clear(); + selectedCreatedBy.clear(); + selectedStatuses.clear(); + emit(UsersLoadedState(users: initialUsers)); + } + + void _filterClear(FilterClearEvent event, Emitter emit) { + selectedRoles.clear(); + selectedJobTitles.clear(); + selectedCreatedBy.clear(); + selectedStatuses.clear(); + emit(UsersLoadedState(users: initialUsers)); + } } + // void _filterOptions(FilterOptionsEvent event, Emitter emit) { + // try { + // final query = event.query.toLowerCase(); + // final filteredOptions = event.fullOptions + // .where((option) => option.toLowerCase().contains(query)) + // .toList(); + // emit(FilterOptionsState(filteredOptions)); + // } catch (e) { + // emit(ErrorState(e.toString())); + // } + // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart index dbcd9a26..a81002ad 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart @@ -119,3 +119,19 @@ class FilterUsersByDeActevateEvent extends UserTableEvent { @override List get props => [selectedActivate]; } + +class FilterOptionsEvent extends UserTableEvent { + final String query; + final List fullOptions; + + FilterOptionsEvent({required this.query, required this.fullOptions}); + + @override + List get props => [query, fullOptions]; +} + +class FilterClearEvent extends UserTableEvent { + FilterClearEvent(); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart index 2a132947..2433f9f5 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart @@ -80,3 +80,12 @@ final class ChangeTapStatus extends UserTableState { @override List get props => [select]; } + +class FilterOptionsState extends UserTableState { + final List filteredOptions; + + FilterOptionsState(this.filteredOptions); + + @override + List get props => [filteredOptions]; +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart index 4f777ee3..813e950c 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -122,7 +122,7 @@ class _DynamicTableScreenState extends State ), if (index != 1 && index != 9 && - index != 7 && + index != 8 && index != 5) FittedBox( child: IconButton( diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index 8d838ba2..3a9f2187 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -110,350 +110,425 @@ class UsersPage extends StatelessWidget { if (state is UsersLoadingState) { _blocRole.add(ChangePage(_blocRole.currentPage)); - return const Center(child: CircularProgressIndicator()); } else if (state is UsersLoadedState) { return Padding( padding: const EdgeInsets.all(20), - child: ListView( - shrinkWrap: true, - children: [ - Row( - children: [ - Container( - decoration: containerDecoration.copyWith( - borderRadius: const BorderRadius.all( - Radius.circular(20), - ), - ), - width: screenSize.width * 0.4, - child: TextFormField( - controller: searchController, - onChanged: (value) { - context.read().add(SearchUsers(value)); - }, - style: const TextStyle(color: Colors.black), - decoration: textBoxDecoration(radios: 15)!.copyWith( - fillColor: ColorsManager.whiteColors, - errorStyle: const TextStyle(height: 0), - hintStyle: context.textTheme.titleSmall?.copyWith( - color: Colors.grey, - fontSize: 12, - ), - hintText: 'Search', - suffixIcon: SizedBox( - child: SvgPicture.asset( - Assets.searchIconUser, - fit: BoxFit.none, - ), - ), - ), - ), - ), - const SizedBox(width: 20), - InkWell( - onTap: () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return const AddNewUserDialog(); - }, - ).then((v) { - if (v != null) { - _blocRole.add(const GetUsers()); - } - }); - }, - child: Container( - decoration: containerWhiteDecoration, - width: screenSize.width * 0.18, - height: 50, - child: const Center( - child: Text( - 'Add New User', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w700, - color: ColorsManager.blueColor, - ), - ), - ), - ), - ), - ], - ), - const SizedBox(height: 25), - DynamicTableScreen( - onFilter: (columnIndex) { - if (columnIndex == 0) { - showNameMenu( - context: context, - isSelected: _blocRole.currentSortOrder, - aToZTap: () { - context - .read() - .add(const SortUsersByNameAsc()); - }, - zToaTap: () { - context - .read() - .add(const SortUsersByNameDesc()); - }, - ); - } - if (columnIndex == 2) { - final Map checkboxStates = { - for (var item in _blocRole.jobTitle) - item: false, // Initialize with false - }; - - showPopUpFilterMenu( - list: _blocRole.jobTitle, - context: context, - checkboxStates: checkboxStates, - onOkPressed: () { - final selectedItems = checkboxStates.entries - .where((entry) => entry.value) - .map((entry) => entry.key) - .toList(); - Navigator.of(context).pop(); - _blocRole.add(FilterUsersByJobEvent(selectedItems)); - }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); - }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); - }, - ); - } - if (columnIndex == 3) { - final Map checkboxStates = { - for (var item in _blocRole.roleTypes) - item: false, // Initialize with false - }; - - showPopUpFilterMenu( - list: _blocRole.roleTypes, - context: context, - checkboxStates: checkboxStates, - onOkPressed: () { - final selectedItems = checkboxStates.entries - .where((entry) => entry.value) - .map((entry) => entry.key) - .toList(); - Navigator.of(context).pop(); - _blocRole.add(FilterUsersByRoleEvent(selectedItems)); - }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); - }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); - }, - ); - } - if (columnIndex == 4) { - showDateFilterMenu( - context: context, - isSelected: _blocRole.currentSortOrderDate, - aToZTap: () { - context - .read() - .add(const DateNewestToOldestEvent()); - }, - zToaTap: () { - context - .read() - .add(const DateOldestToNewestEvent()); - }, - ); - } - if (columnIndex == 6) { - final Map checkboxStates = { - for (var item in _blocRole.createdBy) - item: false, // Initialize with false - }; - - showPopUpFilterMenu( - list: _blocRole.createdBy, - context: context, - checkboxStates: checkboxStates, - onOkPressed: () { - final selectedItems = checkboxStates.entries - .where((entry) => entry.value) - .map((entry) => entry.key) - .toList(); - Navigator.of(context).pop(); - _blocRole - .add(FilterUsersByCreatedEvent(selectedItems)); - }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); - }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); - }, - ); - } - if (columnIndex == 8) { - showDeActivateFilterMenu( - context: context, - isSelected: _blocRole.currentSortOrderDate, - aToZTap: () { - context - .read() - .add(const DateNewestToOldestEvent()); - }, - zToaTap: () { - context - .read() - .add(const DateOldestToNewestEvent()); - }, - ); - } - }, - titles: const [ - "Full Name", - "Email Address", - "Job Title", - "Role", - "Creation Date", - "Creation Time", - "Created By", - "Status", - "De/Activate", - "Action" - ], - rows: state.users.map((user) { - return [ - Text('${user.firstName} ${user.lastName}'), - Text(user.email ?? ''), - Text(user.jobTitle ?? ''), - Text(user.roleType ?? ''), - Text(user.createdDate ?? ''), - Text(user.createdTime ?? ''), - Text(user.invitedBy), - changeIconStatus( - status: - user.isEnabled == false ? 'disabled' : user.status, - userId: user.uuid, - onTap: user.status != "invited" - ? () { - final newStatus = user.status == 'active' - ? 'disabled' - : user.status == 'disabled' - ? 'invited' - : 'active'; - context.read().add( - ChangeUserStatus( - userId: user.uuid, - newStatus: user.isEnabled == false - ? 'disabled' - : user.status)); - } - : null, - ), - status( - status: - user.isEnabled == false ? 'disabled' : user.status, - ), - Row( - children: [ - // actionButton( - // title: "Activity Log", - // onTap: () {}, - // ), - actionButton( - title: "Edit", - onTap: () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return EditUserDialog(userId: user.uuid); - }, - ).then((v) { - if (v != null) { - if (v != null) { - _blocRole.add(const GetUsers()); - } - } - }); - }, - ), - actionButton( - title: "Delete", - onTap: () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return DeleteUserDialog( - onTapDelete: () { - _blocRole.add( - DeleteUserEvent(user.uuid, context)); - }, - ); - }, - ).then((v) { - if (v != null) { - if (v != null) { - _blocRole.add(const GetUsers()); - } - } - }); - }, - ), - ], - ), - ]; - }).toList(), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, + child: Align( + alignment: Alignment.topCenter, + child: ListView( + shrinkWrap: true, + children: [ + Row( children: [ Container( - width: 500, - child: NumberPagination( - buttonRadius: 10, - selectedButtonColor: ColorsManager.secondaryColor, - buttonUnSelectedBorderColor: ColorsManager.grayBorder, - lastPageIcon: - const Icon(Icons.keyboard_double_arrow_right), - firstPageIcon: - const Icon(Icons.keyboard_double_arrow_left), - totalPages: - (_blocRole.users.length / _blocRole.itemsPerPage) - .ceil(), - currentPage: _blocRole.currentPage, - onPageChanged: (int pageNumber) { - _blocRole.currentPage = pageNumber; + decoration: containerDecoration.copyWith( + borderRadius: const BorderRadius.all( + Radius.circular(20), + ), + ), + width: screenSize.width * 0.4, + child: TextFormField( + controller: searchController, + onChanged: (value) { context .read() - .add(ChangePage(pageNumber)); + .add(SearchUsers(value)); }, + style: const TextStyle(color: Colors.black), + decoration: textBoxDecoration(radios: 15)!.copyWith( + fillColor: ColorsManager.whiteColors, + errorStyle: const TextStyle(height: 0), + hintStyle: context.textTheme.titleSmall?.copyWith( + color: Colors.grey, + fontSize: 12, + ), + hintText: 'Search', + suffixIcon: SizedBox( + child: SvgPicture.asset( + Assets.searchIconUser, + fit: BoxFit.none, + ), + ), + ), + ), + ), + const SizedBox(width: 20), + InkWell( + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return const AddNewUserDialog(); + }, + ).then((v) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + }); + }, + child: Container( + decoration: containerWhiteDecoration, + width: screenSize.width * 0.18, + height: 50, + child: const Center( + child: Text( + 'Add New User', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: ColorsManager.blueColor, + ), + ), + ), ), ), ], ), - ), - ], + const SizedBox(height: 25), + DynamicTableScreen( + onFilter: (columnIndex) { + _blocRole.add(FilterClearEvent()); + + if (columnIndex == 0) { + showNameMenu( + context: context, + isSelected: _blocRole.currentSortOrder, + aToZTap: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + zToaTap: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 2) { + final Map checkboxStates = { + for (var item in _blocRole.jobTitle) + item: false, // Initialize with false + }; + final RenderBox overlay = Overlay.of(context) + .context + .findRenderObject() as RenderBox; + showPopUpFilterMenu( + position: RelativeRect.fromLTRB( + overlay.size.width / 4, + 240, + overlay.size.width / 4, + 0, + ), + list: _blocRole.jobTitle, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole.add(FilterUsersByJobEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 3) { + final Map checkboxStates = { + for (var item in _blocRole.roleTypes) + item: _blocRole.selectedRoles.contains(item), + }; + final RenderBox overlay = Overlay.of(context) + .context + .findRenderObject() as RenderBox; + showPopUpFilterMenu( + position: RelativeRect.fromLTRB( + overlay.size.width / 4, + 240, + overlay.size.width / 4, + 0, + ), + list: _blocRole.roleTypes, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + context + .read() + .add(FilterUsersByRoleEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 4) { + showDateFilterMenu( + context: context, + isSelected: _blocRole.currentSortOrderDate, + aToZTap: () { + context + .read() + .add(const DateNewestToOldestEvent()); + }, + zToaTap: () { + context + .read() + .add(const DateOldestToNewestEvent()); + }, + ); + } + if (columnIndex == 6) { + final Map checkboxStates = { + for (var item in _blocRole.createdBy) + item: _blocRole.selectedCreatedBy.contains(item), + }; + final RenderBox overlay = Overlay.of(context) + .context + .findRenderObject() as RenderBox; + showPopUpFilterMenu( + position: RelativeRect.fromLTRB( + overlay.size.width / 1, + 240, + overlay.size.width / 4, + 0, + ), + list: _blocRole.createdBy, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole + .add(FilterUsersByCreatedEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + + if (columnIndex == 7) { + final Map checkboxStates = { + for (var item in _blocRole.status) + item: _blocRole.selectedCreatedBy.contains(item), + }; + final RenderBox overlay = Overlay.of(context) + .context + .findRenderObject() as RenderBox; + showPopUpFilterMenu( + position: RelativeRect.fromLTRB( + overlay.size.width / 0, + 240, + overlay.size.width / 4, + 0, + ), + list: _blocRole.status, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole + .add(FilterUsersByCreatedEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 8) { + showDeActivateFilterMenu( + context: context, + isSelected: _blocRole.currentSortOrderDate, + aToZTap: () { + context + .read() + .add(const DateNewestToOldestEvent()); + }, + zToaTap: () { + context + .read() + .add(const DateOldestToNewestEvent()); + }, + ); + } + }, + titles: const [ + "Full Name", + "Email Address", + "Job Title", + "Role", + "Creation Date", + "Creation Time", + "Created By", + "Status", + "De/Activate", + "Action" + ], + rows: state.users.map((user) { + return [ + Text('${user.firstName} ${user.lastName}'), + Text(user.email ?? ''), + Text(user.jobTitle ?? ''), + Text(user.roleType ?? ''), + Text(user.createdDate ?? ''), + Text(user.createdTime ?? ''), + Text(user.invitedBy), + status( + status: user.isEnabled == false + ? 'disabled' + : user.status, + ), + changeIconStatus( + status: user.isEnabled == false + ? 'disabled' + : user.status, + userId: user.uuid, + onTap: user.status != "invited" + ? () { + final newStatus = user.status == 'active' + ? 'disabled' + : user.status == 'disabled' + ? 'invited' + : 'active'; + context.read().add( + ChangeUserStatus( + userId: user.uuid, + newStatus: user.isEnabled == false + ? 'disabled' + : user.status)); + } + : null, + ), + Row( + children: [ + // actionButton( + // title: "Activity Log", + // onTap: () {}, + // ), + actionButton( + title: "Edit", + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return EditUserDialog(userId: user.uuid); + }, + ).then((v) { + if (v != null) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + } + }); + }, + ), + actionButton( + title: "Delete", + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return DeleteUserDialog( + onTapDelete: () { + _blocRole.add(DeleteUserEvent( + user.uuid, context)); + }, + ); + }, + ).then((v) { + if (v != null) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + } + }); + }, + ), + ], + ), + ]; + }).toList(), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + width: 500, + child: NumberPagination( + buttonRadius: 10, + selectedButtonColor: ColorsManager.secondaryColor, + buttonUnSelectedBorderColor: + ColorsManager.grayBorder, + lastPageIcon: + const Icon(Icons.keyboard_double_arrow_right), + firstPageIcon: + const Icon(Icons.keyboard_double_arrow_left), + totalPages: (_blocRole.users.length / + _blocRole.itemsPerPage) + .ceil(), + currentPage: _blocRole.currentPage, + onPageChanged: (int pageNumber) { + _blocRole.currentPage = pageNumber; + context + .read() + .add(ChangePage(pageNumber)); + }, + ), + ), + ], + ), + ), + ], + ), ), ); } else if (state is ErrorState) { From d721f6f774c31575c212e60fce7b30999a6454ea Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 6 Jan 2025 14:42:32 +0300 Subject: [PATCH 15/17] table size --- .../users_table/bloc/user_table_bloc.dart | 2 +- .../users_page/users_table/view/user_table.dart | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart index cdfef350..174726f6 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -23,7 +23,7 @@ class UserTableBloc extends Bloc { on(_deleteUser); on(_filterClear); } - int itemsPerPage = 10; + int itemsPerPage = 20; int currentPage = 1; List users = []; List initialUsers = []; diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart index 813e950c..6662a9e2 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -29,7 +29,9 @@ class _DynamicTableScreenState extends State @override void initState() { super.initState(); - columnWidths = List.filled(widget.titles.length, 150.0); + setState(() { + columnWidths = List.filled(widget.titles.length, 150.0); + }); WidgetsBinding.instance.addObserver(this); } @@ -212,6 +214,19 @@ class _DynamicTableScreenState extends State shrinkWrap: true, itemCount: widget.rows.length, itemBuilder: (context, rowIndex) { + if (columnWidths + .every((width) => width == 120.0)) { + columnWidths = List.generate( + widget.titles.length, (index) { + if (index == 1) { + return screenWidth * 0.11; + } else if (index == 9) { + return screenWidth * 0.2; + } + return screenWidth * 0.11; + }); + setState(() {}); + } final row = widget.rows[rowIndex]; return Column( children: [ From 8fd1259f9c731659b311bafca43dcbfd75a8ae18 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 6 Jan 2025 15:15:01 +0300 Subject: [PATCH 16/17] remove warnings --- .../bloc/roles_permission_state.dart | 6 ++- .../model/edit_user_model.dart | 4 +- .../add_user_dialog/bloc/users_bloc.dart | 19 ++++------ .../add_user_dialog/bloc/users_event.dart | 38 +++++++++---------- .../add_user_dialog/bloc/users_status.dart | 13 ++++--- .../model/permission_option_model.dart | 4 +- .../add_user_dialog/view/basics_view.dart | 11 +++--- .../view/popup_menu_filter.dart | 3 -- .../view/roles_and_permission.dart | 2 - .../view/spaces_access_view.dart | 6 +-- .../users_table/bloc/user_table_bloc.dart | 19 ++-------- .../users_table/bloc/user_table_state.dart | 10 ++--- .../users_table/view/users_page.dart | 14 +++---- lib/services/user_permission.dart | 2 +- 14 files changed, 67 insertions(+), 84 deletions(-) diff --git a/lib/pages/roles_and_permission/bloc/roles_permission_state.dart b/lib/pages/roles_and_permission/bloc/roles_permission_state.dart index 55c2a8cb..7979f7db 100644 --- a/lib/pages/roles_and_permission/bloc/roles_permission_state.dart +++ b/lib/pages/roles_and_permission/bloc/roles_permission_state.dart @@ -13,6 +13,7 @@ final class RolesLoadingState extends RolesPermissionState { @override List get props => []; } + final class UsersLoadingState extends RolesPermissionState { @override List get props => []; @@ -22,6 +23,7 @@ final class RolesLoadedState extends RolesPermissionState { @override List get props => []; } + final class UsersLoadedState extends RolesPermissionState { @override List get props => []; @@ -68,9 +70,9 @@ final class SosAutomationReportErrorState extends RolesPermissionState { } final class ChangeTapStatus extends RolesPermissionState { - bool select = true; + final bool select; - ChangeTapStatus({required this.select}); + const ChangeTapStatus({required this.select}); @override List get props => [select]; diff --git a/lib/pages/roles_and_permission/model/edit_user_model.dart b/lib/pages/roles_and_permission/model/edit_user_model.dart index 17fba1a4..4075ac12 100644 --- a/lib/pages/roles_and_permission/model/edit_user_model.dart +++ b/lib/pages/roles_and_permission/model/edit_user_model.dart @@ -149,8 +149,8 @@ class EditUserModel { final String createdTime; // e.g. "8:41:43 AM" final String status; // e.g. "invited" final String invitedBy; // e.g. "SUPER_ADMIN" - final String phoneNumber; // can be empty - final String jobTitle; // can be empty + final String? phoneNumber; // can be empty + final String? jobTitle; // can be empty final String roleType; // e.g. "ADMIN" final List spaces; diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index c06c0969..563fca93 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -29,7 +29,7 @@ class UsersBloc extends Bloc { on(getUserById); on(_onToggleNodeExpansion); on(_onToggleNodeCheck); - on(_editInvitUser); + on(_editInviteUser); } void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { @@ -88,7 +88,6 @@ class UsersBloc extends Bloc { await CommunitySpaceManagementApi().fetchCommunities(); updatedCommunities = await Future.wait( communities.map((community) async { - print(community.uuid); List spaces = await _fetchSpacesForCommunity(community.uuid); spacesNodes = _buildTreeNodes(spaces); @@ -102,7 +101,7 @@ class UsersBloc extends Bloc { ); }).toList(), ); - emit(SpacesLoadedState()); + emit(const SpacesLoadedState()); return updatedCommunities; } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); @@ -112,7 +111,7 @@ class UsersBloc extends Bloc { List _buildTreeNodes(List spaces) { return spaces.map((space) { List childNodes = - space.children != null ? _buildTreeNodes(space.children) : []; + space.children.isNotEmpty ? _buildTreeNodes(space.children) : []; return TreeNode( uuid: space.uuid!, title: space.name, @@ -212,7 +211,7 @@ class UsersBloc extends Bloc { _sendInvitUser(SendInviteUsers event, Emitter emit) async { try { emit(UsersLoadingState()); - List selectedIds = getSelectedIds(updatedCommunities) ?? []; + List selectedIds = getSelectedIds(updatedCommunities); bool res = await UserPermissionApi().sendInviteUser( email: emailController.text, firstName: firstNameController.text, @@ -249,10 +248,10 @@ class UsersBloc extends Bloc { } } - _editInvitUser(EditInviteUsers event, Emitter emit) async { + _editInviteUser(EditInviteUsers event, Emitter emit) async { try { emit(UsersLoadingState()); - List selectedIds = getSelectedIds(updatedCommunities) ?? []; + List selectedIds = getSelectedIds(updatedCommunities); bool res = await UserPermissionApi().editInviteUser( userId: event.userId, firstName: firstNameController.text, @@ -383,8 +382,6 @@ class UsersBloc extends Bloc { // Print all IDs and mark nodes in updatedCommunities debugPrint('Printing and marking nodes in updatedCommunities:'); _printAndMarkNodes(updatedCommunities, uuidsToMark); - } else { - print('updatedCommunities is empty!'); } final roleId = roles .firstWhere((element) => @@ -397,9 +394,7 @@ class UsersBloc extends Bloc { emit(ChangeStatusSteps()); } else {} } else {} - } catch (e) { - print("Failed to fetch user data: $e"); - } + } catch (_) {} } void _printAndMarkNodes(List nodes, List uuidsToMark, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart index c193bbf3..2e82168c 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart @@ -63,31 +63,31 @@ class GetBatchStatus extends UsersEvent { //isEditUser:widget.userId!=''? false:true class CheckStepStatus extends UsersEvent { final int? steps; - bool? isEditUser = false; - CheckStepStatus({this.steps, required this.isEditUser}); + final bool? isEditUser; + const CheckStepStatus({this.steps, required this.isEditUser}); @override List get props => [steps]; } class SearchAnode extends UsersEvent { - List? nodes; - String? searchTerm; - SearchAnode({this.nodes, this.searchTerm}); + final List? nodes; + final String? searchTerm; + const SearchAnode({this.nodes, this.searchTerm}); @override List get props => [nodes, searchTerm]; } class SearchPermission extends UsersEvent { - List? nodes; - String? searchTerm; - SearchPermission({this.nodes, this.searchTerm}); + final List? nodes; + final String? searchTerm; + const SearchPermission({this.nodes, this.searchTerm}); @override List get props => [nodes, searchTerm]; } -class SelecteId extends UsersEvent { - List? nodes; - SelecteId({ +class SelectedId extends UsersEvent { + final List? nodes; + const SelectedId({ this.nodes, }); @override @@ -95,7 +95,7 @@ class SelecteId extends UsersEvent { } class ValidateBasicsStep extends UsersEvent { - ValidateBasicsStep(); + const ValidateBasicsStep(); @override List get props => []; } @@ -116,7 +116,7 @@ class GetUserByIdEvent extends UsersEvent { class ToggleNodeExpansion extends UsersEvent { final TreeNode node; - ToggleNodeExpansion({required this.node}); + const ToggleNodeExpansion({required this.node}); @override List get props => [node]; @@ -125,14 +125,15 @@ class ToggleNodeExpansion extends UsersEvent { class UpdateNodeCheckStatus extends UsersEvent { final TreeNode node; - UpdateNodeCheckStatus({required this.node}); + const UpdateNodeCheckStatus({required this.node}); @override List get props => [node]; } + class ToggleNodeHighlightEvent extends UsersEvent { final TreeNode node; - ToggleNodeHighlightEvent(this.node); + const ToggleNodeHighlightEvent(this.node); @override List get props => [node]; } @@ -155,14 +156,15 @@ class ClearSelectionsEvent extends UsersEvent { class ToggleNodeCheckEvent extends UsersEvent { final TreeNode node; - ToggleNodeCheckEvent(this.node); + const ToggleNodeCheckEvent(this.node); @override List get props => []; } + class ToggleNodeCheck extends UsersEvent { final TreeNode node; - ToggleNodeCheck(this.node); + const ToggleNodeCheck(this.node); @override List get props => []; } @@ -172,5 +174,3 @@ class EditUserEvent extends UsersEvent { @override List get props => []; } - - diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart index 646dccfd..4361c37f 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart @@ -1,5 +1,4 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; sealed class UsersState extends Equatable { @@ -32,7 +31,7 @@ final class SaveState extends UsersState { } final class SpacesLoadedState extends UsersState { - SpacesLoadedState(); + const SpacesLoadedState(); @override List get props => []; } @@ -58,9 +57,9 @@ final class RolesErrorState extends UsersState { /// automation reports final class ChangeTapStatus extends UsersState { - bool select = true; + final bool select; - ChangeTapStatus({required this.select}); + const ChangeTapStatus({required this.select}); @override List get props => [select]; @@ -77,14 +76,16 @@ class BasicsStepInvalidState extends UsersState { @override List get props => []; } + final class ValidateBasics extends UsersState { @override List get props => []; } + class UsersLoadedState extends UsersState { final List updatedCommunities; - UsersLoadedState({required this.updatedCommunities}); - @override + const UsersLoadedState({required this.updatedCommunities}); + @override List get props => []; } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart index c476ebb4..4141ccdd 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart @@ -16,7 +16,9 @@ class PermissionOption { factory PermissionOption.fromJson(Map json) { return PermissionOption( id: json['id'] ?? '', - title: json['title'].toString().toLowerCase().replaceAll("_", " ") ?? '', + title: json['title'] != null + ? json['title'].toString().toLowerCase().replaceAll("_", " ") + : '', isChecked: json['isChecked'] ?? false, isHighlighted: json['isHighlighted'] ?? false, subOptions: (json['subOptions'] as List?) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index bbca9aaa..c5025fc3 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -11,8 +11,8 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class BasicsView extends StatelessWidget { - String? userId = ''; - BasicsView({super.key, this.userId}); + final String? userId; + const BasicsView({super.key, this.userId = ''}); @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { @@ -185,11 +185,12 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( - enabled: userId!=''? false:true, + enabled: userId != '' ? false : true, onChanged: (value) { Future.delayed(const Duration(milliseconds: 200), () { - _blocRole.add(CheckStepStatus(isEditUser:userId!=''? false:true)); - _blocRole.add( ValidateBasicsStep()); + _blocRole.add(CheckStepStatus( + isEditUser: userId != '' ? false : true)); + _blocRole.add(ValidateBasicsStep()); }); }, controller: _blocRole.emailController, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart index fbadd07e..7c3c1ba5 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.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'; import 'package:syncrow_web/utils/style.dart'; Future showPopUpFilterMenu({ @@ -12,7 +10,6 @@ Future showPopUpFilterMenu({ Function()? cancelButton, required Map checkboxStates, required RelativeRect position, - // Function(String)? onTextFieldChanged, Function()? onOkPressed, List? list, }) async { diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart index 0bc16a94..f4b91747 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart @@ -15,8 +15,6 @@ class RolesAndPermission extends StatelessWidget { const RolesAndPermission({super.key}); @override Widget build(BuildContext context) { - final size = MediaQuery.of(context).size; - return BlocBuilder(builder: (context, state) { final _blocRole = BlocProvider.of(context); return Container( diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart index dc2eefc3..f4ccfafc 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart @@ -11,12 +11,10 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class SpacesAccessView extends StatelessWidget { - String? userId = ''; - SpacesAccessView({super.key, this.userId}); + final String? userId; + const SpacesAccessView({super.key, this.userId = ''}); @override Widget build(BuildContext context) { - final size = MediaQuery.of(context).size; - return BlocBuilder(builder: (context, state) { final _blocRole = BlocProvider.of(context); return Container( diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart index 174726f6..aa014bd5 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -210,7 +210,7 @@ class UserTableBloc extends Bloc { final query = event.query.toLowerCase(); final filteredUsers = initialUsers.where((user) { final fullName = "${user.firstName} ${user.lastName}".toLowerCase(); - final email = user.email?.toLowerCase() ?? ""; + final email = user.email.toLowerCase() ; return fullName.contains(query) || email.contains(query); }).toList(); emit(UsersLoadedState(users: filteredUsers)); @@ -224,7 +224,7 @@ class UserTableBloc extends Bloc { final startIndex = (pageNumber - 1) * itemsPerPage; final endIndex = startIndex + itemsPerPage; if (startIndex >= users.length) { - emit(UsersLoadedState(users: const [])); + emit(const UsersLoadedState(users: [])); return; } final paginatedUsers = users.sublist( @@ -235,11 +235,11 @@ class UserTableBloc extends Bloc { } void _handlePageChange(ChangePage event, Emitter emit) { - final itemsPerPage = 10; + const itemsPerPage = 10; final startIndex = (event.pageNumber - 1) * itemsPerPage; final endIndex = startIndex + itemsPerPage; if (startIndex >= users.length) { - emit(UsersLoadedState(users: [])); + emit(const UsersLoadedState(users: [])); return; } final paginatedUsers = users.sublist( @@ -318,14 +318,3 @@ class UserTableBloc extends Bloc { emit(UsersLoadedState(users: initialUsers)); } } - // void _filterOptions(FilterOptionsEvent event, Emitter emit) { - // try { - // final query = event.query.toLowerCase(); - // final filteredOptions = event.fullOptions - // .where((option) => option.toLowerCase().contains(query)) - // .toList(); - // emit(FilterOptionsState(filteredOptions)); - // } catch (e) { - // emit(ErrorState(e.toString())); - // } - // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart index 2433f9f5..62037315 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart @@ -26,8 +26,8 @@ final class RolesLoadedState extends UserTableState { } final class UsersLoadedState extends UserTableState { - List users = []; - UsersLoadedState({required this.users}); + final List users; + const UsersLoadedState({required this.users}); @override List get props => [users]; } @@ -73,9 +73,9 @@ final class SosAutomationReportErrorState extends UserTableState { } final class ChangeTapStatus extends UserTableState { - bool select = true; + final bool select; - ChangeTapStatus({required this.select}); + const ChangeTapStatus({required this.select}); @override List get props => [select]; @@ -84,7 +84,7 @@ final class ChangeTapStatus extends UserTableState { class FilterOptionsState extends UserTableState { final List filteredOptions; - FilterOptionsState(this.filteredOptions); + const FilterOptionsState(this.filteredOptions); @override List get props => [filteredOptions]; diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index 3a9f2187..1c93d44f 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -412,7 +412,7 @@ class UsersPage extends StatelessWidget { rows: state.users.map((user) { return [ Text('${user.firstName} ${user.lastName}'), - Text(user.email ?? ''), + Text(user.email ), Text(user.jobTitle ?? ''), Text(user.roleType ?? ''), Text(user.createdDate ?? ''), @@ -430,11 +430,11 @@ class UsersPage extends StatelessWidget { userId: user.uuid, onTap: user.status != "invited" ? () { - final newStatus = user.status == 'active' - ? 'disabled' - : user.status == 'disabled' - ? 'invited' - : 'active'; + // final newStatus = user.status == 'active' + // ? 'disabled' + // : user.status == 'disabled' + // ? 'invited' + // : 'active'; context.read().add( ChangeUserStatus( userId: user.uuid, @@ -501,7 +501,7 @@ class UsersPage extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - Container( + SizedBox( width: 500, child: NumberPagination( buttonRadius: 10, diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index f62437ac..ab05523f 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -131,7 +131,7 @@ class UserPermissionApi { } } - Future fetchUserById(userUuid) async { + Future fetchUserById(userUuid) async { final response = await _httpService.get( path: ApiEndpoints.getUserById.replaceAll("{userUuid}", userUuid), showServerMessage: true, From 81345f7154c9317fb9a6073cf6da32433a55090c Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 6 Jan 2025 15:41:52 +0300 Subject: [PATCH 17/17] change model url --- .../users_page/add_user_dialog/bloc/users_bloc.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index 563fca93..26a1bcc7 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -7,8 +7,8 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialo import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/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/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/user_permission.dart'; import 'package:syncrow_web/utils/constants/assets.dart';