edit_user and pagination and search and filter

This commit is contained in:
mohammad
2025-01-05 17:27:26 +03:00
parent b264f6042f
commit 3876909bea
25 changed files with 2156 additions and 413 deletions

View File

@ -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<String, dynamic> json) {
// return UserSpaceModel(
// uuid: json['uuid'],
// createdAt: DateTime.parse(json['createdAt']),
// updatedAt: DateTime.parse(json['updatedAt']),
// );
// }
// Map<String, dynamic> 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<UserSpaceModel> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> json) {
return UserProjectResponse(
statusCode: json['statusCode'] as int,
message: json['message'] as String,
data: EditUserModel.fromJson(json['data'] as Map<String, dynamic>),
success: json['success'] as bool,
);
}
/// Convert the [UserProjectResponse] to JSON
Map<String, dynamic> 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<UserSpaceModel> 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<String, dynamic> 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<dynamic>)
.map((e) => UserSpaceModel.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
/// Convert the [UserData] to JSON
Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> toJson() {
return {
'uuid': uuid,
'createdAt': createdAt,
'updatedAt': updatedAt,
'spaceTuyaUuid': spaceTuyaUuid,
'spaceName': spaceName,
'invitationCode': invitationCode,
'disabled': disabled,
'x': x,
'y': y,
'icon': icon,
};
}
}

View File

@ -1,22 +1,50 @@
class RolesUserModel { class RolesUserModel {
String? id; final String uuid;
String? userName; final DateTime createdAt;
String? userEmail; final String email;
String? userRole; final dynamic firstName;
String? creationDate; final dynamic lastName;
String? creationTime; final dynamic roleType;
String? createdBy; final dynamic status;
String? status; final bool isEnabled;
String? action; final String invitedBy;
RolesUserModel( final dynamic phoneNumber;
{this.id, final dynamic jobTitle;
this.userName, final dynamic createdDate;
this.userEmail, final dynamic createdTime;
this.userRole,
this.creationDate, RolesUserModel({
this.creationTime, required this.uuid,
this.status, required this.createdAt,
this.action, required this.email,
this.createdBy, 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<String, dynamic> 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'],
);
}
} }

View File

@ -1,6 +1,7 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/custom_dialog.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/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_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/bloc/users_status.dart';
@ -25,6 +26,11 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
on<ValidateBasicsStep>(_validateBasicsStep); on<ValidateBasicsStep>(_validateBasicsStep);
on<CheckRoleStepStatus>(isCompleteRoleFun); on<CheckRoleStepStatus>(isCompleteRoleFun);
on<CheckEmailEvent>(checkEmail); on<CheckEmailEvent>(checkEmail);
on<GetUserByIdEvent>(getUserById);
on<ToggleNodeExpansion>(_onToggleNodeExpansion);
on<ToggleNodeCheck>(_onToggleNodeCheck);
on<EditInviteUsers>(_editInvitUser);
} }
void _validateBasicsStep(ValidateBasicsStep event, Emitter<UsersState> emit) { void _validateBasicsStep(ValidateBasicsStep event, Emitter<UsersState> emit) {
if (formKey.currentState?.validate() ?? false) { if (formKey.currentState?.validate() ?? false) {
@ -42,9 +48,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
final TextEditingController emailController = TextEditingController(); final TextEditingController emailController = TextEditingController();
final TextEditingController phoneController = TextEditingController(); final TextEditingController phoneController = TextEditingController();
final TextEditingController jobTitleController = TextEditingController(); final TextEditingController jobTitleController = TextEditingController();
final TextEditingController roleSearchController = TextEditingController(); final TextEditingController roleSearchController = TextEditingController();
// final TextEditingController jobTitleController = TextEditingController();
bool? isCompleteBasics; bool? isCompleteBasics;
bool? isCompleteRolePermissions; bool? isCompleteRolePermissions;
@ -84,6 +88,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
await CommunitySpaceManagementApi().fetchCommunities(); await CommunitySpaceManagementApi().fetchCommunities();
updatedCommunities = await Future.wait( updatedCommunities = await Future.wait(
communities.map((community) async { communities.map((community) async {
print(community.uuid);
List<SpaceModel> spaces = List<SpaceModel> spaces =
await _fetchSpacesForCommunity(community.uuid); await _fetchSpacesForCommunity(community.uuid);
spacesNodes = _buildTreeNodes(spaces); spacesNodes = _buildTreeNodes(spaces);
@ -228,8 +233,8 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.of(event.context).pop(); Navigator.of(event.context).pop(true);
Navigator.of(event.context).pop(); Navigator.of(event.context).pop(true);
}, },
child: const Text('OK'), child: const Text('OK'),
), ),
@ -244,6 +249,48 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
} }
} }
_editInvitUser(EditInviteUsers event, Emitter<UsersState> emit) async {
try {
emit(UsersLoadingState());
List<String> 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: <Widget>[
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<UsersState> emit) { void searchRolePermission(SearchPermission event, Emitter<UsersState> emit) {
emit(UsersLoadingState()); emit(UsersLoadingState());
if (event.searchTerm!.isEmpty) { if (event.searchTerm!.isEmpty) {
@ -268,6 +315,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
bool isCompleteBasicsFun(CheckStepStatus event, Emitter<UsersState> emit) { bool isCompleteBasicsFun(CheckStepStatus event, Emitter<UsersState> emit) {
emit(UsersLoadingState()); emit(UsersLoadingState());
if (event.isEditUser == false) {
add(const CheckEmailEvent()); add(const CheckEmailEvent());
final emailRegex = RegExp( final emailRegex = RegExp(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
@ -279,7 +327,10 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
emailController.text.isNotEmpty && emailController.text.isNotEmpty &&
isEmailValid && isEmailValid &&
isEmailServerValid; isEmailServerValid;
} else {
isCompleteBasics = firstNameController.text.isNotEmpty &&
lastNameController.text.isNotEmpty;
}
emit(ChangeStatusSteps()); emit(ChangeStatusSteps());
emit(ValidateBasics()); emit(ValidateBasics());
return isCompleteBasics!; return isCompleteBasics!;
@ -293,4 +344,153 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
} }
} }
} }
EditUserModel? res = EditUserModel(
spaces: [],
jobTitle: '',
phoneNumber: '',
uuid: '',
email: '',
firstName: '',
lastName: '',
roleType: '',
status: '',
invitedBy: '',
createdDate: '',
createdTime: '');
Future<void> getUserById(
GetUserByIdEvent event,
Emitter<UsersState> 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<TreeNode> nodes, List<String> 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<UsersState> emit,
) {
emit(UsersLoadingState());
event.node.isExpanded = !event.node.isExpanded;
emit(ChangeStatusSteps());
}
void _onToggleNodeCheck(
ToggleNodeCheck event,
Emitter<UsersState> 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<TreeNode> 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;
}
} }

View File

@ -14,6 +14,14 @@ class SendInviteUsers extends UsersEvent {
List<Object?> get props => [context]; List<Object?> get props => [context];
} }
class EditInviteUsers extends UsersEvent {
final BuildContext context;
final String userId;
const EditInviteUsers({required this.context, required this.userId});
@override
List<Object?> get props => [context, userId];
}
class CheckSpacesStepStatus extends UsersEvent { class CheckSpacesStepStatus extends UsersEvent {
const CheckSpacesStepStatus(); const CheckSpacesStepStatus();
@override @override
@ -52,9 +60,11 @@ class GetBatchStatus extends UsersEvent {
List<Object?> get props => [uuids]; List<Object?> get props => [uuids];
} }
//isEditUser:widget.userId!=''? false:true
class CheckStepStatus extends UsersEvent { class CheckStepStatus extends UsersEvent {
final int? steps; final int? steps;
const CheckStepStatus({this.steps}); bool? isEditUser = false;
CheckStepStatus({this.steps, required this.isEditUser});
@override @override
List<Object?> get props => [steps]; List<Object?> get props => [steps];
} }
@ -85,7 +95,7 @@ class SelecteId extends UsersEvent {
} }
class ValidateBasicsStep extends UsersEvent { class ValidateBasicsStep extends UsersEvent {
const ValidateBasicsStep(); ValidateBasicsStep();
@override @override
List<Object?> get props => []; List<Object?> get props => [];
} }
@ -95,3 +105,79 @@ class CheckEmailEvent extends UsersEvent {
@override @override
List<Object?> get props => []; List<Object?> get props => [];
} }
class GetUserByIdEvent extends UsersEvent {
final String? uuid;
const GetUserByIdEvent({this.uuid});
@override
List<Object?> get props => [uuid];
}
class ToggleNodeExpansion extends UsersEvent {
final TreeNode node;
ToggleNodeExpansion({required this.node});
@override
List<Object?> get props => [node];
}
class UpdateNodeCheckStatus extends UsersEvent {
final TreeNode node;
UpdateNodeCheckStatus({required this.node});
@override
List<Object?> get props => [node];
}
// Define new events
class ToggleNodeHighlightEvent extends UsersEvent {
final TreeNode node;
ToggleNodeHighlightEvent(this.node);
@override
List<Object?> get props => [node];
}
class ExpandAllNodesEvent extends UsersEvent {
@override
List<Object?> get props => [];
}
class CollapseAllNodesEvent extends UsersEvent {
@override
List<Object?> get props => [];
}
class ClearSelectionsEvent extends UsersEvent {
@override
List<Object?> get props => [];
}
class ToggleNodeCheckEvent extends UsersEvent {
final TreeNode node;
ToggleNodeCheckEvent(this.node);
@override
List<Object?> 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<Object?> get props => [];
}
class EditUserEvent extends UsersEvent {
const EditUserEvent();
@override
List<Object?> get props => [];
}

View File

@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart'; 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/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 { sealed class UsersState extends Equatable {
const UsersState(); const UsersState();
@ -80,3 +81,10 @@ final class ValidateBasics extends UsersState {
@override @override
List<Object> get props => []; List<Object> get props => [];
} }
class UsersLoadedState extends UsersState {
final List<TreeNode> updatedCommunities;
UsersLoadedState({required this.updatedCommunities});
@override
List<Object> get props => [];
}

View File

@ -113,7 +113,8 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
if (currentStep < 3) { if (currentStep < 3) {
currentStep++; currentStep++;
if (currentStep == 2) { if (currentStep == 2) {
_blocRole.add(const CheckStepStatus()); _blocRole.add(
CheckStepStatus(isEditUser: false));
} else if (currentStep == 3) { } else if (currentStep == 3) {
_blocRole _blocRole
.add(const CheckSpacesStepStatus()); .add(const CheckSpacesStepStatus());
@ -150,9 +151,11 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
Widget _getFormContent() { Widget _getFormContent() {
switch (currentStep) { switch (currentStep) {
case 1: case 1:
return const BasicsView(); return BasicsView(
userId: '',
);
case 2: case 2:
return const SpacesAccessView(); return SpacesAccessView();
case 3: case 3:
return const RolesAndPermission(); return const RolesAndPermission();
default: default:
@ -169,7 +172,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
bloc.add(const CheckSpacesStepStatus()); bloc.add(const CheckSpacesStepStatus());
currentStep = step; currentStep = step;
Future.delayed(const Duration(milliseconds: 500), () { Future.delayed(const Duration(milliseconds: 500), () {
bloc.add(const ValidateBasicsStep()); bloc.add(ValidateBasicsStep());
}); });
}); });
@ -234,7 +237,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
onTap: () { onTap: () {
setState(() { setState(() {
currentStep = step; currentStep = step;
bloc.add(const CheckStepStatus()); bloc.add(CheckStepStatus(isEditUser: false));
if (step3 == 3) { if (step3 == 3) {
bloc.add(const CheckRoleStepStatus()); bloc.add(const CheckRoleStepStatus());
} }
@ -299,7 +302,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
currentStep = step; currentStep = step;
step3 = step; step3 = step;
bloc.add(const CheckSpacesStepStatus()); bloc.add(const CheckSpacesStepStatus());
bloc.add(const CheckStepStatus()); bloc.add(CheckStepStatus(isEditUser: false));
}); });
}, },
child: Column( child: Column(

View File

@ -11,7 +11,8 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class BasicsView extends StatelessWidget { class BasicsView extends StatelessWidget {
const BasicsView({super.key}); String? userId = '';
BasicsView({super.key, this.userId});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<UsersBloc, UsersState>(builder: (context, state) { return BlocBuilder<UsersBloc, UsersState>(builder: (context, state) {
@ -184,9 +185,10 @@ class BasicsView extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: TextFormField( child: TextFormField(
enabled: userId!=''? false:true,
onChanged: (value) { onChanged: (value) {
Future.delayed(const Duration(milliseconds: 200), () { Future.delayed(const Duration(milliseconds: 200), () {
_blocRole.add(const CheckStepStatus()); _blocRole.add(CheckStepStatus(isEditUser:userId!=''? false:true));
_blocRole.add( ValidateBasicsStep()); _blocRole.add( ValidateBasicsStep());
}); });
}, },

View File

@ -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<UsersBloc>(context);
debugPrint('TreeView constructed with userId = $userId');
return BlocProvider(
create: (_) => UsersBloc(),
// ..add(const LoadCommunityAndSpacesEvent()),
child: BlocConsumer<UsersBloc, UsersState>(
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<TreeNode> 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)));
}
}

View File

@ -1,12 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.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'; import 'package:syncrow_web/utils/color_manager.dart';
class DeleteUserDialog extends StatefulWidget { class DeleteUserDialog extends StatefulWidget {
const DeleteUserDialog({super.key}); final Function()? onTapDelete;
DeleteUserDialog({super.key, this.onTapDelete});
@override @override
_DeleteUserDialogState createState() => _DeleteUserDialogState(); _DeleteUserDialogState createState() => _DeleteUserDialogState();
@ -17,21 +15,16 @@ class _DeleteUserDialogState extends State<DeleteUserDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => UsersBloc(),
child: BlocConsumer<UsersBloc, UsersState>(
listener: (context, state) {},
builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
return Dialog( return Dialog(
child: Container( child: Container(
height: 160,
width: 200,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20))), borderRadius: BorderRadius.all(Radius.circular(20))),
child: const Column( child: Column(
children: [ children: [
Padding( const Padding(
padding: EdgeInsets.all(8.0), padding: EdgeInsets.all(8.0),
child: SizedBox( child: SizedBox(
child: Text( child: Text(
@ -43,21 +36,73 @@ class _DeleteUserDialogState extends State<DeleteUserDialog> {
), ),
), ),
), ),
Divider(), const Padding(
Expanded( 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( child: Text(
"Are you sure you want to delete this user?", "Are you sure you want to delete this user?",
textAlign: TextAlign.center, textAlign: TextAlign.center,
),
)), )),
Row( Row(
children: [ children: [
Expanded(child: Text('Cancel')), Expanded(
Expanded(child: Text('Delete')), child: InkWell(
onTap: () {
Navigator.of(context).pop(true);
},
child: Container(
padding: const EdgeInsets.all(10),
decoration: const BoxDecoration(
border: Border(
right: BorderSide(
color: ColorsManager.grayBorder,
width: 0.5,
),
top: BorderSide(
color: ColorsManager.grayBorder,
width: 1,
),
),
),
child: const Center(child: Text('Cancel'))),
)),
Expanded(
child: InkWell(
onTap: widget.onTapDelete,
child: Container(
padding: const EdgeInsets.all(10),
decoration: const BoxDecoration(
border: Border(
left: BorderSide(
color: ColorsManager.grayBorder,
width: 0.5,
),
top: BorderSide(
color: ColorsManager.grayBorder,
width: 1,
),
),
),
child: const Center(
child: Text(
'Delete',
style: TextStyle(
color: ColorsManager.red,
),
))),
)),
], ],
) )
], ],
), ),
)); ));
}));
} }
} }

View File

@ -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<EditUserDialog> {
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<UsersBloc, UsersState>(listener: (context, state) {
if (state is SpacesLoadedState) {
BlocProvider.of<UsersBloc>(context)
.add(GetUserByIdEvent(uuid: widget.userId));
}
}, builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(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,
),
),
)
],
),
);
}
}

View File

@ -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<void> showPopUpFilterMenu({
required BuildContext context,
Function()? onSortAtoZ,
Function()? onSortZtoA,
Function()? cancelButton,
required Map<String, bool> checkboxStates,
Function()? onOkPressed,
List<String>? 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: <PopupMenuEntry>[
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,
),
),
),
],
),
),
],
);
}

View File

@ -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_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_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/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/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class SpacesAccessView extends StatelessWidget { class SpacesAccessView extends StatelessWidget {
const SpacesAccessView({super.key}); String? userId = '';
SpacesAccessView({super.key, this.userId});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
@ -109,9 +110,7 @@ class SpacesAccessView extends StatelessWidget {
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Container( child: Container(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
child: TreeView( child: TreeView(userId: userId))))
bloc: _blocRole,
))))
], ],
), ),
), ),
@ -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<TreeView> {
Widget _buildTree(List<TreeNode> 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<TreeNode> 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),
);
}
}

View File

@ -1,8 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc/bloc.dart'; 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/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_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart';
import 'package:syncrow_web/services/user_permission.dart';
class UserTableBloc extends Bloc<UserTableEvent, UserTableState> { class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
UserTableBloc() : super(TableInitial()) { UserTableBloc() : super(TableInitial()) {
@ -12,79 +14,108 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
on<SortUsersByNameDesc>(_toggleSortUsersByNameDesc); on<SortUsersByNameDesc>(_toggleSortUsersByNameDesc);
on<DateOldestToNewestEvent>(_toggleSortUsersByDateOldestToNewest); on<DateOldestToNewestEvent>(_toggleSortUsersByDateOldestToNewest);
on<DateNewestToOldestEvent>(_toggleSortUsersByDateNewestToOldest); on<DateNewestToOldestEvent>(_toggleSortUsersByDateNewestToOldest);
on<SearchUsers>(_searchUsers);
on<ChangePage>(_handlePageChange);
on<FilterUsersByRoleEvent>(_filterUsersByRole);
on<FilterUsersByJobEvent>(_filterUsersByJobTitle);
on<FilterUsersByCreatedEvent>(_filterUsersByCreated);
on<FilterUsersByDeActevateEvent>(_filterUserActevate);
on<DeleteUserEvent>(_deleteUser);
} }
int itemsPerPage = 10;
int currentPage = 1;
List<RolesUserModel> users = []; List<RolesUserModel> users = [];
List<RolesUserModel> initialUsers = []; // Save the initial state List<RolesUserModel> initialUsers = [];
String currentSortOrder = ''; // Keeps track of the current sorting order String currentSortOrder = '';
String currentSortOrderDate = ''; // Keeps track of the current sorting order String currentSortOrderDate = '';
List<String> roleTypes = [];
List<String> jobTitle = [];
List<String> createdBy = [];
List<String> deActivate = [];
Future<void> _getUsers(GetUsers event, Emitter<UserTableState> emit) async { Future<void> _getUsers(GetUsers event, Emitter<UserTableState> emit) async {
emit(UsersLoadingState()); emit(UsersLoadingState());
try { try {
users = [ roleTypes.clear();
RolesUserModel( jobTitle.clear();
id: '1', createdBy.clear();
userName: 'b 1', deActivate.clear();
userEmail: 'test1@test.com', users = await UserPermissionApi().fetchUsers();
action: '', for (var user in users) {
createdBy: 'Admin', roleTypes.add(user.roleType.toString());
creationDate: '25/10/2024', }
creationTime: '10:30 AM', for (var user in users) {
status: 'Invited', jobTitle.add(user.jobTitle.toString());
), }
RolesUserModel( for (var user in users) {
id: '2', createdBy.add(user.invitedBy.toString());
userName: 'a 2', }
userEmail: 'test2@test.com', for (var user in users) {
action: '', deActivate.add(user.status.toString());
createdBy: 'Admin', }
creationDate: '24/10/2024', roleTypes = roleTypes.toSet().toList();
creationTime: '2:30 PM', jobTitle = jobTitle.toSet().toList();
status: 'Active', createdBy = createdBy.toSet().toList();
), deActivate = deActivate.toSet().toList();
RolesUserModel(
id: '3',
userName: 'c 3',
userEmail: 'test3@test.com',
action: '',
createdBy: 'Admin',
creationDate: '23/10/2024',
creationTime: '9:00 AM',
status: 'Disabled',
),
];
// Sort users by newest to oldest as default
users.sort((a, b) { users.sort((a, b) {
final dateA = _parseDateTime(a.creationDate!); final dateA = _parseDateTime(a.createdDate);
final dateB = _parseDateTime(b.creationDate!); final dateB = _parseDateTime(b.createdDate);
return dateB.compareTo(dateA); // Newest to oldest return dateB.compareTo(dateA);
}); });
initialUsers = List.from(users); // Save the initial state initialUsers = List.from(users);
_handlePageChange(ChangePage(1), emit);
emit(UsersLoadedState(users: users)); emit(UsersLoadedState(users: users));
} catch (e) { } catch (e) {
emit(ErrorState(e.toString())); emit(ErrorState(e.toString()));
} }
} }
void _changeUserStatus(ChangeUserStatus event, Emitter<UserTableState> emit) { Future<void> _deleteUser(
DeleteUserEvent event, Emitter<UserTableState> emit) async {
emit(UsersLoadingState());
try { try {
users = users.map((user) { bool res = await UserPermissionApi().deleteUserById(event.userId);
if (user.id == event.userId) { if (res == true) {
return RolesUserModel( Navigator.of(event.context).pop(true);
id: user.id, } else {
userName: user.userName, emit(const ErrorState('Something error'));
userEmail: user.userEmail, }
createdBy: user.createdBy, emit(UsersLoadedState(users: users));
creationDate: user.creationDate, } catch (e) {
creationTime: user.creationTime, emit(ErrorState(e.toString()));
status: event.newStatus, }
action: user.action,
);
} }
return user;
}).toList();
Future<void> _changeUserStatus(
ChangeUserStatus event, Emitter<UserTableState> 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)); emit(UsersLoadedState(users: users));
} catch (e) { } catch (e) {
emit(ErrorState(e.toString())); emit(ErrorState(e.toString()));
@ -94,16 +125,14 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
void _toggleSortUsersByNameAsc( void _toggleSortUsersByNameAsc(
SortUsersByNameAsc event, Emitter<UserTableState> emit) { SortUsersByNameAsc event, Emitter<UserTableState> emit) {
if (currentSortOrder == "Asc") { if (currentSortOrder == "Asc") {
// If already sorted ascending, reset to the initial state
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = ""; currentSortOrder = "";
users = List.from(initialUsers); // Reset to saved initial state users = List.from(initialUsers);
emit(UsersLoadedState(users: users)); emit(UsersLoadedState(users: users));
} else { } else {
// Sort ascending
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = "Asc"; currentSortOrder = "Asc";
users.sort((a, b) => a.userName!.compareTo(b.userName!)); users.sort((a, b) => a.firstName!.compareTo(b.firstName!));
emit(UsersLoadedState(users: users)); emit(UsersLoadedState(users: users));
} }
} }
@ -111,7 +140,6 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
void _toggleSortUsersByNameDesc( void _toggleSortUsersByNameDesc(
SortUsersByNameDesc event, Emitter<UserTableState> emit) { SortUsersByNameDesc event, Emitter<UserTableState> emit) {
if (currentSortOrder == "Desc") { if (currentSortOrder == "Desc") {
// If already sorted descending, reset to the initial state
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = ""; currentSortOrder = "";
users = List.from(initialUsers); // Reset to saved initial state users = List.from(initialUsers); // Reset to saved initial state
@ -120,7 +148,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
// Sort descending // Sort descending
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = "Desc"; currentSortOrder = "Desc";
users.sort((a, b) => b.userName!.compareTo(a.userName!)); users.sort((a, b) => b.firstName!.compareTo(a.firstName!));
emit(UsersLoadedState(users: users)); emit(UsersLoadedState(users: users));
} }
} }
@ -128,19 +156,17 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
void _toggleSortUsersByDateNewestToOldest( void _toggleSortUsersByDateNewestToOldest(
DateNewestToOldestEvent event, Emitter<UserTableState> emit) { DateNewestToOldestEvent event, Emitter<UserTableState> emit) {
if (currentSortOrderDate == "NewestToOldest") { if (currentSortOrderDate == "NewestToOldest") {
// If already sorted ascending, reset to the initial state
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = ""; currentSortOrder = "";
currentSortOrderDate = ""; currentSortOrderDate = "";
users = List.from(initialUsers); // Reset to saved initial state users = List.from(initialUsers);
emit(UsersLoadedState(users: users)); emit(UsersLoadedState(users: users));
} else { } else {
// Sort ascending
emit(UsersLoadingState()); emit(UsersLoadingState());
users.sort((a, b) { users.sort((a, b) {
final dateA = _parseDateTime(a.creationDate!); final dateA = _parseDateTime(a.createdDate);
final dateB = _parseDateTime(b.creationDate!); final dateB = _parseDateTime(b.createdDate);
return dateB.compareTo(dateA); // Newest to oldest return dateB.compareTo(dateA);
}); });
emit(UsersLoadedState(users: users)); emit(UsersLoadedState(users: users));
} }
@ -149,19 +175,17 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
void _toggleSortUsersByDateOldestToNewest( void _toggleSortUsersByDateOldestToNewest(
DateOldestToNewestEvent event, Emitter<UserTableState> emit) { DateOldestToNewestEvent event, Emitter<UserTableState> emit) {
if (currentSortOrderDate == "OldestToNewest") { if (currentSortOrderDate == "OldestToNewest") {
// If already sorted ascending, reset to the initial state
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = ""; currentSortOrder = "";
currentSortOrderDate = ""; currentSortOrderDate = "";
users = List.from(initialUsers); // Reset to saved initial state users = List.from(initialUsers);
emit(UsersLoadedState(users: users)); emit(UsersLoadedState(users: users));
} else { } else {
// Sort ascending
emit(UsersLoadingState()); emit(UsersLoadingState());
users.sort((a, b) { users.sort((a, b) {
final dateA = _parseDateTime(a.creationDate!); final dateA = _parseDateTime(a.createdDate);
final dateB = _parseDateTime(b.creationDate!); final dateB = _parseDateTime(b.createdDate);
return dateA.compareTo(dateB); // Newest to oldest return dateA.compareTo(dateB);
}); });
emit(UsersLoadedState(users: users)); emit(UsersLoadedState(users: users));
} }
@ -169,17 +193,94 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
DateTime _parseDateTime(String date) { DateTime _parseDateTime(String date) {
try { try {
// Split the date into day, month, and year
final dateParts = date.split('/'); final dateParts = date.split('/');
final day = int.parse(dateParts[0]); final day = int.parse(dateParts[0]);
final month = int.parse(dateParts[1]); final month = int.parse(dateParts[1]);
final year = int.parse(dateParts[2]); final year = int.parse(dateParts[2]);
// Split the time into hours and minutes
return DateTime(year, month, day); return DateTime(year, month, day);
} catch (e) { } catch (e) {
throw FormatException('Invalid date or time format: $date '); throw FormatException('Invalid date or time format: $date ');
} }
} }
Future<void> _searchUsers(
SearchUsers event, Emitter<UserTableState> 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<UserTableState> 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<UserTableState> 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<UserTableState> emit) {
emit(UsersLoadingState());
final filteredUsers = initialUsers.where((user) {
return event.selectedRoles.contains(user.roleType);
}).toList();
emit(UsersLoadedState(users: filteredUsers));
}
void _filterUsersByJobTitle(
FilterUsersByJobEvent event, Emitter<UserTableState> emit) {
emit(UsersLoadingState());
final filteredUsers = users.where((user) {
return event.selectedJob.contains(user.jobTitle);
}).toList();
emit(UsersLoadedState(users: filteredUsers));
}
void _filterUsersByCreated(
FilterUsersByCreatedEvent event, Emitter<UserTableState> emit) {
emit(UsersLoadingState());
final filteredUsers = initialUsers.where((user) {
return event.selectedCreatedBy.contains(user.invitedBy);
}).toList();
emit(UsersLoadedState(users: filteredUsers));
}
void _filterUserActevate(
FilterUsersByDeActevateEvent event, Emitter<UserTableState> emit) {
emit(UsersLoadingState());
final filteredUsers = initialUsers.where((user) {
return event.selectedActivate.contains(user.status);
}).toList();
emit(UsersLoadedState(users: filteredUsers));
}
} }

View File

@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
sealed class UserTableEvent extends Equatable { sealed class UserTableEvent extends Equatable {
const UserTableEvent(); const UserTableEvent();
@ -47,7 +48,6 @@ class StoreUsersEvent extends UserTableEvent {
List<Object?> get props => []; List<Object?> get props => [];
} }
class DateNewestToOldestEvent extends UserTableEvent { class DateNewestToOldestEvent extends UserTableEvent {
const DateNewestToOldestEvent(); const DateNewestToOldestEvent();
@ -61,3 +61,61 @@ class DateOldestToNewestEvent extends UserTableEvent {
@override @override
List<Object?> get props => []; List<Object?> get props => [];
} }
class SearchUsers extends UserTableEvent {
final String query;
SearchUsers(this.query);
@override
List<Object?> get props => [];
}
class ChangePage extends UserTableEvent {
final int pageNumber;
ChangePage(this.pageNumber);
@override
List<Object> get props => [pageNumber];
}
class DeleteUserEvent extends UserTableEvent {
final String userId;
final BuildContext context;
const DeleteUserEvent(this.userId, this.context);
@override
List<Object> get props => [userId, context];
}
class FilterUsersByRoleEvent extends UserTableEvent {
final List<String> selectedRoles;
FilterUsersByRoleEvent(this.selectedRoles);
@override
List<Object?> get props => [selectedRoles];
}
class FilterUsersByJobEvent extends UserTableEvent {
final List<String> selectedJob;
FilterUsersByJobEvent(this.selectedJob);
@override
List<Object?> get props => [selectedJob];
}
class FilterUsersByCreatedEvent extends UserTableEvent {
final List<String> selectedCreatedBy;
FilterUsersByCreatedEvent(this.selectedCreatedBy);
@override
List<Object?> get props => [selectedCreatedBy];
}
class FilterUsersByDeActevateEvent extends UserTableEvent {
final List<String> selectedActivate;
FilterUsersByDeActevateEvent(this.selectedActivate);
@override
List<Object?> get props => [selectedActivate];
}

View File

@ -81,15 +81,17 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Container( child: Container(
decoration: containerDecoration.copyWith( decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.all(Radius.circular(20))), borderRadius: const BorderRadius.all(Radius.circular(20))),
child: FittedBox( child: FittedBox(
child: Column( child: Column(
children: [ children: [
// Header Row with Resizable Columns // Header Row with Resizable Columns
Container( Container(
width: MediaQuery.of(context).size.width,
decoration: containerDecoration.copyWith( decoration: containerDecoration.copyWith(
color: ColorsManager.circleRolesBackground, color: ColorsManager.circleRolesBackground,
borderRadius: BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15), topLeft: Radius.circular(15),
topRight: Radius.circular(15))), topRight: Radius.circular(15))),
child: Row( child: Row(
@ -167,23 +169,62 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
), ),
), ),
// Data Rows with Dividers // Data Rows with Dividers
Container( widget.rows.isEmpty
? Container(
child: SizedBox(
height: MediaQuery.of(context).size.height / 2,
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
SvgPicture.asset(Assets.emptyTable),
const SizedBox(
height: 15,
),
const Text(
'No Users',
style: TextStyle(
color: ColorsManager.lightGrayColor,
fontSize: 16,
fontWeight: FontWeight.w700),
)
],
),
],
),
),
),
)
: Center(
child: Container(
// height: MediaQuery.of(context).size.height * 0.59,
width: MediaQuery.of(context).size.width,
decoration: containerDecoration.copyWith( decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(15), bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15))), bottomRight: Radius.circular(15))),
child: Column( child: ListView.builder(
children: widget.rows.map((row) { physics: const NeverScrollableScrollPhysics(),
int rowIndex = widget.rows.indexOf(row); shrinkWrap: true,
itemCount: widget.rows.length,
itemBuilder: (context, rowIndex) {
final row = widget.rows[rowIndex];
return Column( return Column(
children: [ children: [
Container( Container(
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 5, top: 10, right: 5, bottom: 10), left: 5,
top: 10,
right: 5,
bottom: 10),
child: Row( child: Row(
children: List.generate(row.length, (index) { children:
List.generate(row.length, (index) {
return SizedBox( return SizedBox(
width: columnWidths[index], width: columnWidths[index],
child: SizedBox( child: SizedBox(
@ -200,8 +241,8 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
), ),
if (rowIndex < widget.rows.length - 1) if (rowIndex < widget.rows.length - 1)
Row( Row(
children: List.generate(widget.titles.length, children: List.generate(
(index) { widget.titles.length, (index) {
return SizedBox( return SizedBox(
width: columnWidths[index], width: columnWidths[index],
child: const Divider( child: const Divider(
@ -210,11 +251,12 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
height: 1, height: 1,
), ),
); );
})) }),
// Add a Divider below each row except the last one ),
], ],
); );
}).toList(), },
),
), ),
), ),
], ],

View File

@ -1,7 +1,11 @@
import 'package:flutter/material.dart'; 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: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/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_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_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/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'; import 'package:syncrow_web/utils/style.dart';
class UsersPage extends StatelessWidget { class UsersPage extends StatelessWidget {
const UsersPage({super.key}); UsersPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final TextEditingController searchController = TextEditingController(); final TextEditingController searchController = TextEditingController();
@ -44,9 +49,9 @@ class UsersPage extends StatelessWidget {
padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(20)), borderRadius: const BorderRadius.all(Radius.circular(20)),
color: status == "Invited" color: status == "invited"
? ColorsManager.invitedOrange.withOpacity(0.5) ? ColorsManager.invitedOrange.withOpacity(0.5)
: status == "Active" : status == "active"
? ColorsManager.activeGreen.withOpacity(0.5) ? ColorsManager.activeGreen.withOpacity(0.5)
: ColorsManager.disabledPink.withOpacity(0.5), : ColorsManager.disabledPink.withOpacity(0.5),
), ),
@ -60,9 +65,9 @@ class UsersPage extends StatelessWidget {
Text( Text(
status, status,
style: Theme.of(context).textTheme.bodySmall?.copyWith( style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: status == "Invited" color: status == "invited"
? ColorsManager.invitedOrangeText ? ColorsManager.invitedOrangeText
: status == "Active" : status == "active"
? ColorsManager.activeGreenText ? ColorsManager.activeGreenText
: ColorsManager.disabledRedText, : ColorsManager.disabledRedText,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@ -81,23 +86,14 @@ class UsersPage extends StatelessWidget {
required Function()? onTap}) { required Function()? onTap}) {
return Center( return Center(
child: InkWell( child: InkWell(
onTap: () { onTap: onTap,
final newStatus = status == 'Active'
? 'Disabled'
: status == 'Disabled'
? 'Invited'
: 'Active';
context
.read<UserTableBloc>()
.add(ChangeUserStatus(userId: userId, newStatus: newStatus));
},
child: Padding( child: Padding(
padding: padding:
const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5),
child: SvgPicture.asset( child: SvgPicture.asset(
status == "Invited" status == "invited"
? Assets.invitedIcon ? Assets.invitedIcon
: status == "Active" : status == "active"
? Assets.activeUser ? Assets.activeUser
: Assets.deActiveUser, : Assets.deActiveUser,
height: 35, height: 35,
@ -113,12 +109,14 @@ class UsersPage extends StatelessWidget {
final _blocRole = BlocProvider.of<UserTableBloc>(context); final _blocRole = BlocProvider.of<UserTableBloc>(context);
if (state is UsersLoadingState) { if (state is UsersLoadingState) {
_blocRole.add(ChangePage(_blocRole.currentPage));
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else if (state is UsersLoadedState) { } else if (state is UsersLoadedState) {
return Padding( return Padding(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Column( child: ListView(
crossAxisAlignment: CrossAxisAlignment.start, shrinkWrap: true,
children: [ children: [
Row( Row(
children: [ children: [
@ -131,6 +129,9 @@ class UsersPage extends StatelessWidget {
width: screenSize.width * 0.4, width: screenSize.width * 0.4,
child: TextFormField( child: TextFormField(
controller: searchController, controller: searchController,
onChanged: (value) {
context.read<UserTableBloc>().add(SearchUsers(value));
},
style: const TextStyle(color: Colors.black), style: const TextStyle(color: Colors.black),
decoration: textBoxDecoration(radios: 15)!.copyWith( decoration: textBoxDecoration(radios: 15)!.copyWith(
fillColor: ColorsManager.whiteColors, fillColor: ColorsManager.whiteColors,
@ -158,9 +159,9 @@ class UsersPage extends StatelessWidget {
builder: (BuildContext context) { builder: (BuildContext context) {
return const AddNewUserDialog(); return const AddNewUserDialog();
}, },
).then((listDevice) { ).then((v) {
if (listDevice != null) { if (v != null) {
_blocRole.add(const GetUsers());
} }
}); });
}, },
@ -201,6 +202,66 @@ class UsersPage extends StatelessWidget {
}, },
); );
} }
if (columnIndex == 2) {
final Map<String, bool> 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<UserTableBloc>()
.add(const SortUsersByNameAsc());
},
onSortZtoA: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
},
);
}
if (columnIndex == 3) {
final Map<String, bool> 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<UserTableBloc>()
.add(const SortUsersByNameAsc());
},
onSortZtoA: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
},
);
}
if (columnIndex == 4) { if (columnIndex == 4) {
showDateFilterMenu( showDateFilterMenu(
context: context, context: context,
@ -217,6 +278,37 @@ class UsersPage extends StatelessWidget {
}, },
); );
} }
if (columnIndex == 6) {
final Map<String, bool> 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<UserTableBloc>()
.add(const SortUsersByNameAsc());
},
onSortZtoA: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
},
);
}
if (columnIndex == 8) { if (columnIndex == 8) {
showDeActivateFilterMenu( showDeActivateFilterMenu(
context: context, context: context,
@ -248,19 +340,37 @@ class UsersPage extends StatelessWidget {
], ],
rows: state.users.map((user) { rows: state.users.map((user) {
return [ return [
Text(user.userName!), Text('${user.firstName} ${user.lastName}'),
Text(user.userEmail!), Text(user.email ?? ''),
const Text("Test"), Text(user.jobTitle ?? ''),
const Text("Member"), Text(user.roleType ?? ''),
Text(user.creationDate!), Text(user.createdDate ?? ''),
Text(user.creationTime!), Text(user.createdTime ?? ''),
Text(user.createdBy!), Text(user.invitedBy),
changeIconStatus( changeIconStatus(
status: user.status!, status:
userId: user.id!, user.isEnabled == false ? 'disabled' : user.status,
onTap: () {}, userId: user.uuid,
onTap: user.status != "invited"
? () {
final newStatus = user.status == 'active'
? 'disabled'
: user.status == 'disabled'
? 'invited'
: 'active';
context.read<UserTableBloc>().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( Row(
children: [ children: [
// actionButton( // actionButton(
@ -269,17 +379,80 @@ class UsersPage extends StatelessWidget {
// ), // ),
actionButton( actionButton(
title: "Edit", 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( actionButton(
title: "Delete", 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(), }).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<UserTableBloc>()
.add(ChangePage(pageNumber));
},
),
),
],
),
),
], ],
), ),
); );

View File

@ -77,7 +77,7 @@ class RolesAndPermissionPage extends StatelessWidget {
), ),
scaffoldBody: BlocProvider<UserTableBloc>( scaffoldBody: BlocProvider<UserTableBloc>(
create: (context) => UserTableBloc()..add(const GetUsers()), create: (context) => UserTableBloc()..add(const GetUsers()),
child: const UsersPage(), child: UsersPage(),
) )
// _blocRole.tapSelect == false // _blocRole.tapSelect == false
// ? UsersPage( // ? UsersPage(

View File

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

View File

@ -1,6 +1,9 @@
import 'dart:convert'; import 'dart:convert';
import 'package:dio/dio.dart'; 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/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/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/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.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 { class UserPermissionApi {
static final HTTPService _httpService = HTTPService(); static final HTTPService _httpService = HTTPService();
Future<List<RolesUserModel>> fetchUsers() async {
try {
final response = await _httpService.get(
path: ApiEndpoints.getUsers,
showServerMessage: true,
expectedResponseModel: (json) {
debugPrint('fetchUsers Response: $json');
final List<dynamic> 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 { fetchRoles() async {
final response = await _httpService.get( final response = await _httpService.get(
path: ApiEndpoints.roleTypes, path: ApiEndpoints.roleTypes,
@ -67,6 +89,7 @@ class UserPermissionApi {
} }
}, },
); );
print('sendInviteUser=$body');
return response ?? []; return response ?? [];
} on DioException catch (e) { } on DioException catch (e) {
@ -107,4 +130,95 @@ class UserPermissionApi {
return e.toString(); return e.toString();
} }
} }
Future<EditUserModel> 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<bool> editInviteUser({
String? firstName,
String? userId,
String? lastName,
String? jobTitle,
String? phoneNumber,
String? roleUuid,
List<String>? spaceUuids,
}) async {
try {
final body = <String, dynamic>{
"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<bool> 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<bool> changeUserStatusById(userUuid, status) async {
try {
Map<String, dynamic> 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);
}
}
} }

View File

@ -100,7 +100,13 @@ abstract class ApiEndpoints {
static const String roleTypes = '/role/types'; static const String roleTypes = '/role/types';
static const String permission = '/permission/{roleUuid}'; static const String permission = '/permission/{roleUuid}';
static const String inviteUser = '/invite-user'; static const String inviteUser = '/invite-user';
static const String checkEmail = '/invite-user/check-email'; 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}'; // static const String updateAutomation = '/automation/{automationId}';
// https://syncrow-dev.azurewebsites.net/invite-user/check-email
} }

36
macos/Podfile.lock Normal file
View File

@ -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

View File

@ -21,6 +21,8 @@
/* End PBXAggregateTarget section */ /* End PBXAggregateTarget section */
/* Begin PBXBuildFile 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 */; }; 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
@ -60,11 +62,12 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference 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 = "<group>"; };
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = "<group>"; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
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 = "<group>"; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
@ -76,8 +79,15 @@
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
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 = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -85,6 +95,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
2D0F1F294F673EF0DB5E4CA1 /* Pods_RunnerTests.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -92,6 +103,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
108157F896CD9F637B06D7C0 /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -125,6 +137,7 @@
331C80D6294CF71000263BE5 /* RunnerTests */, 331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */, 33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */, D73912EC22F37F3D000D13A0 /* Frameworks */,
75DCDFECC7757C5159E8F0C5 /* Pods */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -172,9 +185,25 @@
path = Runner; path = Runner;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
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 = "<group>";
};
D73912EC22F37F3D000D13A0 /* Frameworks */ = { D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5DAF1C60594A51D692304366 /* Pods_Runner.framework */,
E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@ -186,6 +215,7 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = ( buildPhases = (
A1935203066F42991FF0ED43 /* [CP] Check Pods Manifest.lock */,
331C80D1294CF70F00263BE5 /* Sources */, 331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */, 331C80D3294CF70F00263BE5 /* Resources */,
@ -204,11 +234,13 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
8ECFD939A4D371A145DBA191 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */, 33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */, 33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */, 33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */, 3399D490228B24CF009A79C7 /* ShellScript */,
92D754792F50A5D35F6D5AEE /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@ -329,6 +361,67 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; 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 */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
@ -380,6 +473,7 @@
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = { 331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
@ -394,6 +488,7 @@
}; };
331C80DC294CF71000263BE5 /* Release */ = { 331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
@ -408,6 +503,7 @@
}; };
331C80DD294CF71000263BE5 /* Profile */ = { 331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;

View File

@ -4,4 +4,7 @@
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "group:Runner.xcodeproj">
</FileRef> </FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace> </Workspace>

View File

@ -392,6 +392,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" 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: path:
dependency: transitive dependency: transitive
description: description:

View File

@ -53,6 +53,7 @@ dependencies:
uuid: ^4.4.2 uuid: ^4.4.2
time_picker_spinner: ^1.0.0 time_picker_spinner: ^1.0.0
intl_phone_field: ^3.2.0 intl_phone_field: ^3.2.0
number_pagination: ^1.1.6
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: