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
+1 -1
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,
@@ -16,7 +16,7 @@ class RoleTypeModel {
uuid: json['uuid'],
createdAt: json['createdAt'],
updatedAt: json['updatedAt'],
type: json['type'],
type: json['type'].toString().toLowerCase().replaceAll("_", " "),
);
}
}
@@ -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: '[email protected]',
action: '',
createdBy: 'Admin',
creationDate: '25/10/2024',
creationTime: '10:30 AM',
status: 'Invited',
),
RolesUserModel(
id: '2',
userName: 'user 2',
userEmail: '[email protected]',
action: '',
createdBy: 'Admin',
creationDate: '25/10/2024',
creationTime: '10:30 AM',
status: 'Active',
),
RolesUserModel(
id: '3',
userName: 'user 3',
userEmail: '[email protected]',
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;
@@ -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 => [];
}
@@ -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 => [];
}
@@ -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 }
@@ -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,
),
),
)
],
),
);
}
}
@@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl_phone_field/countries.dart';
import 'package:intl_phone_field/country_picker_dialog.dart';
import 'package:intl_phone_field/intl_phone_field.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
@@ -30,7 +32,7 @@ class BasicsView extends StatelessWidget {
color: Colors.black),
),
const SizedBox(
height: 80,
height: 50,
),
Text(
'To get started, fill out some basic information about who you’re adding as a user.',
@@ -40,11 +42,10 @@ class BasicsView extends StatelessWidget {
),
),
const SizedBox(
height: 25,
height: 35,
),
Row(
children: [
// First Name
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -52,9 +53,6 @@ class BasicsView extends StatelessWidget {
SizedBox(
child: Row(
children: [
// SizedBox(
// width: 15,
// ),
const Text(
" * ",
style: TextStyle(
@@ -75,7 +73,14 @@ class BasicsView extends StatelessWidget {
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
style: TextStyle(color: Colors.black),
style:
const TextStyle(color: ColorsManager.blackColor),
onChanged: (value) {
Future.delayed(const Duration(milliseconds: 200),
() {
_blocRole.add(ValidateBasicsStep());
});
},
controller: _blocRole.firstNameController,
decoration: inputTextFormDeco(
hintText: "Enter first name",
@@ -96,10 +101,7 @@ class BasicsView extends StatelessWidget {
],
),
),
SizedBox(width: 10),
// Last Name
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -125,8 +127,14 @@ class BasicsView extends StatelessWidget {
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
onChanged: (value) {
Future.delayed(const Duration(milliseconds: 200),
() {
_blocRole.add(ValidateBasicsStep());
});
},
controller: _blocRole.lastNameController,
style: TextStyle(color: Colors.black),
style: const TextStyle(color: Colors.black),
decoration:
inputTextFormDeco(hintText: "Enter last name")
.copyWith(
@@ -149,7 +157,7 @@ class BasicsView extends StatelessWidget {
),
],
),
SizedBox(height: 10),
const SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -176,8 +184,14 @@ class BasicsView extends StatelessWidget {
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
onChanged: (value) {
Future.delayed(const Duration(milliseconds: 200), () {
_blocRole.add(const CheckStepStatus());
_blocRole.add(ValidateBasicsStep());
});
},
controller: _blocRole.emailController,
style: TextStyle(color: Colors.black),
style: const TextStyle(color: ColorsManager.blackColor),
decoration: inputTextFormDeco(hintText: "[email protected]")
.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),
],
),
);
@@ -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 {
@@ -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(),
],
);
},
);
}
}
@@ -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,
),
),
),
),
),
),
],
),
);
}
}
@@ -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,
))))
],
),
),
),
],
),
),
);
});
}
}
@@ -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';
@@ -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: '[email protected]',
action: '',
createdBy: 'Admin',
creationDate: '25/10/2024',
creationTime: '10:30 AM',
status: 'Invited',
),
RolesUserModel(
id: '2',
userName: 'a 2',
userEmail: '[email protected]',
action: '',
createdBy: 'Admin',
creationDate: '24/10/2024',
creationTime: '2:30 PM',
status: 'Active',
),
RolesUserModel(
id: '3',
userName: 'c 3',
userEmail: '[email protected]',
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 ');
}
}
}
@@ -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 => [];
}
@@ -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];
}
@@ -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;
// });
});
}
@@ -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;
// });
});
}
@@ -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;
// });
});
}
@@ -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(),
// ],
// ),
// ),
// ),
// );
// }
@@ -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: () {},
@@ -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(),
};
}
}
@@ -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(),
// ],
// ),
// ),
// ),
// );
// }
@@ -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
-1
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();
+53 -15
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();
}
}
}
+25 -13
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
}
+3
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