diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index b931d90d..74980c2d 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -31,7 +31,8 @@ class AuthBloc extends Bloc { ////////////////////////////// forget password ////////////////////////////////// final TextEditingController forgetEmailController = TextEditingController(); - final TextEditingController forgetPasswordController = TextEditingController(); + final TextEditingController forgetPasswordController = + TextEditingController(); final TextEditingController forgetOtp = TextEditingController(); final forgetFormKey = GlobalKey(); final forgetEmailKey = GlobalKey(); @@ -48,7 +49,8 @@ class AuthBloc extends Bloc { return; } _remainingTime = 1; - add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); + add(UpdateTimerEvent( + remainingTime: _remainingTime, isButtonEnabled: false)); try { forgetEmailValidate = ''; _remainingTime = (await AuthenticationAPI.sendOtp( @@ -85,7 +87,8 @@ class AuthBloc extends Bloc { _timer?.cancel(); add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true)); } else { - add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); + add(UpdateTimerEvent( + remainingTime: _remainingTime, isButtonEnabled: false)); } }); } @@ -95,7 +98,8 @@ class AuthBloc extends Bloc { emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); } - Future changePassword(ChangePasswordEvent event, Emitter emit) async { + Future changePassword( + ChangePasswordEvent event, Emitter emit) async { emit(LoadingForgetState()); try { var response = await AuthenticationAPI.verifyOtp( @@ -111,7 +115,8 @@ class AuthBloc extends Bloc { } } on DioException catch (e) { final errorData = e.response!.data; - String errorMessage = errorData['error']['message'] ?? 'something went wrong'; + String errorMessage = + errorData['error']['message'] ?? 'something went wrong'; validate = errorMessage; emit(AuthInitialState()); } @@ -125,7 +130,9 @@ class AuthBloc extends Bloc { } void _onUpdateTimer(UpdateTimerEvent event, Emitter emit) { - emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime)); + emit(TimerState( + isButtonEnabled: event.isButtonEnabled, + remainingTime: event.remainingTime)); } ///////////////////////////////////// login ///////////////////////////////////// @@ -161,15 +168,23 @@ class AuthBloc extends Bloc { password: event.password, ), ); - } catch (failure) { - validate = 'Invalid Credentials!'; + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = errorData['error']['message']; + if (errorMessage == "Access denied for web platform") { + validate = errorMessage; + } else { + validate = 'Invalid Credentials!'; + } emit(LoginInitial()); return; } + if (token.accessTokenIsNotEmpty) { FlutterSecureStorage storage = const FlutterSecureStorage(); - await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken); + await storage.write( + key: Token.loginAccessTokenKey, value: token.accessToken); const FlutterSecureStorage().write( key: UserModel.userUuidKey, value: Token.decodeToken(token.accessToken)['uuid'].toString()); @@ -327,12 +342,14 @@ class AuthBloc extends Bloc { static Future getTokenAndValidate() async { try { const storage = FlutterSecureStorage(); - final firstLaunch = - await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true; + final firstLaunch = await SharedPreferencesHelper.readBoolFromSP( + StringsManager.firstLaunch) ?? + true; if (firstLaunch) { storage.deleteAll(); } - await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false); + await SharedPreferencesHelper.saveBoolToSP( + StringsManager.firstLaunch, false); final value = await storage.read(key: Token.loginAccessTokenKey) ?? ''; if (value.isEmpty) { return 'Token not found'; @@ -385,7 +402,9 @@ class AuthBloc extends Bloc { final String formattedTime = [ if (days > 0) '${days}d', // Append 'd' for days if (days > 0 || hours > 0) - hours.toString().padLeft(2, '0'), // Show hours if there are days or hours + hours + .toString() + .padLeft(2, '0'), // Show hours if there are days or hours minutes.toString().padLeft(2, '0'), seconds.toString().padLeft(2, '0'), ].join(':'); diff --git a/lib/pages/auth/model/login_with_email_model.dart b/lib/pages/auth/model/login_with_email_model.dart index ec3d4d98..dca3f8f9 100644 --- a/lib/pages/auth/model/login_with_email_model.dart +++ b/lib/pages/auth/model/login_with_email_model.dart @@ -21,7 +21,9 @@ class LoginWithEmailModel { return { 'email': email, 'password': password, + "platform": "web" // 'regionUuid': regionUuid, }; } } +//tst@tst.com \ No newline at end of file diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 1d4bdf8b..57adc6ff 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -62,6 +62,7 @@ class HomeBloc extends Bloc { emit(LoadingHome()); terms = await HomeApi().fetchTerms(); add(FetchPolicyEvent()); + emit(PolicyAgreement()); } catch (e) { return; } @@ -71,6 +72,7 @@ class HomeBloc extends Bloc { try { emit(LoadingHome()); policy = await HomeApi().fetchPolicy(); + emit(PolicyAgreement()); } catch (e) { return; } diff --git a/lib/pages/home/view/agreement_and_privacy_dialog.dart b/lib/pages/home/view/agreement_and_privacy_dialog.dart index e9371ae9..67c33984 100644 --- a/lib/pages/home/view/agreement_and_privacy_dialog.dart +++ b/lib/pages/home/view/agreement_and_privacy_dialog.dart @@ -38,7 +38,7 @@ class _AgreementAndPrivacyDialogState extends State { final scrollPosition = _scrollController.position; if (scrollPosition.maxScrollExtent <= 0) { setState(() { - _isAtEnd = true; + _isAtEnd = true; }); } } @@ -63,9 +63,11 @@ class _AgreementAndPrivacyDialogState extends State { } String get _dialogTitle => - _currentPage == 2 ? 'User Agreement' : 'Privacy Policy'; + _currentPage == 1 ? 'User Agreement' : 'Privacy Policy'; - String get _dialogContent => _currentPage == 2 ? widget.terms : widget.policy; + String get _dialogContent => _currentPage == 1 ? widget.terms : widget.policy; + final String staticText = + '
If you cancel you will be logged out.
'; Widget _buildScrollableContent() { return Container( @@ -85,7 +87,7 @@ class _AgreementAndPrivacyDialogState extends State { controller: _scrollController, padding: const EdgeInsets.all(25), child: Html( - data: _dialogContent, + data: "$_dialogContent $staticText", onLinkTap: (url, attributes, element) async { if (url != null) { final uri = Uri.parse(url); diff --git a/lib/pages/home/view/home_page_web.dart b/lib/pages/home/view/home_page_web.dart index 8e7225d2..46e0a835 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -24,7 +24,7 @@ class HomeWebPage extends StatelessWidget { listener: (BuildContext context, state) { if (state is HomeInitial) { if (homeBloc.user!.hasAcceptedWebAgreement == false) { - Future.delayed(const Duration(seconds: 1), () { + Future.delayed(const Duration(seconds: 2), () { showDialog( context: context, barrierDismissible: false, diff --git a/lib/pages/roles_and_permission/model/roles_user_model.dart b/lib/pages/roles_and_permission/model/roles_user_model.dart index 44e3f493..e502370a 100644 --- a/lib/pages/roles_and_permission/model/roles_user_model.dart +++ b/lib/pages/roles_and_permission/model/roles_user_model.dart @@ -42,7 +42,9 @@ class RolesUserModel { invitedBy: json['invitedBy'].toString().toLowerCase().replaceAll("_", " "), phoneNumber: json['phoneNumber'], - jobTitle: json['jobTitle'] ?? "-", + jobTitle: json['jobTitle'] == null || json['jobTitle'] == " " + ? "_" + : json['jobTitle'], createdDate: json['createdDate'], createdTime: json['createdTime'], ); diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index 26a1bcc7..16d6cbe6 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -79,13 +79,14 @@ class UsersBloc extends Bloc { List updatedCommunities = []; List spacesNodes = []; - + List communityIds = []; _onLoadCommunityAndSpaces( LoadCommunityAndSpacesEvent event, Emitter emit) async { try { emit(UsersLoadingState()); List communities = await CommunitySpaceManagementApi().fetchCommunities(); + communityIds = communities.map((community) => community.uuid).toList(); updatedCommunities = await Future.wait( communities.map((community) async { List spaces = @@ -102,7 +103,6 @@ class UsersBloc extends Bloc { }).toList(), ); emit(const SpacesLoadedState()); - return updatedCommunities; } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); } @@ -177,7 +177,6 @@ class UsersBloc extends Bloc { try { emit(UsersLoadingState()); roles = await UserPermissionApi().fetchRoles(); - // add(PermissionEvent(roleUuid: roles.first.uuid)); emit(RolePermissionInitial()); } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); @@ -208,10 +207,13 @@ class UsersBloc extends Bloc { return anyMatch; } - _sendInvitUser(SendInviteUsers event, Emitter emit) async { + void _sendInvitUser(SendInviteUsers event, Emitter emit) async { try { emit(UsersLoadingState()); - List selectedIds = getSelectedIds(updatedCommunities); + List selectedIds = getSelectedIds(updatedCommunities) + .where((id) => !communityIds.contains(id)) + .toList(); + bool res = await UserPermissionApi().sendInviteUser( email: emailController.text, firstName: firstNameController.text, @@ -219,9 +221,10 @@ class UsersBloc extends Bloc { lastName: lastNameController.text, phoneNumber: phoneController.text, roleUuid: roleSelected, - spaceUuids: selectedIds, + spaceUuids: selectedIds, ); - if (res == true) { + + if (res) { showCustomDialog( barrierDismissible: false, context: event.context, @@ -248,10 +251,14 @@ class UsersBloc extends Bloc { } } + _editInviteUser(EditInviteUsers event, Emitter emit) async { try { emit(UsersLoadingState()); - List selectedIds = getSelectedIds(updatedCommunities); + List selectedIds = getSelectedIds(updatedCommunities) + .where((id) => !communityIds.contains(id)) + .toList(); + bool res = await UserPermissionApi().editInviteUser( userId: event.userId, firstName: firstNameController.text, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index 53d9a333..308c6c67 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -218,7 +218,7 @@ class BasicsView extends StatelessWidget { if (_blocRole.checkEmailValid != "Valid email") { return _blocRole.checkEmailValid; } - return null; + // return null; }, ), ), diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart index 120a1a3a..a7f6c2b5 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart @@ -81,7 +81,7 @@ Future showPopUpFilterMenu({ ), const Divider(), const Text( - "Filter by Status", + "Filter by ", style: TextStyle(fontWeight: FontWeight.bold), ), Container( diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart index c50667be..3d09e5b4 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -40,9 +40,7 @@ class UserTableBloc extends Bloc { roleTypes.clear(); jobTitle.clear(); createdBy.clear(); - // deActivate.clear(); users = await UserPermissionApi().fetchUsers(); - users.sort((a, b) { final dateA = _parseDateTime(a.createdDate); final dateB = _parseDateTime(b.createdDate); @@ -57,15 +55,12 @@ class UserTableBloc extends Bloc { for (var user in users) { createdBy.add(user.invitedBy.toString()); } - // for (var user in users) { - // deActivate.add(user.status.toString()); - // } initialUsers = List.from(users); roleTypes = roleTypes.toSet().toList(); jobTitle = jobTitle.toSet().toList(); createdBy = createdBy.toSet().toList(); - // deActivate = deActivate.toSet().toList(); _handlePageChange(ChangePage(1), emit); + add(ChangePage(currentPage)); emit(UsersLoadedState(users: users)); } catch (e) { emit(ErrorState(e.toString())); @@ -125,6 +120,10 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByNameAsc( SortUsersByNameAsc event, Emitter emit) { + selectedRoles.clear(); + selectedJobTitles.clear(); + selectedCreatedBy.clear(); + selectedStatuses.clear(); if (currentSortOrder == "Asc") { emit(UsersLoadingState()); currentSortOrder = ""; @@ -143,13 +142,16 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByNameDesc( SortUsersByNameDesc event, Emitter emit) { + selectedRoles.clear(); + selectedJobTitles.clear(); + selectedCreatedBy.clear(); + selectedStatuses.clear(); if (currentSortOrder == "Desc") { emit(UsersLoadingState()); currentSortOrder = ""; - users = List.from(initialUsers); // Reset to saved initial state + users = List.from(initialUsers); emit(UsersLoadedState(users: users)); } else { - // Sort descending emit(UsersLoadingState()); currentSortOrder = "Desc"; users.sort((a, b) => b.firstName!.compareTo(a.firstName!)); @@ -159,6 +161,10 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByDateNewestToOldest( DateNewestToOldestEvent event, Emitter emit) { + selectedRoles.clear(); + selectedJobTitles.clear(); + selectedCreatedBy.clear(); + selectedStatuses.clear(); if (currentSortOrderDate == "NewestToOldest") { emit(UsersLoadingState()); currentSortOrder = ""; @@ -179,6 +185,10 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByDateOldestToNewest( DateOldestToNewestEvent event, Emitter emit) { + selectedRoles.clear(); + selectedJobTitles.clear(); + selectedCreatedBy.clear(); + selectedStatuses.clear(); if (currentSortOrderDate == "OldestToNewest") { emit(UsersLoadingState()); currentSortOrder = ""; @@ -337,7 +347,20 @@ class UserTableBloc extends Bloc { final filteredUsers = initialUsers.where((user) { if (selectedStatuses.isEmpty) return true; - return selectedStatuses.contains(user.status); + + return selectedStatuses.any((status) { + final userStatus = user.status?.toLowerCase() ?? ''; + switch (status.toLowerCase()) { + case 'active': + return user.isEnabled == true && userStatus != 'invited'; + case 'disabled': + return user.isEnabled == false; + case 'invited': + return userStatus == 'invited'; + default: + return false; + } + }); }).toList(); if (event.sortOrder == "Asc") { currentSortOrder = "Asc"; @@ -351,9 +374,11 @@ class UserTableBloc extends Bloc { } else { currentSortOrder = ""; } + emit(UsersLoadedState(users: filteredUsers)); } + void _resetAllFilters(Emitter emit) { selectedRoles.clear(); selectedJobTitles.clear(); diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart b/lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart index 45ebc3ae..7b7da0bd 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart @@ -12,7 +12,7 @@ Future showDateFilterMenu({ Overlay.of(context).context.findRenderObject() as RenderBox; final RelativeRect position = RelativeRect.fromRect( Rect.fromLTRB( - overlay.size.width / 2, + overlay.size.width / 3, 240, 0, overlay.size.height, @@ -40,7 +40,6 @@ Future showDateFilterMenu({ ), title: Text( "Sort from newest to oldest", - // style: context.textTheme.bodyMedium, style: TextStyle( color: isSelected == "NewestToOldest" ? Colors.black @@ -65,9 +64,5 @@ Future showDateFilterMenu({ ), ), ], - ).then((value) { - // setState(() { - // _isDropdownOpen = false; - // }); - }); + ).then((value) {}); } diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart b/lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart index e78eae6b..c8742ea5 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart @@ -40,7 +40,6 @@ Future showDeActivateFilterMenu({ ), title: Text( "Sort A to Z", - // style: context.textTheme.bodyMedium, style: TextStyle( color: isSelected == "NewestToOldest" ? Colors.black @@ -65,9 +64,5 @@ Future showDeActivateFilterMenu({ ), ), ], - ).then((value) { - // setState(() { - // _isDropdownOpen = false; - // }); - }); + ).then((value) {}); } diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart b/lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart index e869e10b..5ff10b20 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart @@ -12,7 +12,7 @@ Future showNameMenu({ Overlay.of(context).context.findRenderObject() as RenderBox; final RelativeRect position = RelativeRect.fromRect( Rect.fromLTRB( - overlay.size.width / 25, + overlay.size.width / 35, 240, 0, overlay.size.height, @@ -40,7 +40,6 @@ Future showNameMenu({ ), title: Text( "Sort A to Z", - // style: context.textTheme.bodyMedium, style: TextStyle( color: isSelected == "Asc" ? Colors.black : Colors.blueGrey), ), @@ -61,9 +60,5 @@ Future showNameMenu({ ), ), ], - ).then((value) { - // setState(() { - // _isDropdownOpen = false; - // }); - }); + ).then((value) {}); } diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart index 92229643..b26c09c4 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -1,256 +1,59 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/style.dart'; -class DynamicTableScreen extends StatefulWidget { - final List titles; - final List> rows; - final void Function(int columnIndex)? onFilter; +class _HeaderColumn extends StatelessWidget { + final String title; + final double width; + final bool showFilter; + final VoidCallback? onFilter; + final Function(double) onResize; - DynamicTableScreen( - {required this.titles, required this.rows, required this.onFilter}); - - @override - _DynamicTableScreenState createState() => _DynamicTableScreenState(); -} - -class _DynamicTableScreenState extends State - with WidgetsBindingObserver { - late List columnWidths; - late double totalWidth; - - @override - void initState() { - super.initState(); - columnWidths = List.filled(widget.titles.length, 150.0); - totalWidth = columnWidths.reduce((a, b) => a + b); - WidgetsBinding.instance.addObserver(this); - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - - @override - void didChangeMetrics() { - super.didChangeMetrics(); - final newScreenWidth = MediaQuery.of(context).size.width; - setState(() { - columnWidths = List.generate(widget.titles.length, (index) { - if (index == 1) { - return newScreenWidth * - 0.12; // 20% of screen width for the second column - } else if (index == 9) { - return newScreenWidth * - 0.1; // 25% of screen width for the tenth column - } - return newScreenWidth * - 0.09; // Default to 10% of screen width for other columns - }); - }); - } + const _HeaderColumn({ + required this.title, + required this.width, + required this.showFilter, + required this.onResize, + this.onFilter, + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { - final screenWidth = MediaQuery.of(context).size.width; - if (columnWidths.every((width) => width == screenWidth * 7)) { - columnWidths = List.generate(widget.titles.length, (index) { - if (index == 1) { - return screenWidth * 0.11; - } else if (index == 9) { - return screenWidth * 0.1; - } - return screenWidth * 0.09; - }); - setState(() {}); - } - return SingleChildScrollView( - clipBehavior: Clip.none, - scrollDirection: Axis.horizontal, - child: Container( - decoration: containerDecoration.copyWith( - color: ColorsManager.whiteColors, - borderRadius: const BorderRadius.all(Radius.circular(20))), - child: FittedBox( - child: Column( + return MouseRegion( + cursor: SystemMouseCursors.resizeColumn, + child: GestureDetector( + onHorizontalDragUpdate: (details) => onResize(details.delta.dx), + child: Container( + width: width, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: const BoxDecoration( + border: Border(right: BorderSide(color: ColorsManager.boxDivider)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Container( - width: totalWidth, - decoration: containerDecoration.copyWith( - color: ColorsManager.circleRolesBackground, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(15), - topRight: Radius.circular(15))), - child: Row( - children: List.generate(widget.titles.length, (index) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - FittedBox( - child: Container( - padding: const EdgeInsets.only(left: 5, right: 5), - width: columnWidths[index], - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - child: Text( - widget.titles[index], - maxLines: 2, - style: const TextStyle( - overflow: TextOverflow.ellipsis, - fontWeight: FontWeight.w400, - fontSize: 13, - color: ColorsManager.grayColor, - ), - ), - ), - if (index != 1 && - index != 9 && - index != 8 && - index != 5) - FittedBox( - child: IconButton( - icon: SvgPicture.asset( - Assets.filterTableIcon, - fit: BoxFit.none, - ), - onPressed: () { - if (widget.onFilter != null) { - widget.onFilter!(index); - } - }, - ), - ) - ], - ), - ), - ), - GestureDetector( - onHorizontalDragUpdate: (details) { - setState(() { - columnWidths[index] = - (columnWidths[index] + details.delta.dx) - .clamp(150.0, 300.0); - totalWidth = columnWidths.reduce((a, b) => a + b); - }); - }, - child: MouseRegion( - cursor: SystemMouseCursors.resizeColumn, - child: Container( - color: Colors.green, - child: Container( - color: ColorsManager.boxDivider, - width: 1, - height: 50, - ), - ), - ), - ), - ], - ); - }), + Expanded( + child: Text( + title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.w400, + fontSize: 13, + color: ColorsManager.grayColor, + ), ), ), - widget.rows.isEmpty - ? SizedBox( - height: MediaQuery.of(context).size.height / 2, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - children: [ - SvgPicture.asset(Assets.emptyTable), - const SizedBox( - height: 15, - ), - const Text( - 'No Users', - style: TextStyle( - color: ColorsManager.lightGrayColor, - fontSize: 16, - fontWeight: FontWeight.w700), - ) - ], - ), - ], - ), - ) - : Center( - child: Container( - width: totalWidth, - decoration: containerDecoration.copyWith( - color: ColorsManager.whiteColors, - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(15), - bottomRight: Radius.circular(15))), - child: ListView.builder( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: widget.rows.length, - itemBuilder: (context, rowIndex) { - if (columnWidths.every((width) => width == 120.0)) { - columnWidths = List.generate( - widget.titles.length, (index) { - if (index == 1) { - return screenWidth * 0.11; - } else if (index == 9) { - return screenWidth * 0.2; - } - return screenWidth * 0.11; - }); - setState(() {}); - } - final row = widget.rows[rowIndex]; - return Column( - children: [ - Container( - child: Padding( - padding: const EdgeInsets.only( - left: 5, top: 10, right: 5, bottom: 10), - child: Row( - children: - List.generate(row.length, (index) { - return SizedBox( - width: columnWidths[index], - child: SizedBox( - child: Padding( - padding: const EdgeInsets.only( - left: 15, right: 10), - child: row[index], - ), - ), - ); - }), - ), - ), - ), - if (rowIndex < widget.rows.length - 1) - Row( - children: List.generate( - widget.titles.length, (index) { - return SizedBox( - width: columnWidths[index], - child: const Divider( - color: ColorsManager.boxDivider, - thickness: 1, - height: 1, - ), - ); - }), - ), - ], - ); - }, - ), - ), - ), + if (showFilter) + IconButton( + icon: SvgPicture.asset(Assets.filterTableIcon), + onPressed: onFilter, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), ], ), ), @@ -258,3 +61,204 @@ class _DynamicTableScreenState extends State ); } } + +class _TableRow extends StatelessWidget { + final List cells; + final List columnWidths; + final bool isLast; + + const _TableRow({ + required this.cells, + required this.columnWidths, + required this.isLast, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + children: [ + for (int i = 0; i < cells.length; i++) + Container( + width: columnWidths[i], + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + // decoration: BoxDecoration( + // border: Border( + // right: BorderSide(color: ColorsManager.boxDivider), + // ), + // ), + child: cells[i], + ), + ], + ), + if (!isLast) + Divider( + height: 1, + thickness: 1, + color: ColorsManager.boxDivider, + ), + ], + ); + } +} +//=========================================================================== + +class DynamicTableScreen extends StatefulWidget { + final List titles; + final List> rows; + final void Function(int columnIndex)? onFilter; + + const DynamicTableScreen({ + required this.titles, + required this.rows, + required this.onFilter, + Key? key, + }) : super(key: key); + + @override + _DynamicTableScreenState createState() => _DynamicTableScreenState(); +} + +class _DynamicTableScreenState extends State { + late List columnWidths; + final double _minColumnWidth = 100.0; + final double _maxColumnWidth = 300.0; + final double _dividerWidth = 1.0; + double _lastAvailableWidth = 0; + + @override + void initState() { + super.initState(); + columnWidths = List.filled(widget.titles.length, _minColumnWidth); + } + + void _handleColumnResize(int index, double delta) { + setState(() { + double newWidth = columnWidths[index] + delta; + newWidth = newWidth.clamp(_minColumnWidth, _maxColumnWidth); + double actualDelta = newWidth - columnWidths[index]; + if (actualDelta == 0) return; + + int nextIndex = (index + 1) % columnWidths.length; + columnWidths[index] = newWidth; + columnWidths[nextIndex] = (columnWidths[nextIndex] - actualDelta) + .clamp(_minColumnWidth, _maxColumnWidth); + }); + } + + Widget _buildHeader() { + return Container( + decoration: containerDecoration.copyWith( + color: ColorsManager.circleRolesBackground, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(15), + topRight: Radius.circular(15), + ), + ), + child: Row( + children: [ + for (int i = 0; i < widget.titles.length; i++) + _HeaderColumn( + title: widget.titles[i], + width: columnWidths[i], + showFilter: i != 1 && i != 9 && i != 8 && i != 5, + onFilter: () => widget.onFilter?.call(i), + onResize: (delta) => _handleColumnResize(i, delta), + ), + ], + ), + ); + } + + Widget _buildBody() { + if (widget.rows.isEmpty) { + return SizedBox( + height: 300, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset(Assets.emptyTable), + const SizedBox(height: 15), + const Text( + 'No Users', + style: TextStyle( + color: ColorsManager.lightGrayColor, + fontSize: 16, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ), + ); + } + + return Container( + decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(15), + bottomRight: Radius.circular(15), + ), + ), + child: Column( + children: [ + for (int rowIndex = 0; rowIndex < widget.rows.length; rowIndex++) + _TableRow( + cells: widget.rows[rowIndex], + columnWidths: columnWidths, + isLast: rowIndex == widget.rows.length - 1, + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final availableWidth = constraints.maxWidth; + final totalDividersWidth = (widget.titles.length - 1) * _dividerWidth; + + if (_lastAvailableWidth != availableWidth) { + final equalWidth = + (availableWidth - totalDividersWidth) / widget.titles.length; + final clampedWidth = + equalWidth.clamp(_minColumnWidth, _maxColumnWidth); + + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + columnWidths = List.filled(widget.titles.length, clampedWidth); + _lastAvailableWidth = availableWidth; + }); + }); + } + + final totalTableWidth = + columnWidths.fold(0.0, (sum, w) => sum + w) + totalDividersWidth; + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Container( + width: totalTableWidth, + decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(), + _buildBody(), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index dae47196..ddfe63ff 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -108,7 +108,6 @@ class UsersPage extends StatelessWidget { final screenSize = MediaQuery.of(context).size; final _blocRole = BlocProvider.of(context); if (state is UsersLoadingState) { - _blocRole.add(ChangePage(_blocRole.currentPage)); return const Center(child: CircularProgressIndicator()); } else if (state is UsersLoadedState) { return Padding( @@ -215,7 +214,7 @@ class UsersPage extends StatelessWidget { showPopUpFilterMenu( position: RelativeRect.fromLTRB( - overlay.size.width / 4, + overlay.size.width / 5.3, 240, overlay.size.width / 4, 0, @@ -225,6 +224,7 @@ class UsersPage extends StatelessWidget { checkboxStates: checkboxStates, isSelected: _blocRole.currentSortOrder, onOkPressed: () { + searchController.clear(); _blocRole.add(FilterClearEvent()); final selectedItems = checkboxStates.entries .where((entry) => entry.value) @@ -265,6 +265,7 @@ class UsersPage extends StatelessWidget { checkboxStates: checkboxStates, isSelected: _blocRole.currentSortOrder, onOkPressed: () { + searchController.clear(); _blocRole.add(FilterClearEvent()); final selectedItems = checkboxStates.entries .where((entry) => entry.value) @@ -320,6 +321,7 @@ class UsersPage extends StatelessWidget { checkboxStates: checkboxStates, isSelected: _blocRole.currentSortOrder, onOkPressed: () { + searchController.clear(); _blocRole.add(FilterClearEvent()); final selectedItems = checkboxStates.entries .where((entry) => entry.value) @@ -343,6 +345,7 @@ class UsersPage extends StatelessWidget { for (var item in _blocRole.status) item: _blocRole.selectedStatuses.contains(item), }; + final RenderBox overlay = Overlay.of(context) .context .findRenderObject() as RenderBox; @@ -350,7 +353,7 @@ class UsersPage extends StatelessWidget { position: RelativeRect.fromLTRB( overlay.size.width / 0, 240, - overlay.size.width / 4, + overlay.size.width / 5, 0, ), list: _blocRole.status, @@ -358,8 +361,8 @@ class UsersPage extends StatelessWidget { checkboxStates: checkboxStates, isSelected: _blocRole.currentSortOrder, onOkPressed: () { + searchController.clear(); _blocRole.add(FilterClearEvent()); - final selectedItems = checkboxStates.entries .where((entry) => entry.value) .map((entry) => entry.key) @@ -410,7 +413,7 @@ class UsersPage extends StatelessWidget { return [ Text('${user.firstName} ${user.lastName}'), Text(user.email), - Text(user.jobTitle ?? '-'), + Text(user.jobTitle), Text(user.roleType ?? ''), Text(user.createdDate ?? ''), Text(user.createdTime ?? ''), @@ -427,11 +430,6 @@ class UsersPage extends StatelessWidget { userId: user.uuid, onTap: user.status != "invited" ? () { - // final newStatus = user.status == 'active' - // ? 'disabled' - // : user.status == 'disabled' - // ? 'invited' - // : 'active'; context.read().add( ChangeUserStatus( userId: user.uuid, @@ -443,10 +441,6 @@ class UsersPage extends StatelessWidget { ), Row( children: [ - // actionButton( - // title: "Activity Log", - // onTap: () {}, - // ), actionButton( title: "Edit", onTap: () { @@ -487,9 +481,7 @@ class UsersPage extends StatelessWidget { }, ).then((v) { if (v != null) { - if (v != null) { - _blocRole.add(const GetUsers()); - } + _blocRole.add(const GetUsers()); } }); }, diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 527010d0..f3088794 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -18,8 +18,7 @@ class UserPermissionApi { showServerMessage: true, expectedResponseModel: (json) { debugPrint('fetchUsers Response: $json'); - final List data = - json['data'] ?? []; // Default to an empty list if no data + final List data = json['data'] ?? []; return data.map((item) => RolesUserModel.fromJson(item)).toList(); }, ); @@ -119,7 +118,7 @@ class UserPermissionApi { ); return response ?? 'Unknown error occurred'; } on DioException catch (e) { - final errorMessage = e.response?.data['error']; + final errorMessage = e.response?.data['error']['message']; return errorMessage; } catch (e) { return e.toString(); @@ -205,7 +204,6 @@ class UserPermissionApi { .replaceAll("{invitedUserUuid}", userUuid), body: bodya, expectedResponseModel: (json) { - print('changeUserStatusById==${json['success']}'); return json['success']; }, ); @@ -213,7 +211,6 @@ class UserPermissionApi { return response; } catch (e) { return false; - print(e); } } } diff --git a/pubspec.lock b/pubspec.lock index d1c39c65..7925b06f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f" + url: "https://pub.dev" + source: hosted + version: "0.17.3" cupertino_icons: dependency: "direct main" description: @@ -182,6 +190,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" + flutter_html: + dependency: "direct main" + description: + name: flutter_html + sha256: "02ad69e813ecfc0728a455e4bf892b9379983e050722b1dce00192ee2e41d1ee" + url: "https://pub.dev" + source: hosted + version: "3.0.0-beta.2" flutter_lints: dependency: "direct dev" description: @@ -280,6 +296,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + html: + dependency: transitive + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" http: dependency: transitive description: @@ -352,6 +376,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + list_counter: + dependency: transitive + description: + name: list_counter + sha256: c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237 + url: "https://pub.dev" + source: hosted + version: "1.0.2" logging: dependency: transitive description: @@ -629,6 +661,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 + url: "https://pub.dev" + source: hosted + version: "6.3.9" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" uuid: dependency: "direct main" description: