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/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/assets/icons/atoz_icon.png b/assets/icons/atoz_icon.png new file mode 100644 index 00000000..33a9c351 Binary files /dev/null and b/assets/icons/atoz_icon.png differ diff --git a/assets/icons/box_checked.png b/assets/icons/box_checked.png new file mode 100644 index 00000000..d93b9d76 Binary files /dev/null and b/assets/icons/box_checked.png differ 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 00000000..71e79875 Binary files /dev/null and b/assets/icons/empty_box.png differ diff --git a/assets/icons/filter_table_icon.svg b/assets/icons/filter_table_icon.svg new file mode 100644 index 00000000..d90e983e --- /dev/null +++ b/assets/icons/filter_table_icon.svg @@ -0,0 +1,3 @@ + + + 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 00000000..3404c79c Binary files /dev/null and b/assets/icons/rectangle_check_box.png differ 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/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/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/assets/icons/ztoa_icon.png b/assets/icons/ztoa_icon.png new file mode 100644 index 00000000..003e5725 Binary files /dev/null and b/assets/icons/ztoa_icon.png differ 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/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 1772ef88..416e9d92 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) { 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..7979f7db --- /dev/null +++ b/lib/pages/roles_and_permission/bloc/roles_permission_state.dart @@ -0,0 +1,79 @@ +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 { + final bool 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 new file mode 100644 index 00000000..4075ac12 --- /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/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/role_type_model.dart b/lib/pages/roles_and_permission/model/role_type_model.dart new file mode 100644 index 00000000..16b24ec5 --- /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'].toString().toLowerCase().replaceAll("_", " "), + ); + } +} 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..6298dbe6 --- /dev/null +++ b/lib/pages/roles_and_permission/model/roles_user_model.dart @@ -0,0 +1,50 @@ +class RolesUserModel { + 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 new file mode 100644 index 00000000..26a1bcc7 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -0,0 +1,478 @@ +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'; +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/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'; + +class UsersBloc extends Bloc { + UsersBloc() : super(UsersInitial()) { + on(isCompleteBasicsFun); + on(_onLoadCommunityAndSpaces); + on(searchTreeNode); + on(isCompleteSpacesFun); + on(_getRolePermission); + on(_getPermissions); + on(searchRolePermission); + on(_sendInvitUser); + on(_validateBasicsStep); + on(isCompleteRoleFun); + on(checkEmail); + on(getUserById); + on(_onToggleNodeExpansion); + on(_onToggleNodeCheck); + on(_editInviteUser); + } + + void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { + if (formKey.currentState?.validate() ?? false) { + emit(const BasicsStepValidState()); + } else { + emit(const BasicsStepInvalidState()); + } + } + + String roleSelected = ''; + + final formKey = GlobalKey(); + final TextEditingController firstNameController = TextEditingController(); + final TextEditingController lastNameController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + final TextEditingController phoneController = TextEditingController(); + final TextEditingController jobTitleController = TextEditingController(); + final TextEditingController roleSearchController = TextEditingController(); + + bool? isCompleteBasics; + bool? isCompleteRolePermissions; + bool? isCompleteSpaces; + + int numberBasics = 0; + int numberSpaces = 0; + int numberRole = 0; + + void isCompleteSpacesFun( + CheckSpacesStepStatus event, Emitter emit) { + emit(UsersLoadingState()); + List selectedIds = getSelectedIds(updatedCommunities); + isCompleteSpaces = selectedIds.isNotEmpty; + emit(ChangeStatusSteps()); + } + + void isCompleteRoleFun(CheckRoleStepStatus event, Emitter emit) { + emit(UsersLoadingState()); + isCompleteRolePermissions = roleSelected != ''; + emit(ChangeStatusSteps()); + } + + Future> _fetchSpacesForCommunity( + String communityUuid) async { + return await CommunitySpaceManagementApi().getSpaceHierarchy(communityUuid); + } + + List updatedCommunities = []; + List spacesNodes = []; + + _onLoadCommunityAndSpaces( + LoadCommunityAndSpacesEvent event, Emitter emit) async { + try { + emit(UsersLoadingState()); + List communities = + await CommunitySpaceManagementApi().fetchCommunities(); + updatedCommunities = await Future.wait( + communities.map((community) async { + List spaces = + await _fetchSpacesForCommunity(community.uuid); + spacesNodes = _buildTreeNodes(spaces); + return TreeNode( + uuid: community.uuid, + title: community.name, + children: spacesNodes, + isChecked: false, + isHighlighted: false, + isExpanded: true, + ); + }).toList(), + ); + emit(const SpacesLoadedState()); + return updatedCommunities; + } catch (e) { + emit(ErrorState('Error loading communities and spaces: $e')); + } + } + + List _buildTreeNodes(List spaces) { + return spaces.map((space) { + List childNodes = + space.children.isNotEmpty ? _buildTreeNodes(space.children) : []; + return TreeNode( + uuid: space.uuid!, + title: space.name, + isChecked: false, + isHighlighted: false, + isExpanded: childNodes.isNotEmpty, + children: childNodes, + ); + }).toList(); + } + + void searchTreeNode(SearchAnode event, Emitter emit) { + emit(UsersLoadingState()); + if (event.searchTerm!.isEmpty) { + _clearHighlights(updatedCommunities); + } else { + _searchAndHighlightNodes(updatedCommunities, event.searchTerm!); + } + emit(ChangeStatusSteps()); + } + + void _clearHighlights(List nodes) { + for (var node in nodes) { + node.isHighlighted = false; + if (node.children.isNotEmpty) { + _clearHighlights(node.children); + } + } + } + + bool _searchAndHighlightNodes(List nodes, String searchTerm) { + bool anyMatch = false; + for (var node in nodes) { + bool isMatch = + node.title.toLowerCase().contains(searchTerm.toLowerCase()); + bool childMatch = _searchAndHighlightNodes(node.children, searchTerm); + node.isHighlighted = isMatch || childMatch; + + 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; + } + + 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); + roleSelected = 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; + } + + _sendInvitUser(SendInviteUsers event, Emitter emit) async { + try { + emit(UsersLoadingState()); + 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(true); + Navigator.of(event.context).pop(true); + }, + child: const Text('OK'), + ), + ], + ); + } else { + emit(const ErrorState('Failed to send invite.')); + } + emit(SaveState()); + } catch (e) { + emit(ErrorState('Failed to send invite: ${e.toString()}')); + } + } + + _editInviteUser(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) { + _clearHighlightsRolePermission(permissions); + } else { + _searchRolePermission(permissions, event.searchTerm!); + } + 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()); + 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!; + } + + void _clearHighlightsRolePermission(List nodes) { + for (var node in nodes) { + node.isHighlighted = false; + if (node.subOptions.isNotEmpty) { + _clearHighlightsRolePermission(node.subOptions); + } + } + } + + 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 + debugPrint('Printing and marking nodes in updatedCommunities:'); + _printAndMarkNodes(updatedCommunities, uuidsToMark); + } + final roleId = roles + .firstWhere((element) => + element.type == + res.roleType.toString().toLowerCase().replaceAll("_", " ")) + .uuid; + debugPrint('Role ID: $roleId'); + roleSelected = roleId; + add(PermissionEvent(roleUuid: roleSelected)); + emit(ChangeStatusSteps()); + } else {} + } else {} + } catch (_) {} + } + + void _printAndMarkNodes(List nodes, List uuidsToMark, + [int level = 0]) { + for (final node in nodes) { + if (uuidsToMark.contains(node.uuid)) { + node.isChecked = true; + debugPrint( + '${' ' * level}MATCH FOUND: Node ID: ${node.uuid}, Title: ${node.title} is marked as checked.'); + } else { + debugPrint( + '${' ' * 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()); + } + + 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))); + } + + 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 new file mode 100644 index 00000000..2e82168c --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart @@ -0,0 +1,176 @@ +import 'package:equatable/equatable.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 SendInviteUsers extends UsersEvent { + final BuildContext context; + const SendInviteUsers({required this.context}); + @override + 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 + List get props => []; +} + +class CheckRoleStepStatus extends UsersEvent { + const CheckRoleStepStatus(); + @override + List get props => []; +} + +class LoadCommunityAndSpacesEvent extends UsersEvent { + const LoadCommunityAndSpacesEvent(); + @override + 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); + @override + List get props => [uuids]; +} + +//isEditUser:widget.userId!=''? false:true +class CheckStepStatus extends UsersEvent { + final int? steps; + final bool? isEditUser; + const CheckStepStatus({this.steps, required this.isEditUser}); + @override + List get props => [steps]; +} + +class SearchAnode extends UsersEvent { + final List? nodes; + final String? searchTerm; + const SearchAnode({this.nodes, this.searchTerm}); + @override + List get props => [nodes, searchTerm]; +} + +class SearchPermission extends UsersEvent { + final List? nodes; + final String? searchTerm; + const SearchPermission({this.nodes, this.searchTerm}); + @override + List get props => [nodes, searchTerm]; +} + +class SelectedId extends UsersEvent { + final List? nodes; + const SelectedId({ + this.nodes, + }); + @override + List get props => [nodes]; +} + +class ValidateBasicsStep extends UsersEvent { + const ValidateBasicsStep(); + @override + List get props => []; +} + +class CheckEmailEvent extends UsersEvent { + const CheckEmailEvent(); + @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; + + const ToggleNodeExpansion({required this.node}); + + @override + List get props => [node]; +} + +class UpdateNodeCheckStatus extends UsersEvent { + final TreeNode node; + + const UpdateNodeCheckStatus({required this.node}); + @override + List get props => [node]; +} + +class ToggleNodeHighlightEvent extends UsersEvent { + final TreeNode node; + + const 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; + + const ToggleNodeCheckEvent(this.node); + @override + List get props => []; +} + +class ToggleNodeCheck extends UsersEvent { + final TreeNode node; + + const 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 new file mode 100644 index 00000000..4361c37f --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart @@ -0,0 +1,91 @@ +import 'package:equatable/equatable.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(); +} + +final class UsersInitial extends UsersState { + @override + List get props => []; +} + +final class RolePermissionInitial 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 SaveState extends UsersState { + @override + List get props => []; +} + +final class SpacesLoadedState extends UsersState { + const SpacesLoadedState(); + @override + List get props => []; +} + +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 { + final bool select; + + const ChangeTapStatus({required this.select}); + + @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 => []; +} + +final class ValidateBasics extends UsersState { + @override + List get props => []; +} + +class UsersLoadedState extends UsersState { + final List updatedCommunities; + + 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 new file mode 100644 index 00000000..4141ccdd --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart @@ -0,0 +1,42 @@ +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'] != null + ? 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/add_user_dialog/model/tree_node_model.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart new file mode 100644 index 00000000..a5e622dc --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/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/add_user_dialog/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart new file mode 100644 index 00000000..48df801a --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -0,0 +1,359 @@ +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 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() + ..add(const LoadCommunityAndSpacesEvent()) + ..add(const RoleEvent()), + 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: 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(), + ), + 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: false)); + } else if (currentStep == 3) { + _blocRole + .add(const CheckSpacesStepStatus()); + } + } else { + _blocRole + .add(SendInviteUsers(context: context)); + } + }); + }, + 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() { + switch (currentStep) { + case 1: + return BasicsView( + userId: '', + ); + case 2: + return SpacesAccessView(); + 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: false)); + 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: false)); + }); + }, + 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/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart new file mode 100644 index 00000000..c5025fc3 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -0,0 +1,336 @@ +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/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'; + +class BasicsView extends StatelessWidget { + final String? userId; + const BasicsView({super.key, this.userId = ''}); + @override + 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( + shrinkWrap: true, + children: [ + Text( + 'Set up the basics', + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 20, + color: Colors.black), + ), + const SizedBox( + height: 50, + ), + 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: 35, + ), + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + const Text( + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), + ), + Text( + 'First Name', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + 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", + ).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; + }, + ), + ), + ], + ), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + const Text( + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), + ), + Text('Last Name', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + )), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + onChanged: (value) { + Future.delayed(const Duration(milliseconds: 200), + () { + _blocRole.add(ValidateBasicsStep()); + }); + }, + controller: _blocRole.lastNameController, + style: const 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; + }, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + const Text( + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), + ), + Text( + 'Email Address', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + enabled: userId != '' ? false : true, + onChanged: (value) { + Future.delayed(const Duration(milliseconds: 200), () { + _blocRole.add(CheckStepStatus( + isEditUser: userId != '' ? false : true)); + _blocRole.add(ValidateBasicsStep()); + }); + }, + controller: _blocRole.emailController, + style: const TextStyle(color: ColorsManager.blackColor), + 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 Email Address'; + } + 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; + }, + ), + ), + ], + ), + const SizedBox(height: 10), + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + 'Mobile Number', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + IntlPhoneField( + pickerDialogStyle: PickerDialogStyle(), + dropdownIconPosition: IconPosition.leading, + disableLengthCheck: true, + dropdownTextStyle: + const TextStyle(color: ColorsManager.blackColor), + 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', + 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, + ) + ], + ), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + 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: + const TextStyle(color: ColorsManager.blackColor), + decoration: inputTextFormDeco( + hintText: "Job Title (Optional)") + .copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + keyboardType: TextInputType.phone, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 20), + ], + ), + ); + }); + } +} 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 new file mode 100644 index 00000000..837ee62c --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DeleteUserDialog extends StatefulWidget { + final Function()? onTapDelete; + DeleteUserDialog({super.key, this.onTapDelete}); + + @override + _DeleteUserDialogState createState() => _DeleteUserDialogState(); +} + +class _DeleteUserDialogState extends State { + int currentStep = 1; + + @override + Widget build(BuildContext context) { + 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, + ), + ), + ), + 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( + '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/permission_management.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart new file mode 100644 index 00000000..266d431e --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart @@ -0,0 +1,295 @@ +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 Container( + color: option.isHighlighted + ? Colors.blue.shade50 + : Colors.white, + 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), + ), + ], + ), + ); + }, + ), + ) + ], + ); + }).toList(), + ], + ); + }, + ); + } +} + + + // 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 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..7c3c1ba5 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/style.dart'; + +Future showPopUpFilterMenu({ + required BuildContext context, + Function()? onSortAtoZ, + Function()? onSortZtoA, + Function()? cancelButton, + required Map checkboxStates, + required RelativeRect position, + Function()? onOkPressed, + List? list, +}) async { + + + await showMenu( + context: context, + position:position, + + + 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), + ) + // 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: 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: 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(); + }, + ); + }, + ), + ), + ), + ), + 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/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..f4b91747 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart @@ -0,0 +1,125 @@ +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) { + 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/add_user_dialog/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart new file mode 100644 index 00000000..f4ccfafc --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart @@ -0,0 +1,122 @@ +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/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 { + final String? userId; + const SpacesAccessView({super.key, this.userId = ''}); + @override + Widget build(BuildContext context) { + 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, + onChanged: (value) { + _blocRole.add(SearchAnode( + nodes: _blocRole.updatedCommunities, + 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: TreeView(userId: userId)))) + ], + ), + ), + ), + ], + ), + ), + ); + }); + } +} 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..aa014bd5 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -0,0 +1,320 @@ +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()) { + on(_getUsers); + on(_changeUserStatus); + on(_toggleSortUsersByNameAsc); + on(_toggleSortUsersByNameDesc); + on(_toggleSortUsersByDateOldestToNewest); + on(_toggleSortUsersByDateNewestToOldest); + on(_searchUsers); + on(_handlePageChange); + on(_filterUsersByRole); + on(_filterUsersByJobTitle); + on(_filterUsersByCreated); + on(_filterUserStatus); + on(_deleteUser); + on(_filterClear); + } + int itemsPerPage = 20; + int currentPage = 1; + List users = []; + List initialUsers = []; + String currentSortOrder = ''; + String currentSortOrderDate = ''; + List roleTypes = []; + List jobTitle = []; + List createdBy = []; + List status = ['active', 'invited', 'disabled']; + + Future _getUsers(GetUsers event, Emitter emit) async { + emit(UsersLoadingState()); + try { + roleTypes.clear(); + jobTitle.clear(); + createdBy.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()); + } + 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()); + // } + initialUsers = List.from(users); + roleTypes = roleTypes.toSet().toList(); + jobTitle = jobTitle.toSet().toList(); + createdBy = createdBy.toSet().toList(); + // deActivate = deActivate.toSet().toList(); + _handlePageChange(ChangePage(1), emit); + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + Future _deleteUser( + DeleteUserEvent event, Emitter emit) async { + emit(UsersLoadingState()); + try { + 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())); + } + } + + void _toggleSortUsersByNameAsc( + SortUsersByNameAsc event, Emitter emit) { + if (currentSortOrder == "Asc") { + emit(UsersLoadingState()); + currentSortOrder = ""; + users = List.from(users); + emit(UsersLoadedState(users: users)); + } else { + emit(UsersLoadingState()); + currentSortOrder = "Asc"; + users.sort((a, b) => a.firstName!.compareTo(b.firstName!)); + emit(UsersLoadedState(users: users)); + } + } + + void _toggleSortUsersByNameDesc( + SortUsersByNameDesc event, Emitter emit) { + if (currentSortOrder == "Desc") { + 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.firstName!.compareTo(a.firstName!)); + emit(UsersLoadedState(users: users)); + } + } + + void _toggleSortUsersByDateNewestToOldest( + DateNewestToOldestEvent event, Emitter emit) { + if (currentSortOrderDate == "NewestToOldest") { + emit(UsersLoadingState()); + currentSortOrder = ""; + currentSortOrderDate = ""; + users = List.from(initialUsers); + emit(UsersLoadedState(users: users)); + } else { + emit(UsersLoadingState()); + users.sort((a, b) { + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateB.compareTo(dateA); + }); + emit(UsersLoadedState(users: users)); + } + } + + void _toggleSortUsersByDateOldestToNewest( + DateOldestToNewestEvent event, Emitter emit) { + if (currentSortOrderDate == "OldestToNewest") { + emit(UsersLoadingState()); + currentSortOrder = ""; + currentSortOrderDate = ""; + users = List.from(initialUsers); + emit(UsersLoadedState(users: users)); + } else { + emit(UsersLoadingState()); + users.sort((a, b) { + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateA.compareTo(dateB); + }); + emit(UsersLoadedState(users: users)); + } + } + + DateTime _parseDateTime(String date) { + try { + final dateParts = date.split('/'); + final day = int.parse(dateParts[0]); + final month = int.parse(dateParts[1]); + final year = int.parse(dateParts[2]); + 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(const UsersLoadedState(users: [])); + return; + } + final paginatedUsers = users.sublist( + startIndex, + endIndex > users.length ? users.length : endIndex, + ); + emit(UsersLoadedState(users: paginatedUsers)); + } + + void _handlePageChange(ChangePage event, Emitter emit) { + const itemsPerPage = 10; + final startIndex = (event.pageNumber - 1) * itemsPerPage; + final endIndex = startIndex + itemsPerPage; + if (startIndex >= users.length) { + emit(const UsersLoadedState(users: [])); + return; + } + final paginatedUsers = users.sublist( + startIndex, + endIndex > users.length ? users.length : endIndex, + ); + emit(UsersLoadedState(users: paginatedUsers)); + } + + Set selectedRoles = {}; + Set selectedJobTitles = {}; + Set selectedCreatedBy = {}; + Set selectedStatuses = {}; + + void _filterUsersByRole( + FilterUsersByRoleEvent event, Emitter emit) { + selectedRoles = event.selectedRoles.toSet(); + + final filteredUsers = initialUsers.where((user) { + if (selectedRoles.isEmpty) return true; + return selectedRoles.contains(user.roleType); + }).toList(); + + emit(UsersLoadedState(users: filteredUsers)); + } + + void _filterUsersByJobTitle( + FilterUsersByJobEvent event, Emitter emit) { + 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) { + selectedCreatedBy = event.selectedCreatedBy.toSet(); + + final filteredUsers = initialUsers.where((user) { + if (selectedCreatedBy.isEmpty) return true; + return selectedCreatedBy.contains(user.invitedBy); + }).toList(); + + emit(UsersLoadedState(users: filteredUsers)); + } + + void _filterUserStatus( + FilterUsersByDeActevateEvent event, Emitter emit) { + selectedStatuses = event.selectedActivate.toSet(); + + final filteredUsers = initialUsers.where((user) { + 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)); + } +} 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..a81002ad --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart @@ -0,0 +1,137 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.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 => []; +} + +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]; +} + +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 new file mode 100644 index 00000000..62037315 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart @@ -0,0 +1,91 @@ +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 { + final List users; + const 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 { + final bool select; + + const ChangeTapStatus({required this.select}); + + @override + List get props => [select]; +} + +class FilterOptionsState extends UserTableState { + final List filteredOptions; + + const FilterOptionsState(this.filteredOptions); + + @override + List get props => [filteredOptions]; +} 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..6662a9e2 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -0,0 +1,356 @@ +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(); + setState(() { + 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( + 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: const 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 != 8 && + 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 + 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), + ) + ], + ), + ], + ), + ), + ), + ) + : 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) { + 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: [ + 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, + ), + ); + }), + ), + ], + ); + }, + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} + + + + // 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/users_table/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart new file mode 100644 index 00000000..1c93d44f --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -0,0 +1,542 @@ +import 'package:flutter/material.dart'; +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'; +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'; +import 'package:syncrow_web/utils/style.dart'; + +class UsersPage extends StatelessWidget { + 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: onTap, + 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 BlocBuilder( + builder: (context, state) { + final screenSize = MediaQuery.of(context).size; + 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: Align( + alignment: Alignment.topCenter, + 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) { + _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: [ + SizedBox( + 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) { + 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..b08b14ea --- /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..44944ed3 --- /dev/null +++ b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart @@ -0,0 +1,94 @@ +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_state.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'; + +class RolesAndPermissionPage extends StatelessWidget { + const RolesAndPermissionPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => RolesPermissionBloc(), + 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) => UserTableBloc()..add(const GetUsers()), + child: 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/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index 56961892..791bf947 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -83,7 +83,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/services/space_mana_api.dart b/lib/services/space_mana_api.dart index da70d46f..be789844 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -260,6 +260,7 @@ class CommunitySpaceManagementApi { .replaceAll('{communityId}', communityId) .replaceAll('{projectId}', TempConst.projectId), 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 new file mode 100644 index 00000000..ab05523f --- /dev/null +++ b/lib/services/user_permission.dart @@ -0,0 +1,224 @@ +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'; + +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, + 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 ?? []; + } + + Future sendInviteUser({ + String? firstName, + String? lastName, + String? email, + String? jobTitle, + String? phoneNumber, + String? roleUuid, + List? spaceUuids, + }) async { + try { + final body = { + "firstName": firstName, + "lastName": lastName, + "email": email, + "jobTitle": jobTitle != '' ? jobTitle : " ", + "phoneNumber": phoneNumber != '' ? phoneNumber : " ", + "roleUuid": roleUuid, + "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; + } + }, + ); + print('sendInviteUser=$body'); + + return response ?? []; + } on DioException catch (e) { + return false; + } catch (e) { + return false; + } + } + + Future checkEmail(String email) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.checkEmail, + showServerMessage: true, + body: {"email": email}, + expectedResponseModel: (json) { + if (json['statusCode'] != 400) { + var message = json["message"]; + if (message is String) { + return message; + } else { + return 'Unexpected message format'; + } + } + return null; + }, + ); + return response ?? 'Unknown error occurred'; + } on DioException catch (e) { + if (e.response != null) { + final errorMessage = e.response?.data['error']; + return errorMessage is String + ? errorMessage + : 'Error occurred while checking email'; + } + return 'Error occurred while checking email'; + } catch (e) { + 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/app_routes.dart b/lib/utils/app_routes.dart index 31329d8a..ccc450fe 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/all_spaces/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) => const 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 60aad8d5..d1d111dd 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -60,5 +60,14 @@ abstract class ColorsManager { static const Color softGray = Color(0xFFD5D5D5); static const Color semiTransparentBlack = Color(0x19000000); static const Color dataHeaderGrey = Color(0x33999999); + 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/api_const.dart b/lib/utils/constants/api_const.dart index a2ff63dc..92a2581c 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'; @@ -101,4 +102,18 @@ abstract class ApiEndpoints { static const String listSpaceModels = '/projects/{projectId}/space-models'; static const String createSpaceModel = '/projects/{projectId}/space-models'; static const String getSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}'; + + 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}'; } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 958c2c1c..1f4074c4 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,36 @@ 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'; + 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'; + 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 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/lib/utils/user_drop_down_menu.dart b/lib/utils/user_drop_down_menu.dart index cee9bbe9..2d740e27 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, @@ -85,29 +86,31 @@ class _UserDropdownMenuState extends State { // ), // ), // ), - // PopupMenuItem( - // onTap: () {}, - // child: ListTile( - // leading: SvgPicture.asset(Assets.settings), - // title: Text( - // "Settings", - // style: context.textTheme.bodyMedium, - // ), - // ), - // ), + PopupMenuItem( + onTap: () { + context.go(RoutesConst.rolesAndPermissions); + }, + child: ListTile( + leading: SvgPicture.asset(Assets.userManagement), + title: Text( + "User Management", + style: context.textTheme.bodyMedium, + ), + ), + ), PopupMenuItem( onTap: () { showDialog( context: context, builder: (BuildContext context) { - final size = MediaQuery.of(context).size; return AlertDialog( alignment: Alignment.center, content: SizedBox( 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 +123,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 +157,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 +173,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 +202,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, ), 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 192106d7..d1c39c65 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -304,6 +304,14 @@ 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" js: dependency: transitive description: @@ -316,18 +324,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 +372,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: @@ -384,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: @@ -593,10 +609,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 +673,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: diff --git a/pubspec.yaml b/pubspec.yaml index ffff62ac..786a39c9 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 + number_pagination: ^1.1.6 dev_dependencies: flutter_test: