From 3876909beade9c20ef85842367554c1e8547256b Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 5 Jan 2025 17:27:26 +0300 Subject: [PATCH] edit_user and pagination and search and filter --- .../model/edit_user_model.dart | 267 +++++++++++++ .../model/roles_user_model.dart | 68 +++- .../add_user_dialog/bloc/users_bloc.dart | 232 ++++++++++- .../add_user_dialog/bloc/users_event.dart | 90 ++++- .../add_user_dialog/bloc/users_status.dart | 8 + .../add_user_dialog/view/add_user_dialog.dart | 15 +- .../add_user_dialog/view/basics_view.dart | 8 +- .../add_user_dialog/view/build_tree_view.dart | 146 +++++++ .../view/delete_user_dialog.dart | 129 +++++-- .../view/edit_user_dialog.dart | 364 ++++++++++++++++++ .../view/popup_menu_filter.dart | 108 ++++++ .../view/spaces_access_view.dart | 161 +------- .../users_table/bloc/user_table_bloc.dart | 259 +++++++++---- .../users_table/bloc/user_table_event.dart | 62 ++- .../users_table/view/user_table.dart | 136 ++++--- .../users_table/view/users_page.dart | 245 ++++++++++-- .../view/roles_and_permission_page.dart | 2 +- lib/services/space_mana_api.dart | 1 + lib/services/user_permission.dart | 114 ++++++ lib/utils/constants/api_const.dart | 8 +- macos/Podfile.lock | 36 ++ macos/Runner.xcodeproj/project.pbxproj | 98 ++++- .../contents.xcworkspacedata | 3 + pubspec.lock | 8 + pubspec.yaml | 1 + 25 files changed, 2156 insertions(+), 413 deletions(-) create mode 100644 lib/pages/roles_and_permission/model/edit_user_model.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart create mode 100644 macos/Podfile.lock diff --git a/lib/pages/roles_and_permission/model/edit_user_model.dart b/lib/pages/roles_and_permission/model/edit_user_model.dart new file mode 100644 index 00000000..17fba1a4 --- /dev/null +++ b/lib/pages/roles_and_permission/model/edit_user_model.dart @@ -0,0 +1,267 @@ +// import 'dart:convert'; + +// // Model for Space +// class UserSpaceModel { +// final String uuid; +// final DateTime createdAt; +// final DateTime updatedAt; + +// UserSpaceModel({ +// required this.uuid, +// required this.createdAt, +// required this.updatedAt, +// }); + +// factory UserSpaceModel.fromJson(Map json) { +// return UserSpaceModel( +// uuid: json['uuid'], +// createdAt: DateTime.parse(json['createdAt']), +// updatedAt: DateTime.parse(json['updatedAt']), +// ); +// } + +// Map toJson() { +// return { +// 'uuid': uuid, +// 'createdAt': createdAt.toIso8601String(), +// 'updatedAt': updatedAt.toIso8601String(), +// }; +// } +// } + +// // Model for User +// class EditUserModel { +// final String uuid; +// final DateTime createdAt; +// final dynamic email; +// final dynamic? jobTitle; +// final dynamic status; +// final String firstName; +// final String lastName; +// final String? phoneNumber; +// final bool isEnabled; +// final dynamic invitedBy; +// final dynamic roleType; +// final List spaces; +// final String createdDate; +// final String createdTime; + +// EditUserModel({ +// required this.uuid, +// required this.createdAt, +// required this.email, +// this.jobTitle, +// required this.status, +// required this.firstName, +// required this.lastName, +// this.phoneNumber, +// required this.isEnabled, +// required this.invitedBy, +// required this.roleType, +// required this.spaces, +// required this.createdDate, +// required this.createdTime, +// }); + +// factory EditUserModel.fromJson(Map json) { +// var spacesList = (json['spaces'] as List) +// .map((spaceJson) => UserSpaceModel.fromJson(spaceJson)) +// .toList(); + +// return EditUserModel( +// uuid: json['uuid'], +// createdAt: DateTime.parse(json['createdAt']), +// email: json['email'], +// jobTitle: json['jobTitle'], +// status: json['status'], +// firstName: json['firstName'], +// lastName: json['lastName'], +// phoneNumber: json['phoneNumber'], +// isEnabled: json['isEnabled'], +// invitedBy: json['invitedBy'], +// roleType: json['roleType'], +// spaces: spacesList, +// createdDate: json['createdDate'], +// createdTime: json['createdTime'], +// ); +// } + +// Map toJson() { +// return { +// 'uuid': uuid, +// 'createdAt': createdAt.toIso8601String(), +// 'email': email, +// 'jobTitle': jobTitle, +// 'status': status, +// 'firstName': firstName, +// 'lastName': lastName, +// 'phoneNumber': phoneNumber, +// 'isEnabled': isEnabled, +// 'invitedBy': invitedBy, +// 'roleType': roleType, +// 'spaces': spaces.map((space) => space.toJson()).toList(), +// 'createdDate': createdDate, +// 'createdTime': createdTime, +// }; +// } +// } + +class UserProjectResponse { + final int statusCode; + final String message; + final EditUserModel data; + final bool success; + + UserProjectResponse({ + required this.statusCode, + required this.message, + required this.data, + required this.success, + }); + + /// Create a [UserProjectResponse] from JSON data + factory UserProjectResponse.fromJson(Map json) { + return UserProjectResponse( + statusCode: json['statusCode'] as int, + message: json['message'] as String, + data: EditUserModel.fromJson(json['data'] as Map), + success: json['success'] as bool, + ); + } + + /// Convert the [UserProjectResponse] to JSON + Map toJson() { + return { + 'statusCode': statusCode, + 'message': message, + 'data': data.toJson(), + 'success': success, + }; + } +} + +class EditUserModel { + final String uuid; + final String firstName; + final String lastName; + final String email; + final String createdDate; // e.g. "1/3/2025" + final String createdTime; // e.g. "8:41:43 AM" + final String status; // e.g. "invited" + final String invitedBy; // e.g. "SUPER_ADMIN" + final String phoneNumber; // can be empty + final String jobTitle; // can be empty + final String roleType; // e.g. "ADMIN" + final List spaces; + + EditUserModel({ + required this.uuid, + required this.firstName, + required this.lastName, + required this.email, + required this.createdDate, + required this.createdTime, + required this.status, + required this.invitedBy, + required this.phoneNumber, + required this.jobTitle, + required this.roleType, + required this.spaces, + }); + + /// Create a [UserData] from JSON data + factory EditUserModel.fromJson(Map json) { + return EditUserModel( + uuid: json['uuid'] as String, + firstName: json['firstName'] as String, + lastName: json['lastName'] as String, + email: json['email'] as String, + createdDate: json['createdDate'] as String, + createdTime: json['createdTime'] as String, + status: json['status'] as String, + invitedBy: json['invitedBy'] as String, + phoneNumber: json['phoneNumber'] ?? '', + jobTitle: json['jobTitle'] ?? '', + roleType: json['roleType'] as String, + spaces: (json['spaces'] as List) + .map((e) => UserSpaceModel.fromJson(e as Map)) + .toList(), + ); + } + + /// Convert the [UserData] to JSON + Map toJson() { + return { + 'uuid': uuid, + 'firstName': firstName, + 'lastName': lastName, + 'email': email, + 'createdDate': createdDate, + 'createdTime': createdTime, + 'status': status, + 'invitedBy': invitedBy, + 'phoneNumber': phoneNumber, + 'jobTitle': jobTitle, + 'roleType': roleType, + 'spaces': spaces.map((e) => e.toJson()).toList(), + }; + } +} + +class UserSpaceModel { + final String uuid; + final String createdAt; // e.g. "2024-11-04T07:20:35.940Z" + final String updatedAt; // e.g. "2024-11-28T18:47:29.736Z" + final dynamic spaceTuyaUuid; + final dynamic spaceName; + final dynamic invitationCode; + final bool disabled; + final double x; + final double y; + final String icon; + + UserSpaceModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.spaceTuyaUuid, + required this.spaceName, + required this.invitationCode, + required this.disabled, + required this.x, + required this.y, + required this.icon, + }); + + /// Create a [UserSpaceModel] from JSON data + factory UserSpaceModel.fromJson(Map json) { + return UserSpaceModel( + uuid: json['uuid'] as String, + createdAt: json['createdAt'] as String, + updatedAt: json['updatedAt'] as String, + spaceTuyaUuid: json['spaceTuyaUuid'] as String?, + spaceName: json['spaceName'] as String, + invitationCode: json['invitationCode'] as String?, + disabled: json['disabled'] as bool, + x: (json['x'] as num).toDouble(), + y: (json['y'] as num).toDouble(), + icon: json['icon'] as String, + ); + } + + /// Convert the [UserSpaceModel] to JSON + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt, + 'updatedAt': updatedAt, + 'spaceTuyaUuid': spaceTuyaUuid, + 'spaceName': spaceName, + 'invitationCode': invitationCode, + 'disabled': disabled, + 'x': x, + 'y': y, + 'icon': icon, + }; + } +} diff --git a/lib/pages/roles_and_permission/model/roles_user_model.dart b/lib/pages/roles_and_permission/model/roles_user_model.dart index 244c58de..6298dbe6 100644 --- a/lib/pages/roles_and_permission/model/roles_user_model.dart +++ b/lib/pages/roles_and_permission/model/roles_user_model.dart @@ -1,22 +1,50 @@ class RolesUserModel { - String? id; - String? userName; - String? userEmail; - String? userRole; - String? creationDate; - String? creationTime; - String? createdBy; - String? status; - String? action; - RolesUserModel( - {this.id, - this.userName, - this.userEmail, - this.userRole, - this.creationDate, - this.creationTime, - this.status, - this.action, - this.createdBy, - }); + final String uuid; + final DateTime createdAt; + final String email; + final dynamic firstName; + final dynamic lastName; + final dynamic roleType; + final dynamic status; + final bool isEnabled; + final String invitedBy; + final dynamic phoneNumber; + final dynamic jobTitle; + final dynamic createdDate; + final dynamic createdTime; + + RolesUserModel({ + required this.uuid, + required this.createdAt, + required this.email, + required this.firstName, + required this.lastName, + required this.roleType, + required this.status, + required this.isEnabled, + required this.invitedBy, + this.phoneNumber, + this.jobTitle, + required this.createdDate, + required this.createdTime, + }); + + factory RolesUserModel.fromJson(Map json) { + return RolesUserModel( + uuid: json['uuid'], + createdAt: DateTime.parse(json['createdAt']), + email: json['email'], + firstName: json['firstName'], + lastName: json['lastName'], + roleType: json['roleType'].toString().toLowerCase().replaceAll("_", " "), + status: json['status'], + isEnabled: json['isEnabled'], + invitedBy: + json['invitedBy'].toString().toLowerCase().replaceAll("_", " "), + phoneNumber: json['phoneNumber'], + jobTitle: json['jobTitle'].toString(), + createdDate: json['createdDate'], + createdTime: json['createdTime'], + ); + } } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index dbace8ad..730825cb 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -1,6 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/common/custom_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/edit_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; @@ -25,6 +26,11 @@ class UsersBloc extends Bloc { on(_validateBasicsStep); on(isCompleteRoleFun); on(checkEmail); + on(getUserById); + on(_onToggleNodeExpansion); + on(_onToggleNodeCheck); + on(_editInvitUser); + } void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { if (formKey.currentState?.validate() ?? false) { @@ -42,9 +48,7 @@ class UsersBloc extends Bloc { final TextEditingController emailController = TextEditingController(); final TextEditingController phoneController = TextEditingController(); final TextEditingController jobTitleController = TextEditingController(); - final TextEditingController roleSearchController = TextEditingController(); - // final TextEditingController jobTitleController = TextEditingController(); bool? isCompleteBasics; bool? isCompleteRolePermissions; @@ -84,6 +88,7 @@ class UsersBloc extends Bloc { await CommunitySpaceManagementApi().fetchCommunities(); updatedCommunities = await Future.wait( communities.map((community) async { + print(community.uuid); List spaces = await _fetchSpacesForCommunity(community.uuid); spacesNodes = _buildTreeNodes(spaces); @@ -228,8 +233,8 @@ class UsersBloc extends Bloc { actions: [ TextButton( onPressed: () { - Navigator.of(event.context).pop(); - Navigator.of(event.context).pop(); + Navigator.of(event.context).pop(true); + Navigator.of(event.context).pop(true); }, child: const Text('OK'), ), @@ -244,6 +249,48 @@ class UsersBloc extends Bloc { } } + _editInvitUser(EditInviteUsers event, Emitter emit) async { + try { + emit(UsersLoadingState()); + List selectedIds = getSelectedIds(updatedCommunities) ?? []; + bool res = await UserPermissionApi().editInviteUser( + userId: event.userId, + firstName: firstNameController.text, + jobTitle: jobTitleController.text, + lastName: lastNameController.text, + phoneNumber: phoneController.text, + roleUuid: roleSelected, + spaceUuids: selectedIds, + ); + if (res == true) { + showCustomDialog( + barrierDismissible: false, + context: event.context, + message: "The invite was sent successfully.", + iconPath: Assets.deviceNoteIcon, + title: "Invite Success", + dialogHeight: MediaQuery.of(event.context).size.height * 0.3, + actions: [ + TextButton( + onPressed: () { + Navigator.of(event.context).pop(true); + Navigator.of(event.context).pop(true); + }, + child: const Text('OK'), + ), + ], + ).then( + (value) {}, + ); + } else { + emit(const ErrorState('Failed to send invite.')); + } + emit(SaveState()); + } catch (e) { + emit(ErrorState('Failed to send invite: ${e.toString()}')); + } + } + void searchRolePermission(SearchPermission event, Emitter emit) { emit(UsersLoadingState()); if (event.searchTerm!.isEmpty) { @@ -268,18 +315,22 @@ class UsersBloc extends Bloc { bool isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { emit(UsersLoadingState()); - add(const CheckEmailEvent()); - final emailRegex = RegExp( - r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', - ); - bool isEmailValid = emailRegex.hasMatch(emailController.text); - bool isEmailServerValid = checkEmailValid == 'Valid email'; - isCompleteBasics = firstNameController.text.isNotEmpty && - lastNameController.text.isNotEmpty && - emailController.text.isNotEmpty && - isEmailValid && - isEmailServerValid; - + if (event.isEditUser == false) { + add(const CheckEmailEvent()); + final emailRegex = RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + ); + bool isEmailValid = emailRegex.hasMatch(emailController.text); + bool isEmailServerValid = checkEmailValid == 'Valid email'; + isCompleteBasics = firstNameController.text.isNotEmpty && + lastNameController.text.isNotEmpty && + emailController.text.isNotEmpty && + isEmailValid && + isEmailServerValid; + } else { + isCompleteBasics = firstNameController.text.isNotEmpty && + lastNameController.text.isNotEmpty; + } emit(ChangeStatusSteps()); emit(ValidateBasics()); return isCompleteBasics!; @@ -293,4 +344,153 @@ class UsersBloc extends Bloc { } } } + + EditUserModel? res = EditUserModel( + spaces: [], + jobTitle: '', + phoneNumber: '', + uuid: '', + email: '', + firstName: '', + lastName: '', + roleType: '', + status: '', + invitedBy: '', + createdDate: '', + createdTime: ''); + + Future getUserById( + GetUserByIdEvent event, + Emitter emit, + ) async { + emit(UsersLoadingState()); + + try { + if (event.uuid?.isNotEmpty ?? false) { + final res = await UserPermissionApi().fetchUserById(event.uuid); + + if (res != null) { + // Populate the text controllers + firstNameController.text = res.firstName; + lastNameController.text = res.lastName; + emailController.text = res.email; + phoneController.text = res.phoneNumber ?? ''; + jobTitleController.text = res.jobTitle ?? ''; + res.roleType; + if (updatedCommunities.isNotEmpty) { + // Create a list of UUIDs to mark + final uuidsToMark = res.spaces.map((space) => space.uuid).toList(); + // Print all IDs and mark nodes in updatedCommunities + print('Printing and marking nodes in updatedCommunities:'); + _printAndMarkNodes(updatedCommunities, uuidsToMark); + } else { + print('updatedCommunities is empty!'); + } + final roleId = roles + .firstWhere((element) => + element.type == + res.roleType.toString().toLowerCase().replaceAll("_", " ")) + .uuid; + print('Role ID: $roleId'); + roleSelected = roleId; + add(PermissionEvent(roleUuid: roleSelected)); + emit(ChangeStatusSteps()); + } else { + // emit(UsersErrorState("User not found")); + } + } else { + // emit(UsersErrorState("Invalid user ID")); + } + } catch (e) { + print("Failed to fetch user data: $e"); + // emit(UsersErrorState("Failed to fetch user data: $e")); + } + } + + /// Recursively print all the node IDs, including nested children. + /// Recursively print all node IDs and mark nodes as `isChecked` if their UUID exists in the list. + void _printAndMarkNodes(List nodes, List uuidsToMark, + [int level = 0]) { + for (final node in nodes) { + // Check if the current node's UUID exists in the list of UUIDs to mark. + if (uuidsToMark.contains(node.uuid)) { + node.isChecked = true; // Mark the node as checked. + print( + '${' ' * level}MATCH FOUND: Node ID: ${node.uuid}, Title: ${node.title} is marked as checked.'); + } else { + print('${' ' * level}Node ID: ${node.uuid}, Title: ${node.title}'); + } + if (node.children.isNotEmpty) { + _printAndMarkNodes(node.children, uuidsToMark, level + 1); + } + } + } + + void _onToggleNodeExpansion( + ToggleNodeExpansion event, + Emitter emit, + ) { + emit(UsersLoadingState()); + event.node.isExpanded = !event.node.isExpanded; + emit(ChangeStatusSteps()); + } + + void _onToggleNodeCheck( + ToggleNodeCheck event, + Emitter emit, + ) { + emit(UsersLoadingState()); + //Toggle node's checked state + event.node.isChecked = !event.node.isChecked; + debugPrint( + 'Node toggled. ID: ${event.node.uuid}, isChecked: ${event.node.isChecked}', + ); + // Update children and parent + _updateChildrenCheckStatus(event.node, event.node.isChecked); + _updateParentCheckStatus(event.node); + + // Finally, emit a new state + emit(ChangeStatusSteps()); + } + +// Existing methods that remain in the BLoC: + + void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { + for (var child in node.children) { + child.isChecked = isChecked; + _updateChildrenCheckStatus(child, isChecked); + } + } + + void _updateParentCheckStatus(TreeNode node) { + TreeNode? parent = _findParent(updatedCommunities, node); + if (parent != null) { + parent.isChecked = _areAllChildrenChecked(parent); + _updateParentCheckStatus(parent); + } + } + + bool _areAllChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.every((child) => + child.isChecked && + (child.children.isEmpty || _areAllChildrenChecked(child))); + } + +// Private helper method to find the parent of a given node. + TreeNode? _findParent(List nodes, TreeNode target) { + for (var node in nodes) { + if (node.children.contains(target)) { + return node; + } + final parent = _findParent(node.children, target); + if (parent != null) { + return parent; + } + } + return null; + } + + + } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart index 950726d4..0f4631ff 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart @@ -14,6 +14,14 @@ class SendInviteUsers extends UsersEvent { List get props => [context]; } +class EditInviteUsers extends UsersEvent { + final BuildContext context; + final String userId; + const EditInviteUsers({required this.context, required this.userId}); + @override + List get props => [context, userId]; +} + class CheckSpacesStepStatus extends UsersEvent { const CheckSpacesStepStatus(); @override @@ -52,9 +60,11 @@ class GetBatchStatus extends UsersEvent { List get props => [uuids]; } +//isEditUser:widget.userId!=''? false:true class CheckStepStatus extends UsersEvent { final int? steps; - const CheckStepStatus({this.steps}); + bool? isEditUser = false; + CheckStepStatus({this.steps, required this.isEditUser}); @override List get props => [steps]; } @@ -85,7 +95,7 @@ class SelecteId extends UsersEvent { } class ValidateBasicsStep extends UsersEvent { - const ValidateBasicsStep(); + ValidateBasicsStep(); @override List get props => []; } @@ -95,3 +105,79 @@ class CheckEmailEvent extends UsersEvent { @override List get props => []; } + +class GetUserByIdEvent extends UsersEvent { + final String? uuid; + const GetUserByIdEvent({this.uuid}); + @override + List get props => [uuid]; +} + +class ToggleNodeExpansion extends UsersEvent { + final TreeNode node; + + ToggleNodeExpansion({required this.node}); + + @override + List get props => [node]; +} + +class UpdateNodeCheckStatus extends UsersEvent { + final TreeNode node; + + UpdateNodeCheckStatus({required this.node}); + @override + List get props => [node]; +} + +// Define new events +class ToggleNodeHighlightEvent extends UsersEvent { + final TreeNode node; + + ToggleNodeHighlightEvent(this.node); + @override + List get props => [node]; +} + +class ExpandAllNodesEvent extends UsersEvent { + @override + List get props => []; +} + +class CollapseAllNodesEvent extends UsersEvent { + @override + List get props => []; +} + +class ClearSelectionsEvent extends UsersEvent { + @override + List get props => []; +} + +class ToggleNodeCheckEvent extends UsersEvent { + final TreeNode node; + + ToggleNodeCheckEvent(this.node); + @override + List get props => []; +} + +// users_event.dart + +// 1. Extend UsersEvent +class ToggleNodeCheck extends UsersEvent { + final TreeNode node; + + // 2. Add a constructor that takes the node to toggle + ToggleNodeCheck(this.node); + @override + List get props => []; +} + +class EditUserEvent extends UsersEvent { + const EditUserEvent(); + @override + List get props => []; +} + + diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart index c1bf3512..646dccfd 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; sealed class UsersState extends Equatable { const UsersState(); @@ -80,3 +81,10 @@ final class ValidateBasics extends UsersState { @override List get props => []; } +class UsersLoadedState extends UsersState { + final List updatedCommunities; + + UsersLoadedState({required this.updatedCommunities}); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart index 14f7a0fb..48df801a 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -113,7 +113,8 @@ class _AddNewUserDialogState extends State { if (currentStep < 3) { currentStep++; if (currentStep == 2) { - _blocRole.add(const CheckStepStatus()); + _blocRole.add( + CheckStepStatus(isEditUser: false)); } else if (currentStep == 3) { _blocRole .add(const CheckSpacesStepStatus()); @@ -150,9 +151,11 @@ class _AddNewUserDialogState extends State { Widget _getFormContent() { switch (currentStep) { case 1: - return const BasicsView(); + return BasicsView( + userId: '', + ); case 2: - return const SpacesAccessView(); + return SpacesAccessView(); case 3: return const RolesAndPermission(); default: @@ -169,7 +172,7 @@ class _AddNewUserDialogState extends State { bloc.add(const CheckSpacesStepStatus()); currentStep = step; Future.delayed(const Duration(milliseconds: 500), () { - bloc.add(const ValidateBasicsStep()); + bloc.add(ValidateBasicsStep()); }); }); @@ -234,7 +237,7 @@ class _AddNewUserDialogState extends State { onTap: () { setState(() { currentStep = step; - bloc.add(const CheckStepStatus()); + bloc.add(CheckStepStatus(isEditUser: false)); if (step3 == 3) { bloc.add(const CheckRoleStepStatus()); } @@ -299,7 +302,7 @@ class _AddNewUserDialogState extends State { currentStep = step; step3 = step; bloc.add(const CheckSpacesStepStatus()); - bloc.add(const CheckStepStatus()); + bloc.add(CheckStepStatus(isEditUser: false)); }); }, child: Column( diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index a6ec686b..bbca9aaa 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -11,7 +11,8 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class BasicsView extends StatelessWidget { - const BasicsView({super.key}); + String? userId = ''; + BasicsView({super.key, this.userId}); @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { @@ -184,10 +185,11 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( + enabled: userId!=''? false:true, onChanged: (value) { Future.delayed(const Duration(milliseconds: 200), () { - _blocRole.add(const CheckStepStatus()); - _blocRole.add(ValidateBasicsStep()); + _blocRole.add(CheckStepStatus(isEditUser:userId!=''? false:true)); + _blocRole.add( ValidateBasicsStep()); }); }, controller: _blocRole.emailController, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart new file mode 100644 index 00000000..b7fc1085 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart @@ -0,0 +1,146 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class TreeView extends StatelessWidget { + final String? userId; + + const TreeView({ + super.key, + this.userId, + }); + + @override + Widget build(BuildContext context) { + final _blocRole = BlocProvider.of(context); + debugPrint('TreeView constructed with userId = $userId'); + return BlocProvider( + create: (_) => UsersBloc(), + // ..add(const LoadCommunityAndSpacesEvent()), + child: BlocConsumer( + listener: (context, state) { + // if (state is SpacesLoadedState) { + // _blocRole.add(GetUserByIdEvent(uuid: userId)); + // } + }, + builder: (context, state) { + if (state is UsersLoadingState) { + return const Center(child: CircularProgressIndicator()); + } + return SingleChildScrollView( + child: _buildTree(_blocRole.updatedCommunities, _blocRole), + ); + }, + ), + ); + } + + Widget _buildTree( + List nodes, + UsersBloc bloc, { + int level = 0, + }) { + return Column( + children: nodes.map((node) { + return Container( + color: node.isHighlighted ? Colors.blue.shade50 : Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + /// Checkbox (GestureDetector) + GestureDetector( + onTap: () { + bloc.add(ToggleNodeCheck(node)); + }, + child: Image.asset( + _getCheckBoxImage(node), + width: 20, + height: 20, + ), + ), + const SizedBox(width: 15), + Expanded( + child: Padding( + padding: EdgeInsets.only(left: level * 10.0), + child: Row( + children: [ + GestureDetector( + onTap: () { + bloc.add(ToggleNodeExpansion(node: node)); + }, + child: node.children.isNotEmpty + ? SvgPicture.asset( + node.isExpanded + ? Assets.arrowDown + : Assets.arrowForward, + fit: BoxFit.none, + ) + : const SizedBox(width: 16), + ), + const SizedBox(width: 20), + Text( + node.title, + style: TextStyle( + fontSize: 16, + color: node.isHighlighted + ? ColorsManager.blackColor + : ColorsManager.textGray, + ), + ), + ], + ), + ), + ), + ], + ), + ), + if (node.isExpanded) + _buildTree( + node.children, + bloc, + level: level + 1, + ), + ], + ), + ); + }).toList(), + ); + } + + String _getCheckBoxImage(TreeNode node) { + if (node.children.isEmpty) { + return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox; + } + if (_areAllChildrenChecked(node)) { + return Assets.CheckBoxChecked; + } else if (_areSomeChildrenChecked(node)) { + return Assets.rectangleCheckBox; + } else { + return Assets.emptyBox; + } + } + + bool _areAllChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.every((child) => + child.isChecked && + (child.children.isEmpty || _areAllChildrenChecked(child))); + } + + bool _areSomeChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.any((child) => + child.isChecked || + (child.children.isNotEmpty && _areSomeChildrenChecked(child))); + } +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart index 002b0171..837ee62c 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class DeleteUserDialog extends StatefulWidget { - const DeleteUserDialog({super.key}); + final Function()? onTapDelete; + DeleteUserDialog({super.key, this.onTapDelete}); @override _DeleteUserDialogState createState() => _DeleteUserDialogState(); @@ -17,47 +15,94 @@ class _DeleteUserDialogState extends State { @override Widget build(BuildContext context) { - return BlocProvider( - create: (BuildContext context) => UsersBloc(), - child: BlocConsumer( - listener: (context, state) {}, - builder: (context, state) { - final _blocRole = BlocProvider.of(context); - - return Dialog( - child: Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(20))), - child: const Column( - children: [ - Padding( - padding: EdgeInsets.all(8.0), - child: SizedBox( - child: Text( - "Delete User", - style: TextStyle( - color: ColorsManager.red, - fontSize: 18, - fontWeight: FontWeight.bold), + return Dialog( + child: Container( + height: 160, + width: 200, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + child: Column( + children: [ + const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Delete User", + style: TextStyle( + color: ColorsManager.red, + fontSize: 18, + fontWeight: FontWeight.bold), + ), + ), + ), + const Padding( + padding: EdgeInsets.only( + left: 25, + right: 25, + ), + child: Divider(), + ), + const Expanded( + child: Padding( + padding: EdgeInsets.only(left: 25, right: 25, top: 10, bottom: 10), + child: Text( + "Are you sure you want to delete this user?", + textAlign: TextAlign.center, + ), + )), + Row( + children: [ + Expanded( + child: InkWell( + onTap: () { + Navigator.of(context).pop(true); + }, + child: Container( + padding: const EdgeInsets.all(10), + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.grayBorder, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.grayBorder, + width: 1, ), ), ), - Divider(), - Expanded( + child: const Center(child: Text('Cancel'))), + )), + Expanded( + child: InkWell( + onTap: widget.onTapDelete, + child: Container( + padding: const EdgeInsets.all(10), + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.grayBorder, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.grayBorder, + width: 1, + ), + ), + ), + child: const Center( child: Text( - "Are you sure you want to delete this user?", - textAlign: TextAlign.center, - )), - Row( - children: [ - Expanded(child: Text('Cancel')), - Expanded(child: Text('Delete')), - ], - ) - ], - ), - )); - })); + 'Delete', + style: TextStyle( + color: ColorsManager.red, + ), + ))), + )), + ], + ) + ], + ), + )); } } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart new file mode 100644 index 00000000..8b6600e0 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart @@ -0,0 +1,364 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class EditUserDialog extends StatefulWidget { + final String? userId; + const EditUserDialog({super.key, this.userId}); + + @override + _EditUserDialogState createState() => _EditUserDialogState(); +} + +class _EditUserDialogState extends State { + int currentStep = 1; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => UsersBloc() + ..add(const LoadCommunityAndSpacesEvent()) + ..add(const RoleEvent()) + ..add(GetUserByIdEvent(uuid: widget.userId)), + child: BlocConsumer(listener: (context, state) { + if (state is SpacesLoadedState) { + BlocProvider.of(context) + .add(GetUserByIdEvent(uuid: widget.userId)); + } + }, builder: (context, state) { + final _blocRole = BlocProvider.of(context); + + return Dialog( + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + width: 900, + child: Column( + children: [ + // Title + const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Edit User", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: ColorsManager.secondaryColor), + ), + ), + ), + const Divider(), + Expanded( + child: Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildStep1Indicator(1, "Basics", _blocRole), + _buildStep2Indicator(2, "Spaces", _blocRole), + _buildStep3Indicator( + 3, "Role & Permissions", _blocRole), + ], + ), + ), + ), + Container( + width: 1, + color: ColorsManager.grayBorder, + ), + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + Expanded( + child: _getFormContent(widget.userId), + ), + const SizedBox(height: 20), + ], + ), + ), + ), + ], + ), + ), + const Divider(), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + InkWell( + onTap: () { + // _blocRole.add(const CheckEmailEvent()); + + setState(() { + if (currentStep < 3) { + currentStep++; + if (currentStep == 2) { + _blocRole + .add(CheckStepStatus(isEditUser: true)); + } else if (currentStep == 3) { + _blocRole.add(const CheckSpacesStepStatus()); + } + } else { + _blocRole.add(EditInviteUsers( + context: context, + userId: widget.userId!)); + } + }); + }, + child: Text( + currentStep < 3 ? "Next" : "Save", + style: TextStyle( + color: (_blocRole.isCompleteSpaces == false || + _blocRole.isCompleteBasics == false || + _blocRole.isCompleteRolePermissions == + false) && + currentStep == 3 + ? ColorsManager.grayColor + : ColorsManager.secondaryColor), + ), + ), + ], + ), + ), + ], + ), + )); + })); + } + + Widget _getFormContent(userid) { + switch (currentStep) { + case 1: + return BasicsView( + userId: userid, + ); + case 2: + return SpacesAccessView( + userId: userid, + ); + case 3: + return const RolesAndPermission(); + default: + return Container(); + } + } + + int step3 = 0; + + Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + bloc.add(const CheckSpacesStepStatus()); + currentStep = step; + Future.delayed(const Duration(milliseconds: 500), () { + bloc.add(ValidateBasicsStep()); + }); + }); + + if (step3 == 3) { + bloc.add(const CheckRoleStepStatus()); + } + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + SvgPicture.asset( + currentStep == step + ? Assets.currentProcessIcon + : bloc.isCompleteBasics == false + ? Assets.wrongProcessIcon + : bloc.isCompleteBasics == true + ? Assets.completeProcessIcon + : Assets.uncomplete_ProcessIcon, + width: 25, + height: 25, + ), + const SizedBox(width: 10), + Text( + label, + style: TextStyle( + fontSize: 16, + color: currentStep == step + ? ColorsManager.blackColor + : ColorsManager.greyColor, + fontWeight: currentStep == step + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + ), + if (step != 3) + Padding( + padding: const EdgeInsets.all(5.0), + child: Padding( + padding: const EdgeInsets.only(left: 12), + child: Container( + height: 60, + width: 1, + color: Colors.grey, + ), + ), + ) + ], + ), + ); + } + + Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + currentStep = step; + bloc.add(CheckStepStatus(isEditUser: true)); + if (step3 == 3) { + bloc.add(const CheckRoleStepStatus()); + } + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + SvgPicture.asset( + currentStep == step + ? Assets.currentProcessIcon + : bloc.isCompleteSpaces == false + ? Assets.wrongProcessIcon + : bloc.isCompleteSpaces == true + ? Assets.completeProcessIcon + : Assets.uncomplete_ProcessIcon, + width: 25, + height: 25, + ), + const SizedBox(width: 10), + Text( + label, + style: TextStyle( + fontSize: 16, + color: currentStep == step + ? ColorsManager.blackColor + : ColorsManager.greyColor, + fontWeight: currentStep == step + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + ), + if (step != 3) + Padding( + padding: const EdgeInsets.all(5.0), + child: Padding( + padding: const EdgeInsets.only(left: 12), + child: Container( + height: 60, + width: 1, + color: Colors.grey, + ), + ), + ) + ], + ), + ); + } + + Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + currentStep = step; + step3 = step; + bloc.add(const CheckSpacesStepStatus()); + bloc.add(CheckStepStatus(isEditUser: true)); + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + SvgPicture.asset( + currentStep == step + ? Assets.currentProcessIcon + : bloc.isCompleteRolePermissions == false + ? Assets.wrongProcessIcon + : bloc.isCompleteRolePermissions == true + ? Assets.completeProcessIcon + : Assets.uncomplete_ProcessIcon, + width: 25, + height: 25, + ), + const SizedBox(width: 10), + Text( + label, + style: TextStyle( + fontSize: 16, + color: currentStep == step + ? ColorsManager.blackColor + : ColorsManager.greyColor, + fontWeight: currentStep == step + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + ), + if (step != 3) + Padding( + padding: const EdgeInsets.all(5.0), + child: Padding( + padding: const EdgeInsets.only(left: 12), + child: Container( + height: 60, + width: 1, + color: Colors.grey, + ), + ), + ) + ], + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart new file mode 100644 index 00000000..02eac036 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +Future showPopUpFilterMenu({ + required BuildContext context, + Function()? onSortAtoZ, + Function()? onSortZtoA, + Function()? cancelButton, + required Map checkboxStates, + Function()? onOkPressed, + List? list, +}) async { + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; + + await showMenu( + context: context, + position: RelativeRect.fromLTRB( + overlay.size.width / 4, + 240, + overlay.size.width / 4, + 0, + ), + color: ColorsManager.whiteColors, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + items: [ + PopupMenuItem( + onTap: onSortAtoZ, + child: ListTile( + leading: Image.asset( + Assets.AtoZIcon, + width: 25, + ), + title: const Text( + "Sort A to Z", + style: TextStyle(color: Colors.blueGrey), + ), + ), + ), + PopupMenuItem( + onTap: onSortZtoA, + child: ListTile( + leading: Image.asset( + Assets.ZtoAIcon, + width: 25, + ), + title: const Text( + "Sort Z to A", + style: TextStyle(color: Colors.blueGrey), + ), + ), + ), + const PopupMenuDivider(), + const PopupMenuItem( + child: Text( + "Filter by Status", + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + PopupMenuItem( + child: SizedBox( + height: 200, + width: 400, + child: ListView.builder( + itemCount: list?.length ?? 0, + itemBuilder: (context, index) { + final item = list![index]; + return CheckboxListTile( + title: Text(item), + value: checkboxStates[item], + onChanged: (bool? newValue) { + checkboxStates[item] = newValue ?? false; + (context as Element).markNeedsBuild(); + }, + ); + }, + ), + ), + ), + PopupMenuItem( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); // Close the menu + }, + child: const Text("Cancel"), + ), + GestureDetector( + onTap: onOkPressed, + child: const Text( + "OK", + style: TextStyle( + color: ColorsManager.spaceColor, + ), + ), + ), + ], + ), + ), + ], + ); +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart index 081fab40..dc2eefc3 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart @@ -4,14 +4,15 @@ import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class SpacesAccessView extends StatelessWidget { - const SpacesAccessView({super.key}); + String? userId = ''; + SpacesAccessView({super.key, this.userId}); @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; @@ -109,9 +110,7 @@ class SpacesAccessView extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: Container( color: ColorsManager.whiteColors, - child: TreeView( - bloc: _blocRole, - )))) + child: TreeView(userId: userId)))) ], ), ), @@ -123,155 +122,3 @@ class SpacesAccessView extends StatelessWidget { }); } } - -// ignore: must_be_immutable - -class TreeView extends StatefulWidget { - UsersBloc? bloc; - TreeView({super.key, this.bloc}); - @override - _TreeViewState createState() => _TreeViewState(); -} - -class _TreeViewState extends State { - Widget _buildTree(List nodes, {int level = 0}) { - return Column( - children: nodes.map((node) => _buildNode(node, level: level)).toList(), - ); - } - - Widget _buildNode(TreeNode node, {int level = 0}) { - return Container( - color: node.isHighlighted ? Colors.blue.shade50 : Colors.transparent, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(5.0), - child: Row( - children: [ - GestureDetector( - onTap: () { - setState(() { - node.isChecked = !node.isChecked; - _updateChildrenCheckStatus(node, node.isChecked); - _updateParentCheckStatus(node); - // widget.bloc!.add( - // SelecteId(nodes: widget.bloc!.updatedCommunities)); - }); - }, - child: Image.asset( - _getCheckBoxImage(node), - width: 20, - height: 20, - ), - ), - const SizedBox(width: 15), - Expanded( - child: Padding( - padding: EdgeInsets.only(left: level * 10.0), - child: Row( - children: [ - GestureDetector( - onTap: () { - setState(() { - node.isExpanded = !node.isExpanded; - }); - }, - child: SizedBox( - child: SvgPicture.asset( - node.children.isNotEmpty - ? (node.isExpanded - ? Assets.arrowDown - : Assets.arrowForward) - : Assets.arrowForward, - fit: BoxFit.none, - ), - ), - ), - const SizedBox(width: 20), - Text( - node.title, - style: TextStyle( - fontSize: 16, - color: node.isHighlighted - ? ColorsManager.blackColor - : ColorsManager.textGray, - ), - ), - ], - ), - ), - ), - ], - ), - ), - if (node.isExpanded && node.children.isNotEmpty) - _buildTree(node.children, level: level + 1), - ], - ), - ); - } - - String _getCheckBoxImage(TreeNode node) { - if (node.children.isEmpty) { - return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox; - } - if (_areAllChildrenChecked(node)) { - return Assets.CheckBoxChecked; - } else if (_areSomeChildrenChecked(node)) { - return Assets.rectangleCheckBox; - } else { - return Assets.emptyBox; - } - } - - bool _areAllChildrenChecked(TreeNode node) { - return node.children.isNotEmpty && - node.children.every((child) => - child.isChecked && - (child.children.isEmpty || _areAllChildrenChecked(child))); - } - - bool _areSomeChildrenChecked(TreeNode node) { - return node.children.isNotEmpty && - node.children.any((child) => - child.isChecked || - (child.children.isNotEmpty && _areSomeChildrenChecked(child))); - } - - void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { - for (var child in node.children) { - child.isChecked = isChecked; - _updateChildrenCheckStatus(child, isChecked); - } - } - - void _updateParentCheckStatus(TreeNode node) { - TreeNode? parent = _findParent(widget.bloc!.updatedCommunities, node); - if (parent != null) { - setState(() { - parent.isChecked = _areAllChildrenChecked(parent); - _updateParentCheckStatus(parent); - }); - } - } - - TreeNode? _findParent(List nodes, TreeNode target) { - for (var node in nodes) { - if (node.children.contains(target)) { - return node; - } - var parent = _findParent(node.children, target); - if (parent != null) return parent; - } - return null; - } - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: _buildTree(widget.bloc!.updatedCommunities), - ); - } -} diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart index 2a31a91f..82212103 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart'; +import 'package:syncrow_web/services/user_permission.dart'; class UserTableBloc extends Bloc { UserTableBloc() : super(TableInitial()) { @@ -12,79 +14,108 @@ class UserTableBloc extends Bloc { on(_toggleSortUsersByNameDesc); on(_toggleSortUsersByDateOldestToNewest); on(_toggleSortUsersByDateNewestToOldest); + on(_searchUsers); + on(_handlePageChange); + on(_filterUsersByRole); + on(_filterUsersByJobTitle); + on(_filterUsersByCreated); + on(_filterUserActevate); + on(_deleteUser); } - + int itemsPerPage = 10; + int currentPage = 1; List users = []; - List initialUsers = []; // Save the initial state - String currentSortOrder = ''; // Keeps track of the current sorting order - String currentSortOrderDate = ''; // Keeps track of the current sorting order + List initialUsers = []; + String currentSortOrder = ''; + String currentSortOrderDate = ''; + List roleTypes = []; + List jobTitle = []; + List createdBy = []; + List deActivate = []; Future _getUsers(GetUsers event, Emitter emit) async { emit(UsersLoadingState()); try { - users = [ - RolesUserModel( - id: '1', - userName: 'b 1', - userEmail: 'test1@test.com', - action: '', - createdBy: 'Admin', - creationDate: '25/10/2024', - creationTime: '10:30 AM', - status: 'Invited', - ), - RolesUserModel( - id: '2', - userName: 'a 2', - userEmail: 'test2@test.com', - action: '', - createdBy: 'Admin', - creationDate: '24/10/2024', - creationTime: '2:30 PM', - status: 'Active', - ), - RolesUserModel( - id: '3', - userName: 'c 3', - userEmail: 'test3@test.com', - action: '', - createdBy: 'Admin', - creationDate: '23/10/2024', - creationTime: '9:00 AM', - status: 'Disabled', - ), - ]; - // Sort users by newest to oldest as default + roleTypes.clear(); + jobTitle.clear(); + createdBy.clear(); + deActivate.clear(); + users = await UserPermissionApi().fetchUsers(); + for (var user in users) { + roleTypes.add(user.roleType.toString()); + } + for (var user in users) { + jobTitle.add(user.jobTitle.toString()); + } + for (var user in users) { + createdBy.add(user.invitedBy.toString()); + } + for (var user in users) { + deActivate.add(user.status.toString()); + } + roleTypes = roleTypes.toSet().toList(); + jobTitle = jobTitle.toSet().toList(); + createdBy = createdBy.toSet().toList(); + deActivate = deActivate.toSet().toList(); + users.sort((a, b) { - final dateA = _parseDateTime(a.creationDate!); - final dateB = _parseDateTime(b.creationDate!); - return dateB.compareTo(dateA); // Newest to oldest + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateB.compareTo(dateA); }); - initialUsers = List.from(users); // Save the initial state + initialUsers = List.from(users); + _handlePageChange(ChangePage(1), emit); emit(UsersLoadedState(users: users)); } catch (e) { emit(ErrorState(e.toString())); } } - void _changeUserStatus(ChangeUserStatus event, Emitter emit) { + Future _deleteUser( + DeleteUserEvent event, Emitter emit) async { + emit(UsersLoadingState()); try { - users = users.map((user) { - if (user.id == event.userId) { - return RolesUserModel( - id: user.id, - userName: user.userName, - userEmail: user.userEmail, - createdBy: user.createdBy, - creationDate: user.creationDate, - creationTime: user.creationTime, - status: event.newStatus, - action: user.action, - ); - } - return user; - }).toList(); + bool res = await UserPermissionApi().deleteUserById(event.userId); + if (res == true) { + Navigator.of(event.context).pop(true); + } else { + emit(const ErrorState('Something error')); + } + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + Future _changeUserStatus( + ChangeUserStatus event, Emitter emit) async { + try { + emit(UsersLoadingState()); + bool res = await UserPermissionApi().changeUserStatusById( + event.userId, event.newStatus == "disabled" ? true : false); + if (res == true) { + add(const GetUsers()); + // users = users.map((user) { + // if (user.uuid == event.userId) { + // return RolesUserModel( + // uuid: user.uuid, + // createdAt: user.createdAt, + // email: user.email, + // firstName: user.firstName, + // lastName: user.lastName, + // roleType: user.roleType, + // status: event.newStatus, + // isEnabled: event.newStatus == "disabled" ? false : true, + // invitedBy: user.invitedBy, + // phoneNumber: user.phoneNumber, + // jobTitle: user.jobTitle, + // createdDate: user.createdDate, + // createdTime: user.createdTime, + // ); + // } + // return user; + // }).toList(); + } emit(UsersLoadedState(users: users)); } catch (e) { emit(ErrorState(e.toString())); @@ -94,16 +125,14 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByNameAsc( SortUsersByNameAsc event, Emitter emit) { if (currentSortOrder == "Asc") { - // If already sorted ascending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; - users = List.from(initialUsers); // Reset to saved initial state + users = List.from(initialUsers); emit(UsersLoadedState(users: users)); } else { - // Sort ascending emit(UsersLoadingState()); currentSortOrder = "Asc"; - users.sort((a, b) => a.userName!.compareTo(b.userName!)); + users.sort((a, b) => a.firstName!.compareTo(b.firstName!)); emit(UsersLoadedState(users: users)); } } @@ -111,7 +140,6 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByNameDesc( SortUsersByNameDesc event, Emitter emit) { if (currentSortOrder == "Desc") { - // If already sorted descending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; users = List.from(initialUsers); // Reset to saved initial state @@ -120,7 +148,7 @@ class UserTableBloc extends Bloc { // Sort descending emit(UsersLoadingState()); currentSortOrder = "Desc"; - users.sort((a, b) => b.userName!.compareTo(a.userName!)); + users.sort((a, b) => b.firstName!.compareTo(a.firstName!)); emit(UsersLoadedState(users: users)); } } @@ -128,19 +156,17 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByDateNewestToOldest( DateNewestToOldestEvent event, Emitter emit) { if (currentSortOrderDate == "NewestToOldest") { - // If already sorted ascending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; currentSortOrderDate = ""; - users = List.from(initialUsers); // Reset to saved initial state + users = List.from(initialUsers); emit(UsersLoadedState(users: users)); } else { - // Sort ascending emit(UsersLoadingState()); users.sort((a, b) { - final dateA = _parseDateTime(a.creationDate!); - final dateB = _parseDateTime(b.creationDate!); - return dateB.compareTo(dateA); // Newest to oldest + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateB.compareTo(dateA); }); emit(UsersLoadedState(users: users)); } @@ -149,19 +175,17 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByDateOldestToNewest( DateOldestToNewestEvent event, Emitter emit) { if (currentSortOrderDate == "OldestToNewest") { - // If already sorted ascending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; currentSortOrderDate = ""; - users = List.from(initialUsers); // Reset to saved initial state + users = List.from(initialUsers); emit(UsersLoadedState(users: users)); } else { - // Sort ascending emit(UsersLoadingState()); users.sort((a, b) { - final dateA = _parseDateTime(a.creationDate!); - final dateB = _parseDateTime(b.creationDate!); - return dateA.compareTo(dateB); // Newest to oldest + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateA.compareTo(dateB); }); emit(UsersLoadedState(users: users)); } @@ -169,17 +193,94 @@ class UserTableBloc extends Bloc { DateTime _parseDateTime(String date) { try { - // Split the date into day, month, and year final dateParts = date.split('/'); final day = int.parse(dateParts[0]); final month = int.parse(dateParts[1]); final year = int.parse(dateParts[2]); - - // Split the time into hours and minutes - return DateTime(year, month, day); } catch (e) { throw FormatException('Invalid date or time format: $date '); } } + + Future _searchUsers( + SearchUsers event, Emitter emit) async { + try { + final query = event.query.toLowerCase(); + final filteredUsers = initialUsers.where((user) { + final fullName = "${user.firstName} ${user.lastName}".toLowerCase(); + final email = user.email?.toLowerCase() ?? ""; + return fullName.contains(query) || email.contains(query); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + void _paginateUsers( + int pageNumber, int itemsPerPage, Emitter emit) { + final startIndex = (pageNumber - 1) * itemsPerPage; + final endIndex = startIndex + itemsPerPage; + if (startIndex >= users.length) { + emit(UsersLoadedState(users: const [])); + return; + } + final paginatedUsers = users.sublist( + startIndex, + endIndex > users.length ? users.length : endIndex, + ); + emit(UsersLoadedState(users: paginatedUsers)); + } + + void _handlePageChange(ChangePage event, Emitter emit) { + final itemsPerPage = 10; + final startIndex = (event.pageNumber - 1) * itemsPerPage; + final endIndex = startIndex + itemsPerPage; + if (startIndex >= users.length) { + emit(UsersLoadedState(users: [])); + return; + } + final paginatedUsers = users.sublist( + startIndex, + endIndex > users.length ? users.length : endIndex, + ); + emit(UsersLoadedState(users: paginatedUsers)); + } + + void _filterUsersByRole( + FilterUsersByRoleEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = initialUsers.where((user) { + return event.selectedRoles.contains(user.roleType); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } + + void _filterUsersByJobTitle( + FilterUsersByJobEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = users.where((user) { + return event.selectedJob.contains(user.jobTitle); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } + + void _filterUsersByCreated( + FilterUsersByCreatedEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = initialUsers.where((user) { + return event.selectedCreatedBy.contains(user.invitedBy); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } + + void _filterUserActevate( + FilterUsersByDeActevateEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = initialUsers.where((user) { + return event.selectedActivate.contains(user.status); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } } diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart index a6c77bd3..dbcd9a26 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; sealed class UserTableEvent extends Equatable { const UserTableEvent(); @@ -47,7 +48,6 @@ class StoreUsersEvent extends UserTableEvent { List get props => []; } - class DateNewestToOldestEvent extends UserTableEvent { const DateNewestToOldestEvent(); @@ -60,4 +60,62 @@ class DateOldestToNewestEvent extends UserTableEvent { @override List get props => []; -} \ No newline at end of file +} + +class SearchUsers extends UserTableEvent { + final String query; + SearchUsers(this.query); + @override + List get props => []; +} + +class ChangePage extends UserTableEvent { + final int pageNumber; + + ChangePage(this.pageNumber); + + @override + List get props => [pageNumber]; +} + +class DeleteUserEvent extends UserTableEvent { + final String userId; + final BuildContext context; + + const DeleteUserEvent(this.userId, this.context); + + @override + List get props => [userId, context]; +} + +class FilterUsersByRoleEvent extends UserTableEvent { + final List selectedRoles; + + FilterUsersByRoleEvent(this.selectedRoles); + @override + List get props => [selectedRoles]; +} + +class FilterUsersByJobEvent extends UserTableEvent { + final List selectedJob; + + FilterUsersByJobEvent(this.selectedJob); + @override + List get props => [selectedJob]; +} + +class FilterUsersByCreatedEvent extends UserTableEvent { + final List selectedCreatedBy; + + FilterUsersByCreatedEvent(this.selectedCreatedBy); + @override + List get props => [selectedCreatedBy]; +} + +class FilterUsersByDeActevateEvent extends UserTableEvent { + final List selectedActivate; + + FilterUsersByDeActevateEvent(this.selectedActivate); + @override + List get props => [selectedActivate]; +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart index f42c0c03..4f777ee3 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -81,15 +81,17 @@ class _DynamicTableScreenState extends State scrollDirection: Axis.horizontal, child: Container( decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, borderRadius: const BorderRadius.all(Radius.circular(20))), child: FittedBox( child: Column( children: [ // Header Row with Resizable Columns Container( + width: MediaQuery.of(context).size.width, decoration: containerDecoration.copyWith( color: ColorsManager.circleRolesBackground, - borderRadius: BorderRadius.only( + borderRadius: const BorderRadius.only( topLeft: Radius.circular(15), topRight: Radius.circular(15))), child: Row( @@ -167,56 +169,96 @@ class _DynamicTableScreenState extends State ), ), // Data Rows with Dividers - Container( - decoration: containerDecoration.copyWith( - color: ColorsManager.whiteColors, - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(15), - bottomRight: Radius.circular(15))), - child: Column( - children: widget.rows.map((row) { - int rowIndex = widget.rows.indexOf(row); - return Column( - children: [ - Container( - child: Padding( - padding: const EdgeInsets.only( - left: 5, top: 10, right: 5, bottom: 10), - child: Row( - children: List.generate(row.length, (index) { - return SizedBox( - width: columnWidths[index], - child: SizedBox( - child: Padding( - padding: const EdgeInsets.only( - left: 15, right: 10), - child: row[index], - ), + widget.rows.isEmpty + ? Container( + child: SizedBox( + height: MediaQuery.of(context).size.height / 2, + child: Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + SvgPicture.asset(Assets.emptyTable), + const SizedBox( + height: 15, ), - ); - }), - ), + const Text( + 'No Users', + style: TextStyle( + color: ColorsManager.lightGrayColor, + fontSize: 16, + fontWeight: FontWeight.w700), + ) + ], + ), + ], ), ), - if (rowIndex < widget.rows.length - 1) - Row( - children: List.generate(widget.titles.length, - (index) { - return SizedBox( - width: columnWidths[index], - child: const Divider( - color: ColorsManager.boxDivider, - thickness: 1, - height: 1, - ), + ), + ) + : Center( + child: Container( + // height: MediaQuery.of(context).size.height * 0.59, + width: MediaQuery.of(context).size.width, + decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(15), + bottomRight: Radius.circular(15))), + child: ListView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: widget.rows.length, + itemBuilder: (context, rowIndex) { + final row = widget.rows[rowIndex]; + return Column( + children: [ + Container( + child: Padding( + padding: const EdgeInsets.only( + left: 5, + top: 10, + right: 5, + bottom: 10), + child: Row( + children: + List.generate(row.length, (index) { + return SizedBox( + width: columnWidths[index], + child: SizedBox( + child: Padding( + padding: const EdgeInsets.only( + left: 15, right: 10), + child: row[index], + ), + ), + ); + }), + ), + ), + ), + if (rowIndex < widget.rows.length - 1) + Row( + children: List.generate( + widget.titles.length, (index) { + return SizedBox( + width: columnWidths[index], + child: const Divider( + color: ColorsManager.boxDivider, + thickness: 1, + height: 1, + ), + ); + }), + ), + ], ); - })) - // Add a Divider below each row except the last one - ], - ); - }).toList(), - ), - ), + }, + ), + ), + ), ], ), ), diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index 758f062b..8d838ba2 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:number_pagination/number_pagination.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart'; @@ -15,7 +19,8 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class UsersPage extends StatelessWidget { - const UsersPage({super.key}); + UsersPage({super.key}); + @override Widget build(BuildContext context) { final TextEditingController searchController = TextEditingController(); @@ -44,9 +49,9 @@ class UsersPage extends StatelessWidget { padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(20)), - color: status == "Invited" + color: status == "invited" ? ColorsManager.invitedOrange.withOpacity(0.5) - : status == "Active" + : status == "active" ? ColorsManager.activeGreen.withOpacity(0.5) : ColorsManager.disabledPink.withOpacity(0.5), ), @@ -60,9 +65,9 @@ class UsersPage extends StatelessWidget { Text( status, style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: status == "Invited" + color: status == "invited" ? ColorsManager.invitedOrangeText - : status == "Active" + : status == "active" ? ColorsManager.activeGreenText : ColorsManager.disabledRedText, fontWeight: FontWeight.w400, @@ -81,23 +86,14 @@ class UsersPage extends StatelessWidget { required Function()? onTap}) { return Center( child: InkWell( - onTap: () { - final newStatus = status == 'Active' - ? 'Disabled' - : status == 'Disabled' - ? 'Invited' - : 'Active'; - context - .read() - .add(ChangeUserStatus(userId: userId, newStatus: newStatus)); - }, + onTap: onTap, child: Padding( padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), child: SvgPicture.asset( - status == "Invited" + status == "invited" ? Assets.invitedIcon - : status == "Active" + : status == "active" ? Assets.activeUser : Assets.deActiveUser, height: 35, @@ -113,12 +109,14 @@ class UsersPage extends StatelessWidget { final _blocRole = BlocProvider.of(context); if (state is UsersLoadingState) { + _blocRole.add(ChangePage(_blocRole.currentPage)); + return const Center(child: CircularProgressIndicator()); } else if (state is UsersLoadedState) { return Padding( padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: ListView( + shrinkWrap: true, children: [ Row( children: [ @@ -131,6 +129,9 @@ class UsersPage extends StatelessWidget { width: screenSize.width * 0.4, child: TextFormField( controller: searchController, + onChanged: (value) { + context.read().add(SearchUsers(value)); + }, style: const TextStyle(color: Colors.black), decoration: textBoxDecoration(radios: 15)!.copyWith( fillColor: ColorsManager.whiteColors, @@ -158,9 +159,9 @@ class UsersPage extends StatelessWidget { builder: (BuildContext context) { return const AddNewUserDialog(); }, - ).then((listDevice) { - if (listDevice != null) { - + ).then((v) { + if (v != null) { + _blocRole.add(const GetUsers()); } }); }, @@ -201,6 +202,66 @@ class UsersPage extends StatelessWidget { }, ); } + if (columnIndex == 2) { + final Map checkboxStates = { + for (var item in _blocRole.jobTitle) + item: false, // Initialize with false + }; + + showPopUpFilterMenu( + list: _blocRole.jobTitle, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole.add(FilterUsersByJobEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 3) { + final Map checkboxStates = { + for (var item in _blocRole.roleTypes) + item: false, // Initialize with false + }; + + showPopUpFilterMenu( + list: _blocRole.roleTypes, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole.add(FilterUsersByRoleEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } if (columnIndex == 4) { showDateFilterMenu( context: context, @@ -217,6 +278,37 @@ class UsersPage extends StatelessWidget { }, ); } + if (columnIndex == 6) { + final Map checkboxStates = { + for (var item in _blocRole.createdBy) + item: false, // Initialize with false + }; + + showPopUpFilterMenu( + list: _blocRole.createdBy, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole + .add(FilterUsersByCreatedEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } if (columnIndex == 8) { showDeActivateFilterMenu( context: context, @@ -248,19 +340,37 @@ class UsersPage extends StatelessWidget { ], rows: state.users.map((user) { return [ - Text(user.userName!), - Text(user.userEmail!), - const Text("Test"), - const Text("Member"), - Text(user.creationDate!), - Text(user.creationTime!), - Text(user.createdBy!), + Text('${user.firstName} ${user.lastName}'), + Text(user.email ?? ''), + Text(user.jobTitle ?? ''), + Text(user.roleType ?? ''), + Text(user.createdDate ?? ''), + Text(user.createdTime ?? ''), + Text(user.invitedBy), changeIconStatus( - status: user.status!, - userId: user.id!, - onTap: () {}, + status: + user.isEnabled == false ? 'disabled' : user.status, + userId: user.uuid, + onTap: user.status != "invited" + ? () { + final newStatus = user.status == 'active' + ? 'disabled' + : user.status == 'disabled' + ? 'invited' + : 'active'; + context.read().add( + ChangeUserStatus( + userId: user.uuid, + newStatus: user.isEnabled == false + ? 'disabled' + : user.status)); + } + : null, + ), + status( + status: + user.isEnabled == false ? 'disabled' : user.status, ), - status(status: user.status!), Row( children: [ // actionButton( @@ -269,17 +379,80 @@ class UsersPage extends StatelessWidget { // ), actionButton( title: "Edit", - onTap: () {}, + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return EditUserDialog(userId: user.uuid); + }, + ).then((v) { + if (v != null) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + } + }); + }, ), actionButton( title: "Delete", - onTap: () {}, + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return DeleteUserDialog( + onTapDelete: () { + _blocRole.add( + DeleteUserEvent(user.uuid, context)); + }, + ); + }, + ).then((v) { + if (v != null) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + } + }); + }, ), ], ), ]; }).toList(), ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + width: 500, + child: NumberPagination( + buttonRadius: 10, + selectedButtonColor: ColorsManager.secondaryColor, + buttonUnSelectedBorderColor: ColorsManager.grayBorder, + lastPageIcon: + const Icon(Icons.keyboard_double_arrow_right), + firstPageIcon: + const Icon(Icons.keyboard_double_arrow_left), + totalPages: + (_blocRole.users.length / _blocRole.itemsPerPage) + .ceil(), + currentPage: _blocRole.currentPage, + onPageChanged: (int pageNumber) { + _blocRole.currentPage = pageNumber; + context + .read() + .add(ChangePage(pageNumber)); + }, + ), + ), + ], + ), + ), ], ), ); diff --git a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart index da003536..44944ed3 100644 --- a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart +++ b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart @@ -77,7 +77,7 @@ class RolesAndPermissionPage extends StatelessWidget { ), scaffoldBody: BlocProvider( create: (context) => UserTableBloc()..add(const GetUsers()), - child: const UsersPage(), + child: UsersPage(), ) // _blocRole.tapSelect == false // ? UsersPage( diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 5d2464e6..abf151fb 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -252,6 +252,7 @@ class CommunitySpaceManagementApi { path: ApiEndpoints.getSpaceHierarchy .replaceAll('{communityId}', communityId), expectedResponseModel: (json) { + print(json); final spaceModels = (json['data'] as List) .map((spaceJson) => SpaceModel.fromJson(spaceJson)) .toList(); diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 0e31795a..f62437ac 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -1,6 +1,9 @@ import 'dart:convert'; import 'package:dio/dio.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/edit_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -8,6 +11,25 @@ import 'package:syncrow_web/utils/constants/api_const.dart'; class UserPermissionApi { static final HTTPService _httpService = HTTPService(); + Future> fetchUsers() async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getUsers, + showServerMessage: true, + expectedResponseModel: (json) { + debugPrint('fetchUsers Response: $json'); + final List data = + json['data'] ?? []; // Default to an empty list if no data + return data.map((item) => RolesUserModel.fromJson(item)).toList(); + }, + ); + return response; + } catch (e, stackTrace) { + debugPrint('Error in fetchUsers: $e'); + rethrow; + } + } + fetchRoles() async { final response = await _httpService.get( path: ApiEndpoints.roleTypes, @@ -67,6 +89,7 @@ class UserPermissionApi { } }, ); + print('sendInviteUser=$body'); return response ?? []; } on DioException catch (e) { @@ -107,4 +130,95 @@ class UserPermissionApi { return e.toString(); } } + + Future fetchUserById(userUuid) async { + final response = await _httpService.get( + path: ApiEndpoints.getUserById.replaceAll("{userUuid}", userUuid), + showServerMessage: true, + expectedResponseModel: (json) { + EditUserModel res = EditUserModel.fromJson(json['data']); + return res; + }, + ); + return response; + } + + Future editInviteUser({ + String? firstName, + String? userId, + String? lastName, + String? jobTitle, + String? phoneNumber, + String? roleUuid, + List? spaceUuids, + }) async { + try { + final body = { + "firstName": firstName, + "lastName": lastName, + "jobTitle": jobTitle != '' ? jobTitle : " ", + "phoneNumber": phoneNumber != '' ? phoneNumber : " ", + "roleUuid": roleUuid, + "projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c", + "spaceUuids": spaceUuids, + }; + final response = await _httpService.put( + path: ApiEndpoints.editUser.replaceAll('{inviteUserUuid}', userId!), + body: jsonEncode(body), + expectedResponseModel: (json) { + if (json['statusCode'] != 400) { + return json["success"]; + } else { + return false; + } + }, + ); + return response; + } on DioException catch (e) { + return false; + } catch (e) { + return false; + } + } + + Future deleteUserById(userUuid) async { + try { + final response = await _httpService.delete( + path: ApiEndpoints.deleteUser.replaceAll("{inviteUserUuid}", userUuid), + showServerMessage: true, + expectedResponseModel: (json) { + return json['success']; + }, + ); + return response; + } catch (e) { + return false; + } + } + + Future changeUserStatusById(userUuid, status) async { + try { + Map bodya = { + "disable": status, + "projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c" + }; + print('changeUserStatusById==$bodya'); + print('changeUserStatusById==$userUuid'); + + final response = await _httpService.put( + path: ApiEndpoints.changeUserStatus + .replaceAll("{invitedUserUuid}", userUuid), + body: bodya, + expectedResponseModel: (json) { + print('changeUserStatusById==${json['success']}'); + return json['success']; + }, + ); + + return response; + } catch (e) { + return false; + print(e); + } + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index da746fbd..a229c5f6 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -100,7 +100,13 @@ abstract class ApiEndpoints { static const String roleTypes = '/role/types'; static const String permission = '/permission/{roleUuid}'; static const String inviteUser = '/invite-user'; + static const String checkEmail = '/invite-user/check-email'; + static const String getUsers = '/projects/${projectUuid}/user'; + static const String getUserById = '/projects/${projectUuid}/user/{userUuid}'; + static const String editUser = '/invite-user/{inviteUserUuid}'; + static const String deleteUser = '/invite-user/{inviteUserUuid}'; + static const String changeUserStatus = '/invite-user/{invitedUserUuid}/disable'; + // static const String updateAutomation = '/automation/{automationId}'; - // https://syncrow-dev.azurewebsites.net/invite-user/check-email } diff --git a/macos/Podfile.lock b/macos/Podfile.lock new file mode 100644 index 00000000..0639648b --- /dev/null +++ b/macos/Podfile.lock @@ -0,0 +1,36 @@ +PODS: + - flutter_secure_storage_macos (6.1.1): + - FlutterMacOS + - FlutterMacOS (1.0.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + +EXTERNAL SOURCES: + flutter_secure_storage_macos: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos + FlutterMacOS: + :path: Flutter/ephemeral + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + +SPEC CHECKSUMS: + flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9 + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + +PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 + +COCOAPODS: 1.16.2 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index e1af4c91..eec16af6 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -21,6 +21,8 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 108157F896CD9F637B06D7C0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DAF1C60594A51D692304366 /* Pods_Runner.framework */; }; + 2D0F1F294F673EF0DB5E4CA1 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */; }; 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; @@ -60,11 +62,12 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 24D7BEF98D33245EFB9F6A1B /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* syncrow_web.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "syncrow_web.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* syncrow_web.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = syncrow_web.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -76,8 +79,15 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 5DAF1C60594A51D692304366 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + AB949539E0D0A8E2BDAB9ADF /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F244F079A053D959E1C5C362 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,6 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 2D0F1F294F673EF0DB5E4CA1 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -92,6 +103,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 108157F896CD9F637B06D7C0 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -125,6 +137,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 75DCDFECC7757C5159E8F0C5 /* Pods */, ); sourceTree = ""; }; @@ -172,9 +185,25 @@ path = Runner; sourceTree = ""; }; + 75DCDFECC7757C5159E8F0C5 /* Pods */ = { + isa = PBXGroup; + children = ( + 24D7BEF98D33245EFB9F6A1B /* Pods-Runner.debug.xcconfig */, + F244F079A053D959E1C5C362 /* Pods-Runner.release.xcconfig */, + AB949539E0D0A8E2BDAB9ADF /* Pods-Runner.profile.xcconfig */, + 96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */, + A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */, + 81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 5DAF1C60594A51D692304366 /* Pods_Runner.framework */, + E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -186,6 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + A1935203066F42991FF0ED43 /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -204,11 +234,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 8ECFD939A4D371A145DBA191 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 92D754792F50A5D35F6D5AEE /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -329,6 +361,67 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 8ECFD939A4D371A145DBA191 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 92D754792F50A5D35F6D5AEE /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + A1935203066F42991FF0ED43 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -380,6 +473,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -394,6 +488,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -408,6 +503,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16..21a3cc14 100644 --- a/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/pubspec.lock b/pubspec.lock index 86d132c3..d1c39c65 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -392,6 +392,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + number_pagination: + dependency: "direct main" + description: + name: number_pagination + sha256: "75d3a28616196e7c8df431d0fb7c48e811e462155f4cf3b5b4167b3408421327" + url: "https://pub.dev" + source: hosted + version: "1.1.6" path: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6951987c..786a39c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,7 @@ dependencies: uuid: ^4.4.2 time_picker_spinner: ^1.0.0 intl_phone_field: ^3.2.0 + number_pagination: ^1.1.6 dev_dependencies: flutter_test: