add_user_dialog

This commit is contained in:
mohammad
2024-12-26 16:32:18 +03:00
parent 573852b4b4
commit ed2187b7ff
32 changed files with 1783 additions and 1048 deletions

BIN
assets/icons/atoz_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,3 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0001 0.416701C10.0001 0.647137 9.81341 0.833403 9.58339 0.833403H4.16627C3.93625 0.833403 3.74957 0.647137 3.74957 0.416701C3.74957 0.186266 3.93625 0 4.16627 0H9.58339C9.81341 0 10.0001 0.186266 10.0001 0.416701ZM8.33328 2.08351H4.16627C3.93625 2.08351 3.74957 2.26977 3.74957 2.50021C3.74957 2.73064 3.93625 2.91691 4.16627 2.91691H8.33328C8.5633 2.91691 8.74998 2.73064 8.74998 2.50021C8.74998 2.26977 8.5633 2.08351 8.33328 2.08351ZM7.08318 4.16701H4.16627C3.93625 4.16701 3.74957 4.35328 3.74957 4.58372C3.74957 4.81415 3.93625 5.00042 4.16627 5.00042H7.08318C7.3132 5.00042 7.49988 4.81415 7.49988 4.58372C7.49988 4.35328 7.3132 4.16701 7.08318 4.16701ZM5.83307 6.25052H4.16627C3.93625 6.25052 3.74957 6.43679 3.74957 6.66722C3.74957 6.89766 3.93625 7.08392 4.16627 7.08392H5.83307C6.06309 7.08392 6.24977 6.89766 6.24977 6.66722C6.24977 6.43679 6.06309 6.25052 5.83307 6.25052ZM3.21077 8.03942L2.49946 8.75073V0.416701C2.49946 0.186266 2.31278 0 2.08276 0C1.85274 0 1.66606 0.186266 1.66606 0.416701V8.75073L0.954333 8.039C0.791403 7.87607 0.528048 7.87607 0.365118 8.039C0.202187 8.20193 0.202187 8.46529 0.365118 8.62822L1.49313 9.75623C1.65564 9.91874 1.86899 10 2.08276 10C2.29653 10 2.50946 9.91874 2.67198 9.75623L3.79999 8.62822C3.96292 8.46529 3.96292 8.20193 3.79999 8.039C3.63706 7.87607 3.3737 7.87649 3.21077 8.03942Z" fill="#999999"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/icons/ztoa_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -12,7 +12,7 @@ Future<void> showCustomDialog({
double? iconWidth,
VoidCallback? onOkPressed,
bool barrierDismissible = false,
required List<Widget> actions,
List<Widget>? actions,
}) {
return showDialog(
context: context,

View File

@ -16,7 +16,7 @@ class RoleTypeModel {
uuid: json['uuid'],
createdAt: json['createdAt'],
updatedAt: json['updatedAt'],
type: json['type'],
type: json['type'].toString().toLowerCase().replaceAll("_", " "),
);
}
}

View File

@ -1,20 +1,19 @@
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/custom_dialog.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/services/user_permission.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class UsersBloc extends Bloc<UsersEvent, UsersState> {
UsersBloc() : super(UsersInitial()) {
on<GetUsers>(_getUsers);
on<ChangeUserStatus>(_changeUserStatus);
on<CheckStepStatus>(isCompleteBasicsFun);
on<LoadCommunityAndSpacesEvent>(_onLoadCommunityAndSpaces);
on<SearchAnode>(searchTreeNode);
@ -25,6 +24,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
on<SendInviteUsers>(_sendInvitUser);
on<ValidateBasicsStep>(_validateBasicsStep);
on<CheckRoleStepStatus>(isCompleteRoleFun);
on<CheckEmailEvent>(checkEmail);
}
void _validateBasicsStep(ValidateBasicsStep event, Emitter<UsersState> emit) {
if (formKey.currentState?.validate() ?? false) {
@ -34,74 +34,8 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
}
}
List<RolesUserModel> users = [];
String roleSelected = '';
Future<void> _getUsers(GetUsers event, Emitter<UsersState> emit) async {
emit(UsersLoadingState());
try {
users = [
RolesUserModel(
id: '1',
userName: 'user 1',
userEmail: 'test1@test.com',
action: '',
createdBy: 'Admin',
creationDate: '25/10/2024',
creationTime: '10:30 AM',
status: 'Invited',
),
RolesUserModel(
id: '2',
userName: 'user 2',
userEmail: 'test2@test.com',
action: '',
createdBy: 'Admin',
creationDate: '25/10/2024',
creationTime: '10:30 AM',
status: 'Active',
),
RolesUserModel(
id: '3',
userName: 'user 3',
userEmail: 'test3@test.com',
action: '',
createdBy: 'Admin',
creationDate: '25/10/2024',
creationTime: '10:30 AM',
status: 'Disabled',
),
];
emit(UsersLoadedState(users: users));
} catch (e) {
emit(ErrorState(e.toString()));
}
}
void _changeUserStatus(ChangeUserStatus event, Emitter<UsersState> emit) {
try {
users = users.map((user) {
if (user.id == event.userId) {
return RolesUserModel(
id: user.id,
userName: user.userName,
userEmail: user.userEmail,
createdBy: user.createdBy,
creationDate: user.creationDate,
creationTime: user.creationTime,
status: event.newStatus,
action: user.action,
);
}
return user;
}).toList();
emit(UsersLoadedState(users: users));
} catch (e) {
emit(ErrorState(e.toString()));
}
}
final formKey = GlobalKey<FormState>();
final TextEditingController firstNameController = TextEditingController();
final TextEditingController lastNameController = TextEditingController();
@ -109,6 +43,9 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
final TextEditingController phoneController = TextEditingController();
final TextEditingController jobTitleController = TextEditingController();
final TextEditingController roleSearchController = TextEditingController();
// final TextEditingController jobTitleController = TextEditingController();
bool? isCompleteBasics;
bool? isCompleteRolePermissions;
bool? isCompleteSpaces;
@ -117,30 +54,6 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
int numberSpaces = 0;
int numberRole = 0;
// isCompleteBasicsFun(CheckStepStatus event, Emitter<UsersState> emit) {
// emit(UsersLoadingState());
// isCompleteBasics = firstNameController.text.isNotEmpty &&
// lastNameController.text.isNotEmpty &&
// emailController.text.isNotEmpty;
// emit(ChangeStatusSteps());
// return isCompleteBasics;
// }
bool isCompleteBasicsFun(CheckStepStatus event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
final emailRegex = RegExp(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
);
isCompleteBasics = firstNameController.text.isNotEmpty &&
lastNameController.text.isNotEmpty &&
emailController.text.isNotEmpty &&
emailRegex.hasMatch(emailController.text);
emit(ChangeStatusSteps());
return isCompleteBasics!;
}
void isCompleteSpacesFun(
CheckSpacesStepStatus event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
@ -184,7 +97,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
);
}).toList(),
);
emit(ChangeStatusSteps());
emit(SpacesLoadedState());
return updatedCommunities;
} catch (e) {
emit(ErrorState('Error loading communities and spaces: $e'));
@ -239,6 +152,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
}
List<String> selectedIds = [];
List<String> getSelectedIds(List<TreeNode> nodes) {
List<String> selectedIds = [];
for (var node in nodes) {
@ -259,7 +173,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
try {
emit(UsersLoadingState());
roles = await UserPermissionApi().fetchRoles();
add(PermissionEvent(roleUuid: roles.first.uuid));
// add(PermissionEvent(roleUuid: roles.first.uuid));
emit(RolePermissionInitial());
} catch (e) {
emit(ErrorState('Error loading communities and spaces: $e'));
@ -294,18 +208,40 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
_sendInvitUser(SendInviteUsers event, Emitter<UsersState> emit) async {
try {
emit(UsersLoadingState());
List<String> selectedIds = getSelectedIds(updatedCommunities);
await UserPermissionApi().sendInviteUser(
email: emailController.text,
firstName: firstNameController.text,
jobTitle: jobTitleController.text,
lastName: lastNameController.text,
phoneNumber: phoneController.text,
roleUuid: roleSelected,
spaceUuids: selectedIds);
List<String> selectedIds = getSelectedIds(updatedCommunities) ?? [];
bool res = await UserPermissionApi().sendInviteUser(
email: emailController.text,
firstName: firstNameController.text,
jobTitle: jobTitleController.text,
lastName: lastNameController.text,
phoneNumber: phoneController.text,
roleUuid: roleSelected,
spaceUuids: selectedIds,
);
if (res == true) {
showCustomDialog(
barrierDismissible: false,
context: event.context,
message: "The invite was sent successfully.",
iconPath: Assets.deviceNoteIcon,
title: "Invite Success",
dialogHeight: MediaQuery.of(event.context).size.height * 0.3,
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(event.context).pop();
Navigator.of(event.context).pop();
},
child: const Text('OK'),
),
],
);
} else {
emit(const ErrorState('Failed to send invite.'));
}
emit(SaveState());
} catch (e) {
emit(ErrorState('Error: $e'));
emit(ErrorState('Failed to send invite: ${e.toString()}'));
}
}
@ -319,6 +255,37 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
emit(ChangeStatusSteps());
}
String checkEmailValid = '';
Future<void> checkEmail(
CheckEmailEvent event, Emitter<UsersState> emit) async {
emit(UsersLoadingState());
String? res = await UserPermissionApi().checkEmail(
emailController.text,
);
checkEmailValid = res!;
emit(ChangeStatusSteps());
}
bool isCompleteBasicsFun(CheckStepStatus event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
add(const CheckEmailEvent());
final emailRegex = RegExp(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
);
bool isEmailValid = emailRegex.hasMatch(emailController.text);
bool isEmailServerValid = checkEmailValid == 'Valid email';
isCompleteBasics = firstNameController.text.isNotEmpty &&
lastNameController.text.isNotEmpty &&
emailController.text.isNotEmpty &&
isEmailValid &&
isEmailServerValid;
emit(ChangeStatusSteps());
emit(ValidateBasics());
return isCompleteBasics!;
}
void _clearHighlightsRolePermission(List<PermissionOption> nodes) {
for (var node in nodes) {
node.isHighlighted = false;

View File

@ -1,21 +1,17 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart';
sealed class UsersEvent extends Equatable {
const UsersEvent();
}
class GetUsers extends UsersEvent {
const GetUsers();
@override
List<Object?> get props => [];
}
class SendInviteUsers extends UsersEvent {
const SendInviteUsers();
final BuildContext context;
const SendInviteUsers({required this.context});
@override
List<Object?> get props => [];
List<Object?> get props => [context];
}
class CheckSpacesStepStatus extends UsersEvent {
@ -30,7 +26,6 @@ class CheckRoleStepStatus extends UsersEvent {
List<Object?> get props => [];
}
class LoadCommunityAndSpacesEvent extends UsersEvent {
const LoadCommunityAndSpacesEvent();
@override
@ -57,16 +52,6 @@ class GetBatchStatus extends UsersEvent {
List<Object?> get props => [uuids];
}
class ChangeUserStatus extends UsersEvent {
final String userId;
final String newStatus;
const ChangeUserStatus({required this.userId, required this.newStatus});
@override
List<Object?> get props => [userId, newStatus];
}
class CheckStepStatus extends UsersEvent {
final int? steps;
const CheckStepStatus({this.steps});
@ -104,3 +89,9 @@ class ValidateBasicsStep extends UsersEvent {
@override
List<Object?> get props => [];
}
class CheckEmailEvent extends UsersEvent {
const CheckEmailEvent();
@override
List<Object?> get props => [];
}

View File

@ -30,11 +30,10 @@ final class SaveState extends UsersState {
List<Object> get props => [];
}
final class UsersLoadedState extends UsersState {
List<RolesUserModel> users = [];
UsersLoadedState({required this.users});
final class SpacesLoadedState extends UsersState {
SpacesLoadedState();
@override
List<Object> get props => [users];
List<Object> get props => [];
}
final class ErrorState extends UsersState {
@ -77,3 +76,7 @@ class BasicsStepInvalidState extends UsersState {
@override
List<Object> get props => [];
}
final class ValidateBasics extends UsersState {
@override
List<Object> get props => [];
}

View File

@ -0,0 +1,40 @@
class PermissionOption {
String id;
String title;
bool isChecked;
bool isHighlighted;
List<PermissionOption> subOptions;
PermissionOption({
required this.id,
required this.title,
this.isChecked = false,
this.isHighlighted = false,
this.subOptions = const [],
});
factory PermissionOption.fromJson(Map<String, dynamic> json) {
return PermissionOption(
id: json['id'] ?? '',
title: json['title'].toString().toLowerCase().replaceAll("_", " ") ?? '',
isChecked: json['isChecked'] ?? false,
isHighlighted: json['isHighlighted'] ?? false,
subOptions: (json['subOptions'] as List?)
?.map((sub) => PermissionOption.fromJson(sub))
.toList() ??
[],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'isChecked': isChecked,
'isHighlighted': isHighlighted,
'subOptions': subOptions.map((sub) => sub.toJson()).toList(),
};
}
}
enum CheckState { none, some, all }

View File

@ -1,12 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/view/basics_view.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/view/spaces_access_view.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
@ -56,7 +56,6 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
Expanded(
child: Row(
children: [
// Sidebar for Steps
Expanded(
child: Container(
padding: const EdgeInsets.all(20),
@ -75,7 +74,6 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
width: 1,
color: ColorsManager.grayBorder,
),
// Main content (Form)
Expanded(
flex: 2,
child: Padding(
@ -109,6 +107,8 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
),
InkWell(
onTap: () {
_blocRole.add(const CheckEmailEvent());
setState(() {
if (currentStep < 3) {
currentStep++;
@ -117,11 +117,10 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
} else if (currentStep == 3) {
_blocRole
.add(const CheckSpacesStepStatus());
_blocRole
.add(const CheckSpacesStepStatus());
} else {
_blocRole.add(const SendInviteUsers());
}
} else {
_blocRole
.add(SendInviteUsers(context: context));
}
});
},
@ -161,12 +160,84 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
}
}
int step3 = 0;
Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
bloc.add(const CheckSpacesStepStatus());
currentStep = step;
Future.delayed(const Duration(milliseconds: 500), () {
bloc.add(const ValidateBasicsStep());
});
});
if (step3 == 3) {
bloc.add(const CheckRoleStepStatus());
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteBasics == false
? Assets.wrongProcessIcon
: bloc.isCompleteBasics == true
? Assets.completeProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
currentStep = step;
bloc.add(const CheckStepStatus());
if (step3 == 3) {
bloc.add(const CheckRoleStepStatus());
}
});
},
child: Column(
@ -221,15 +292,14 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
);
}
Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) {
Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
bloc.add(const CheckSpacesStepStatus());
currentStep = step;
});
Future.delayed(const Duration(milliseconds: 500), () {
bloc.add(const ValidateBasicsStep());
step3 = step;
bloc.add(const CheckSpacesStepStatus());
bloc.add(const CheckStepStatus());
});
},
child: Column(
@ -243,9 +313,9 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteBasics == false
: bloc.isCompleteRolePermissions == false
? Assets.wrongProcessIcon
: bloc.isCompleteBasics == true
: bloc.isCompleteRolePermissions == true
? Assets.completeProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
@ -283,63 +353,4 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
),
);
}
Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
currentStep = step;
bloc.add(const CheckSpacesStepStatus());
bloc.add(const CheckStepStatus());
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteRolePermissions == false
? Assets.wrongProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
}

View File

@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl_phone_field/countries.dart';
import 'package:intl_phone_field/country_picker_dialog.dart';
import 'package:intl_phone_field/intl_phone_field.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
@ -30,7 +32,7 @@ class BasicsView extends StatelessWidget {
color: Colors.black),
),
const SizedBox(
height: 80,
height: 50,
),
Text(
'To get started, fill out some basic information about who youre adding as a user.',
@ -40,11 +42,10 @@ class BasicsView extends StatelessWidget {
),
),
const SizedBox(
height: 25,
height: 35,
),
Row(
children: [
// First Name
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -52,9 +53,6 @@ class BasicsView extends StatelessWidget {
SizedBox(
child: Row(
children: [
// SizedBox(
// width: 15,
// ),
const Text(
" * ",
style: TextStyle(
@ -75,7 +73,14 @@ class BasicsView extends StatelessWidget {
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
style: TextStyle(color: Colors.black),
style:
const TextStyle(color: ColorsManager.blackColor),
onChanged: (value) {
Future.delayed(const Duration(milliseconds: 200),
() {
_blocRole.add(ValidateBasicsStep());
});
},
controller: _blocRole.firstNameController,
decoration: inputTextFormDeco(
hintText: "Enter first name",
@ -96,10 +101,7 @@ class BasicsView extends StatelessWidget {
],
),
),
SizedBox(width: 10),
// Last Name
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -125,8 +127,14 @@ class BasicsView extends StatelessWidget {
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
onChanged: (value) {
Future.delayed(const Duration(milliseconds: 200),
() {
_blocRole.add(ValidateBasicsStep());
});
},
controller: _blocRole.lastNameController,
style: TextStyle(color: Colors.black),
style: const TextStyle(color: Colors.black),
decoration:
inputTextFormDeco(hintText: "Enter last name")
.copyWith(
@ -149,7 +157,7 @@ class BasicsView extends StatelessWidget {
),
],
),
SizedBox(height: 10),
const SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -176,8 +184,14 @@ class BasicsView extends StatelessWidget {
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
onChanged: (value) {
Future.delayed(const Duration(milliseconds: 200), () {
_blocRole.add(const CheckStepStatus());
_blocRole.add(ValidateBasicsStep());
});
},
controller: _blocRole.emailController,
style: TextStyle(color: Colors.black),
style: const TextStyle(color: ColorsManager.blackColor),
decoration: inputTextFormDeco(hintText: "name@example.com")
.copyWith(
hintStyle: context.textTheme.bodyMedium?.copyWith(
@ -189,24 +203,24 @@ class BasicsView extends StatelessWidget {
if (value == null || value.isEmpty) {
return 'Enter Email Address';
}
// Regular expression for email validation
final emailRegex = RegExp(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
);
if (!emailRegex.hasMatch(value)) {
return 'Enter a valid Email Address';
}
if (_blocRole.checkEmailValid != "Valid email") {
return _blocRole.checkEmailValid;
}
return null;
},
),
),
],
),
SizedBox(height: 10),
const SizedBox(height: 10),
Row(
children: [
// Phone Number
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -227,7 +241,8 @@ class BasicsView extends StatelessWidget {
pickerDialogStyle: PickerDialogStyle(),
dropdownIconPosition: IconPosition.leading,
disableLengthCheck: true,
dropdownTextStyle: TextStyle(color: Colors.black),
dropdownTextStyle:
const TextStyle(color: ColorsManager.blackColor),
textInputAction: TextInputAction.done,
decoration: inputTextFormDeco(
hintText: "05x xxx xxxx",
@ -238,18 +253,39 @@ class BasicsView extends StatelessWidget {
color: ColorsManager.textGray),
),
initialCountryCode: 'AE',
style: TextStyle(color: Colors.black),
onChanged: (phone) {
print(phone.completeNumber);
},
countries: const [
Country(
name: "United Arab Emirates",
nameTranslations: {
"en": "United Arab Emirates",
"ar": "الإمارات العربية المتحدة",
},
flag: "🇦🇪",
code: "AE",
dialCode: "971",
minLength: 9,
maxLength: 9,
),
Country(
name: "Saudi Arabia",
nameTranslations: {
"en": "Saudi Arabia",
"ar": "السعودية",
},
flag: "🇸🇦",
code: "SA",
dialCode: "966",
minLength: 9,
maxLength: 9,
),
],
style: const TextStyle(color: Colors.black),
controller: _blocRole.phoneController,
)
],
),
),
SizedBox(width: 10),
// Job Title
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -270,7 +306,8 @@ class BasicsView extends StatelessWidget {
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: _blocRole.jobTitleController,
style: TextStyle(color: Colors.black),
style:
const TextStyle(color: ColorsManager.blackColor),
decoration: inputTextFormDeco(
hintText: "Job Title (Optional)")
.copyWith(
@ -287,7 +324,7 @@ class BasicsView extends StatelessWidget {
),
],
),
SizedBox(height: 20),
const SizedBox(height: 20),
],
),
);

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DeleteUserDialog extends StatefulWidget {

View File

@ -0,0 +1,239 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class PermissionManagement extends StatefulWidget {
final UsersBloc? bloc;
const PermissionManagement({Key? key, this.bloc}) : super(key: key);
@override
_PermissionManagementState createState() => _PermissionManagementState();
}
class _PermissionManagementState extends State<PermissionManagement> {
void toggleOptionById(String id) {
setState(() {
for (var mainOption in widget.bloc!.permissions) {
if (mainOption.id == id) {
final isChecked =
checkifOneOfthemChecked(mainOption) == CheckState.all;
mainOption.isChecked = !isChecked;
for (var subOption in mainOption.subOptions) {
subOption.isChecked = !isChecked;
for (var child in subOption.subOptions) {
child.isChecked = !isChecked;
}
}
return;
}
for (var subOption in mainOption.subOptions) {
if (subOption.id == id) {
subOption.isChecked = !subOption.isChecked;
for (var child in subOption.subOptions) {
child.isChecked = subOption.isChecked;
}
mainOption.isChecked =
mainOption.subOptions.every((sub) => sub.isChecked);
return;
}
for (var child in subOption.subOptions) {
if (child.id == id) {
child.isChecked = !child.isChecked;
subOption.isChecked =
subOption.subOptions.every((child) => child.isChecked);
mainOption.isChecked =
mainOption.subOptions.every((sub) => sub.isChecked);
return;
}
}
}
}
});
}
CheckState checkifOneOfthemChecked(PermissionOption mainOption) {
bool allSelected = true;
bool someSelected = false;
for (var subOption in mainOption.subOptions) {
if (subOption.isChecked) {
someSelected = true;
} else {
allSelected = false;
}
for (var child in subOption.subOptions) {
if (child.isChecked) {
someSelected = true;
} else {
allSelected = false;
}
}
}
if (allSelected) {
return CheckState.all;
} else if (someSelected) {
return CheckState.some;
} else {
return CheckState.none;
}
}
@override
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: widget.bloc!.permissions.length,
itemBuilder: (context, index) {
final option = widget.bloc!.permissions[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
InkWell(
// onTap: () => toggleOptionById(option.id),
child: Builder(
builder: (context) {
final checkState = checkifOneOfthemChecked(option);
if (checkState == CheckState.all) {
return Image.asset(
Assets.CheckBoxChecked,
width: 20,
height: 20,
);
} else if (checkState == CheckState.some) {
return Image.asset(
Assets.rectangleCheckBox,
width: 20,
height: 20,
);
} else {
return Image.asset(
Assets.emptyBox,
width: 20,
height: 20,
);
}
},
),
),
const SizedBox(width: 8),
Text(
option.title,
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 12,
color: ColorsManager.blackColor),
),
],
),
const SizedBox(
height: 10,
),
...option.subOptions.map((subOption) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: option.isHighlighted
? Colors.blue.shade50
: Colors.white,
child: Row(
children: [
InkWell(
// onTap: () => toggleOptionById(subOption.id),
child: Builder(
builder: (context) {
final checkState =
checkifOneOfthemChecked(PermissionOption(
id: subOption.id,
title: subOption.title,
subOptions: [subOption],
));
if (checkState == CheckState.all) {
return Image.asset(
Assets.CheckBoxChecked,
width: 20,
height: 20,
);
} else if (checkState == CheckState.some) {
return Image.asset(
Assets.rectangleCheckBox,
width: 20,
height: 20,
);
} else {
return Image.asset(
Assets.emptyBox,
width: 20,
height: 20,
);
}
},
),
),
const SizedBox(width: 8),
Text(
subOption.title,
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 12,
color: ColorsManager.lightGreyColor),
),
],
),
),
Padding(
padding: const EdgeInsets.only(left: 50.0),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 2.0,
crossAxisSpacing: 0.2,
childAspectRatio: 5,
),
itemCount: subOption.subOptions.length,
itemBuilder: (context, index) {
final child = subOption.subOptions[index];
return CheckboxListTile(
selectedTileColor: child.isHighlighted
? Colors.blue.shade50
: Colors.white,
dense: true,
controlAffinity: ListTileControlAffinity.leading,
title: Text(
child.title,
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.lightGreyColor),
),
value: child.isChecked,
onChanged: (value) => toggleOptionById(child.id),
enabled: false,
);
},
),
)
],
);
}).toList(),
],
);
},
);
}
}

View File

@ -0,0 +1,115 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart';
class RoleDropdown extends StatefulWidget {
final UsersBloc? bloc;
const RoleDropdown({super.key, this.bloc});
@override
_RoleDropdownState createState() => _RoleDropdownState();
}
class _RoleDropdownState extends State<RoleDropdown> {
late String selectedRole;
@override
void initState() {
super.initState();
selectedRole = widget.bloc!.roleSelected;
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Text(
" * ",
style: TextStyle(
color: ColorsManager.red,
fontWeight: FontWeight.w900,
fontSize: 15,
),
),
Text(
"Role",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: Colors.black,
),
),
],
),
const SizedBox(height: 8),
SizedBox(
child: DropdownButtonFormField<String>(
dropdownColor: ColorsManager.whiteColors,
alignment: Alignment.center,
focusColor: Colors.white,
autofocus: true,
value: selectedRole.isNotEmpty ? selectedRole : null,
items: widget.bloc!.roles.map((role) {
return DropdownMenuItem<String>(
value: role.uuid,
child: Text(role.type),
);
}).toList(),
onChanged: (value) {
setState(() {
selectedRole = value!;
});
widget.bloc!.roleSelected = selectedRole;
widget.bloc!
.add(PermissionEvent(roleUuid: widget.bloc!.roleSelected));
},
icon: const SizedBox.shrink(),
borderRadius: const BorderRadius.all(Radius.circular(10)),
hint: const Padding(
padding: EdgeInsets.only(left: 10),
child: Text(
"Please Select",
style: TextStyle(
color: ColorsManager.textGray,
),
),
),
decoration: inputTextFormDeco().copyWith(
contentPadding: EdgeInsets.zero,
suffixIcon: Container(
padding: EdgeInsets.zero,
width: 70,
height: 45,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(10),
topRight: Radius.circular(10),
),
border: Border.all(
color: ColorsManager.textGray,
width: 1.0,
),
),
child: const Center(
child: Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
),
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class RolesAndPermission extends StatelessWidget {
const RolesAndPermission({super.key});
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return BlocBuilder<UsersBloc, UsersState>(builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
return Container(
color: Colors.white,
child: Form(
key: _blocRole.formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'Role & Permissions',
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 20,
color: Colors.black),
),
const SizedBox(
height: 15,
),
SizedBox(
width: 350,
height: 100,
child: RoleDropdown(
bloc: _blocRole,
)),
const SizedBox(height: 10),
Expanded(
child: SizedBox(
child: Column(
children: [
Expanded(
flex: 2,
child: Container(
decoration: const BoxDecoration(
color: ColorsManager.CircleRolesBackground,
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
topLeft: Radius.circular(20)),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(20)),
border: Border.all(
color: ColorsManager.grayBorder)),
child: TextFormField(
style:
const TextStyle(color: Colors.black),
controller:
_blocRole.roleSearchController,
onChanged: (value) {
_blocRole.add(SearchPermission(
nodes: _blocRole.permissions,
searchTerm: value));
},
decoration: textBoxDecoration(radios: 20)!
.copyWith(
fillColor: Colors.white,
suffixIcon: Padding(
padding:
const EdgeInsets.only(right: 16),
child: SvgPicture.asset(
Assets.textFieldSearch,
width: 24,
height: 24,
),
),
hintStyle: context.textTheme.bodyMedium
?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
),
),
),
],
),
),
),
),
Expanded(
flex: 7,
child: Container(
color: ColorsManager.CircleRolesBackground,
padding: const EdgeInsets.all(8.0),
child: Container(
color: ColorsManager.whiteColors,
child: PermissionManagement(
bloc: _blocRole,
))))
],
),
),
),
],
),
),
);
});
}
}

View File

@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';

View File

@ -0,0 +1,185 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart';
class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
UserTableBloc() : super(TableInitial()) {
on<GetUsers>(_getUsers);
on<ChangeUserStatus>(_changeUserStatus);
on<SortUsersByNameAsc>(_toggleSortUsersByNameAsc);
on<SortUsersByNameDesc>(_toggleSortUsersByNameDesc);
on<DateOldestToNewestEvent>(_toggleSortUsersByDateOldestToNewest);
on<DateNewestToOldestEvent>(_toggleSortUsersByDateNewestToOldest);
}
List<RolesUserModel> users = [];
List<RolesUserModel> initialUsers = []; // Save the initial state
String currentSortOrder = ''; // Keeps track of the current sorting order
String currentSortOrderDate = ''; // Keeps track of the current sorting order
Future<void> _getUsers(GetUsers event, Emitter<UserTableState> emit) async {
emit(UsersLoadingState());
try {
users = [
RolesUserModel(
id: '1',
userName: 'b 1',
userEmail: 'test1@test.com',
action: '',
createdBy: 'Admin',
creationDate: '25/10/2024',
creationTime: '10:30 AM',
status: 'Invited',
),
RolesUserModel(
id: '2',
userName: 'a 2',
userEmail: 'test2@test.com',
action: '',
createdBy: 'Admin',
creationDate: '24/10/2024',
creationTime: '2:30 PM',
status: 'Active',
),
RolesUserModel(
id: '3',
userName: 'c 3',
userEmail: 'test3@test.com',
action: '',
createdBy: 'Admin',
creationDate: '23/10/2024',
creationTime: '9:00 AM',
status: 'Disabled',
),
];
// Sort users by newest to oldest as default
users.sort((a, b) {
final dateA = _parseDateTime(a.creationDate!);
final dateB = _parseDateTime(b.creationDate!);
return dateB.compareTo(dateA); // Newest to oldest
});
initialUsers = List.from(users); // Save the initial state
emit(UsersLoadedState(users: users));
} catch (e) {
emit(ErrorState(e.toString()));
}
}
void _changeUserStatus(ChangeUserStatus event, Emitter<UserTableState> emit) {
try {
users = users.map((user) {
if (user.id == event.userId) {
return RolesUserModel(
id: user.id,
userName: user.userName,
userEmail: user.userEmail,
createdBy: user.createdBy,
creationDate: user.creationDate,
creationTime: user.creationTime,
status: event.newStatus,
action: user.action,
);
}
return user;
}).toList();
emit(UsersLoadedState(users: users));
} catch (e) {
emit(ErrorState(e.toString()));
}
}
void _toggleSortUsersByNameAsc(
SortUsersByNameAsc event, Emitter<UserTableState> emit) {
if (currentSortOrder == "Asc") {
// If already sorted ascending, reset to the initial state
emit(UsersLoadingState());
currentSortOrder = "";
users = List.from(initialUsers); // Reset to saved initial state
emit(UsersLoadedState(users: users));
} else {
// Sort ascending
emit(UsersLoadingState());
currentSortOrder = "Asc";
users.sort((a, b) => a.userName!.compareTo(b.userName!));
emit(UsersLoadedState(users: users));
}
}
void _toggleSortUsersByNameDesc(
SortUsersByNameDesc event, Emitter<UserTableState> emit) {
if (currentSortOrder == "Desc") {
// If already sorted descending, reset to the initial state
emit(UsersLoadingState());
currentSortOrder = "";
users = List.from(initialUsers); // Reset to saved initial state
emit(UsersLoadedState(users: users));
} else {
// Sort descending
emit(UsersLoadingState());
currentSortOrder = "Desc";
users.sort((a, b) => b.userName!.compareTo(a.userName!));
emit(UsersLoadedState(users: users));
}
}
void _toggleSortUsersByDateNewestToOldest(
DateNewestToOldestEvent event, Emitter<UserTableState> emit) {
if (currentSortOrderDate == "NewestToOldest") {
// If already sorted ascending, reset to the initial state
emit(UsersLoadingState());
currentSortOrder = "";
currentSortOrderDate = "";
users = List.from(initialUsers); // Reset to saved initial state
emit(UsersLoadedState(users: users));
} else {
// Sort ascending
emit(UsersLoadingState());
users.sort((a, b) {
final dateA = _parseDateTime(a.creationDate!);
final dateB = _parseDateTime(b.creationDate!);
return dateB.compareTo(dateA); // Newest to oldest
});
emit(UsersLoadedState(users: users));
}
}
void _toggleSortUsersByDateOldestToNewest(
DateOldestToNewestEvent event, Emitter<UserTableState> emit) {
if (currentSortOrderDate == "OldestToNewest") {
// If already sorted ascending, reset to the initial state
emit(UsersLoadingState());
currentSortOrder = "";
currentSortOrderDate = "";
users = List.from(initialUsers); // Reset to saved initial state
emit(UsersLoadedState(users: users));
} else {
// Sort ascending
emit(UsersLoadingState());
users.sort((a, b) {
final dateA = _parseDateTime(a.creationDate!);
final dateB = _parseDateTime(b.creationDate!);
return dateA.compareTo(dateB); // Newest to oldest
});
emit(UsersLoadedState(users: users));
}
}
DateTime _parseDateTime(String date) {
try {
// Split the date into day, month, and year
final dateParts = date.split('/');
final day = int.parse(dateParts[0]);
final month = int.parse(dateParts[1]);
final year = int.parse(dateParts[2]);
// Split the time into hours and minutes
return DateTime(year, month, day);
} catch (e) {
throw FormatException('Invalid date or time format: $date ');
}
}
}

View File

@ -0,0 +1,63 @@
import 'package:equatable/equatable.dart';
sealed class UserTableEvent extends Equatable {
const UserTableEvent();
}
class GetRoles extends UserTableEvent {
const GetRoles();
@override
List<Object?> get props => [];
}
class GetUsers extends UserTableEvent {
const GetUsers();
@override
List<Object?> get props => [];
}
class ChangeUserStatus extends UserTableEvent {
final String userId;
final String newStatus;
const ChangeUserStatus({required this.userId, required this.newStatus});
@override
List<Object?> get props => [userId, newStatus];
}
class SortUsersByNameAsc extends UserTableEvent {
const SortUsersByNameAsc();
@override
List<Object?> get props => [];
}
class SortUsersByNameDesc extends UserTableEvent {
const SortUsersByNameDesc();
@override
List<Object?> get props => [];
}
class StoreUsersEvent extends UserTableEvent {
const StoreUsersEvent();
@override
List<Object?> get props => [];
}
class DateNewestToOldestEvent extends UserTableEvent {
const DateNewestToOldestEvent();
@override
List<Object?> get props => [];
}
class DateOldestToNewestEvent extends UserTableEvent {
const DateOldestToNewestEvent();
@override
List<Object?> get props => [];
}

View File

@ -0,0 +1,82 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart';
sealed class UserTableState extends Equatable {
const UserTableState();
}
final class TableInitial extends UserTableState {
@override
List<Object> get props => [];
}
final class RolesLoadingState extends UserTableState {
@override
List<Object> get props => [];
}
final class UsersLoadingState extends UserTableState {
@override
List<Object> get props => [];
}
final class RolesLoadedState extends UserTableState {
@override
List<Object> get props => [];
}
final class UsersLoadedState extends UserTableState {
List<RolesUserModel> users = [];
UsersLoadedState({required this.users});
@override
List<Object> get props => [users];
}
final class ErrorState extends UserTableState {
final String message;
const ErrorState(this.message);
@override
List<Object> get props => [message];
}
/// report state
final class SosReportLoadingState extends UserTableState {
@override
List<Object> get props => [];
}
final class RolesErrorState extends UserTableState {
final String message;
const RolesErrorState(this.message);
@override
List<Object> get props => [message];
}
/// automation reports
final class SosAutomationReportLoadingState extends UserTableState {
@override
List<Object> get props => [];
}
final class SosAutomationReportErrorState extends UserTableState {
final String message;
const SosAutomationReportErrorState(this.message);
@override
List<Object> get props => [message];
}
final class ChangeTapStatus extends UserTableState {
bool select = true;
ChangeTapStatus({required this.select});
@override
List<Object> get props => [select];
}

View File

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
Future<void> showDateFilterMenu({
required BuildContext context,
Function()? aToZTap,
Function()? zToaTap,
String? isSelected,
}) async {
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB(
overlay.size.width / 2,
240,
0,
overlay.size.height,
),
Offset.zero & overlay.size,
);
await showMenu(
context: context,
position: position,
color: ColorsManager.whiteColors,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
),
items: <PopupMenuEntry>[
PopupMenuItem(
onTap: aToZTap,
child: ListTile(
leading: Image.asset(
Assets.AtoZIcon,
width: 25,
),
title: Text(
"Sort from newest to oldest",
// style: context.textTheme.bodyMedium,
style: TextStyle(
color: isSelected == "NewestToOldest"
? Colors.black
: Colors.blueGrey),
),
),
),
PopupMenuItem(
onTap: zToaTap,
child: ListTile(
leading: Image.asset(
Assets.ZtoAIcon,
width: 25,
),
title: Text(
"Sort from oldest to newest",
style: TextStyle(
color: isSelected == "OldestToNewest"
? Colors.black
: Colors.blueGrey),
),
),
),
],
).then((value) {
// setState(() {
// _isDropdownOpen = false;
// });
});
}

View File

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
Future<void> showDeActivateFilterMenu({
required BuildContext context,
Function()? aToZTap,
Function()? zToaTap,
String? isSelected,
}) async {
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB(
overlay.size.width / 2,
240,
0,
overlay.size.height,
),
Offset.zero & overlay.size,
);
await showMenu(
context: context,
position: position,
color: ColorsManager.whiteColors,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
),
items: <PopupMenuEntry>[
PopupMenuItem(
onTap: aToZTap,
child: ListTile(
leading: Image.asset(
Assets.AtoZIcon,
width: 25,
),
title: Text(
"Sort A to Z",
// style: context.textTheme.bodyMedium,
style: TextStyle(
color: isSelected == "NewestToOldest"
? Colors.black
: Colors.blueGrey),
),
),
),
PopupMenuItem(
onTap: zToaTap,
child: ListTile(
leading: Image.asset(
Assets.ZtoAIcon,
width: 25,
),
title: Text(
"Sort Z to A",
style: TextStyle(
color: isSelected == "OldestToNewest"
? Colors.black
: Colors.blueGrey),
),
),
),
],
).then((value) {
// setState(() {
// _isDropdownOpen = false;
// });
});
}

View File

@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
Future<void> showNameMenu({
required BuildContext context,
Function()? aToZTap,
Function()? zToaTap,
String? isSelected,
}) async {
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB(
overlay.size.width / 25,
240,
0,
overlay.size.height,
),
Offset.zero & overlay.size,
);
await showMenu(
context: context,
position: position,
color: ColorsManager.whiteColors,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
),
items: <PopupMenuEntry>[
PopupMenuItem(
onTap: aToZTap,
child: ListTile(
leading: Image.asset(
Assets.AtoZIcon,
width: 25,
),
title: Text(
"Sort A to Z",
// style: context.textTheme.bodyMedium,
style: TextStyle(
color: isSelected == "Asc" ? Colors.black : Colors.blueGrey),
),
),
),
PopupMenuItem(
onTap: zToaTap,
child: ListTile(
leading: Image.asset(
Assets.ZtoAIcon,
width: 25,
),
title: Text(
"Sort Z to A",
style: TextStyle(
color: isSelected == "Desc" ? Colors.black : Colors.blueGrey),
),
),
),
],
).then((value) {
// setState(() {
// _isDropdownOpen = false;
// });
});
}

View File

@ -0,0 +1,299 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart';
class DynamicTableScreen extends StatefulWidget {
final List<String> titles;
final List<List<Widget>> rows;
final void Function(int columnIndex)? onFilter;
DynamicTableScreen(
{required this.titles, required this.rows, required this.onFilter});
@override
_DynamicTableScreenState createState() => _DynamicTableScreenState();
}
class _DynamicTableScreenState extends State<DynamicTableScreen>
with WidgetsBindingObserver {
late List<double> columnWidths;
// @override
// void initState() {
// super.initState();
// // Initialize column widths with default sizes proportional to the screen width
// // Assigning placeholder values here. The actual sizes will be updated in `build`.
// }
@override
void initState() {
super.initState();
columnWidths = List<double>.filled(widget.titles.length, 150.0);
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeMetrics() {
super.didChangeMetrics();
// Screen size might have changed
final newScreenWidth = MediaQuery.of(context).size.width;
setState(() {
columnWidths = List<double>.generate(widget.titles.length, (index) {
if (index == 1) {
return newScreenWidth *
0.12; // 20% of screen width for the second column
} else if (index == 9) {
return newScreenWidth *
0.2; // 25% of screen width for the tenth column
}
return newScreenWidth *
0.09; // Default to 10% of screen width for other columns
});
});
}
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
// Initialize column widths if they are still set to placeholder values
if (columnWidths.every((width) => width == 120.0)) {
columnWidths = List<double>.generate(widget.titles.length, (index) {
if (index == 1) {
return screenWidth * 0.11;
} else if (index == 9) {
return screenWidth * 0.2;
}
return screenWidth * 0.11;
});
setState(() {});
}
return Container(
child: SingleChildScrollView(
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
child: Container(
decoration: containerDecoration.copyWith(
borderRadius: const BorderRadius.all(Radius.circular(20))),
child: FittedBox(
child: Column(
children: [
// Header Row with Resizable Columns
Container(
decoration: containerDecoration.copyWith(
color: ColorsManager.CircleRolesBackground,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15))),
child: Row(
children: List.generate(widget.titles.length, (index) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
FittedBox(
child: Container(
padding: const EdgeInsets.only(left: 5, right: 5),
width: columnWidths[index],
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
child: Text(
widget.titles[index],
maxLines: 2,
style: const TextStyle(
overflow: TextOverflow.ellipsis,
fontWeight: FontWeight.w400,
fontSize: 13,
color: ColorsManager.grayColor,
),
),
),
if (index != 1 &&
index != 9 &&
index != 7 &&
index != 5)
FittedBox(
child: IconButton(
icon: SvgPicture.asset(
Assets.filterTableIcon,
fit: BoxFit.none,
),
onPressed: () {
if (widget.onFilter != null) {
widget.onFilter!(index);
}
},
),
)
],
),
),
),
GestureDetector(
onHorizontalDragUpdate: (details) {
setState(() {
columnWidths[index] = (columnWidths[index] +
details.delta.dx)
.clamp(
150.0, 300.0); // Minimum & Maximum size
});
},
child: MouseRegion(
cursor: SystemMouseCursors
.resizeColumn, // Set the cursor to resize
child: Container(
color: Colors.green,
child: Container(
color: ColorsManager.boxDivider,
width: 1,
height: 50, // Height of the header cell
),
),
),
),
],
);
}),
),
),
// Data Rows with Dividers
Container(
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15))),
child: Column(
children: widget.rows.map((row) {
int rowIndex = widget.rows.indexOf(row);
return Column(
children: [
Container(
child: Padding(
padding: const EdgeInsets.only(
left: 5, top: 10, right: 5, bottom: 10),
child: Row(
children: List.generate(row.length, (index) {
return SizedBox(
width: columnWidths[index],
child: SizedBox(
child: Padding(
padding: const EdgeInsets.only(
left: 15, right: 10),
child: row[index],
),
),
);
}),
),
),
),
if (rowIndex < widget.rows.length - 1)
Row(
children: List.generate(widget.titles.length,
(index) {
return SizedBox(
width: columnWidths[index],
child: const Divider(
color: ColorsManager.boxDivider,
thickness: 1,
height: 1,
),
);
}))
// Add a Divider below each row except the last one
],
);
}).toList(),
),
),
],
),
),
),
),
);
}
}
// Widget build(BuildContext context) {
// return Scaffold(
// body: SingleChildScrollView(
// scrollDirection: Axis.horizontal,
// child: SingleChildScrollView(
// scrollDirection: Axis.vertical,
// child: Column(
// children: [
// // Header Row with Resizable Columns
// Container(
// color: Colors.green,
// child: Row(
// children: List.generate(widget.titles.length, (index) {
// return Row(
// children: [
// Container(
// width: columnWidths[index],
// decoration: const BoxDecoration(
// color: Colors.green,
// ),
// child: Text(
// widget.titles[index],
// style: TextStyle(fontWeight: FontWeight.bold),
// textAlign: TextAlign.center,
// ),
// ),
// GestureDetector(
// onHorizontalDragUpdate: (details) {
// setState(() {
// columnWidths[index] = (columnWidths[index] +
// details.delta.dx)
// .clamp(50.0, 300.0); // Minimum & Maximum size
// });
// },
// child: MouseRegion(
// cursor: SystemMouseCursors
// .resizeColumn, // Set the cursor to resize
// child: Container(
// color: Colors.green,
// child: Container(
// color: Colors.black,
// width: 1,
// height: 50, // Height of the header cell
// ),
// ),
// ),
// ),
// ],
// );
// }),
// ),
// ),
// // Data Rows
// ...widget.rows.map((row) {
// return Row(
// children: List.generate(row.length, (index) {
// return Container(
// width: columnWidths[index],
// child: row[index],
// );
// }),
// );
// }).toList(),
// ],
// ),
// ),
// ),
// );
// }

View File

@ -1,11 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/view/add_user_dialog.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/view/user_table.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/name_filter.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/user_table.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -85,7 +88,7 @@ class UsersPage extends StatelessWidget {
? 'Invited'
: 'Active';
context
.read<UsersBloc>()
.read<UserTableBloc>()
.add(ChangeUserStatus(userId: userId, newStatus: newStatus));
},
child: Padding(
@ -104,12 +107,10 @@ class UsersPage extends StatelessWidget {
);
}
// return RolesAndPermission();
// }
// }
return BlocBuilder<UsersBloc, UsersState>(
return BlocBuilder<UserTableBloc, UserTableState>(
builder: (context, state) {
final screenSize = MediaQuery.of(context).size;
final _blocRole = BlocProvider.of<UserTableBloc>(context);
if (state is UsersLoadingState) {
return const Center(child: CircularProgressIndicator());
@ -158,7 +159,9 @@ class UsersPage extends StatelessWidget {
return const AddNewUserDialog();
},
).then((listDevice) {
if (listDevice != null) {}
if (listDevice != null) {
}
});
},
child: Container(
@ -181,6 +184,56 @@ class UsersPage extends StatelessWidget {
),
const SizedBox(height: 25),
DynamicTableScreen(
onFilter: (columnIndex) {
if (columnIndex == 0) {
showNameMenu(
context: context,
isSelected: _blocRole.currentSortOrder,
aToZTap: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameAsc());
},
zToaTap: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
},
);
}
if (columnIndex == 4) {
showDateFilterMenu(
context: context,
isSelected: _blocRole.currentSortOrderDate,
aToZTap: () {
context
.read<UserTableBloc>()
.add(const DateNewestToOldestEvent());
},
zToaTap: () {
context
.read<UserTableBloc>()
.add(const DateOldestToNewestEvent());
},
);
}
if (columnIndex == 8) {
showDeActivateFilterMenu(
context: context,
isSelected: _blocRole.currentSortOrderDate,
aToZTap: () {
context
.read<UserTableBloc>()
.add(const DateNewestToOldestEvent());
},
zToaTap: () {
context
.read<UserTableBloc>()
.add(const DateOldestToNewestEvent());
},
);
}
},
titles: const [
"Full Name",
"Email Address",
@ -210,10 +263,10 @@ class UsersPage extends StatelessWidget {
status(status: user.status!),
Row(
children: [
actionButton(
title: "Activity Log",
onTap: () {},
),
// actionButton(
// title: "Activity Log",
// onTap: () {},
// ),
actionButton(
title: "Edit",
onTap: () {},

View File

@ -1,489 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class RolesAndPermission extends StatelessWidget {
const RolesAndPermission({super.key});
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return BlocBuilder<UsersBloc, UsersState>(builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
return Container(
color: Colors.white,
child: Form(
key: _blocRole.formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'Role & Permissions',
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 20,
color: Colors.black),
),
const SizedBox(
height: 15,
),
SizedBox(
width: 300,
height: 110,
child: DropdownExample(
bloc: _blocRole,
)),
const SizedBox(height: 10),
Expanded(
child: SizedBox(
child: Column(
children: [
Expanded(
flex: 2,
child: Container(
decoration: const BoxDecoration(
color: ColorsManager.CircleRolesBackground,
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
topLeft: Radius.circular(20)),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(20)),
border: Border.all(
color: ColorsManager.grayBorder)),
child: TextFormField(
style:
const TextStyle(color: Colors.black),
controller: _blocRole.firstNameController,
onChanged: (value) {
_blocRole.add(SearchPermission(
nodes: _blocRole.permissions,
searchTerm: value));
},
decoration: textBoxDecoration(radios: 20)!
.copyWith(
fillColor: Colors.white,
suffixIcon: Padding(
padding:
const EdgeInsets.only(right: 16),
child: SvgPicture.asset(
Assets.textFieldSearch,
width: 24,
height: 24,
),
),
hintStyle: context.textTheme.bodyMedium
?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
),
),
),
],
),
),
),
),
Expanded(
flex: 7,
child: Container(
color: ColorsManager.CircleRolesBackground,
padding: const EdgeInsets.all(8.0),
child: Container(
color: ColorsManager.whiteColors,
child: DeviceManagement(
bloc: _blocRole,
))))
],
),
),
),
],
),
),
);
});
}
}
class DropdownExample extends StatefulWidget {
final UsersBloc? bloc;
const DropdownExample({super.key, this.bloc});
@override
_DropdownExampleState createState() => _DropdownExampleState();
}
class _DropdownExampleState extends State<DropdownExample> {
String? selectedRole;
@override
void initState() {
super.initState();
if (widget.bloc != null && widget.bloc!.roles.isNotEmpty) {
selectedRole = widget.bloc!.roles.first.uuid;
}
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Role",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: Colors.black,
),
),
const SizedBox(height: 8),
SizedBox(
child: DropdownButtonFormField<String>(
alignment: Alignment.center,
focusColor: Colors.white,
autofocus: true,
value: selectedRole,
items: widget.bloc!.roles.map((role) {
return DropdownMenuItem<String>(
value: role.uuid,
child: Text(role.type),
);
}).toList(),
onChanged: (value) {
setState(() {
selectedRole = value;
});
widget.bloc!.add(PermissionEvent(roleUuid: selectedRole));
},
padding: EdgeInsets.zero,
icon: const SizedBox.shrink(),
borderRadius: const BorderRadius.all(Radius.circular(10)),
hint: const Padding(
padding: EdgeInsets.only(left: 20),
child: Text("Please Select"),
),
decoration: inputTextFormDeco().copyWith(
contentPadding: EdgeInsets.zero,
suffixIcon: Container(
padding: EdgeInsets.zero,
width: 70,
height: 50,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(10),
topRight: Radius.circular(10),
),
border: Border.all(
color: Colors.grey,
width: 1.0,
),
),
child: const Center(
child: Icon(Icons.keyboard_arrow_down),
),
),
),
),
),
],
),
);
}
}
class DeviceManagement extends StatefulWidget {
final UsersBloc? bloc;
const DeviceManagement({Key? key, this.bloc}) : super(key: key);
@override
_DeviceManagementState createState() => _DeviceManagementState();
}
class _DeviceManagementState extends State<DeviceManagement> {
void toggleOptionById(String id) {
setState(() {
for (var mainOption in widget.bloc!.permissions) {
if (mainOption.id == id) {
final isChecked =
checkifOneOfthemChecked(mainOption) == CheckState.all;
mainOption.isChecked = !isChecked;
for (var subOption in mainOption.subOptions) {
subOption.isChecked = !isChecked;
for (var child in subOption.subOptions) {
child.isChecked = !isChecked;
}
}
return;
}
for (var subOption in mainOption.subOptions) {
if (subOption.id == id) {
subOption.isChecked = !subOption.isChecked;
for (var child in subOption.subOptions) {
child.isChecked = subOption.isChecked;
}
mainOption.isChecked =
mainOption.subOptions.every((sub) => sub.isChecked);
return;
}
for (var child in subOption.subOptions) {
if (child.id == id) {
child.isChecked = !child.isChecked;
subOption.isChecked =
subOption.subOptions.every((child) => child.isChecked);
mainOption.isChecked =
mainOption.subOptions.every((sub) => sub.isChecked);
return;
}
}
}
}
});
}
CheckState checkifOneOfthemChecked(PermissionOption mainOption) {
bool allSelected = true;
bool someSelected = false;
for (var subOption in mainOption.subOptions) {
if (subOption.isChecked) {
someSelected = true;
} else {
allSelected = false;
}
for (var child in subOption.subOptions) {
if (child.isChecked) {
someSelected = true;
} else {
allSelected = false;
}
}
}
if (allSelected) {
return CheckState.all;
} else if (someSelected) {
return CheckState.some;
} else {
return CheckState.none;
}
}
@override
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: widget.bloc!.permissions.length,
itemBuilder: (context, index) {
final option = widget.bloc!.permissions[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
InkWell(
// onTap: () => toggleOptionById(option.id),
child: Builder(
builder: (context) {
final checkState = checkifOneOfthemChecked(option);
if (checkState == CheckState.all) {
return Image.asset(
Assets.CheckBoxChecked,
width: 20,
height: 20,
);
} else if (checkState == CheckState.some) {
return Image.asset(
Assets.rectangleCheckBox,
width: 20,
height: 20,
);
} else {
return Image.asset(
Assets.emptyBox,
width: 20,
height: 20,
);
}
},
),
),
const SizedBox(width: 8),
Text(
option.title,
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 12,
color: ColorsManager.blackColor),
),
],
),
const SizedBox(
height: 10,
),
...option.subOptions.map((subOption) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: option.isHighlighted
? Colors.blue.shade50
: Colors.white,
child: Row(
children: [
InkWell(
// onTap: () => toggleOptionById(subOption.id),
child: Builder(
builder: (context) {
final checkState =
checkifOneOfthemChecked(PermissionOption(
id: subOption.id,
title: subOption.title,
subOptions: [subOption],
));
if (checkState == CheckState.all) {
return Image.asset(
Assets.CheckBoxChecked,
width: 20,
height: 20,
);
} else if (checkState == CheckState.some) {
return Image.asset(
Assets.rectangleCheckBox,
width: 20,
height: 20,
);
} else {
return Image.asset(
Assets.emptyBox,
width: 20,
height: 20,
);
}
},
),
),
const SizedBox(width: 8),
Text(
subOption.title,
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 12,
color: ColorsManager.lightGreyColor),
),
],
),
),
Padding(
padding: const EdgeInsets.only(left: 50.0),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 2.0,
crossAxisSpacing: 0.2,
childAspectRatio: 5,
),
itemCount: subOption.subOptions.length,
itemBuilder: (context, index) {
final child = subOption.subOptions[index];
return CheckboxListTile(
selectedTileColor: child.isHighlighted
? Colors.blue.shade50
: Colors.white,
dense: true,
controlAffinity: ListTileControlAffinity.leading,
title: Text(
child.title,
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.lightGreyColor),
),
value: child.isChecked,
onChanged: (value) => toggleOptionById(child.id),
enabled: false,
);
},
),
)
],
);
}).toList(),
],
);
},
);
}
}
enum CheckState { none, some, all }
class PermissionOption {
String id;
String title;
bool isChecked;
bool isHighlighted;
List<PermissionOption> subOptions;
PermissionOption({
required this.id,
required this.title,
this.isChecked = false,
this.isHighlighted = false,
this.subOptions = const [],
});
factory PermissionOption.fromJson(Map<String, dynamic> json) {
return PermissionOption(
id: json['id'] ?? '',
title: json['title'] ?? '',
isChecked: json['isChecked'] ?? false,
isHighlighted: json['isHighlighted'] ?? false,
subOptions: (json['subOptions'] as List?)
?.map((sub) => PermissionOption.fromJson(sub))
.toList() ??
[],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'isChecked': isChecked,
'isHighlighted': isHighlighted,
'subOptions': subOptions.map((sub) => sub.toJson()).toList(),
};
}
}

View File

@ -1,256 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DynamicTableScreen extends StatefulWidget {
final List<String> titles;
final List<List<Widget>> rows;
DynamicTableScreen({required this.titles, required this.rows});
@override
_DynamicTableScreenState createState() => _DynamicTableScreenState();
}
class _DynamicTableScreenState extends State<DynamicTableScreen>
with WidgetsBindingObserver {
late List<double> columnWidths;
// @override
// void initState() {
// super.initState();
// // Initialize column widths with default sizes proportional to the screen width
// // Assigning placeholder values here. The actual sizes will be updated in `build`.
// }
@override
void initState() {
super.initState();
columnWidths = List<double>.filled(widget.titles.length, 150.0);
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeMetrics() {
super.didChangeMetrics();
// Screen size might have changed
final newScreenWidth = MediaQuery.of(context).size.width;
setState(() {
columnWidths = List<double>.generate(widget.titles.length, (index) {
if (index == 1) {
return newScreenWidth *
0.12; // 20% of screen width for the second column
} else if (index == 9) {
return newScreenWidth *
0.2; // 25% of screen width for the tenth column
}
return newScreenWidth *
0.09; // Default to 10% of screen width for other columns
});
});
}
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
// Initialize column widths if they are still set to placeholder values
if (columnWidths.every((width) => width == 150.0)) {
columnWidths = List<double>.generate(widget.titles.length, (index) {
if (index == 1) {
return screenWidth *
0.12; // 20% of screen width for the second column
} else if (index == 9) {
return screenWidth * 0.2; // 25% of screen width for the tenth column
}
return screenWidth *
0.09; // Default to 10% of screen width for other columns
});
setState(() {});
}
return SizedBox(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: FittedBox(
child: Column(
children: [
// Header Row with Resizable Columns
Container(
color: ColorsManager.CircleRolesBackground,
child: Row(
children: List.generate(widget.titles.length, (index) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
FittedBox(
child: Container(
padding: const EdgeInsets.only(left: 10, right: 10),
width: columnWidths[index],
child: Text(
widget.titles[index],
style: const TextStyle(
fontWeight: FontWeight.w400,
fontSize: 13,
color: ColorsManager.grayColor,
),
),
),
),
GestureDetector(
onHorizontalDragUpdate: (details) {
setState(() {
columnWidths[index] = (columnWidths[index] +
details.delta.dx)
.clamp(
150.0, 300.0); // Minimum & Maximum size
});
},
child: MouseRegion(
cursor: SystemMouseCursors
.resizeColumn, // Set the cursor to resize
child: Container(
color: Colors.green,
child: Container(
color: ColorsManager.boxDivider,
width: 1,
height: 50, // Height of the header cell
),
),
),
),
],
);
}),
),
),
// Data Rows with Dividers
Container(
color: ColorsManager.whiteColors,
child: Column(
children: widget.rows.map((row) {
int rowIndex = widget.rows.indexOf(row);
return Column(
children: [
Container(
child: Padding(
padding: const EdgeInsets.only(
left: 5, top: 10, right: 5, bottom: 10),
child: Row(
children: List.generate(row.length, (index) {
return SizedBox(
width: columnWidths[index],
child: SizedBox(
child: Padding(
padding: const EdgeInsets.only(
left: 15, right: 10),
child: row[index],
),
),
);
}),
),
),
),
if (rowIndex < widget.rows.length - 1)
Row(
children:
List.generate(widget.titles.length, (index) {
return SizedBox(
width: columnWidths[index],
child: const Divider(
color: ColorsManager.boxDivider,
thickness: 1,
height: 1,
),
);
}))
// Add a Divider below each row except the last one
],
);
}).toList(),
),
),
],
),
),
),
);
}
}
// Widget build(BuildContext context) {
// return Scaffold(
// body: SingleChildScrollView(
// scrollDirection: Axis.horizontal,
// child: SingleChildScrollView(
// scrollDirection: Axis.vertical,
// child: Column(
// children: [
// // Header Row with Resizable Columns
// Container(
// color: Colors.green,
// child: Row(
// children: List.generate(widget.titles.length, (index) {
// return Row(
// children: [
// Container(
// width: columnWidths[index],
// decoration: const BoxDecoration(
// color: Colors.green,
// ),
// child: Text(
// widget.titles[index],
// style: TextStyle(fontWeight: FontWeight.bold),
// textAlign: TextAlign.center,
// ),
// ),
// GestureDetector(
// onHorizontalDragUpdate: (details) {
// setState(() {
// columnWidths[index] = (columnWidths[index] +
// details.delta.dx)
// .clamp(50.0, 300.0); // Minimum & Maximum size
// });
// },
// child: MouseRegion(
// cursor: SystemMouseCursors
// .resizeColumn, // Set the cursor to resize
// child: Container(
// color: Colors.green,
// child: Container(
// color: Colors.black,
// width: 1,
// height: 50, // Height of the header cell
// ),
// ),
// ),
// ),
// ],
// );
// }),
// ),
// ),
// // Data Rows
// ...widget.rows.map((row) {
// return Row(
// children: List.generate(row.length, (index) {
// return Container(
// width: columnWidths[index],
// child: row[index],
// );
// }),
// );
// }).toList(),
// ],
// ),
// ),
// ),
// );
// }

View File

@ -2,11 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_state.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/view/users_page.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/users_page.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
@ -17,8 +16,7 @@ class RolesAndPermissionPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) =>
RolesPermissionBloc()..add(const GetRoles()),
create: (BuildContext context) => RolesPermissionBloc(),
child: BlocConsumer<RolesPermissionBloc, RolesPermissionState>(
listener: (context, state) {},
builder: (context, state) {
@ -77,8 +75,8 @@ class RolesAndPermissionPage extends StatelessWidget {
),
],
),
scaffoldBody: BlocProvider<UsersBloc>(
create: (context) => UsersBloc()..add(const GetUsers()),
scaffoldBody: BlocProvider<UserTableBloc>(
create: (context) => UserTableBloc()..add(const GetUsers()),
child: const UsersPage(),
)
// _blocRole.tapSelect == false

View File

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

View File

@ -1,5 +1,7 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
@ -33,7 +35,7 @@ class UserPermissionApi {
return response ?? [];
}
Future sendInviteUser({
Future<bool> sendInviteUser({
String? firstName,
String? lastName,
String? email,
@ -42,22 +44,58 @@ class UserPermissionApi {
String? roleUuid,
List<String>? spaceUuids,
}) async {
final response = await _httpService.post(
path: ApiEndpoints.permission,
showServerMessage: true,
body: {
try {
final body = <String, dynamic>{
"firstName": firstName,
"lastName": lastName,
"email": email,
"jobTitle": jobTitle,
"phoneNumber": phoneNumber,
"jobTitle": jobTitle != '' ? jobTitle : " ",
"phoneNumber": phoneNumber != '' ? phoneNumber : " ",
"roleUuid": roleUuid,
"spaceUuids": spaceUuids
},
expectedResponseModel: (json) {
print(json);
},
);
return response ?? [];
"projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c",
"spaceUuids": spaceUuids,
};
final response = await _httpService.post(
path: ApiEndpoints.inviteUser,
showServerMessage: true,
body: jsonEncode(body),
expectedResponseModel: (json) {
if (json['statusCode'] != 400) {
return json["success"];
} else {
return false;
}
},
);
return response ?? [];
} on DioException catch (e) {
return false;
} catch (e) {
return false;
}
}
Future<String?> checkEmail(email) async {
try {
final response = await _httpService.post(
path: ApiEndpoints.checkEmail,
showServerMessage: true,
body: {"email": email},
expectedResponseModel: (json) {
if (json['statusCode'] != 400) {
return json["message"];
}
},
);
return response ?? [];
} on DioException catch (e) {
if (e.response != null) {
final errorMessage = e.response?.data['error'];
return errorMessage;
}
} catch (e) {
return e.toString();
}
}
}

View File

@ -1,6 +1,7 @@
import 'package:flutter_dotenv/flutter_dotenv.dart';
abstract class ApiEndpoints {
static const String projectUuid = "0e62577c-06fa-41b9-8a92-99a21fbaf51c";
static String baseUrl = dotenv.env['BASE_URL'] ?? '';
static const String signUp = '/authentication/user/signup';
static const String login = '/authentication/user/login';
@ -38,23 +39,32 @@ abstract class ApiEndpoints {
static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}';
// Space Module
static const String createSpace = '/communities/{communityId}/spaces';
static const String listSpaces = '/communities/{communityId}/spaces';
static const String createSpace =
'/projects/${projectUuid}/communities/{communityId}/spaces';
static const String listSpaces =
'/projects/${projectUuid}/communities/{communityId}/spaces';
static const String deleteSpace =
'/communities/{communityId}/spaces/{spaceId}';
'/projects/${projectUuid}/communities/{communityId}/spaces/{spaceId}';
static const String updateSpace =
'/communities/{communityId}/spaces/{spaceId}';
static const String getSpace = '/communities/{communityId}/spaces/{spaceId}';
static const String getSpaceHierarchy = '/communities/{communityId}/spaces';
'/projects/${projectUuid}/communities/{communityId}/spaces/{spaceId}';
static const String getSpace =
'/projects/${projectUuid}/communities/{communityId}/spaces/{spaceId}';
static const String getSpaceHierarchy =
'/projects/${projectUuid}/communities/{communityId}/spaces';
// Community Module
static const String createCommunity = '/communities';
static const String getCommunityList = '/communities';
static const String getCommunityById = '/communities/{communityId}';
static const String updateCommunity = '/communities/{communityId}';
static const String deleteCommunity = '/communities/{communityId}';
static const String getUserCommunities = '/communities/user/{userUuid}';
static const String createUserCommunity = '/communities/user';
static const String createCommunity = '/projects/${projectUuid}/communities';
static const String getCommunityList = '/projects/${projectUuid}/communities';
static const String getCommunityById =
'/projects/${projectUuid}/communities/{communityId}';
static const String updateCommunity =
'/projects/${projectUuid}/communities/{communityId}';
static const String deleteCommunity =
'/projects/${projectUuid}/communities/{communityId}';
static const String getUserCommunities =
'/projects/${projectUuid}/communities/user/{userUuid}';
static const String createUserCommunity =
'/projects/${projectUuid}/communities/user';
static const String getDeviceLogsByDate =
'/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}';
@ -90,5 +100,7 @@ abstract class ApiEndpoints {
static const String roleTypes = '/role/types';
static const String permission = '/permission/{roleUuid}';
static const String inviteUser = '/invite-user';
static const String checkEmail = '/invite-user/check-email';
// static const String updateAutomation = '/automation/{automationId}';
// https://syncrow-dev.azurewebsites.net/invite-user/check-email
}

View File

@ -394,5 +394,8 @@ class Assets {
static const String arrowDown = 'assets/icons/arrow_down.svg';
static const String userManagement = 'assets/icons/user_management.svg';
static const String filterTableIcon = 'assets/icons/filter_table_icon.svg';
static const String ZtoAIcon = 'assets/icons/ztoa_icon.png';
static const String AtoZIcon = 'assets/icons/atoz_icon.png';
}
//user_management.svg