From 56d4c34321f0f56f3b988a1f37e5dcc41026f161 Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 12 Dec 2024 19:00:51 +0300 Subject: [PATCH 001/106] roles_and_permission --- assets/icons/active_user.svg | 18 + assets/icons/box_checked.png | Bin 0 -> 754 bytes assets/icons/compleate_process_icon.svg | 9 + assets/icons/current_process_icon.svg | 3 + assets/icons/deactive_user.svg | 18 + assets/icons/empty_box.png | Bin 0 -> 425 bytes assets/icons/invited_icon.svg | 7 + assets/icons/rectangle_check_box.png | Bin 0 -> 523 bytes assets/icons/search_icon_user.svg | 4 + assets/icons/uncompleate_process_icon.svg | 3 + assets/icons/wrong_process_icon.svg | 10 + lib/pages/home/bloc/home_bloc.dart | 9 +- .../bloc/roles_permission_bloc.dart | 42 ++ .../bloc/roles_permission_event.dart | 41 ++ .../bloc/roles_permission_state.dart | 77 +++ .../model/role_model.dart | 6 + .../model/roles_user_model.dart | 22 + .../users_page/bloc/users_bloc.dart | 136 +++++ .../users_page/bloc/users_event.dart | 35 ++ .../users_page/bloc/users_status.dart | 57 ++ .../users_page/view/add_user_dialog.dart | 214 ++++++++ .../users_page/view/basics_view.dart | 279 ++++++++++ .../users_page/view/roles_and_permission.dart | 498 ++++++++++++++++++ .../users_page/view/spaces_access_view.dart | 338 ++++++++++++ .../users_page/view/user_table.dart | 256 +++++++++ .../users_page/view/users_page.dart | 241 +++++++++ .../view/create_role_card.dart | 0 .../roles_and_permission/view/role_card.dart | 57 ++ .../view/roles_and_permission_page.dart | 96 ++++ .../roles_and_permission/view/roles_page.dart | 69 +++ lib/utils/app_routes.dart | 6 +- lib/utils/color_manager.dart | 13 +- lib/utils/constants/assets.dart | 158 ++++-- lib/utils/constants/routes_const.dart | 1 + lib/utils/style.dart | 79 ++- pubspec.lock | 24 +- 36 files changed, 2739 insertions(+), 87 deletions(-) create mode 100644 assets/icons/active_user.svg create mode 100644 assets/icons/box_checked.png create mode 100644 assets/icons/compleate_process_icon.svg create mode 100644 assets/icons/current_process_icon.svg create mode 100644 assets/icons/deactive_user.svg create mode 100644 assets/icons/empty_box.png create mode 100644 assets/icons/invited_icon.svg create mode 100644 assets/icons/rectangle_check_box.png create mode 100644 assets/icons/search_icon_user.svg create mode 100644 assets/icons/uncompleate_process_icon.svg create mode 100644 assets/icons/wrong_process_icon.svg create mode 100644 lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart create mode 100644 lib/pages/roles_and_permission/bloc/roles_permission_event.dart create mode 100644 lib/pages/roles_and_permission/bloc/roles_permission_state.dart create mode 100644 lib/pages/roles_and_permission/model/role_model.dart create mode 100644 lib/pages/roles_and_permission/model/roles_user_model.dart create mode 100644 lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart create mode 100644 lib/pages/roles_and_permission/users_page/bloc/users_event.dart create mode 100644 lib/pages/roles_and_permission/users_page/bloc/users_status.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/basics_view.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/user_table.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/users_page.dart create mode 100644 lib/pages/roles_and_permission/view/create_role_card.dart create mode 100644 lib/pages/roles_and_permission/view/role_card.dart create mode 100644 lib/pages/roles_and_permission/view/roles_and_permission_page.dart create mode 100644 lib/pages/roles_and_permission/view/roles_page.dart diff --git a/assets/icons/active_user.svg b/assets/icons/active_user.svg new file mode 100644 index 00000000..5e0806e0 --- /dev/null +++ b/assets/icons/active_user.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/box_checked.png b/assets/icons/box_checked.png new file mode 100644 index 0000000000000000000000000000000000000000..d93b9d76d9df6c22695ef154f7b27f151dc63a79 GIT binary patch literal 754 zcmV#?`b=x}-yKsn#)Vx;QBaZaH++eUHK6j0|-3R)(Zx zVRaOSP9)Yi6PfZm?+51;vwnwUA1{kLszV?46N#Mg@dwDToF(Sb$}CBbACq`IS;Fa) zz~Mszx>#7^n0n&J>Q83N3p~j@QF&2Fag(Hr4@+8w+u7Iz1}AzfIb-srM+h-Y3qE(N zDt$EXMf#No`2s^H1OpPWNEvtIrWza8YK}~GgPtRgl7^_$$OT)PdQMU=bU+uLe(goD z7f6hn>K1OTW|-G^e(}}=-8d7WSv&AXSE-wPVdpsx&Ga9YczXXYLaJ?rRxwGRO_y+P zK)_IKE4enYBkez8KS0#Zs5fO_#r0AmzsWIY_~BZ7{*>Iabp02{SkjN|Dsv}J#7v~8 zi&m>s+EPri#C2Op?P#P{K5(MBbnSQhSF)*?s?By%w@pjtNR1ApEyXFri~0ApRm@P! zK76)fNrM6R(w1Uk*6`u;_iC45Ky4tColDz_sno@j-PYiZa4T&qcH}qI;PrA^iEH=V zNp~eC*45x7cZxkp5GDeMsxbN};$bw$fpRqDH9$J=5} zF4D<{+9n4l>})L{(!BDgE2ENTj!sbSU0Bi^_DHz6T$NIwhx(K`*iV=()K>+!1AYj4 kSrLMNK@o58iCJanAKq9oum;g}8UO$Q07*qoM6N<$g1q%wMF0Q* literal 0 HcmV?d00001 diff --git a/assets/icons/compleate_process_icon.svg b/assets/icons/compleate_process_icon.svg new file mode 100644 index 00000000..a4159de2 --- /dev/null +++ b/assets/icons/compleate_process_icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/current_process_icon.svg b/assets/icons/current_process_icon.svg new file mode 100644 index 00000000..967928e3 --- /dev/null +++ b/assets/icons/current_process_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/deactive_user.svg b/assets/icons/deactive_user.svg new file mode 100644 index 00000000..7011f5fb --- /dev/null +++ b/assets/icons/deactive_user.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/empty_box.png b/assets/icons/empty_box.png new file mode 100644 index 0000000000000000000000000000000000000000..71e798759d2913e747487d2b5ffe9cbe32b5831f GIT binary patch literal 425 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3oCO|{#S9E$svykh8Km+7D9BhG zZ8s34L5V* z+7c~3fABHB^!;!DB8~9lg?8_Yx$F`pq*5c}DlRTyedxRN_uqeqx4jMNsJ&m`KBe4e zS>~m@?aFeOV?{OS3ZJUzxNeel6?|IhSIndsuBl`uQ?PK# zPwm84yY_wX@1ABbA#eNb|3c6BR&^O3w7*x(yh*?BujG-RKY$_2 N;OXk;vd$@?2>^L{tuFuo literal 0 HcmV?d00001 diff --git a/assets/icons/invited_icon.svg b/assets/icons/invited_icon.svg new file mode 100644 index 00000000..5563de14 --- /dev/null +++ b/assets/icons/invited_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/rectangle_check_box.png b/assets/icons/rectangle_check_box.png new file mode 100644 index 0000000000000000000000000000000000000000..3404c79cabb9ea8574f45e1698a32f58dc98cafc GIT binary patch literal 523 zcmV+m0`&cfP)%D|6&E zzQc@&oE1{6ku-PM&?@{)xeCl}4BK+X%q8mQD5dcp6Lh=(m~ zubS%Fuun6V8UnpXhLQ$pQf$VN?wyj<3mwqH(^p>jM}g#uss6(DX@>as$!9kPbko`h z?Yaf;wo2VpYCp#~`h+WX#je;DyJA=T55$x!y7Bw)yO?5(NGw{=O+0L(o480{3Xjej zsErZ}rs|~LcTUep#!Wl+2+G_Ko|z|NEvxEVa`LwRrHgb@u5y*PPFo9zG;h9nvZ^#o zG(g>($?J_h66OjWDFr>$Ri?jJm^SKG!JWVlLN7%j_yg*=#yzu}^BwrGqlvs0I6VLW N002ovPDHLkV1k%v=!XCR literal 0 HcmV?d00001 diff --git a/assets/icons/search_icon_user.svg b/assets/icons/search_icon_user.svg new file mode 100644 index 00000000..61eca62d --- /dev/null +++ b/assets/icons/search_icon_user.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/uncompleate_process_icon.svg b/assets/icons/uncompleate_process_icon.svg new file mode 100644 index 00000000..4ede6757 --- /dev/null +++ b/assets/icons/uncompleate_process_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/wrong_process_icon.svg b/assets/icons/wrong_process_icon.svg new file mode 100644 index 00000000..de5b475c --- /dev/null +++ b/assets/icons/wrong_process_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index c837e40a..04c35295 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -42,7 +42,8 @@ class HomeBloc extends Bloc { Future _fetchUserInfo(FetchUserInfo event, Emitter emit) async { try { - var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); + var uuid = + await const FlutterSecureStorage().read(key: UserModel.userUuidKey); user = await HomeApi().fetchUserInfo(uuid); emit(HomeInitial()); } catch (e) { @@ -93,8 +94,10 @@ class HomeBloc extends Bloc { HomeItemModel( title: 'Move in', icon: Assets.moveinIcon, - active: false, - onPress: (context) {}, + active: true, + onPress: (context) { + context.go(RoutesConst.rolesAndPermissions); + }, color: ColorsManager.primaryColor, ), HomeItemModel( diff --git a/lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart b/lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart new file mode 100644 index 00000000..4f4988b3 --- /dev/null +++ b/lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart @@ -0,0 +1,42 @@ +import 'dart:async'; +import 'package:bloc/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/model/role_model.dart'; + +class RolesPermissionBloc + extends Bloc { + RolesPermissionBloc() : super(RolesInitial()) { + on(_getRoles); + on(changeTapSelected); + } + List roleModel = []; + + FutureOr _getRoles( + GetRoles event, Emitter emit) async { + emit(UsersLoadingState()); + try { + roleModel = [ + RoleModel(roleId: '1', roleImage: '', roleName: 'Admin'), + RoleModel(roleId: '2', roleImage: '', roleName: 'Security'), + RoleModel(roleId: '2', roleImage: '', roleName: 'Reception'), + ]; + emit(UsersLoadedState()); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + bool tapSelect = true; + + changeTapSelected( + ChangeTapSelected event, Emitter emit) { + try { + emit(RolesLoadingState()); + tapSelect = event.selected; + emit(ChangeTapStatus(select: !tapSelect)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } +} diff --git a/lib/pages/roles_and_permission/bloc/roles_permission_event.dart b/lib/pages/roles_and_permission/bloc/roles_permission_event.dart new file mode 100644 index 00000000..d5dce346 --- /dev/null +++ b/lib/pages/roles_and_permission/bloc/roles_permission_event.dart @@ -0,0 +1,41 @@ +import 'package:equatable/equatable.dart'; + +sealed class RolesPermissionEvent extends Equatable { + const RolesPermissionEvent(); +} + +class GetRoles extends RolesPermissionEvent { + const GetRoles(); + @override + List get props => []; +} + +class GetBatchStatus extends RolesPermissionEvent { + final List uuids; + const GetBatchStatus(this.uuids); + @override + List get props => [uuids]; +} + +class GetDeviceRecords extends RolesPermissionEvent { + final String uuid; + + const GetDeviceRecords(this.uuid); + @override + List get props => [uuid]; +} + +class GetDeviceAutomationRecords extends RolesPermissionEvent { + final String uuid; + const GetDeviceAutomationRecords(this.uuid); + @override + List get props => [uuid]; +} + +class ChangeTapSelected extends RolesPermissionEvent { + final bool selected; + const ChangeTapSelected(this.selected); + + @override + List get props => [selected]; +} diff --git a/lib/pages/roles_and_permission/bloc/roles_permission_state.dart b/lib/pages/roles_and_permission/bloc/roles_permission_state.dart new file mode 100644 index 00000000..55c2a8cb --- /dev/null +++ b/lib/pages/roles_and_permission/bloc/roles_permission_state.dart @@ -0,0 +1,77 @@ +import 'package:equatable/equatable.dart'; + +sealed class RolesPermissionState extends Equatable { + const RolesPermissionState(); +} + +final class RolesInitial extends RolesPermissionState { + @override + List get props => []; +} + +final class RolesLoadingState extends RolesPermissionState { + @override + List get props => []; +} +final class UsersLoadingState extends RolesPermissionState { + @override + List get props => []; +} + +final class RolesLoadedState extends RolesPermissionState { + @override + List get props => []; +} +final class UsersLoadedState extends RolesPermissionState { + @override + List get props => []; +} + +final class ErrorState extends RolesPermissionState { + final String message; + + const ErrorState(this.message); + + @override + List get props => [message]; +} + +/// report state +final class SosReportLoadingState extends RolesPermissionState { + @override + List get props => []; +} + +final class RolesErrorState extends RolesPermissionState { + final String message; + + const RolesErrorState(this.message); + + @override + List get props => [message]; +} + +/// automation reports + +final class SosAutomationReportLoadingState extends RolesPermissionState { + @override + List get props => []; +} + +final class SosAutomationReportErrorState extends RolesPermissionState { + final String message; + + const SosAutomationReportErrorState(this.message); + + @override + List get props => [message]; +} + +final class ChangeTapStatus extends RolesPermissionState { + bool select = true; + + ChangeTapStatus({required this.select}); + + @override + List get props => [select]; +} diff --git a/lib/pages/roles_and_permission/model/role_model.dart b/lib/pages/roles_and_permission/model/role_model.dart new file mode 100644 index 00000000..3d139904 --- /dev/null +++ b/lib/pages/roles_and_permission/model/role_model.dart @@ -0,0 +1,6 @@ +class RoleModel { + String? roleId; + String? roleName; + String? roleImage; + RoleModel({this.roleId, this.roleName, this.roleImage}); +} diff --git a/lib/pages/roles_and_permission/model/roles_user_model.dart b/lib/pages/roles_and_permission/model/roles_user_model.dart new file mode 100644 index 00000000..244c58de --- /dev/null +++ b/lib/pages/roles_and_permission/model/roles_user_model.dart @@ -0,0 +1,22 @@ +class RolesUserModel { + String? id; + String? userName; + String? userEmail; + String? userRole; + String? creationDate; + String? creationTime; + String? createdBy; + String? status; + String? action; + RolesUserModel( + {this.id, + this.userName, + this.userEmail, + this.userRole, + this.creationDate, + this.creationTime, + this.status, + this.action, + this.createdBy, + }); +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart new file mode 100644 index 00000000..5de74e4b --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -0,0 +1,136 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; + +class UsersBloc extends Bloc { + UsersBloc() : super(UsersInitial()) { + on(_getUsers); + on(_changeUserStatus); + on(isCompleteBasicsFun); + } + + List users = []; + + Future _getUsers(GetUsers event, Emitter emit) async { + emit(UsersLoadingState()); + try { + users = [ + RolesUserModel( + id: '1', + userName: 'user 1', + userEmail: 'test1@test.com', + action: '', + createdBy: 'Admin', + creationDate: '25/10/2024', + creationTime: '10:30 AM', + status: 'Invited', + ), + RolesUserModel( + id: '2', + userName: 'user 2', + userEmail: 'test2@test.com', + action: '', + createdBy: 'Admin', + creationDate: '25/10/2024', + creationTime: '10:30 AM', + status: 'Active', + ), + RolesUserModel( + id: '3', + userName: 'user 3', + userEmail: 'test3@test.com', + action: '', + createdBy: 'Admin', + creationDate: '25/10/2024', + creationTime: '10:30 AM', + status: 'Disabled', + ), + ]; + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + void _changeUserStatus(ChangeUserStatus event, Emitter emit) { + try { + // Update the user's status + 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(); + final TextEditingController firstNameController = TextEditingController(); + final TextEditingController lastNameController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + final TextEditingController phoneController = TextEditingController(); + final TextEditingController jobTitleController = TextEditingController(); + + bool isCompleteBasics = false; + bool isCompleteRolePermissions = false; + bool isCompleteSpaces = false; + + int numberBasics = 0; + int numberSpaces = 0; + int numberRole = 0; + + isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { + emit(UsersLoadingState()); + isCompleteBasics = firstNameController.text.isNotEmpty && + lastNameController.text.isNotEmpty && + emailController.text.isNotEmpty && + phoneController.text.isNotEmpty && + jobTitleController.text.isNotEmpty; + emit(ChangeStatusSteps()); + } + + // void checkStatus(CheckStepStatus event, Emitter emit) { + // try { + // // Check if basic fields are completed + // isCompleteBasics = firstNameController.text.isNotEmpty && + // lastNameController.text.isNotEmpty && + // emailController.text.isNotEmpty && + // phoneController.text.isNotEmpty && + // jobTitleController.text.isNotEmpty; + // // Emit the updated state + // if (isCompleteBasics && isCompleteRolePermissions && isCompleteSpaces) { + // } else { + // // emit(IncompleteState( + // // isCompleteBasics, isCompleteRolePermissions, isCompleteSpaces)); + // } + // } catch (e) { + // emit(ErrorState(e.toString())); + // } + // } + +// Example placeholder methods for role permissions and spaces + bool checkRolePermissions() { + // Add logic to check if role permissions are completed + return true; // Replace with actual logic + } + + bool checkSpaces() { + // Add logic to check if spaces are completed + return true; // Replace with actual logic + } +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart new file mode 100644 index 00000000..3b679a7e --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -0,0 +1,35 @@ +import 'package:equatable/equatable.dart'; + +sealed class UsersEvent extends Equatable { + const UsersEvent(); +} + +class GetUsers extends UsersEvent { + const GetUsers(); + @override + List get props => []; +} + +class GetBatchStatus extends UsersEvent { + final List uuids; + const GetBatchStatus(this.uuids); + @override + List get props => [uuids]; +} + +class ChangeUserStatus extends UsersEvent { + final String userId; + final String newStatus; + + const ChangeUserStatus({required this.userId, required this.newStatus}); + + @override + List get props => [userId, newStatus]; +} + +class CheckStepStatus extends UsersEvent { + final int? steps; + const CheckStepStatus({this.steps}); + @override + List get props => [steps]; +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart new file mode 100644 index 00000000..b9937f77 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; + +sealed class UsersState extends Equatable { + const UsersState(); +} + +final class UsersInitial extends UsersState { + @override + List get props => []; +} + +final class ChangeStatusSteps extends UsersState { + @override + List get props => []; +} + +final class UsersLoadingState extends UsersState { + @override + List get props => []; +} + +final class UsersLoadedState extends UsersState { + List users = []; + UsersLoadedState({required this.users}); + @override + List get props => [users]; +} + +final class ErrorState extends UsersState { + final String message; + + const ErrorState(this.message); + + @override + List get props => [message]; +} + +final class RolesErrorState extends UsersState { + final String message; + + const RolesErrorState(this.message); + + @override + List get props => [message]; +} + +/// automation reports + +final class ChangeTapStatus extends UsersState { + bool select = true; + + ChangeTapStatus({required this.select}); + + @override + List get props => [select]; +} diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart new file mode 100644 index 00000000..b2ee8cf5 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -0,0 +1,214 @@ +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_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/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class AddNewUserDialog extends StatefulWidget { + const AddNewUserDialog({super.key}); + + @override + _AddNewUserDialogState createState() => _AddNewUserDialogState(); +} + +class _AddNewUserDialogState extends State { + int currentStep = 1; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => UsersBloc(), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + final _blocRole = BlocProvider.of(context); + + return Dialog( + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + width: 900, + child: Column( + children: [ + // Title + const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Add New User", + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), + ), + ), + ), + const Divider(), + Expanded( + child: Row( + children: [ + // Sidebar for Steps + Expanded( + child: Container( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildStepIndicator(1, "Basics", _blocRole), + _buildStepIndicator(2, "Spaces", _blocRole), + _buildStepIndicator( + 3, "Role & Permissions", _blocRole), + ], + ), + ), + ), + Container( + width: 1, + color: ColorsManager.grayBorder, + ), + // Main content (Form) + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + Expanded( + child: _getFormContent(), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + ElevatedButton( + onPressed: () { + if (_blocRole.formKey.currentState + ?.validate() ?? + false) { + // Proceed to next step or finish + setState(() { + if (currentStep < 3) { + currentStep++; + } else { + Navigator.of(context).pop(); + } + }); + } + }, + child: Text(currentStep < 3 + ? "Next" + : "Finish"), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + )); + })); + } + + // Method to get the form content based on the current step + Widget _getFormContent() { + switch (currentStep) { + case 1: + return const BasicsView(); + case 2: + return const SpacesAccessView(); + case 3: + return const RolesAndPermission(); + default: + return Container(); + } + } + + // Helper method to build step indicators + Widget _buildStepIndicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + // if (bloc.formKey.currentState?.validate() ?? false) { + setState(() { + currentStep = step; + if (currentStep == 1) { + bloc.numberBasics = 1; + } else if (currentStep == 2) { + bloc.numberSpaces = 2; + } else if (currentStep == 3) { + bloc.numberRole = 3; + } + }); + // } + }, + 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 + && (bloc.numberBasics != 0 || + bloc.numberRole != 0 || + bloc.numberSpaces != 0) + ? 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, + ), + ), + ) + ], + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/view/basics_view.dart new file mode 100644 index 00000000..d64d837d --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/basics_view.dart @@ -0,0 +1,279 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +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/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class BasicsView extends StatelessWidget { + const BasicsView({super.key}); + @override + Widget build(BuildContext context) { + return BlocBuilder(builder: (context, state) { + final _blocRole = BlocProvider.of(context); + return Form( + key: _blocRole.formKey, + child: ListView( + shrinkWrap: true, + children: [ + Text( + 'Set up the basics', + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 20, + color: Colors.black), + ), + const SizedBox( + height: 80, + ), + Text( + 'To get started, fill out some basic information about who you’re adding as a user.', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ), + const SizedBox( + height: 25, + ), + Row( + children: [ + // First Name + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'First Name', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + style: TextStyle(color: Colors.black), + controller: _blocRole.firstNameController, + decoration: inputTextFormDeco( + hintText: "Enter first name", + ).copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter first name'; + } + return null; + }, + ), + ), + ], + ), + ), + + SizedBox(width: 10), + + // Last Name + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + const Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text('Last Name', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + )), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _blocRole.lastNameController, + style: TextStyle(color: Colors.black), + decoration: + inputTextFormDeco(hintText: "Enter last name") + .copyWith( + hintStyle: context + .textTheme.bodyMedium + ?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray)), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter last name'; + } + return null; + }, + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'Email Address', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _blocRole.emailController, + style: TextStyle(color: Colors.black), + decoration: inputTextFormDeco(hintText: "name@example.com") + .copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter last name'; + } + return null; + }, + ), + ), + ], + ), + SizedBox(height: 10), + Row( + children: [ + // Phone Number + + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'Mobile Number', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + style: const TextStyle(color: Colors.black), + controller: _blocRole.phoneController, + decoration: inputTextFormDeco( + hintText: "05x xxx xxxx", + ).copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a phone number'; + } + return null; + }, + keyboardType: TextInputType.phone, + ), + ), + ], + ), + ), + + SizedBox(width: 10), + + // Job Title + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'Job Title', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _blocRole.jobTitleController, + style: TextStyle(color: Colors.black), + decoration: inputTextFormDeco( + hintText: "Job Title (Optional)") + .copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + keyboardType: TextInputType.phone, + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: 20), + ], + ), + ); + }); + } +} diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart new file mode 100644 index 00000000..c9b58581 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart @@ -0,0 +1,498 @@ +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_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(builder: (context, state) { + final _blocRole = BlocProvider.of(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, + ), + const SizedBox(width: 300, height: 110, child: DropdownExample()), + 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, + 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: const DeviceManagement()))) + ], + ), + ), + ), + ], + ), + ), + ); + }); + } +} + +class DropdownExample extends StatefulWidget { + const DropdownExample({super.key}); + + @override + _DropdownExampleState createState() => _DropdownExampleState(); +} + +class _DropdownExampleState extends State { + String? selectedRole; + List roles = ['Admin', 'User', 'Guest', 'Moderator']; + + @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( + alignment: Alignment.center, + focusColor: ColorsManager.whiteColors, + autofocus: true, + value: selectedRole, + items: roles.map((role) { + return DropdownMenuItem( + value: role, + child: Text(role), + ); + }).toList(), + onChanged: (value) { + setState(() { + selectedRole = value; + }); + }, + 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: ColorsManager.graysColor, + borderRadius: const BorderRadius.only( + bottomRight: Radius.circular(10), + topRight: Radius.circular(10), + ), + border: Border.all( + color: ColorsManager.grayBorder, + width: 1.0, + ), + ), + child: const Center( + child: Icon(Icons.keyboard_arrow_down), + ), + ), + ), + ), + ), + ], + ), + ); + } +} + +class DeviceManagement extends StatefulWidget { + const DeviceManagement({Key? key}) : super(key: key); + + @override + _DeviceManagementState createState() => _DeviceManagementState(); +} + +class _DeviceManagementState extends State { + final List options = [ + MainRoleOption( + id: '1', + title: "Device Management", + subOptions: [ + SubRoleOption( + id: '11', + title: "Manage devices in private spaces", + children: [ + ChildRoleOption(id: '111', title: "Control"), + ChildRoleOption(id: '112', title: "Assign device"), + ChildRoleOption(id: '113', title: "View"), + ], + ), + SubRoleOption( + id: '12', + title: "Manage", + children: [ + ChildRoleOption(id: '121', title: "cc"), + ChildRoleOption(id: '122', title: "Assign"), + ChildRoleOption(id: '123', title: "s"), + ], + ), + ], + ), + MainRoleOption( + id: '2', + title: "Device Management", + subOptions: [ + SubRoleOption( + id: '22', + title: "Manage devices in private spaces", + children: [ + ChildRoleOption(id: '211', title: "Control"), + ChildRoleOption(id: '212', title: "Assign device"), + ChildRoleOption(id: '213', title: "View"), + ], + ), + ], + ), + ]; + + void toggleOptionById(String id) { + setState(() { + for (var mainOption in options) { + 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.children) { + child.isChecked = !isChecked; + } + } + return; + } + + for (var subOption in mainOption.subOptions) { + if (subOption.id == id) { + subOption.isChecked = !subOption.isChecked; + for (var child in subOption.children) { + child.isChecked = subOption.isChecked; + } + mainOption.isChecked = + mainOption.subOptions.every((sub) => sub.isChecked); + return; + } + + for (var child in subOption.children) { + if (child.id == id) { + child.isChecked = !child.isChecked; + subOption.isChecked = + subOption.children.every((child) => child.isChecked); + mainOption.isChecked = + mainOption.subOptions.every((sub) => sub.isChecked); + return; + } + } + } + } + }); + } + + CheckState checkifOneOfthemChecked(MainRoleOption 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.children) { + 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: options.length, + itemBuilder: (context, index) { + final option = options[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: [ + Row( + children: [ + InkWell( + onTap: () => toggleOptionById(subOption.id), + child: Builder( + builder: (context) { + final checkState = + checkifOneOfthemChecked(MainRoleOption( + 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, // 2 items per row + mainAxisSpacing: 2.0, // Space between rows + crossAxisSpacing: 0.2, // Space between columns + childAspectRatio: 5, // Adjust aspect ratio as needed + ), + itemCount: subOption.children.length, + itemBuilder: (context, index) { + final child = subOption.children[index]; + return CheckboxListTile( + 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), + ); + }, + ), + ) + ], + ); + }).toList(), + ], + ); + }, + ); + } +} + +class MainRoleOption { + String id; + String title; + bool isChecked; + List subOptions; + MainRoleOption({ + required this.id, + required this.title, + this.isChecked = false, + this.subOptions = const [], + }); +} + +class SubRoleOption { + String id; + String title; + bool isChecked; + List children; + + SubRoleOption({ + required this.id, + required this.title, + this.isChecked = false, + this.children = const [], + }); +} + +class ChildRoleOption { + String id; + String title; + bool isChecked; + + ChildRoleOption({ + required this.id, + required this.title, + this.isChecked = false, + }); +} + +enum CheckState { none, some, all } diff --git a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart new file mode 100644 index 00000000..9dc35a89 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart @@ -0,0 +1,338 @@ +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_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 SpacesAccessView extends StatelessWidget { + const SpacesAccessView({super.key}); + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + + return BlocBuilder(builder: (context, state) { + final _blocRole = BlocProvider.of(context); + return Container( + color: Colors.white, + child: Form( + key: _blocRole.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Spaces access', + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 20, + color: Colors.black), + ), + const SizedBox( + height: 35, + ), + const SizedBox( + child: Text( + 'Select the spaces you would like to grant access to for the user you are adding'), + ), + const SizedBox( + height: 25, + ), + 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, + 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: TreeView()))) + ], + ), + ), + ), + ], + ), + ), + ); + }); + } +} + +class TreeNode { + String title; + bool isChecked; + bool isHighlighted; + bool isExpanded; // New property to manage expansion + List children; + + TreeNode({ + required this.title, + this.isChecked = false, + this.isHighlighted = false, + this.isExpanded = false, // Default to collapsed + this.children = const [], + }); +} + +class TreeView extends StatefulWidget { + @override + _TreeViewState createState() => _TreeViewState(); +} + +class _TreeViewState extends State { + List treeData = [ + TreeNode( + title: 'Downtown Dubai', + ), + TreeNode(title: 'Dubai Creek Harbour', isHighlighted: true), + TreeNode( + title: 'Dubai Hills Estate', + isHighlighted: true, + children: [ + TreeNode(title: 'North Side'), + TreeNode( + title: 'South Side', + isHighlighted: true, + children: [ + TreeNode(title: 'Hills Business Park'), + TreeNode(title: 'Park Point'), + TreeNode(title: 'Acacia'), + TreeNode( + title: 'Executive Residence', + children: [ + TreeNode(title: 'Residence I'), + TreeNode( + title: 'Residence II', + children: [ + TreeNode(title: 'Ground Floor', isHighlighted: true), + TreeNode(title: '1st Floor', isHighlighted: true), + TreeNode(title: 'Pool', isHighlighted: true), + TreeNode(title: 'Gym', isHighlighted: true), + ], + ), + ], + ), + ], + ), + TreeNode( + title: 'South Side', + isHighlighted: true, + children: [ + TreeNode(title: 'Hills Business Park'), + TreeNode(title: 'Park Point'), + TreeNode(title: 'Acacia'), + TreeNode( + title: 'Executive Residence', + children: [ + TreeNode(title: 'Residence I'), + TreeNode( + title: 'Residence II', + children: [ + TreeNode(title: 'Ground Floor', isHighlighted: true), + TreeNode(title: '1st Floor', isHighlighted: true), + TreeNode(title: 'Pool', isHighlighted: true), + TreeNode(title: 'Gym', isHighlighted: true), + ], + ), + ], + ), + ], + ), + ], + ), + ]; + + Widget _buildTree(List nodes, {int level = 0}) { + return Column( + children: nodes.map((node) => _buildNode(node, level: level)).toList(), + ); + } + + Widget _buildNode(TreeNode node, {int level = 0}) { + return Container( + color: node.isHighlighted ? Colors.blue.shade100 : Colors.transparent, + child: Padding( + padding: EdgeInsets.only(left: level * 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + GestureDetector( + onTap: () { + setState(() { + node.isExpanded = !node.isExpanded; // Toggle expansion + }); + }, + child: Icon( + node.children.isNotEmpty + ? (node.isExpanded + ? Icons.arrow_drop_down + : Icons.arrow_right) + : Icons.arrow_right, + color: node.children.isNotEmpty + ? Colors.black + : Colors.transparent, + ), + ), + GestureDetector( + onTap: () { + setState(() { + node.isChecked = !node.isChecked; + _updateChildrenCheckStatus(node, node.isChecked); + _updateParentCheckStatus(node); + }); + }, + child: Image.asset( + _getCheckBoxImage(node), + width: 20, + height: 20, + ), + ), + Expanded( + child: Text( + node.title, + style: TextStyle( + fontSize: 16, + color: Colors.black87, + ), + ), + ), + ], + ), + if (node.isExpanded && node.children.isNotEmpty) + Padding( + padding: const EdgeInsets.only(left: 24.0), + child: _buildTree(node.children, level: level + 1), + ), + ], + ), + ), + ); + } + + // Determine the appropriate image based on the check state + String _getCheckBoxImage(TreeNode node) { + if (node.children.isEmpty) { + return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox; + } + if (_areAllChildrenChecked(node)) { + return Assets.CheckBoxChecked; + } else if (_areSomeChildrenChecked(node)) { + return Assets.rectangleCheckBox; + } else { + return Assets.emptyBox; + } + } + + // Helper to determine if all children are checked + bool _areAllChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.every((child) => + child.isChecked && + (child.children.isEmpty || _areAllChildrenChecked(child))); + } + + // Helper to determine if some children are checked + bool _areSomeChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.any((child) => + child.isChecked || + (child.children.isNotEmpty && _areSomeChildrenChecked(child))); + } + + // Update the checkbox state for all children + void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { + for (var child in node.children) { + child.isChecked = isChecked; + _updateChildrenCheckStatus(child, isChecked); + } + } + + // Update the checkbox state for parent nodes + void _updateParentCheckStatus(TreeNode node) { + TreeNode? parent = _findParent(treeData, node); + if (parent != null) { + setState(() { + parent.isChecked = _areAllChildrenChecked(parent); + _updateParentCheckStatus(parent); // Recursively update ancestors + }); + } + } + + // Helper to find a node's parent + TreeNode? _findParent(List nodes, TreeNode target) { + for (var node in nodes) { + if (node.children.contains(target)) { + return node; + } + var parent = _findParent(node.children, target); + if (parent != null) return parent; + } + return null; + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: _buildTree(treeData), + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/view/user_table.dart b/lib/pages/roles_and_permission/users_page/view/user_table.dart new file mode 100644 index 00000000..f951f06e --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/user_table.dart @@ -0,0 +1,256 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DynamicTableScreen extends StatefulWidget { + final List titles; + final List> rows; + + DynamicTableScreen({required this.titles, required this.rows}); + + @override + _DynamicTableScreenState createState() => _DynamicTableScreenState(); +} + +class _DynamicTableScreenState extends State + with WidgetsBindingObserver { + late List 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.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.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.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(), + // ], + // ), + // ), + // ), + // ); + // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/view/users_page.dart b/lib/pages/roles_and_permission/users_page/view/users_page.dart new file mode 100644 index 00000000..273b8a34 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/users_page.dart @@ -0,0 +1,241 @@ +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/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 UsersPage extends StatelessWidget { + const UsersPage({super.key}); + @override + Widget build(BuildContext context) { + final TextEditingController searchController = TextEditingController(); + + Widget actionButton({required String title, required Function()? onTap}) { + return InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.only(left: 8, right: 8), + child: Text( + title, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: title == "Delete" + ? ColorsManager.red + : ColorsManager.spaceColor, + fontWeight: FontWeight.w400, + ), + ), + ), + ); + } + + Widget status({required String status}) { + return Center( + child: Container( + padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(20)), + color: status == "Invited" + ? ColorsManager.invitedOrange.withOpacity(0.5) + : status == "Active" + ? ColorsManager.activeGreen.withOpacity(0.5) + : ColorsManager.disabledPink.withOpacity(0.5), + ), + child: Padding( + padding: + const EdgeInsets.only(left: 10, right: 10, bottom: 5, top: 5), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + status, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: status == "Invited" + ? ColorsManager.invitedOrangeText + : status == "Active" + ? ColorsManager.activeGreenText + : ColorsManager.disabledRedText, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ), + ); + } + + Widget changeIconStatus( + {required String userId, + required String status, + required Function()? onTap}) { + return Center( + child: InkWell( + onTap: () { + final newStatus = status == 'Active' + ? 'Disabled' + : status == 'Disabled' + ? 'Invited' + : 'Active'; + context + .read() + .add(ChangeUserStatus(userId: userId, newStatus: newStatus)); + }, + child: Padding( + padding: + const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), + child: SvgPicture.asset( + status == "Invited" + ? Assets.invitedIcon + : status == "Active" + ? Assets.activeUser + : Assets.deActiveUser, + height: 35, + ), + ), + ), + ); + } + +// return RolesAndPermission(); +// } +// } + return BlocBuilder( + builder: (context, state) { + final screenSize = MediaQuery.of(context).size; + + if (state is UsersLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is UsersLoadedState) { + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + decoration: containerDecoration.copyWith( + borderRadius: const BorderRadius.all( + Radius.circular(20), + ), + ), + width: screenSize.width * 0.4, + child: TextFormField( + controller: searchController, + style: const TextStyle(color: Colors.black), + decoration: textBoxDecoration(radios: 15)!.copyWith( + fillColor: ColorsManager.whiteColors, + errorStyle: const TextStyle(height: 0), + hintStyle: context.textTheme.titleSmall?.copyWith( + color: Colors.grey, + fontSize: 12, + ), + hintText: 'Search', + suffixIcon: SizedBox( + child: SvgPicture.asset( + Assets.searchIconUser, + fit: BoxFit.none, + ), + ), + ), + ), + ), + const SizedBox(width: 20), + InkWell( + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return const AddNewUserDialog(); + }, + ).then((listDevice) { + if (listDevice != null) {} + }); + }, + child: Container( + decoration: containerWhiteDecoration, + width: screenSize.width * 0.18, + height: 50, + child: const Center( + child: Text( + 'Add New User', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: ColorsManager.blueColor, + ), + ), + ), + ), + ), + ], + ), + const SizedBox(height: 25), + DynamicTableScreen( + titles: const [ + "Full Name", + "Email Address", + "Job Title", + "Role", + "Creation Date", + "Creation Time", + "Created By", + "Status", + "De/Activate", + "Action" + ], + rows: state.users.map((user) { + return [ + Text(user.userName!), + Text(user.userEmail!), + const Text("Test"), + const Text("Member"), + Text(user.creationDate!), + Text(user.creationTime!), + Text(user.createdBy!), + changeIconStatus( + status: user.status!, + userId: user.id!, + onTap: () {}, + ), + status(status: user.status!), + Row( + children: [ + actionButton( + title: "Activity Log", + onTap: () {}, + ), + actionButton( + title: "Edit", + onTap: () {}, + ), + actionButton( + title: "Delete", + onTap: () {}, + ), + ], + ), + ]; + }).toList(), + ), + ], + ), + ); + } else if (state is ErrorState) { + return Center(child: Text(state.message)); + } else { + return const Center(child: Text('No data available.')); + } + }, + ); + } +} diff --git a/lib/pages/roles_and_permission/view/create_role_card.dart b/lib/pages/roles_and_permission/view/create_role_card.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/pages/roles_and_permission/view/role_card.dart b/lib/pages/roles_and_permission/view/role_card.dart new file mode 100644 index 00000000..b3f59ee9 --- /dev/null +++ b/lib/pages/roles_and_permission/view/role_card.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class RoleCard extends StatelessWidget { + final String name; + const RoleCard({super.key, required this.name}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, // Card background color + borderRadius: BorderRadius.circular(20), // Rounded corners + boxShadow: [ + BoxShadow( + color: ColorsManager.blackColor.withOpacity(0.2), // Shadow color + blurRadius: 20, // Spread of the shadow + offset: const Offset(2, 2), // No directional bias + spreadRadius: 1, // Ensures the shadow is more noticeable + ), + ], + ), + child: Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const CircleAvatar( + backgroundColor: ColorsManager.neutralGray, + radius: 65, + child: CircleAvatar( + backgroundColor: ColorsManager.CircleRolesBackground, + radius: 62, + child: Icon( + Icons.admin_panel_settings, + size: 40, + color: Colors.blue, + ), + ), + ), + Text( + name, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart new file mode 100644 index 00000000..05f924ca --- /dev/null +++ b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart @@ -0,0 +1,96 @@ +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/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/web_layout/web_scaffold.dart'; + +class RolesAndPermissionPage extends StatelessWidget { + const RolesAndPermissionPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => + RolesPermissionBloc()..add(const GetRoles()), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + final _blocRole = BlocProvider.of(context); + + return state is RolesLoadingState + ? const Center(child: CircularProgressIndicator()) + : WebScaffold( + enableMenuSidebar: false, + appBarTitle: FittedBox( + child: Text( + 'Roles & Permissions', + style: Theme.of(context).textTheme.headlineLarge, + ), + ), + rightBody: const NavigateHomeGridView(), + centerBody: Row( + children: [ + // TextButton( + // style: TextButton.styleFrom( + // backgroundColor: null, + // ), + // onPressed: () { + // _blocRole.add(const ChangeTapSelected(true)); + // }, + // child: Text( + // 'Roles', + // style: context.textTheme.titleMedium?.copyWith( + // color: (_blocRole.tapSelect == true) + // ? ColorsManager.whiteColors + // : ColorsManager.grayColor, + // fontWeight: (_blocRole.tapSelect == true) + // ? FontWeight.w700 + // : FontWeight.w400, + // ), + // ), + // ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: null, + ), + onPressed: () { + // _blocRole.add(const ChangeTapSelected(false)); + }, + child: Text( + 'Users', + style: context.textTheme.titleMedium?.copyWith( + color: (_blocRole.tapSelect == true) + ? ColorsManager.whiteColors + : ColorsManager.grayColor, + fontWeight: (_blocRole.tapSelect == true) + ? FontWeight.w700 + : FontWeight.w400, + ), + ), + ), + ], + ), + scaffoldBody: BlocProvider( + create: (context) => UsersBloc()..add(const GetUsers()), + child: const UsersPage(), + ) + // _blocRole.tapSelect == false + // ? UsersPage( + // blocRole: _blocRole, + // ) + // : RolesPage( + // blocRole: _blocRole, + // ) + ); + }, + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/view/roles_page.dart b/lib/pages/roles_and_permission/view/roles_page.dart new file mode 100644 index 00000000..9c8ef0cd --- /dev/null +++ b/lib/pages/roles_and_permission/view/roles_page.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/view/role_card.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 RolesPage extends StatelessWidget { + final RolesPermissionBloc blocRole; + const RolesPage({super.key, required this.blocRole}); + + @override + Widget build(BuildContext context) { + final TextEditingController searchController = TextEditingController(); + double screenWidth = MediaQuery.of(context).size.width; + + int crossAxisCount = (screenWidth ~/ 200).clamp(1, 6); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: containerDecoration.copyWith( + borderRadius: const BorderRadius.all( + Radius.circular(20), + ), + ), + width: 250, + child: TextFormField( + controller: searchController, + style: const TextStyle(color: Colors.black), + decoration: textBoxDecoration(radios: 15)!.copyWith( + fillColor: ColorsManager.whiteColors, + errorStyle: const TextStyle(height: 0), + hintStyle: context.textTheme.titleSmall?.copyWith( + color: Colors.grey, + fontSize: 12, + ), + hintText: 'Search', + suffixIcon: SvgPicture.asset(Assets.searchIconUser)), + ), + ), + Expanded( + child: GridView.builder( + padding: const EdgeInsets.all(10), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + childAspectRatio: 2 / 2.5, + ), + itemCount: blocRole.roleModel.length ?? 0, + itemBuilder: (context, index) { + final role = blocRole.roleModel[index]; + if (role == null) { + return const SizedBox.shrink(); + } + return RoleCard( + name: role.roleName ?? 'Unknown', + ); + }, + ), + ), + ], + ); + } +} diff --git a/lib/utils/app_routes.dart b/lib/utils/app_routes.dart index 20a89e21..246e269e 100644 --- a/lib/utils/app_routes.dart +++ b/lib/utils/app_routes.dart @@ -3,6 +3,7 @@ import 'package:syncrow_web/pages/access_management/view/access_management.dart' import 'package:syncrow_web/pages/auth/view/login_page.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart'; +import 'package:syncrow_web/pages/roles_and_permission/view/roles_and_permission_page.dart'; import 'package:syncrow_web/pages/spaces_management/view/spaces_management_page.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart'; @@ -32,7 +33,10 @@ class AppRoutes { ), GoRoute( path: RoutesConst.spacesManagementPage, - builder: (context, state) => SpaceManagementPage()), + builder: (context, state) => const SpaceManagementPage()), + GoRoute( + path: RoutesConst.rolesAndPermissions, + builder: (context, state) => const RolesAndPermissionPage()), ]; } } diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 95d0f214..5549b566 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -5,7 +5,8 @@ abstract class ColorsManager { static const Color switchOffColor = Color(0x7F8D99AE); static const Color primaryColor = Color(0xFF0030CB); //023DFE static const Color secondaryTextColor = Color(0xFF848484); - static Color primaryColorWithOpacity = const Color(0xFF023DFE).withOpacity(0.6); + static Color primaryColorWithOpacity = + const Color(0xFF023DFE).withOpacity(0.6); static const Color whiteColors = Colors.white; static const Color secondaryColor = Color(0xFF023DFE); static const Color onSecondaryColor = Color(0xFF023DFE); @@ -54,5 +55,13 @@ abstract class ColorsManager { static const Color warningRed = Color(0xFFFF6465); static const Color borderColor = Color(0xFFE5E5E5); static const Color CircleImageBackground = Color(0xFFF4F4F4); + static const Color CircleRolesBackground = Color(0xFFF8F8F8); + static const Color activeGreen = Color(0xFF99FF93); + static const Color activeGreenText = Color(0xFF008905); + static const Color disabledPink = Color(0xFFFF9395); + static const Color disabledRedText = Color(0xFF890002); + static const Color invitedOrange = Color(0xFFFFE193); + static const Color invitedOrangeText = Color(0xFFFFBF00); + //background: #F8F8F8; + } -//background: #background: #5D5D5D; diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 958c2c1c..4e35e84f 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -13,10 +13,12 @@ class Assets { static const String rightLine = "assets/images/right_line.png"; static const String google = "assets/images/google.svg"; static const String facebook = "assets/images/facebook.svg"; - static const String invisiblePassword = "assets/images/Password_invisible.svg"; + static const String invisiblePassword = + "assets/images/Password_invisible.svg"; static const String visiblePassword = "assets/images/password_visible.svg"; static const String accessIcon = "assets/images/access_icon.svg"; - static const String spaseManagementIcon = "assets/images/spase_management_icon.svg"; + static const String spaseManagementIcon = + "assets/images/spase_management_icon.svg"; static const String devicesIcon = "assets/images/devices_icon.svg"; static const String moveinIcon = "assets/images/movein_icon.svg"; static const String constructionIcon = "assets/images/construction_icon.svg"; @@ -29,13 +31,15 @@ class Assets { static const String emptyTable = "assets/images/empty_table.svg"; // General assets - static const String motionlessDetection = "assets/icons/motionless_detection.svg"; + static const String motionlessDetection = + "assets/icons/motionless_detection.svg"; static const String acHeating = "assets/icons/ac_heating.svg"; static const String acPowerOff = "assets/icons/ac_power_off.svg"; static const String acFanMiddle = "assets/icons/ac_fan_middle.svg"; static const String switchAlarmSound = "assets/icons/switch_alarm_sound.svg"; static const String resetOff = "assets/icons/reset_off.svg"; - static const String sensitivityOperationIcon = "assets/icons/sesitivity_operation_icon.svg"; + static const String sensitivityOperationIcon = + "assets/icons/sesitivity_operation_icon.svg"; static const String motionDetection = "assets/icons/motion_detection.svg"; static const String freezing = "assets/icons/freezing.svg"; static const String indicator = "assets/icons/indicator.svg"; @@ -56,7 +60,8 @@ class Assets { static const String celsiusDegrees = "assets/icons/celsius_degrees.svg"; static const String masterState = "assets/icons/master_state.svg"; static const String acPower = "assets/icons/ac_power.svg"; - static const String farDetectionFunction = "assets/icons/far_detection_function.svg"; + static const String farDetectionFunction = + "assets/icons/far_detection_function.svg"; static const String nobodyTime = "assets/icons/nobody_time.svg"; // Automation functions @@ -64,33 +69,47 @@ class Assets { "assets/icons/automation_functions/temp_password_unlock.svg"; static const String doorlockNormalOpen = "assets/icons/automation_functions/doorlock_normal_open.svg"; - static const String doorbell = "assets/icons/automation_functions/doorbell.svg"; + static const String doorbell = + "assets/icons/automation_functions/doorbell.svg"; static const String remoteUnlockViaApp = "assets/icons/automation_functions/remote_unlock_via_app.svg"; - static const String doubleLock = "assets/icons/automation_functions/double_lock.svg"; - static const String selfTestResult = "assets/icons/automation_functions/self_test_result.svg"; - static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg"; - static const String presenceState = "assets/icons/automation_functions/presence_state.svg"; - static const String currentTemp = "assets/icons/automation_functions/current_temp.svg"; - static const String presence = "assets/icons/automation_functions/presence.svg"; + static const String doubleLock = + "assets/icons/automation_functions/double_lock.svg"; + static const String selfTestResult = + "assets/icons/automation_functions/self_test_result.svg"; + static const String lockAlarm = + "assets/icons/automation_functions/lock_alarm.svg"; + static const String presenceState = + "assets/icons/automation_functions/presence_state.svg"; + static const String currentTemp = + "assets/icons/automation_functions/current_temp.svg"; + static const String presence = + "assets/icons/automation_functions/presence.svg"; static const String residualElectricity = "assets/icons/automation_functions/residual_electricity.svg"; - static const String hijackAlarm = "assets/icons/automation_functions/hijack_alarm.svg"; - static const String passwordUnlock = "assets/icons/automation_functions/password_unlock.svg"; + static const String hijackAlarm = + "assets/icons/automation_functions/hijack_alarm.svg"; + static const String passwordUnlock = + "assets/icons/automation_functions/password_unlock.svg"; static const String remoteUnlockRequest = "assets/icons/automation_functions/remote_unlock_req.svg"; - static const String cardUnlock = "assets/icons/automation_functions/card_unlock.svg"; + static const String cardUnlock = + "assets/icons/automation_functions/card_unlock.svg"; static const String motion = "assets/icons/automation_functions/motion.svg"; static const String fingerprintUnlock = "assets/icons/automation_functions/fingerprint_unlock.svg"; // Presence Sensor Assets static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg"; - static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg"; + static const String sensorPresenceIcon = + "assets/icons/sensor_presence_ic.svg"; static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg"; - static const String illuminanceRecordIcon = "assets/icons/illuminance_record_ic.svg"; - static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg"; - static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg"; + static const String illuminanceRecordIcon = + "assets/icons/illuminance_record_ic.svg"; + static const String presenceRecordIcon = + "assets/icons/presence_record_ic.svg"; + static const String helpDescriptionIcon = + "assets/icons/help_description_ic.svg"; static const String lightPulp = "assets/icons/light_pulb.svg"; static const String acDevice = "assets/icons/ac_device.svg"; @@ -140,10 +159,12 @@ class Assets { static const String unit = 'assets/icons/unit_icon.svg'; static const String villa = 'assets/icons/villa_icon.svg'; static const String iconEdit = 'assets/icons/icon_edit_icon.svg'; - static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg'; + static const String textFieldSearch = + 'assets/icons/textfield_search_icon.svg'; static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg'; static const String addIcon = 'assets/icons/add_icon.svg'; - static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg'; + static const String smartThermostatIcon = + 'assets/icons/smart_thermostat_icon.svg'; static const String smartLightIcon = 'assets/icons/smart_light_icon.svg'; static const String presenceSensor = 'assets/icons/presence_sensor.svg'; static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg'; @@ -191,7 +212,8 @@ class Assets { //assets/icons/water_leak_normal.svg static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg'; //assets/icons/water_leak_detected.svg - static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg'; + static const String waterLeakDetected = + 'assets/icons/water_leak_detected.svg'; //assets/icons/automation_records.svg static const String automationRecords = 'assets/icons/automation_records.svg'; @@ -256,40 +278,64 @@ class Assets { static const String delay = 'assets/icons/routine/delay.svg'; // Assets for functions_icons - static const String assetsSensitivityFunction = "assets/icons/functions_icons/sensitivity.svg"; + static const String assetsSensitivityFunction = + "assets/icons/functions_icons/sensitivity.svg"; static const String assetsSensitivityOperationIcon = "assets/icons/functions_icons/sesitivity_operation_icon.svg"; - static const String assetsAcPower = "assets/icons/functions_icons/ac_power.svg"; - static const String assetsAcPowerOFF = "assets/icons/functions_icons/ac_power_off.svg"; - static const String assetsChildLock = "assets/icons/functions_icons/child_lock.svg"; - static const String assetsFreezing = "assets/icons/functions_icons/freezing.svg"; - static const String assetsFanSpeed = "assets/icons/functions_icons/fan_speed.svg"; - static const String assetsAcCooling = "assets/icons/functions_icons/ac_cooling.svg"; - static const String assetsAcHeating = "assets/icons/functions_icons/ac_heating.svg"; - static const String assetsCelsiusDegrees = "assets/icons/functions_icons/celsius_degrees.svg"; - static const String assetsTempreture = "assets/icons/functions_icons/tempreture.svg"; - static const String assetsAcFanLow = "assets/icons/functions_icons/ac_fan_low.svg"; - static const String assetsAcFanMiddle = "assets/icons/functions_icons/ac_fan_middle.svg"; - static const String assetsAcFanHigh = "assets/icons/functions_icons/ac_fan_high.svg"; - static const String assetsAcFanAuto = "assets/icons/functions_icons/ac_fan_auto.svg"; - static const String assetsSceneChildLock = "assets/icons/functions_icons/scene_child_lock.svg"; + static const String assetsAcPower = + "assets/icons/functions_icons/ac_power.svg"; + static const String assetsAcPowerOFF = + "assets/icons/functions_icons/ac_power_off.svg"; + static const String assetsChildLock = + "assets/icons/functions_icons/child_lock.svg"; + static const String assetsFreezing = + "assets/icons/functions_icons/freezing.svg"; + static const String assetsFanSpeed = + "assets/icons/functions_icons/fan_speed.svg"; + static const String assetsAcCooling = + "assets/icons/functions_icons/ac_cooling.svg"; + static const String assetsAcHeating = + "assets/icons/functions_icons/ac_heating.svg"; + static const String assetsCelsiusDegrees = + "assets/icons/functions_icons/celsius_degrees.svg"; + static const String assetsTempreture = + "assets/icons/functions_icons/tempreture.svg"; + static const String assetsAcFanLow = + "assets/icons/functions_icons/ac_fan_low.svg"; + static const String assetsAcFanMiddle = + "assets/icons/functions_icons/ac_fan_middle.svg"; + static const String assetsAcFanHigh = + "assets/icons/functions_icons/ac_fan_high.svg"; + static const String assetsAcFanAuto = + "assets/icons/functions_icons/ac_fan_auto.svg"; + static const String assetsSceneChildLock = + "assets/icons/functions_icons/scene_child_lock.svg"; static const String assetsSceneChildUnlock = "assets/icons/functions_icons/scene_child_unlock.svg"; - static const String assetsSceneRefresh = "assets/icons/functions_icons/scene_refresh.svg"; - static const String assetsLightCountdown = "assets/icons/functions_icons/light_countdown.svg"; - static const String assetsFarDetection = "assets/icons/functions_icons/far_detection.svg"; + static const String assetsSceneRefresh = + "assets/icons/functions_icons/scene_refresh.svg"; + static const String assetsLightCountdown = + "assets/icons/functions_icons/light_countdown.svg"; + static const String assetsFarDetection = + "assets/icons/functions_icons/far_detection.svg"; static const String assetsFarDetectionFunction = "assets/icons/functions_icons/far_detection_function.svg"; - static const String assetsIndicator = "assets/icons/functions_icons/indicator.svg"; - static const String assetsMotionDetection = "assets/icons/functions_icons/motion_detection.svg"; + static const String assetsIndicator = + "assets/icons/functions_icons/indicator.svg"; + static const String assetsMotionDetection = + "assets/icons/functions_icons/motion_detection.svg"; static const String assetsMotionlessDetection = "assets/icons/functions_icons/motionless_detection.svg"; - static const String assetsNobodyTime = "assets/icons/functions_icons/nobody_time.svg"; - static const String assetsFactoryReset = "assets/icons/functions_icons/factory_reset.svg"; - static const String assetsMasterState = "assets/icons/functions_icons/master_state.svg"; + static const String assetsNobodyTime = + "assets/icons/functions_icons/nobody_time.svg"; + static const String assetsFactoryReset = + "assets/icons/functions_icons/factory_reset.svg"; + static const String assetsMasterState = + "assets/icons/functions_icons/master_state.svg"; static const String assetsSwitchAlarmSound = "assets/icons/functions_icons/switch_alarm_sound.svg"; - static const String assetsResetOff = "assets/icons/functions_icons/reset_off.svg"; + static const String assetsResetOff = + "assets/icons/functions_icons/reset_off.svg"; // Assets for automation_functions static const String assetsCardUnlock = @@ -320,11 +366,29 @@ class Assets { "assets/icons/functions_icons/automation_functions/self_test_result.svg"; static const String assetsPresence = "assets/icons/functions_icons/automation_functions/presence.svg"; - static const String assetsMotion = "assets/icons/functions_icons/automation_functions/motion.svg"; + static const String assetsMotion = + "assets/icons/functions_icons/automation_functions/motion.svg"; static const String assetsCurrentTemp = "assets/icons/functions_icons/automation_functions/current_temp.svg"; static const String assetsPresenceState = "assets/icons/functions_icons/automation_functions/presence_state.svg"; //assets/icons/routine/automation.svg static const String automation = 'assets/icons/routine/automation.svg'; + static const String searchIconUser = 'assets/icons/search_icon_user.svg'; + static const String searchIcoUser = 'assets/icons/search_icon_user.svg'; + static const String activeUser = 'assets/icons/active_user.svg'; + static const String deActiveUser = 'assets/icons/deactive_user.svg'; + static const String invitedIcon = 'assets/icons/invited_icon.svg'; + static const String rectangleCheckBox = + 'assets/icons/rectangle_check_box.png'; + static const String CheckBoxChecked = 'assets/icons/box_checked.png'; + static const String emptyBox = 'assets/icons/empty_box.png'; + static const String completeProcessIcon = + 'assets/icons/compleate_process_icon.svg'; + static const String currentProcessIcon = + 'assets/icons/current_process_icon.svg'; + static const String uncomplete_ProcessIcon = + 'assets/icons/uncompleate_process_icon.svg'; + static const String wrongProcessIcon = + 'assets/icons/wrong_process_icon.svg'; } diff --git a/lib/utils/constants/routes_const.dart b/lib/utils/constants/routes_const.dart index 094787d4..8a65e9ae 100644 --- a/lib/utils/constants/routes_const.dart +++ b/lib/utils/constants/routes_const.dart @@ -5,4 +5,5 @@ class RoutesConst { static const String accessManagementPage = '/access-management-page'; static const String deviceManagementPage = '/device-management-page'; static const String spacesManagementPage = '/spaces_management-page'; + static const String rolesAndPermissions = '/roles_and_Permissions-page'; } diff --git a/lib/utils/style.dart b/lib/utils/style.dart index a80c68d6..b5ea59ee 100644 --- a/lib/utils/style.dart +++ b/lib/utils/style.dart @@ -2,51 +2,59 @@ import 'package:flutter/material.dart'; import 'color_manager.dart'; -InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration( +InputDecoration? textBoxDecoration( + {bool suffixIcon = false, double radios = 8}) => + InputDecoration( focusColor: ColorsManager.grayColor, suffixIcon: suffixIcon ? const Icon(Icons.search) : null, hintText: 'Search', filled: true, // Enable background filling fillColor: const Color(0xffF5F6F7), // Set the background color border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Add border radius + borderRadius: BorderRadius.circular(radios), // Add border radius borderSide: BorderSide.none, // Remove the underline ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Add border radius + borderRadius: BorderRadius.circular(radios), // Add border radius borderSide: BorderSide.none, // Remove the underline ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Add border radius + borderRadius: BorderRadius.circular(radios), // Add border radius borderSide: BorderSide.none, // Remove the underline ), errorBorder: OutlineInputBorder( borderSide: const BorderSide(color: Colors.red, width: 2), - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(radios), ), focusedErrorBorder: OutlineInputBorder( borderSide: const BorderSide(color: Colors.red, width: 2), - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(radios), ), ); -BoxDecoration containerDecoration = BoxDecoration(boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.3), - spreadRadius: 2, - blurRadius: 4, - offset: const Offset(0, 5), // changes position of shadow - ), -], color: ColorsManager.boxColor, borderRadius: const BorderRadius.all(Radius.circular(10))); +BoxDecoration containerDecoration = BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + spreadRadius: 2, + blurRadius: 4, + offset: const Offset(0, 5), // changes position of shadow + ), + ], + color: ColorsManager.boxColor, + borderRadius: const BorderRadius.all(Radius.circular(10))); -BoxDecoration containerWhiteDecoration = BoxDecoration(boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.3), - spreadRadius: 2, - blurRadius: 4, - offset: const Offset(0, 5), // changes position of shadow - ), -], color: ColorsManager.whiteColors, borderRadius: const BorderRadius.all(Radius.circular(15))); +BoxDecoration containerWhiteDecoration = BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + spreadRadius: 2, + blurRadius: 4, + offset: const Offset(0, 5), // changes position of shadow + ), + ], + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.all(Radius.circular(15))); BoxDecoration subSectionContainerDecoration = BoxDecoration( color: ColorsManager.whiteColors, @@ -59,3 +67,30 @@ BoxDecoration subSectionContainerDecoration = BoxDecoration( ), ], ); + +InputDecoration inputTextFormDeco({hintText}) => InputDecoration( + hintText: hintText, + border: const OutlineInputBorder( + + borderSide: BorderSide( + width: 1, + color: ColorsManager.textGray, // Border color for unfocused state + ), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide( + width: 2, + color: ColorsManager.textGray, // Border color when focused + ), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide( + width: 1, + color: ColorsManager + .textGray // Border color for enabled (but unfocused) state + ), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + ); diff --git a/pubspec.lock b/pubspec.lock index 192106d7..ea2315d7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -316,18 +316,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: @@ -364,18 +364,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.12.0" nested: dependency: transitive description: @@ -593,10 +593,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.0" time_picker_spinner: dependency: "direct main" description: @@ -657,10 +657,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.1" web: dependency: transitive description: From 879bf99b12243232606a9c9869222793cfbfc5cf Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 16 Dec 2024 16:37:55 +0300 Subject: [PATCH 002/106] add_user_dialog --- assets/icons/arrow_down.svg | 3 + assets/icons/arrow_forward.svg | 3 + .../users_page/bloc/users_bloc.dart | 160 +++++++++++++--- .../users_page/bloc/users_event.dart | 16 ++ .../users_page/model/tree_node_model.dart | 17 ++ .../users_page/view/add_user_dialog.dart | 155 ++++++++++++--- .../users_page/view/roles_and_permission.dart | 2 + .../users_page/view/spaces_access_view.dart | 178 ++++++------------ lib/services/space_mana_api.dart | 1 + lib/utils/constants/assets.dart | 4 + 10 files changed, 369 insertions(+), 170 deletions(-) create mode 100644 assets/icons/arrow_down.svg create mode 100644 assets/icons/arrow_forward.svg create mode 100644 lib/pages/roles_and_permission/users_page/model/tree_node_model.dart diff --git a/assets/icons/arrow_down.svg b/assets/icons/arrow_down.svg new file mode 100644 index 00000000..2b4be77b --- /dev/null +++ b/assets/icons/arrow_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/arrow_forward.svg b/assets/icons/arrow_forward.svg new file mode 100644 index 00000000..e5866360 --- /dev/null +++ b/assets/icons/arrow_forward.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index 5de74e4b..0d153804 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -3,12 +3,18 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/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/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'; class UsersBloc extends Bloc { UsersBloc() : super(UsersInitial()) { on(_getUsers); on(_changeUserStatus); on(isCompleteBasicsFun); + on(_onLoadCommunityAndSpaces); + on(searchTreeNode); } List users = []; @@ -86,9 +92,9 @@ class UsersBloc extends Bloc { final TextEditingController phoneController = TextEditingController(); final TextEditingController jobTitleController = TextEditingController(); - bool isCompleteBasics = false; - bool isCompleteRolePermissions = false; - bool isCompleteSpaces = false; + bool? isCompleteBasics; + bool? isCompleteRolePermissions; + bool? isCompleteSpaces; int numberBasics = 0; int numberSpaces = 0; @@ -102,35 +108,137 @@ class UsersBloc extends Bloc { phoneController.text.isNotEmpty && jobTitleController.text.isNotEmpty; emit(ChangeStatusSteps()); + return isCompleteBasics; } - // void checkStatus(CheckStepStatus event, Emitter emit) { - // try { - // // Check if basic fields are completed - // isCompleteBasics = firstNameController.text.isNotEmpty && - // lastNameController.text.isNotEmpty && - // emailController.text.isNotEmpty && - // phoneController.text.isNotEmpty && - // jobTitleController.text.isNotEmpty; - // // Emit the updated state - // if (isCompleteBasics && isCompleteRolePermissions && isCompleteSpaces) { - // } else { - // // emit(IncompleteState( - // // isCompleteBasics, isCompleteRolePermissions, isCompleteSpaces)); - // } - // } catch (e) { - // emit(ErrorState(e.toString())); - // } - // } + isCompleteSpacesFun(CheckStepStatus event, Emitter emit) { + emit(UsersLoadingState()); + isCompleteSpaces = false; + emit(ChangeStatusSteps()); + print('isCompleteBasics==$isCompleteSpaces'); + return isCompleteSpaces; + } -// Example placeholder methods for role permissions and spaces bool checkRolePermissions() { - // Add logic to check if role permissions are completed - return true; // Replace with actual logic + return true; } bool checkSpaces() { - // Add logic to check if spaces are completed - return true; // Replace with actual logic + return true; + } + + Future> _fetchSpacesForCommunity( + String communityUuid) async { + return await CommunitySpaceManagementApi().getSpaceHierarchy(communityUuid); + } + + List updatedCommunities = []; + List spacesNodes = []; + _onLoadCommunityAndSpaces( + LoadCommunityAndSpacesEvent event, + Emitter emit, + ) async { + emit(UsersLoadingState()); // Emit loading state + try { + // Fetch the list of communities + List communities = + await CommunitySpaceManagementApi().fetchCommunities(); + + // Fetch spaces and create TreeNodes for each community + updatedCommunities = await Future.wait( + communities.map((community) async { + // Fetch spaces for the current community + List spaces = + await _fetchSpacesForCommunity(community.uuid); + + // Recursively build the tree structure + spacesNodes = _buildTreeNodes(spaces); + + // Return a TreeNode for the community, with spaces as its children + return TreeNode( + uuid: community.uuid, + title: community.name, + children: spacesNodes, + isChecked: false, // Initial state; can be updated later + isHighlighted: false, + isExpanded: true, // Default to expanded for better UX + ); + }).toList(), + ); + + // Emit the final state with the structured tree + emit(ChangeStatusSteps()); + + return updatedCommunities; // Return the structured data if needed + } catch (e) { + // Emit error state in case of failure + emit(ErrorState('Error loading communities and spaces: $e')); + } + } + +// Helper function to recursively build tree nodes + List _buildTreeNodes(List spaces) { + return spaces.map((space) { + // If the space has children, recursively build nodes for them + List childNodes = + space.children != null ? _buildTreeNodes(space.children!) : []; + + // Create a TreeNode for the current space + return TreeNode( + uuid: space.uuid!, + title: space.name, + isChecked: false, + isHighlighted: false, + isExpanded: childNodes.isNotEmpty, // Expand if there are children + children: childNodes, + ); + }).toList(); + } + + void searchTreeNode(SearchAnode event, Emitter emit) { + emit(UsersLoadingState()); // Emit loading state + + // Clear all highlights if the search term is empty + if (event.searchTerm!.isEmpty) { + _clearHighlights(updatedCommunities); + } else { + // Perform the search and update the highlights + _searchAndHighlightNodes(updatedCommunities, event.searchTerm!); + } + + // Emit the updated state after processing all nodes + emit(ChangeStatusSteps()); + } + +// Helper function to clear all highlights in the tree + void _clearHighlights(List nodes) { + for (var node in nodes) { + node.isHighlighted = false; + if (node.children.isNotEmpty) { + _clearHighlights(node.children); + } + } + } + +// Helper function to search and highlight nodes recursively + bool _searchAndHighlightNodes(List nodes, String searchTerm) { + bool anyMatch = false; + + for (var node in nodes) { + // Check if this node matches the search term + bool isMatch = + node.title.toLowerCase().contains(searchTerm.toLowerCase()); + + // Recursively check children for matches + bool childMatch = _searchAndHighlightNodes(node.children, searchTerm); + + // Highlight this node if it matches or any of its children match + node.isHighlighted = isMatch || childMatch; + + // Update if any matches were found in this branch + anyMatch = anyMatch || node.isHighlighted; + } + + return anyMatch; } } diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart index 3b679a7e..77890673 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart'; sealed class UsersEvent extends Equatable { const UsersEvent(); @@ -10,6 +11,12 @@ class GetUsers extends UsersEvent { List get props => []; } +class LoadCommunityAndSpacesEvent extends UsersEvent { + const LoadCommunityAndSpacesEvent(); + @override + List get props => []; +} + class GetBatchStatus extends UsersEvent { final List uuids; const GetBatchStatus(this.uuids); @@ -17,6 +24,7 @@ class GetBatchStatus extends UsersEvent { List get props => [uuids]; } +//LoadCommunityAndSpacesEvent class ChangeUserStatus extends UsersEvent { final String userId; final String newStatus; @@ -33,3 +41,11 @@ class CheckStepStatus extends UsersEvent { @override List get props => [steps]; } + +class SearchAnode extends UsersEvent { + List? nodes; + String? searchTerm; + SearchAnode({this.nodes, this.searchTerm}); + @override + List get props => [nodes, searchTerm]; +} diff --git a/lib/pages/roles_and_permission/users_page/model/tree_node_model.dart b/lib/pages/roles_and_permission/users_page/model/tree_node_model.dart new file mode 100644 index 00000000..a5e622dc --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/model/tree_node_model.dart @@ -0,0 +1,17 @@ +class TreeNode { + String uuid; + String title; + bool isChecked; + bool isHighlighted; + bool isExpanded; + List children; + + TreeNode({ + required this.uuid, + required this.title, + this.isChecked = false, + this.isHighlighted = false, + this.isExpanded = false, + this.children = const [], + }); +} \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart index b2ee8cf5..21e92727 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -2,6 +2,7 @@ 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'; @@ -22,7 +23,7 @@ class _AddNewUserDialogState extends State { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => UsersBloc(), + create: (BuildContext context) => UsersBloc()..add(LoadCommunityAndSpacesEvent()), child: BlocConsumer( listener: (context, state) {}, builder: (context, state) { @@ -58,9 +59,9 @@ class _AddNewUserDialogState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildStepIndicator(1, "Basics", _blocRole), - _buildStepIndicator(2, "Spaces", _blocRole), - _buildStepIndicator( + _buildStep1Indicator(1, "Basics", _blocRole), + _buildStep2Indicator(2, "Spaces", _blocRole), + _buildStep3Indicator( 3, "Role & Permissions", _blocRole), ], ), @@ -120,14 +121,13 @@ class _AddNewUserDialogState extends State { ), ], ), - ), - ], - ), - )); - })); + ), + ], + ), + )); + })); } - // Method to get the form content based on the current step Widget _getFormContent() { switch (currentStep) { case 1: @@ -141,22 +141,70 @@ class _AddNewUserDialogState extends State { } } - // Helper method to build step indicators - Widget _buildStepIndicator(int step, String label, UsersBloc bloc) { + Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + currentStep = step; + 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.isCompleteSpaces == 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, + ), + ), + ) + ], + ), + ); + } + + Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) { return GestureDetector( onTap: () { - // if (bloc.formKey.currentState?.validate() ?? false) { setState(() { currentStep = step; - if (currentStep == 1) { - bloc.numberBasics = 1; - } else if (currentStep == 2) { - bloc.numberSpaces = 2; - } else if (currentStep == 3) { - bloc.numberRole = 3; - } }); - // } }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -170,12 +218,67 @@ class _AddNewUserDialogState extends State { currentStep == step ? Assets.currentProcessIcon : bloc.isCompleteBasics == false - && (bloc.numberBasics != 0 || - bloc.numberRole != 0 || - bloc.numberSpaces != 0) - ? Assets.wrongProcessIcon + ? 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 _buildStep3Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + currentStep = step; + }); + }, + 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, ), diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart index c9b58581..812a3170 100644 --- a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart @@ -199,6 +199,8 @@ class DeviceManagement extends StatefulWidget { } class _DeviceManagementState extends State { + + final List options = [ MainRoleOption( id: '1', diff --git a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart index 9dc35a89..5dff9b0a 100644 --- a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart @@ -2,7 +2,9 @@ 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/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -68,7 +70,12 @@ class SpacesAccessView extends StatelessWidget { child: TextFormField( style: const TextStyle(color: Colors.black), - controller: _blocRole.firstNameController, + // controller: _blocRole.firstNameController, + onChanged: (value) { + _blocRole.add(SearchAnode( + nodes: _blocRole.updatedCommunities, + searchTerm: value)); + }, decoration: textBoxDecoration(radios: 20)! .copyWith( fillColor: Colors.white, @@ -102,7 +109,9 @@ class SpacesAccessView extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: Container( color: ColorsManager.whiteColors, - child: TreeView()))) + child: TreeView( + bloc: _blocRole, + )))) ], ), ), @@ -115,90 +124,15 @@ class SpacesAccessView extends StatelessWidget { } } -class TreeNode { - String title; - bool isChecked; - bool isHighlighted; - bool isExpanded; // New property to manage expansion - List children; - - TreeNode({ - required this.title, - this.isChecked = false, - this.isHighlighted = false, - this.isExpanded = false, // Default to collapsed - this.children = const [], - }); -} - +// ignore: must_be_immutable class TreeView extends StatefulWidget { + UsersBloc? bloc; + TreeView({super.key, this.bloc}); @override _TreeViewState createState() => _TreeViewState(); } class _TreeViewState extends State { - List treeData = [ - TreeNode( - title: 'Downtown Dubai', - ), - TreeNode(title: 'Dubai Creek Harbour', isHighlighted: true), - TreeNode( - title: 'Dubai Hills Estate', - isHighlighted: true, - children: [ - TreeNode(title: 'North Side'), - TreeNode( - title: 'South Side', - isHighlighted: true, - children: [ - TreeNode(title: 'Hills Business Park'), - TreeNode(title: 'Park Point'), - TreeNode(title: 'Acacia'), - TreeNode( - title: 'Executive Residence', - children: [ - TreeNode(title: 'Residence I'), - TreeNode( - title: 'Residence II', - children: [ - TreeNode(title: 'Ground Floor', isHighlighted: true), - TreeNode(title: '1st Floor', isHighlighted: true), - TreeNode(title: 'Pool', isHighlighted: true), - TreeNode(title: 'Gym', isHighlighted: true), - ], - ), - ], - ), - ], - ), - TreeNode( - title: 'South Side', - isHighlighted: true, - children: [ - TreeNode(title: 'Hills Business Park'), - TreeNode(title: 'Park Point'), - TreeNode(title: 'Acacia'), - TreeNode( - title: 'Executive Residence', - children: [ - TreeNode(title: 'Residence I'), - TreeNode( - title: 'Residence II', - children: [ - TreeNode(title: 'Ground Floor', isHighlighted: true), - TreeNode(title: '1st Floor', isHighlighted: true), - TreeNode(title: 'Pool', isHighlighted: true), - TreeNode(title: 'Gym', isHighlighted: true), - ], - ), - ], - ), - ], - ), - ], - ), - ]; - Widget _buildTree(List nodes, {int level = 0}) { return Column( children: nodes.map((node) => _buildNode(node, level: level)).toList(), @@ -207,31 +141,14 @@ class _TreeViewState extends State { Widget _buildNode(TreeNode node, {int level = 0}) { return Container( - color: node.isHighlighted ? Colors.blue.shade100 : Colors.transparent, - child: Padding( - padding: EdgeInsets.only(left: level * 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + color: node.isHighlighted ? Colors.blue.shade50 : Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( children: [ - GestureDetector( - onTap: () { - setState(() { - node.isExpanded = !node.isExpanded; // Toggle expansion - }); - }, - child: Icon( - node.children.isNotEmpty - ? (node.isExpanded - ? Icons.arrow_drop_down - : Icons.arrow_right) - : Icons.arrow_right, - color: node.children.isNotEmpty - ? Colors.black - : Colors.transparent, - ), - ), GestureDetector( onTap: () { setState(() { @@ -246,24 +163,49 @@ class _TreeViewState extends State { height: 20, ), ), + const SizedBox(width: 15), Expanded( - child: Text( - node.title, - style: TextStyle( - fontSize: 16, - color: Colors.black87, + child: Padding( + padding: EdgeInsets.only(left: level * 10.0), + child: Row( + children: [ + GestureDetector( + onTap: () { + setState(() { + node.isExpanded = !node.isExpanded; + }); + }, + child: SizedBox( + child: SvgPicture.asset( + node.children.isNotEmpty + ? (node.isExpanded + ? Assets.arrowDown + : Assets.arrowForward) + : Assets.arrowForward, + fit: BoxFit.none, + ), + ), + ), + const SizedBox(width: 20), + Text( + node.title, + style: TextStyle( + fontSize: 16, + color: node.isHighlighted + ? ColorsManager.blackColor + : ColorsManager.textGray, + ), + ), + ], ), ), ), ], ), - if (node.isExpanded && node.children.isNotEmpty) - Padding( - padding: const EdgeInsets.only(left: 24.0), - child: _buildTree(node.children, level: level + 1), - ), - ], - ), + ), + if (node.isExpanded && node.children.isNotEmpty) + _buildTree(node.children, level: level + 1), + ], ), ); } @@ -308,7 +250,7 @@ class _TreeViewState extends State { // Update the checkbox state for parent nodes void _updateParentCheckStatus(TreeNode node) { - TreeNode? parent = _findParent(treeData, node); + TreeNode? parent = _findParent(widget.bloc!.updatedCommunities, node); if (parent != null) { setState(() { parent.isChecked = _areAllChildrenChecked(parent); @@ -332,7 +274,7 @@ class _TreeViewState extends State { @override Widget build(BuildContext context) { return SingleChildScrollView( - child: _buildTree(treeData), + child: _buildTree(widget.bloc!.updatedCommunities), ); } } diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 5d2464e6..58dc0d9d 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -252,6 +252,7 @@ class CommunitySpaceManagementApi { path: ApiEndpoints.getSpaceHierarchy .replaceAll('{communityId}', communityId), expectedResponseModel: (json) { + print('=-=-=-=$json'); final spaceModels = (json['data'] as List) .map((spaceJson) => SpaceModel.fromJson(spaceJson)) .toList(); diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 4e35e84f..acb314d9 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -391,4 +391,8 @@ class Assets { 'assets/icons/uncompleate_process_icon.svg'; static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg'; + static const String arrowForward = + 'assets/icons/arrow_forward.svg'; + static const String arrowDown = + 'assets/icons/arrow_down.svg'; } From a46f69636d1dd213943e3017c621a3492a7c22fc Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 16 Dec 2024 18:19:22 +0300 Subject: [PATCH 003/106] add_user_dialog --- .../users_page/bloc/users_bloc.dart | 87 ++++++++----------- .../users_page/bloc/users_event.dart | 11 +++ .../users_page/view/add_user_dialog.dart | 18 ++-- .../users_page/view/spaces_access_view.dart | 11 +-- 4 files changed, 64 insertions(+), 63 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index 0d153804..72c85bad 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -7,7 +7,6 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_nod 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'; - class UsersBloc extends Bloc { UsersBloc() : super(UsersInitial()) { on(_getUsers); @@ -15,6 +14,7 @@ class UsersBloc extends Bloc { on(isCompleteBasicsFun); on(_onLoadCommunityAndSpaces); on(searchTreeNode); + on(isCompleteSpacesFun); } List users = []; @@ -62,7 +62,6 @@ class UsersBloc extends Bloc { void _changeUserStatus(ChangeUserStatus event, Emitter emit) { try { - // Update the user's status users = users.map((user) { if (user.id == event.userId) { return RolesUserModel( @@ -111,21 +110,27 @@ class UsersBloc extends Bloc { return isCompleteBasics; } - isCompleteSpacesFun(CheckStepStatus event, Emitter emit) { + void isCompleteSpacesFun( + CheckSpacesStepStatus event, Emitter emit) { emit(UsersLoadingState()); - isCompleteSpaces = false; + + try { + List selectedIds = + getSelectedIds(updatedCommunities); + isCompleteSpaces = selectedIds.isNotEmpty; + } catch (e) { + emit(ErrorState('Error while retrieving selected IDs: $e')); + return; + } + emit(ChangeStatusSteps()); - print('isCompleteBasics==$isCompleteSpaces'); - return isCompleteSpaces; } bool checkRolePermissions() { return true; } - bool checkSpaces() { - return true; - } + Future> _fetchSpacesForCommunity( String communityUuid) async { @@ -134,83 +139,60 @@ class UsersBloc extends Bloc { List updatedCommunities = []; List spacesNodes = []; + _onLoadCommunityAndSpaces( - LoadCommunityAndSpacesEvent event, - Emitter emit, - ) async { - emit(UsersLoadingState()); // Emit loading state + LoadCommunityAndSpacesEvent event, Emitter emit) async { try { - // Fetch the list of communities + emit(UsersLoadingState()); List communities = await CommunitySpaceManagementApi().fetchCommunities(); - - // Fetch spaces and create TreeNodes for each community updatedCommunities = await Future.wait( communities.map((community) async { - // Fetch spaces for the current community List spaces = await _fetchSpacesForCommunity(community.uuid); - - // Recursively build the tree structure spacesNodes = _buildTreeNodes(spaces); - - // Return a TreeNode for the community, with spaces as its children return TreeNode( uuid: community.uuid, title: community.name, children: spacesNodes, - isChecked: false, // Initial state; can be updated later + isChecked: false, isHighlighted: false, - isExpanded: true, // Default to expanded for better UX + isExpanded: true, ); }).toList(), ); - - // Emit the final state with the structured tree emit(ChangeStatusSteps()); - - return updatedCommunities; // Return the structured data if needed + return updatedCommunities; } catch (e) { - // Emit error state in case of failure emit(ErrorState('Error loading communities and spaces: $e')); } } -// Helper function to recursively build tree nodes List _buildTreeNodes(List spaces) { return spaces.map((space) { - // If the space has children, recursively build nodes for them List childNodes = - space.children != null ? _buildTreeNodes(space.children!) : []; - - // Create a TreeNode for the current space + space.children != null ? _buildTreeNodes(space.children) : []; return TreeNode( uuid: space.uuid!, title: space.name, isChecked: false, isHighlighted: false, - isExpanded: childNodes.isNotEmpty, // Expand if there are children + isExpanded: childNodes.isNotEmpty, children: childNodes, ); }).toList(); } void searchTreeNode(SearchAnode event, Emitter emit) { - emit(UsersLoadingState()); // Emit loading state - - // Clear all highlights if the search term is empty + emit(UsersLoadingState()); if (event.searchTerm!.isEmpty) { _clearHighlights(updatedCommunities); } else { - // Perform the search and update the highlights _searchAndHighlightNodes(updatedCommunities, event.searchTerm!); } - - // Emit the updated state after processing all nodes emit(ChangeStatusSteps()); } -// Helper function to clear all highlights in the tree void _clearHighlights(List nodes) { for (var node in nodes) { node.isHighlighted = false; @@ -220,25 +202,32 @@ class UsersBloc extends Bloc { } } -// Helper function to search and highlight nodes recursively bool _searchAndHighlightNodes(List nodes, String searchTerm) { bool anyMatch = false; for (var node in nodes) { - // Check if this node matches the search term bool isMatch = node.title.toLowerCase().contains(searchTerm.toLowerCase()); - - // Recursively check children for matches bool childMatch = _searchAndHighlightNodes(node.children, searchTerm); - - // Highlight this node if it matches or any of its children match node.isHighlighted = isMatch || childMatch; - // Update if any matches were found in this branch anyMatch = anyMatch || node.isHighlighted; } - return anyMatch; } + + List selectedIds = []; + List getSelectedIds(List nodes) { + List selectedIds = []; + for (var node in nodes) { + if (node.isChecked) { + selectedIds.add(node.uuid); + } + if (node.children.isNotEmpty) { + selectedIds.addAll(getSelectedIds(node.children)); + } + } + return selectedIds; + } + } diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart index 77890673..f1675b08 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -10,6 +10,11 @@ class GetUsers extends UsersEvent { @override List get props => []; } +class CheckSpacesStepStatus extends UsersEvent { + const CheckSpacesStepStatus(); + @override + List get props => []; +} class LoadCommunityAndSpacesEvent extends UsersEvent { const LoadCommunityAndSpacesEvent(); @@ -49,3 +54,9 @@ class SearchAnode extends UsersEvent { @override List get props => [nodes, searchTerm]; } +class SelecteId extends UsersEvent { + List? nodes; + SelecteId({this.nodes,}); + @override + List get props => [nodes]; +} diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart index 21e92727..895f4825 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -23,7 +23,8 @@ class _AddNewUserDialogState extends State { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => UsersBloc()..add(LoadCommunityAndSpacesEvent()), + create: (BuildContext context) => + UsersBloc()..add(LoadCommunityAndSpacesEvent()), child: BlocConsumer( listener: (context, state) {}, builder: (context, state) { @@ -121,11 +122,11 @@ class _AddNewUserDialogState extends State { ), ], ), - ), - ], - ), - )); - })); + ), + ], + ), + )); + })); } Widget _getFormContent() { @@ -162,7 +163,9 @@ class _AddNewUserDialogState extends State { ? Assets.currentProcessIcon : bloc.isCompleteSpaces == false ? Assets.wrongProcessIcon - : Assets.uncomplete_ProcessIcon, + : bloc.isCompleteSpaces == true + ? Assets.completeProcessIcon + : Assets.uncomplete_ProcessIcon, width: 25, height: 25, ), @@ -263,6 +266,7 @@ class _AddNewUserDialogState extends State { onTap: () { setState(() { currentStep = step; + bloc.add(const CheckSpacesStepStatus()); }); }, child: Column( diff --git a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart index 5dff9b0a..4bc330b2 100644 --- a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart @@ -125,6 +125,7 @@ class SpacesAccessView extends StatelessWidget { } // ignore: must_be_immutable + class TreeView extends StatefulWidget { UsersBloc? bloc; TreeView({super.key, this.bloc}); @@ -155,6 +156,8 @@ class _TreeViewState extends State { node.isChecked = !node.isChecked; _updateChildrenCheckStatus(node, node.isChecked); _updateParentCheckStatus(node); + // widget.bloc!.add( + // SelecteId(nodes: widget.bloc!.updatedCommunities)); }); }, child: Image.asset( @@ -210,7 +213,6 @@ class _TreeViewState extends State { ); } - // Determine the appropriate image based on the check state String _getCheckBoxImage(TreeNode node) { if (node.children.isEmpty) { return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox; @@ -224,7 +226,6 @@ class _TreeViewState extends State { } } - // Helper to determine if all children are checked bool _areAllChildrenChecked(TreeNode node) { return node.children.isNotEmpty && node.children.every((child) => @@ -232,7 +233,6 @@ class _TreeViewState extends State { (child.children.isEmpty || _areAllChildrenChecked(child))); } - // Helper to determine if some children are checked bool _areSomeChildrenChecked(TreeNode node) { return node.children.isNotEmpty && node.children.any((child) => @@ -240,7 +240,6 @@ class _TreeViewState extends State { (child.children.isNotEmpty && _areSomeChildrenChecked(child))); } - // Update the checkbox state for all children void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { for (var child in node.children) { child.isChecked = isChecked; @@ -248,18 +247,16 @@ class _TreeViewState extends State { } } - // Update the checkbox state for parent nodes void _updateParentCheckStatus(TreeNode node) { TreeNode? parent = _findParent(widget.bloc!.updatedCommunities, node); if (parent != null) { setState(() { parent.isChecked = _areAllChildrenChecked(parent); - _updateParentCheckStatus(parent); // Recursively update ancestors + _updateParentCheckStatus(parent); }); } } - // Helper to find a node's parent TreeNode? _findParent(List nodes, TreeNode target) { for (var node in nodes) { if (node.children.contains(target)) { From c31f1262a2ee6e51ec7b4c15f49d4c5f9c441969 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 16 Dec 2024 18:59:56 +0300 Subject: [PATCH 004/106] delete_dialog --- .../users_page/bloc/users_bloc.dart | 18 ++---- .../users_page/view/delete_user_dialog.dart | 63 +++++++++++++++++++ 2 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index 72c85bad..ad5e49ce 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_nod 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'; + class UsersBloc extends Bloc { UsersBloc() : super(UsersInitial()) { on(_getUsers); @@ -103,9 +104,7 @@ class UsersBloc extends Bloc { emit(UsersLoadingState()); isCompleteBasics = firstNameController.text.isNotEmpty && lastNameController.text.isNotEmpty && - emailController.text.isNotEmpty && - phoneController.text.isNotEmpty && - jobTitleController.text.isNotEmpty; + emailController.text.isNotEmpty; emit(ChangeStatusSteps()); return isCompleteBasics; } @@ -115,8 +114,7 @@ class UsersBloc extends Bloc { emit(UsersLoadingState()); try { - List selectedIds = - getSelectedIds(updatedCommunities); + List selectedIds = getSelectedIds(updatedCommunities); isCompleteSpaces = selectedIds.isNotEmpty; } catch (e) { emit(ErrorState('Error while retrieving selected IDs: $e')); @@ -130,8 +128,6 @@ class UsersBloc extends Bloc { return true; } - - Future> _fetchSpacesForCommunity( String communityUuid) async { return await CommunitySpaceManagementApi().getSpaceHierarchy(communityUuid); @@ -204,7 +200,6 @@ class UsersBloc extends Bloc { bool _searchAndHighlightNodes(List nodes, String searchTerm) { bool anyMatch = false; - for (var node in nodes) { bool isMatch = node.title.toLowerCase().contains(searchTerm.toLowerCase()); @@ -218,16 +213,15 @@ class UsersBloc extends Bloc { List selectedIds = []; List getSelectedIds(List nodes) { - List selectedIds = []; + List selectedIds = []; for (var node in nodes) { if (node.isChecked) { - selectedIds.add(node.uuid); + selectedIds.add(node.uuid); } if (node.children.isNotEmpty) { selectedIds.addAll(getSelectedIds(node.children)); } } - return selectedIds; + return selectedIds; } - } diff --git a/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart new file mode 100644 index 00000000..090d4fe3 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart @@ -0,0 +1,63 @@ +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/utils/color_manager.dart'; + +class AddNewUserDialog extends StatefulWidget { + const AddNewUserDialog({super.key}); + + @override + _AddNewUserDialogState createState() => _AddNewUserDialogState(); +} + +class _AddNewUserDialogState extends State { + int currentStep = 1; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => UsersBloc(), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + final _blocRole = BlocProvider.of(context); + + return Dialog( + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + child: const Column( + children: [ + Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Delete User", + style: TextStyle( + color: ColorsManager.red, + fontSize: 18, + fontWeight: FontWeight.bold), + ), + ), + ), + Divider(), + Expanded( + child: Text( + "Are you sure you want to delete this user?", + textAlign: TextAlign.center, + )), + Row( + children: [ + Expanded(child: Text('Cancel')), + Expanded(child: Text('Delete')), + ], + ) + ], + ), + )); + })); + } +} From bd1204c03aa41672a6434037ae118ee91e411785 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 17 Dec 2024 16:51:32 +0300 Subject: [PATCH 005/106] add_user_dialog --- .../model/role_type_model.dart | 22 ++ .../users_page/bloc/users_bloc.dart | 64 +++- .../users_page/bloc/users_event.dart | 29 +- .../users_page/bloc/users_status.dart | 4 + .../users_page/view/add_user_dialog.dart | 9 +- .../users_page/view/basics_view.dart | 150 ++++++--- .../users_page/view/delete_user_dialog.dart | 8 +- .../users_page/view/roles_and_permission.dart | 294 +++++++++--------- lib/services/user_permission.dart | 36 +++ lib/utils/constants/api_const.dart | 35 ++- pubspec.lock | 40 +++ pubspec.yaml | 2 + 12 files changed, 481 insertions(+), 212 deletions(-) create mode 100644 lib/pages/roles_and_permission/model/role_type_model.dart create mode 100644 lib/services/user_permission.dart diff --git a/lib/pages/roles_and_permission/model/role_type_model.dart b/lib/pages/roles_and_permission/model/role_type_model.dart new file mode 100644 index 00000000..1705e25c --- /dev/null +++ b/lib/pages/roles_and_permission/model/role_type_model.dart @@ -0,0 +1,22 @@ +class RoleTypeModel { + final String uuid; + final String createdAt; + final String updatedAt; + final String type; + + RoleTypeModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.type, + }); + + factory RoleTypeModel.fromJson(Map json) { + return RoleTypeModel( + uuid: json['uuid'], + createdAt: json['createdAt'], + updatedAt: json['updatedAt'], + type: json['type'], + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index ad5e49ce..5b846ecf 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -1,12 +1,15 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.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/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'; class UsersBloc extends Bloc { UsersBloc() : super(UsersInitial()) { @@ -16,6 +19,9 @@ class UsersBloc extends Bloc { on(_onLoadCommunityAndSpaces); on(searchTreeNode); on(isCompleteSpacesFun); + on(_getRolePermission); + on(_getPermissions); + on(searchRolePermission); } List users = []; @@ -112,7 +118,6 @@ class UsersBloc extends Bloc { void isCompleteSpacesFun( CheckSpacesStepStatus event, Emitter emit) { emit(UsersLoadingState()); - try { List selectedIds = getSelectedIds(updatedCommunities); isCompleteSpaces = selectedIds.isNotEmpty; @@ -224,4 +229,61 @@ class UsersBloc extends Bloc { } return selectedIds; } + + List roles = []; + List permissions = []; + + _getRolePermission(RoleEvent event, Emitter emit) async { + 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')); + } + } + + _getPermissions(PermissionEvent event, Emitter emit) async { + try { + emit(UsersLoadingState()); + permissions = await UserPermissionApi().fetchPermission( + event.roleUuid == "" ? roles.first.uuid : event.roleUuid); + emit(RolePermissionInitial()); + } catch (e) { + emit(ErrorState('Error loading communities and spaces: $e')); + } + } + + bool _searchRolePermission(List nodes, String searchTerm) { + bool anyMatch = false; + for (var node in nodes) { + bool isMatch = + node.title.toLowerCase().contains(searchTerm.toLowerCase()); + bool childMatch = _searchRolePermission(node.subOptions, searchTerm); + node.isHighlighted = isMatch || childMatch; + + anyMatch = anyMatch || node.isHighlighted; + } + return anyMatch; + } + + void searchRolePermission(SearchPermission event, Emitter emit) { + emit(UsersLoadingState()); + if (event.searchTerm!.isEmpty) { + _clearHighlightsRolePermission(permissions); + } else { + _searchRolePermission(permissions, event.searchTerm!); + } + emit(ChangeStatusSteps()); + } + + void _clearHighlightsRolePermission(List nodes) { + for (var node in nodes) { + node.isHighlighted = false; + if (node.subOptions.isNotEmpty) { + _clearHighlightsRolePermission(node.subOptions); + } + } + } } diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart index f1675b08..6f59b495 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -1,5 +1,6 @@ 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'; sealed class UsersEvent extends Equatable { const UsersEvent(); @@ -10,6 +11,7 @@ class GetUsers extends UsersEvent { @override List get props => []; } + class CheckSpacesStepStatus extends UsersEvent { const CheckSpacesStepStatus(); @override @@ -22,6 +24,19 @@ class LoadCommunityAndSpacesEvent extends UsersEvent { List get props => []; } +class RoleEvent extends UsersEvent { + const RoleEvent(); + @override + List get props => []; +} + +class PermissionEvent extends UsersEvent { + final String? roleUuid; + const PermissionEvent({this.roleUuid = ""}); + @override + List get props => [roleUuid]; +} + class GetBatchStatus extends UsersEvent { final List uuids; const GetBatchStatus(this.uuids); @@ -29,7 +44,6 @@ class GetBatchStatus extends UsersEvent { List get props => [uuids]; } -//LoadCommunityAndSpacesEvent class ChangeUserStatus extends UsersEvent { final String userId; final String newStatus; @@ -54,9 +68,20 @@ class SearchAnode extends UsersEvent { @override List get props => [nodes, searchTerm]; } + +class SearchPermission extends UsersEvent { + List? nodes; + String? searchTerm; + SearchPermission({this.nodes, this.searchTerm}); + @override + List get props => [nodes, searchTerm]; +} + class SelecteId extends UsersEvent { List? nodes; - SelecteId({this.nodes,}); + SelecteId({ + this.nodes, + }); @override List get props => [nodes]; } diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart index b9937f77..5d1a68f6 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart @@ -9,6 +9,10 @@ final class UsersInitial extends UsersState { @override List get props => []; } +final class RolePermissionInitial extends UsersState { + @override + List get props => []; +} final class ChangeStatusSteps extends UsersState { @override diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart index 895f4825..dc45cfdc 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -23,8 +23,9 @@ class _AddNewUserDialogState extends State { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => - UsersBloc()..add(LoadCommunityAndSpacesEvent()), + create: (BuildContext context) => UsersBloc() + ..add(const LoadCommunityAndSpacesEvent()) + ..add(const RoleEvent()), child: BlocConsumer( listener: (context, state) {}, builder: (context, state) { @@ -45,7 +46,9 @@ class _AddNewUserDialogState extends State { child: Text( "Add New User", style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold), + fontSize: 20, + fontWeight: FontWeight.w700, + color: ColorsManager.secondaryColor), ), ), ), diff --git a/lib/pages/roles_and_permission/users_page/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/view/basics_view.dart index d64d837d..abf6642c 100644 --- a/lib/pages/roles_and_permission/users_page/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/view/basics_view.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +import 'package:flutter_bloc/flutter_bloc.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/utils/color_manager.dart'; @@ -47,9 +49,16 @@ class BasicsView extends StatelessWidget { SizedBox( child: Row( children: [ - Text( - "*", - style: TextStyle(color: ColorsManager.red), + // SizedBox( + // width: 15, + // ), + const Text( + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), ), Text( 'First Name', @@ -96,8 +105,12 @@ class BasicsView extends StatelessWidget { child: Row( children: [ const Text( - "*", - style: TextStyle(color: ColorsManager.red), + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), ), Text('Last Name', style: context.textTheme.bodyMedium?.copyWith( @@ -140,9 +153,13 @@ class BasicsView extends StatelessWidget { SizedBox( child: Row( children: [ - Text( - "*", - style: TextStyle(color: ColorsManager.red), + const Text( + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), ), Text( 'Email Address', @@ -187,10 +204,6 @@ class BasicsView extends StatelessWidget { SizedBox( child: Row( children: [ - Text( - "*", - style: TextStyle(color: ColorsManager.red), - ), Text( 'Mobile Number', style: context.textTheme.bodyMedium?.copyWith( @@ -200,28 +213,93 @@ class BasicsView extends StatelessWidget { ), ], )), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - style: const TextStyle(color: Colors.black), - controller: _blocRole.phoneController, - decoration: inputTextFormDeco( - hintText: "05x xxx xxxx", - ).copyWith( - hintStyle: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w400, - fontSize: 12, - color: ColorsManager.textGray), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a phone number'; - } - return null; - }, - keyboardType: TextInputType.phone, + // InternationalPhoneNumberInput( + // spaceBetweenSelectorAndTextField: 50, + // initialValue: PhoneNumber(isoCode: 'AE'), + // inputDecoration: inputTextFormDeco( + // hintText: "x xxx xxxx", + // ).copyWith( + // hintStyle: context.textTheme.bodyMedium?.copyWith( + // fontWeight: FontWeight.w400, + // fontSize: 12, + // color: ColorsManager.textGray), + // ), + // onInputChanged: (PhoneNumber number) { + // print(number.phoneNumber); + // }, + // onInputValidated: (bool value) { + // print(value); + // }, + // selectorConfig: const SelectorConfig( + // selectorType: PhoneInputSelectorType.BOTTOM_SHEET, + // useBottomSheetSafeArea: true, + // leadingPadding: 15, + // trailingSpace: false, + // setSelectorButtonAsPrefixIcon: true), + // ignoreBlank: true, + // autoValidateMode: AutovalidateMode.disabled, + + // selectorTextStyle: + // TextStyle(color: ColorsManager.blackColor), + // // initialValue: number, + // // textFieldController: controller, + // formatInput: true, + // keyboardType: const TextInputType.numberWithOptions( + // signed: true, decimal: true), + // // inputBorder: OutlineInputBorder( + // // borderSide: + // // BorderSide(color: Colors.black,)), + // onSaved: (PhoneNumber number) { + // print('On Saved: $number'); + // }, + // textStyle: + // const TextStyle(color: ColorsManager.blackColor), + // ), + + IntlPhoneField( + pickerDialogStyle: PickerDialogStyle(), + dropdownIconPosition: IconPosition.leading, + disableLengthCheck: true, + dropdownTextStyle: TextStyle(color: Colors.black), + textInputAction: TextInputAction.done, + decoration: inputTextFormDeco( + hintText: "05x xxx xxxx", + ).copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), ), - ), + + initialCountryCode: 'AE', + style: TextStyle(color: Colors.black), + onChanged: (phone) { + print(phone.completeNumber); + }, + ) + + // Padding( + // padding: const EdgeInsets.all(8.0), + // child: TextFormField( + // style: const TextStyle(color: Colors.black), + // controller: _blocRole.phoneController, + // decoration: inputTextFormDeco( + // hintText: "05x xxx xxxx", + // ).copyWith( + // hintStyle: context.textTheme.bodyMedium?.copyWith( + // fontWeight: FontWeight.w400, + // fontSize: 12, + // color: ColorsManager.textGray), + // ), + // validator: (value) { + // if (value == null || value.isEmpty) { + // return 'Please enter a phone number'; + // } + // return null; + // }, + // keyboardType: TextInputType.phone, + // ), + // ), ], ), ), @@ -236,10 +314,6 @@ class BasicsView extends StatelessWidget { SizedBox( child: Row( children: [ - Text( - "*", - style: TextStyle(color: ColorsManager.red), - ), Text( 'Job Title', style: context.textTheme.bodyMedium?.copyWith( diff --git a/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart index 090d4fe3..f10fd4ed 100644 --- a/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart @@ -5,14 +5,14 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_blo import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class AddNewUserDialog extends StatefulWidget { - const AddNewUserDialog({super.key}); +class DeleteUserDialog extends StatefulWidget { + const DeleteUserDialog({super.key}); @override - _AddNewUserDialogState createState() => _AddNewUserDialogState(); + _DeleteUserDialogState createState() => _DeleteUserDialogState(); } -class _AddNewUserDialogState extends State { +class _DeleteUserDialogState extends State { int currentStep = 1; @override diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart index 812a3170..a8c186e8 100644 --- a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package: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'; @@ -34,7 +35,12 @@ class RolesAndPermission extends StatelessWidget { const SizedBox( height: 15, ), - const SizedBox(width: 300, height: 110, child: DropdownExample()), + SizedBox( + width: 300, + height: 110, + child: DropdownExample( + bloc: _blocRole, + )), const SizedBox(height: 10), Expanded( child: SizedBox( @@ -64,6 +70,11 @@ class RolesAndPermission extends StatelessWidget { 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, @@ -97,7 +108,9 @@ class RolesAndPermission extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: Container( color: ColorsManager.whiteColors, - child: const DeviceManagement()))) + child: DeviceManagement( + bloc: _blocRole, + )))) ], ), ), @@ -111,7 +124,8 @@ class RolesAndPermission extends StatelessWidget { } class DropdownExample extends StatefulWidget { - const DropdownExample({super.key}); + final UsersBloc? bloc; + const DropdownExample({super.key, this.bloc}); @override _DropdownExampleState createState() => _DropdownExampleState(); @@ -119,7 +133,13 @@ class DropdownExample extends StatefulWidget { class _DropdownExampleState extends State { String? selectedRole; - List roles = ['Admin', 'User', 'Guest', 'Moderator']; + @override + void initState() { + super.initState(); + if (widget.bloc != null && widget.bloc!.roles.isNotEmpty) { + selectedRole = widget.bloc!.roles.first.uuid; + } + } @override Widget build(BuildContext context) { @@ -140,19 +160,20 @@ class _DropdownExampleState extends State { SizedBox( child: DropdownButtonFormField( alignment: Alignment.center, - focusColor: ColorsManager.whiteColors, + focusColor: Colors.white, autofocus: true, value: selectedRole, - items: roles.map((role) { - return DropdownMenuItem( - value: role, - child: Text(role), + items: widget.bloc!.roles.map((role) { + return DropdownMenuItem( + 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(), @@ -168,13 +189,13 @@ class _DropdownExampleState extends State { width: 70, height: 50, decoration: BoxDecoration( - color: ColorsManager.graysColor, + color: Colors.grey[200], borderRadius: const BorderRadius.only( bottomRight: Radius.circular(10), topRight: Radius.circular(10), ), border: Border.all( - color: ColorsManager.grayBorder, + color: Colors.grey, width: 1.0, ), ), @@ -192,60 +213,17 @@ class _DropdownExampleState extends State { } class DeviceManagement extends StatefulWidget { - const DeviceManagement({Key? key}) : super(key: key); + final UsersBloc? bloc; + const DeviceManagement({Key? key, this.bloc}) : super(key: key); @override _DeviceManagementState createState() => _DeviceManagementState(); } class _DeviceManagementState extends State { - - - final List options = [ - MainRoleOption( - id: '1', - title: "Device Management", - subOptions: [ - SubRoleOption( - id: '11', - title: "Manage devices in private spaces", - children: [ - ChildRoleOption(id: '111', title: "Control"), - ChildRoleOption(id: '112', title: "Assign device"), - ChildRoleOption(id: '113', title: "View"), - ], - ), - SubRoleOption( - id: '12', - title: "Manage", - children: [ - ChildRoleOption(id: '121', title: "cc"), - ChildRoleOption(id: '122', title: "Assign"), - ChildRoleOption(id: '123', title: "s"), - ], - ), - ], - ), - MainRoleOption( - id: '2', - title: "Device Management", - subOptions: [ - SubRoleOption( - id: '22', - title: "Manage devices in private spaces", - children: [ - ChildRoleOption(id: '211', title: "Control"), - ChildRoleOption(id: '212', title: "Assign device"), - ChildRoleOption(id: '213', title: "View"), - ], - ), - ], - ), - ]; - void toggleOptionById(String id) { setState(() { - for (var mainOption in options) { + for (var mainOption in widget.bloc!.permissions) { if (mainOption.id == id) { final isChecked = checkifOneOfthemChecked(mainOption) == CheckState.all; @@ -253,7 +231,7 @@ class _DeviceManagementState extends State { for (var subOption in mainOption.subOptions) { subOption.isChecked = !isChecked; - for (var child in subOption.children) { + for (var child in subOption.subOptions) { child.isChecked = !isChecked; } } @@ -263,7 +241,7 @@ class _DeviceManagementState extends State { for (var subOption in mainOption.subOptions) { if (subOption.id == id) { subOption.isChecked = !subOption.isChecked; - for (var child in subOption.children) { + for (var child in subOption.subOptions) { child.isChecked = subOption.isChecked; } mainOption.isChecked = @@ -271,11 +249,11 @@ class _DeviceManagementState extends State { return; } - for (var child in subOption.children) { + for (var child in subOption.subOptions) { if (child.id == id) { child.isChecked = !child.isChecked; subOption.isChecked = - subOption.children.every((child) => child.isChecked); + subOption.subOptions.every((child) => child.isChecked); mainOption.isChecked = mainOption.subOptions.every((sub) => sub.isChecked); return; @@ -286,7 +264,7 @@ class _DeviceManagementState extends State { }); } - CheckState checkifOneOfthemChecked(MainRoleOption mainOption) { + CheckState checkifOneOfthemChecked(PermissionOption mainOption) { bool allSelected = true; bool someSelected = false; @@ -297,7 +275,7 @@ class _DeviceManagementState extends State { allSelected = false; } - for (var child in subOption.children) { + for (var child in subOption.subOptions) { if (child.isChecked) { someSelected = true; } else { @@ -319,16 +297,16 @@ class _DeviceManagementState extends State { Widget build(BuildContext context) { return ListView.builder( padding: const EdgeInsets.all(8), - itemCount: options.length, + itemCount: widget.bloc!.permissions.length, itemBuilder: (context, index) { - final option = options[index]; + final option = widget.bloc!.permissions[index]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ InkWell( - onTap: () => toggleOptionById(option.id), + // onTap: () => toggleOptionById(option.id), child: Builder( builder: (context) { final checkState = checkifOneOfthemChecked(option); @@ -372,50 +350,55 @@ class _DeviceManagementState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - InkWell( - onTap: () => toggleOptionById(subOption.id), - child: Builder( - builder: (context) { - final checkState = - checkifOneOfthemChecked(MainRoleOption( - id: subOption.id, - title: subOption.title, - subOptions: [subOption], - )); + 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, - ); - } - }, + 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), - ), - ], + 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), @@ -424,15 +407,18 @@ class _DeviceManagementState extends State { physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, // 2 items per row - mainAxisSpacing: 2.0, // Space between rows - crossAxisSpacing: 0.2, // Space between columns - childAspectRatio: 5, // Adjust aspect ratio as needed + crossAxisCount: 2, + mainAxisSpacing: 2.0, + crossAxisSpacing: 0.2, + childAspectRatio: 5, ), - itemCount: subOption.children.length, + itemCount: subOption.subOptions.length, itemBuilder: (context, index) { - final child = subOption.children[index]; + final child = subOption.subOptions[index]; return CheckboxListTile( + selectedTileColor: child.isHighlighted + ? Colors.blue.shade50 + : Colors.white, dense: true, controlAffinity: ListTileControlAffinity.leading, title: Text( @@ -444,6 +430,7 @@ class _DeviceManagementState extends State { ), value: child.isChecked, onChanged: (value) => toggleOptionById(child.id), + enabled: false, ); }, ), @@ -458,43 +445,44 @@ class _DeviceManagementState extends State { } } -class MainRoleOption { - String id; - String title; - bool isChecked; - List subOptions; - MainRoleOption({ - required this.id, - required this.title, - this.isChecked = false, - this.subOptions = const [], - }); -} - -class SubRoleOption { - String id; - String title; - bool isChecked; - List children; - - SubRoleOption({ - required this.id, - required this.title, - this.isChecked = false, - this.children = const [], - }); -} - -class ChildRoleOption { - String id; - String title; - bool isChecked; - - ChildRoleOption({ - required this.id, - required this.title, - this.isChecked = false, - }); -} enum CheckState { none, some, all } + +class PermissionOption { + String id; + String title; + bool isChecked; + bool isHighlighted; + List subOptions; + + PermissionOption({ + required this.id, + required this.title, + this.isChecked = false, + this.isHighlighted = false, + this.subOptions = const [], + }); + + factory PermissionOption.fromJson(Map 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 toJson() { + return { + 'id': id, + 'title': title, + 'isChecked': isChecked, + 'isHighlighted': isHighlighted, + 'subOptions': subOptions.map((sub) => sub.toJson()).toList(), + }; + } +} diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart new file mode 100644 index 00000000..91091dbe --- /dev/null +++ b/lib/services/user_permission.dart @@ -0,0 +1,36 @@ + +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/services/api/http_service.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; + +class UserPermissionApi { + static final HTTPService _httpService = HTTPService(); + + fetchRoles() async { + final response = await _httpService.get( + path: ApiEndpoints.roleTypes, + showServerMessage: true, + expectedResponseModel: (json) { + final List fetchedRoles = (json['data'] as List) + .map((item) => RoleTypeModel.fromJson(item)) + .toList(); + return fetchedRoles; + }, + ); + return response; + } + + Future> fetchPermission(roleUuid) async { + final response = await _httpService.get( + path: ApiEndpoints.permission.replaceAll("roleUuid", roleUuid), + showServerMessage: true, + expectedResponseModel: (json) { + return (json as List) + .map((data) => PermissionOption.fromJson(data)) + .toList(); + }, + ); + return response ?? []; + } +} diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 007b488d..752c5bea 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -11,12 +11,14 @@ abstract class ApiEndpoints { static const String visitorPassword = '/visitor-password'; static const String getDevices = '/visitor-password/devices'; - static const String sendOnlineOneTime = '/visitor-password/temporary-password/online/one-time'; + static const String sendOnlineOneTime = + '/visitor-password/temporary-password/online/one-time'; static const String sendOnlineMultipleTime = '/visitor-password/temporary-password/online/multiple-time'; //offline Password - static const String sendOffLineOneTime = '/visitor-password/temporary-password/offline/one-time'; + static const String sendOffLineOneTime = + '/visitor-password/temporary-password/offline/one-time'; static const String sendOffLineMultipleTime = '/visitor-password/temporary-password/offline/multiple-time'; @@ -38,8 +40,10 @@ abstract class ApiEndpoints { // Space Module static const String createSpace = '/communities/{communityId}/spaces'; static const String listSpaces = '/communities/{communityId}/spaces'; - static const String deleteSpace = '/communities/{communityId}/spaces/{spaceId}'; - static const String updateSpace = '/communities/{communityId}/spaces/{spaceId}'; + static const String deleteSpace = + '/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'; @@ -55,11 +59,15 @@ abstract class ApiEndpoints { '/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; static const String scheduleByDeviceId = '/schedule/{deviceUuid}'; - static const String getScheduleByDeviceId = '/schedule/{deviceUuid}?category={category}'; - static const String deleteScheduleByDeviceId = '/schedule/{deviceUuid}/{scheduleUuid}'; - static const String updateScheduleByDeviceId = '/schedule/enable/{deviceUuid}'; + static const String getScheduleByDeviceId = + '/schedule/{deviceUuid}?category={category}'; + static const String deleteScheduleByDeviceId = + '/schedule/{deviceUuid}/{scheduleUuid}'; + static const String updateScheduleByDeviceId = + '/schedule/enable/{deviceUuid}'; static const String factoryReset = '/device/factory/reset/{deviceUuid}'; - static const String powerClamp = '/device/{powerClampUuid}/power-clamp/status'; + static const String powerClamp = + '/device/{powerClampUuid}/power-clamp/status'; //product static const String listProducts = '/products'; @@ -68,13 +76,18 @@ abstract class ApiEndpoints { static const String getIconScene = '/scene/icon'; static const String createScene = '/scene/tap-to-run'; static const String createAutomation = '/automation'; - static const String getUnitScenes = '/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; - static const String getAutomationDetails = '/automation/details/{automationId}'; + static const String getUnitScenes = + '/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; + static const String getAutomationDetails = + '/automation/details/{automationId}'; static const String getScene = '/scene/tap-to-run/{sceneId}'; static const String deleteScene = '/scene/tap-to-run/{sceneId}'; static const String deleteAutomation = '/automation/{automationId}'; - static const String updateScene = '/scene/tap-to-run/{sceneId}'; + static const String updateScene = '/scene/tap-to-run/{sceneId}'; static const String updateAutomation = '/automation/{automationId}'; + static const String roleTypes = '/role/types'; + static const String permission = '/permission/{roleUuid}'; + // static const String updateAutomation = '/automation/{automationId}'; } diff --git a/pubspec.lock b/pubspec.lock index ea2315d7..98c62a94 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -304,6 +304,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.19.0" + intl_phone_field: + dependency: "direct main" + description: + name: intl_phone_field + sha256: "73819d3dfcb68d2c85663606f6842597c3ddf6688ac777f051b17814fe767bbf" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + intl_phone_number_input: + dependency: "direct main" + description: + name: intl_phone_number_input + sha256: "1c4328713a9503ab26a1fdbb6b00b4cada68c18aac922b35bedbc72eff1297c3" + url: "https://pub.dev" + source: hosted + version: "0.7.4" js: dependency: transitive description: @@ -336,6 +352,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + libphonenumber_platform_interface: + dependency: transitive + description: + name: libphonenumber_platform_interface + sha256: f801f6c65523f56504b83f0890e6dad584ab3a7507dca65fec0eed640afea40f + url: "https://pub.dev" + source: hosted + version: "0.4.2" + libphonenumber_plugin: + dependency: transitive + description: + name: libphonenumber_plugin + sha256: c615021d9816fbda2b2587881019ed595ecdf54d999652d7e4cce0e1f026368c + url: "https://pub.dev" + source: hosted + version: "0.3.3" + libphonenumber_web: + dependency: transitive + description: + name: libphonenumber_web + sha256: "8186f420dbe97c3132283e52819daff1e55d60d6db46f7ea5ac42f42a28cc2ef" + url: "https://pub.dev" + source: hosted + version: "0.3.2" lints: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ffff62ac..c99481d1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,6 +52,8 @@ dependencies: fl_chart: ^0.69.0 uuid: ^4.4.2 time_picker_spinner: ^1.0.0 + intl_phone_field: ^3.2.0 + intl_phone_number_input: ^0.7.4 dev_dependencies: flutter_test: From 298aab5116cb6eca92cdf4f846ccb834c0af678a Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 17 Dec 2024 17:07:14 +0300 Subject: [PATCH 006/106] send_invite_user --- .../users_page/bloc/users_bloc.dart | 13 +++++++++ lib/services/user_permission.dart | 29 ++++++++++++++++++- lib/utils/constants/api_const.dart | 1 + 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index 5b846ecf..ec3139b7 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -268,6 +268,19 @@ class UsersBloc extends Bloc { return anyMatch; } + _sendInvitUser(List nodes, String searchTerm) async { + emit(UsersLoadingState()); + await UserPermissionApi().sendInviteUser( + email: emailController.text, + firstName: firstNameController.text, + jobTitle: jobTitleController.text, + lastName: lastNameController.text, + phoneNumber: phoneController.text, + roleUuid: '', + spaceUuids: selectedIds); + emit(RolePermissionInitial()); + } + void searchRolePermission(SearchPermission event, Emitter emit) { emit(UsersLoadingState()); if (event.searchTerm!.isEmpty) { diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 91091dbe..22083191 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -1,4 +1,3 @@ - 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/services/api/http_service.dart'; @@ -33,4 +32,32 @@ class UserPermissionApi { ); return response ?? []; } + + Future sendInviteUser({ + String? firstName, + String? lastName, + String? email, + String? jobTitle, + String? phoneNumber, + String? roleUuid, + List? spaceUuids, + }) async { + final response = await _httpService.post( + path: ApiEndpoints.permission, + showServerMessage: true, + body: { + "firstName": firstName, + "lastName": lastName, + "email": email, + "jobTitle": jobTitle, + "phoneNumber": phoneNumber, + "roleUuid": roleUuid, + "spaceUuids": spaceUuids + }, + expectedResponseModel: (json) { + print(json); + }, + ); + return response ?? []; + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 752c5bea..28923538 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -89,5 +89,6 @@ abstract class ApiEndpoints { static const String updateAutomation = '/automation/{automationId}'; static const String roleTypes = '/role/types'; static const String permission = '/permission/{roleUuid}'; + static const String inviteUser = '/invite-user'; // static const String updateAutomation = '/automation/{automationId}'; } From 573852b4b4c72f538b50e2bc6ef7726fdce0ef96 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 18 Dec 2024 17:11:37 +0300 Subject: [PATCH 007/106] user_invite --- assets/icons/user_management.svg | 7 ++ lib/pages/home/bloc/home_bloc.dart | 4 +- .../users_page/bloc/users_bloc.dart | 76 +++++++++++------ .../users_page/bloc/users_event.dart | 19 +++++ .../users_page/bloc/users_status.dart | 18 ++++ .../users_page/view/add_user_dialog.dart | 83 ++++++++++++------- .../users_page/view/basics_view.dart | 79 +++--------------- .../users_page/view/roles_and_permission.dart | 1 + lib/utils/constants/assets.dart | 12 +-- lib/utils/user_drop_down_menu.dart | 43 +++++++--- 10 files changed, 198 insertions(+), 144 deletions(-) create mode 100644 assets/icons/user_management.svg diff --git a/assets/icons/user_management.svg b/assets/icons/user_management.svg new file mode 100644 index 00000000..3255117a --- /dev/null +++ b/assets/icons/user_management.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 04c35295..022354fa 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -95,9 +95,7 @@ class HomeBloc extends Bloc { title: 'Move in', icon: Assets.moveinIcon, active: true, - onPress: (context) { - context.go(RoutesConst.rolesAndPermissions); - }, + onPress: (context) {}, color: ColorsManager.primaryColor, ), HomeItemModel( diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index ec3139b7..f0e3c9ad 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -22,9 +22,20 @@ class UsersBloc extends Bloc { on(_getRolePermission); on(_getPermissions); on(searchRolePermission); + on(_sendInvitUser); + on(_validateBasicsStep); + on(isCompleteRoleFun); + } + void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { + if (formKey.currentState?.validate() ?? false) { + emit(const BasicsStepValidState()); + } else { + emit(const BasicsStepInvalidState()); + } } List users = []; + String roleSelected = ''; Future _getUsers(GetUsers event, Emitter emit) async { emit(UsersLoadingState()); @@ -106,31 +117,42 @@ class UsersBloc extends Bloc { int numberSpaces = 0; int numberRole = 0; - isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { + // isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { + // emit(UsersLoadingState()); + // isCompleteBasics = firstNameController.text.isNotEmpty && + // lastNameController.text.isNotEmpty && + // emailController.text.isNotEmpty; + // emit(ChangeStatusSteps()); + // return isCompleteBasics; + // } + + bool isCompleteBasicsFun(CheckStepStatus event, Emitter 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; + emailController.text.isNotEmpty && + emailRegex.hasMatch(emailController.text); + emit(ChangeStatusSteps()); - return isCompleteBasics; + return isCompleteBasics!; } void isCompleteSpacesFun( CheckSpacesStepStatus event, Emitter emit) { emit(UsersLoadingState()); - try { - List selectedIds = getSelectedIds(updatedCommunities); - isCompleteSpaces = selectedIds.isNotEmpty; - } catch (e) { - emit(ErrorState('Error while retrieving selected IDs: $e')); - return; - } - + List selectedIds = getSelectedIds(updatedCommunities); + isCompleteSpaces = selectedIds.isNotEmpty; emit(ChangeStatusSteps()); } - bool checkRolePermissions() { - return true; + void isCompleteRoleFun(CheckRoleStepStatus event, Emitter emit) { + emit(UsersLoadingState()); + isCompleteRolePermissions = roleSelected != ''; + emit(ChangeStatusSteps()); } Future> _fetchSpacesForCommunity( @@ -249,6 +271,7 @@ class UsersBloc extends Bloc { emit(UsersLoadingState()); permissions = await UserPermissionApi().fetchPermission( event.roleUuid == "" ? roles.first.uuid : event.roleUuid); + roleSelected = event.roleUuid!; emit(RolePermissionInitial()); } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); @@ -268,17 +291,22 @@ class UsersBloc extends Bloc { return anyMatch; } - _sendInvitUser(List nodes, String searchTerm) async { - emit(UsersLoadingState()); - await UserPermissionApi().sendInviteUser( - email: emailController.text, - firstName: firstNameController.text, - jobTitle: jobTitleController.text, - lastName: lastNameController.text, - phoneNumber: phoneController.text, - roleUuid: '', - spaceUuids: selectedIds); - emit(RolePermissionInitial()); + _sendInvitUser(SendInviteUsers event, Emitter emit) async { + try { + emit(UsersLoadingState()); + List 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); + emit(SaveState()); + } catch (e) { + emit(ErrorState('Error: $e')); + } } void searchRolePermission(SearchPermission event, Emitter emit) { diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart index 6f59b495..633c1ea5 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -12,12 +12,25 @@ class GetUsers extends UsersEvent { List get props => []; } +class SendInviteUsers extends UsersEvent { + const SendInviteUsers(); + @override + List get props => []; +} + class CheckSpacesStepStatus extends UsersEvent { const CheckSpacesStepStatus(); @override List get props => []; } +class CheckRoleStepStatus extends UsersEvent { + const CheckRoleStepStatus(); + @override + List get props => []; +} + + class LoadCommunityAndSpacesEvent extends UsersEvent { const LoadCommunityAndSpacesEvent(); @override @@ -85,3 +98,9 @@ class SelecteId extends UsersEvent { @override List get props => [nodes]; } + +class ValidateBasicsStep extends UsersEvent { + const ValidateBasicsStep(); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart index 5d1a68f6..b5fc01d7 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart @@ -9,6 +9,7 @@ final class UsersInitial extends UsersState { @override List get props => []; } + final class RolePermissionInitial extends UsersState { @override List get props => []; @@ -24,6 +25,11 @@ final class UsersLoadingState extends UsersState { List get props => []; } +final class SaveState extends UsersState { + @override + List get props => []; +} + final class UsersLoadedState extends UsersState { List users = []; UsersLoadedState({required this.users}); @@ -59,3 +65,15 @@ final class ChangeTapStatus extends UsersState { @override List get props => [select]; } + +final class BasicsStepValidState extends UsersState { + const BasicsStepValidState(); + @override + List get props => []; +} + +class BasicsStepInvalidState extends UsersState { + const BasicsStepInvalidState(); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart index dc45cfdc..cae89058 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -88,37 +88,6 @@ class _AddNewUserDialogState extends State { child: _getFormContent(), ), const SizedBox(height: 20), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("Cancel"), - ), - ElevatedButton( - onPressed: () { - if (_blocRole.formKey.currentState - ?.validate() ?? - false) { - // Proceed to next step or finish - setState(() { - if (currentStep < 3) { - currentStep++; - } else { - Navigator.of(context).pop(); - } - }); - } - }, - child: Text(currentStep < 3 - ? "Next" - : "Finish"), - ), - ], - ), ], ), ), @@ -126,6 +95,53 @@ class _AddNewUserDialogState extends State { ], ), ), + const Divider(), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + InkWell( + onTap: () { + setState(() { + if (currentStep < 3) { + currentStep++; + if (currentStep == 2) { + _blocRole.add(const CheckStepStatus()); + } else if (currentStep == 3) { + _blocRole + .add(const CheckSpacesStepStatus()); + _blocRole + .add(const CheckSpacesStepStatus()); + } else { + _blocRole.add(const SendInviteUsers()); + } + } + }); + }, + child: Text( + currentStep < 3 ? "Next" : "Save", + style: TextStyle( + color: (_blocRole.isCompleteSpaces == false || + _blocRole.isCompleteBasics == + false || + _blocRole + .isCompleteRolePermissions == + false) && + currentStep == 3 + ? ColorsManager.grayColor + : ColorsManager.secondaryColor), + ), + ), + ], + ), + ), ], ), )); @@ -209,8 +225,12 @@ class _AddNewUserDialogState extends State { return GestureDetector( onTap: () { setState(() { + bloc.add(const CheckSpacesStepStatus()); currentStep = step; }); + Future.delayed(const Duration(milliseconds: 500), () { + bloc.add(const ValidateBasicsStep()); + }); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -270,6 +290,7 @@ class _AddNewUserDialogState extends State { setState(() { currentStep = step; bloc.add(const CheckSpacesStepStatus()); + bloc.add(const CheckStepStatus()); }); }, child: Column( diff --git a/lib/pages/roles_and_permission/users_page/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/view/basics_view.dart index abf6642c..22398ed3 100644 --- a/lib/pages/roles_and_permission/users_page/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/view/basics_view.dart @@ -14,6 +14,9 @@ class BasicsView extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { final _blocRole = BlocProvider.of(context); + if (state is BasicsStepInvalidState) { + _blocRole.formKey.currentState?.validate(); + } return Form( key: _blocRole.formKey, child: ListView( @@ -184,7 +187,14 @@ class BasicsView extends StatelessWidget { ), validator: (value) { if (value == null || value.isEmpty) { - return 'Enter last name'; + 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'; } return null; }, @@ -213,49 +223,6 @@ class BasicsView extends StatelessWidget { ), ], )), - // InternationalPhoneNumberInput( - // spaceBetweenSelectorAndTextField: 50, - // initialValue: PhoneNumber(isoCode: 'AE'), - // inputDecoration: inputTextFormDeco( - // hintText: "x xxx xxxx", - // ).copyWith( - // hintStyle: context.textTheme.bodyMedium?.copyWith( - // fontWeight: FontWeight.w400, - // fontSize: 12, - // color: ColorsManager.textGray), - // ), - // onInputChanged: (PhoneNumber number) { - // print(number.phoneNumber); - // }, - // onInputValidated: (bool value) { - // print(value); - // }, - // selectorConfig: const SelectorConfig( - // selectorType: PhoneInputSelectorType.BOTTOM_SHEET, - // useBottomSheetSafeArea: true, - // leadingPadding: 15, - // trailingSpace: false, - // setSelectorButtonAsPrefixIcon: true), - // ignoreBlank: true, - // autoValidateMode: AutovalidateMode.disabled, - - // selectorTextStyle: - // TextStyle(color: ColorsManager.blackColor), - // // initialValue: number, - // // textFieldController: controller, - // formatInput: true, - // keyboardType: const TextInputType.numberWithOptions( - // signed: true, decimal: true), - // // inputBorder: OutlineInputBorder( - // // borderSide: - // // BorderSide(color: Colors.black,)), - // onSaved: (PhoneNumber number) { - // print('On Saved: $number'); - // }, - // textStyle: - // const TextStyle(color: ColorsManager.blackColor), - // ), - IntlPhoneField( pickerDialogStyle: PickerDialogStyle(), dropdownIconPosition: IconPosition.leading, @@ -270,36 +237,12 @@ class BasicsView extends StatelessWidget { fontSize: 12, color: ColorsManager.textGray), ), - initialCountryCode: 'AE', style: TextStyle(color: Colors.black), onChanged: (phone) { print(phone.completeNumber); }, ) - - // Padding( - // padding: const EdgeInsets.all(8.0), - // child: TextFormField( - // style: const TextStyle(color: Colors.black), - // controller: _blocRole.phoneController, - // decoration: inputTextFormDeco( - // hintText: "05x xxx xxxx", - // ).copyWith( - // hintStyle: context.textTheme.bodyMedium?.copyWith( - // fontWeight: FontWeight.w400, - // fontSize: 12, - // color: ColorsManager.textGray), - // ), - // validator: (value) { - // if (value == null || value.isEmpty) { - // return 'Please enter a phone number'; - // } - // return null; - // }, - // keyboardType: TextInputType.phone, - // ), - // ), ], ), ), diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart index a8c186e8..9b7ab542 100644 --- a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart @@ -71,6 +71,7 @@ class RolesAndPermission extends StatelessWidget { const TextStyle(color: Colors.black), controller: _blocRole.firstNameController, onChanged: (value) { + _blocRole.add(SearchPermission( nodes: _blocRole.permissions, searchTerm: value)); diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index acb314d9..8b271d5e 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -389,10 +389,10 @@ class Assets { 'assets/icons/current_process_icon.svg'; static const String uncomplete_ProcessIcon = 'assets/icons/uncompleate_process_icon.svg'; - static const String wrongProcessIcon = - 'assets/icons/wrong_process_icon.svg'; - static const String arrowForward = - 'assets/icons/arrow_forward.svg'; - static const String arrowDown = - 'assets/icons/arrow_down.svg'; + static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg'; + static const String arrowForward = 'assets/icons/arrow_forward.svg'; + static const String arrowDown = 'assets/icons/arrow_down.svg'; + + static const String userManagement = 'assets/icons/user_management.svg'; } +//user_management.svg diff --git a/lib/utils/user_drop_down_menu.dart b/lib/utils/user_drop_down_menu.dart index 3a0c4194..5bdc18fd 100644 --- a/lib/utils/user_drop_down_menu.dart +++ b/lib/utils/user_drop_down_menu.dart @@ -53,7 +53,8 @@ class _UserDropdownMenuState extends State { } Future _showPopupMenu(BuildContext context) async { - final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox; + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; final RelativeRect position = RelativeRect.fromRect( Rect.fromLTRB( overlay.size.width, @@ -86,11 +87,13 @@ class _UserDropdownMenuState extends State { ), ), PopupMenuItem( - onTap: () {}, + onTap: () { + context.go(RoutesConst.rolesAndPermissions); + }, child: ListTile( - leading: SvgPicture.asset(Assets.settings), + leading: SvgPicture.asset(Assets.userManagement), title: Text( - "Settings", + "User Management", style: context.textTheme.bodyMedium, ), ), @@ -107,7 +110,8 @@ class _UserDropdownMenuState extends State { height: 200, width: 400, child: Padding( - padding: const EdgeInsets.only(top: 24, left: 24, right: 24), + padding: + const EdgeInsets.only(top: 24, left: 24, right: 24), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -120,7 +124,10 @@ class _UserDropdownMenuState extends State { padding: const EdgeInsets.only(top: 16), child: Text( 'Log out of your Syncrow account', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black, @@ -151,11 +158,15 @@ class _UserDropdownMenuState extends State { ), Expanded( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Text( '${widget.user?.firstName ?? ''} ${widget.user?.lastName}', - style: Theme.of(context).textTheme.titleMedium!.copyWith( + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith( color: Colors.black, fontWeight: FontWeight.bold, fontSize: 20, @@ -163,7 +174,10 @@ class _UserDropdownMenuState extends State { ), Text( ' ${widget.user?.email}', - style: Theme.of(context).textTheme.bodySmall!.copyWith( + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( color: Colors.black, ), ), @@ -189,7 +203,10 @@ class _UserDropdownMenuState extends State { elevation: 1, child: Text( 'Cancel', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( fontSize: 12, color: Colors.black, ), @@ -211,8 +228,10 @@ class _UserDropdownMenuState extends State { elevation: 1, child: Text( 'Logout', - style: - Theme.of(context).textTheme.bodyMedium!.copyWith(fontSize: 12, color: Colors.white), + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(fontSize: 12, color: Colors.white), ), ), ), From ed2187b7ff4db67579fa7a68c838d0c8c9e4278e Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 26 Dec 2024 16:32:18 +0300 Subject: [PATCH 008/106] add_user_dialog --- assets/icons/atoz_icon.png | Bin 0 -> 1540 bytes assets/icons/filter_table_icon.svg | 3 + assets/icons/ztoa_icon.png | Bin 0 -> 1513 bytes lib/pages/common/custom_dialog.dart | 2 +- .../model/role_type_model.dart | 2 +- .../bloc/users_bloc.dart | 185 +++---- .../bloc/users_event.dart | 33 +- .../bloc/users_status.dart | 11 +- .../model/permission_option_model.dart | 40 ++ .../model/tree_node_model.dart | 0 .../view/add_user_dialog.dart | 167 +++--- .../view/basics_view.dart | 99 ++-- .../view/delete_user_dialog.dart | 4 +- .../view/permission_management.dart | 239 +++++++++ .../add_user_dialog/view/role_dropdown.dart | 115 ++++ .../view/roles_and_permission.dart | 127 +++++ .../view/spaces_access_view.dart | 8 +- .../users_table/bloc/user_table_bloc.dart | 185 +++++++ .../users_table/bloc/user_table_event.dart | 63 +++ .../users_table/bloc/user_table_state.dart | 82 +++ .../view/creation_date_filter.dart | 73 +++ .../users_table/view/de_activate_filter.dart | 73 +++ .../users_table/view/name_filter.dart | 69 +++ .../users_table/view/user_table.dart | 299 +++++++++++ .../{ => users_table}/view/users_page.dart | 83 ++- .../users_page/view/roles_and_permission.dart | 489 ------------------ .../users_page/view/user_table.dart | 256 --------- .../view/roles_and_permission_page.dart | 14 +- lib/services/space_mana_api.dart | 1 - lib/services/user_permission.dart | 68 ++- lib/utils/constants/api_const.dart | 38 +- lib/utils/constants/assets.dart | 3 + 32 files changed, 1783 insertions(+), 1048 deletions(-) create mode 100644 assets/icons/atoz_icon.png create mode 100644 assets/icons/filter_table_icon.svg create mode 100644 assets/icons/ztoa_icon.png rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/bloc/users_bloc.dart (73%) rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/bloc/users_event.dart (82%) rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/bloc/users_status.dart (88%) create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/model/tree_node_model.dart (100%) rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/view/add_user_dialog.dart (92%) rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/view/basics_view.dart (76%) rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/view/delete_user_dialog.dart (96%) create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/view/spaces_access_view.dart (98%) create mode 100644 lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart rename lib/pages/roles_and_permission/users_page/{ => users_table}/view/users_page.dart (73%) delete mode 100644 lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart delete mode 100644 lib/pages/roles_and_permission/users_page/view/user_table.dart diff --git a/assets/icons/atoz_icon.png b/assets/icons/atoz_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..33a9c351375b827aba0c385af50da41414fec209 GIT binary patch literal 1540 zcmV+f2K)JmP)5XvcS(j2Vzz()WZZ2Y}5eP`EeFL=L94RP~Gqn(}E9Y22O{hD_H{)ZsN zOs0=87KRgIdk-LAG@g6$G7}pf?oPl75Dq^K$eHx$?%rRiCu_4Y{hWwx0b3AG3Q}nX zU@{qb^k`%7B*cY6ae@*OVZh)<|9E0$buM0=Ln#O0^bkbikCAu~8U)krK4CDSpA+Ep z(J=Aw(EU-+?>>W2s4o)GZqI@=M?=H~B7Av#tSFQt)p@fQi)GcotXf%yrV~dg2Q}3| z^6Pj>@jfH)nI-9D8enr5JwwvE@nR1q}DkSS?O!4WuNr_btO zNt^(gsB}_DE`g3Ism%!#lpP?Ybeb8F;oHw_2)SGU2Z!c<`uU%SR<9?Biyd)?-wfRk z$v_@!_p)4qSG&*k@1(p`ylNpNu@||n`Xd|zPW@I|r!4bBTaOOCxFn%K3`uuL>)9d4P zX|tiE74@)mu{CUN6(OIuHvrL1ptI8hPQjBVDwn6qxHKk9SL5r`iRxNLFYFyS*VA;G zLN@Cx32F0-AbFi_En87l*WXlAW{#FCTyBAB?R3EqWj|AD0>Y$%t z{~&9=#q}}+Jdi<(%+dZ|decLBPv7&Irwlbsz^KI{znizp9 zup!N$zpGR?{y|4=?5$khu^~1KZ=QOTD1qfI)wlMd8ua>AiLSo#C`QWhVnc*<)?7U< zOC|W|Vh)-K8`1zfIdwC-q*^++lNYMqV>3_SAaA}#o7kA@e0}q$J5)WPB(yO+)Q(`5 z7sBq$^3%CR+kD#RR;B3H8Jkb!Rwz_RQD7FUcr4EWN5QK~G+&tJef{1so^SI3qNJ|Y zq}OSebyyfS}$90fhOymn97 z1zSUWxv%bTr$nLaoa&tU7@t%6NeMDp^Am7~SB73+V+l7WjuPkH5smQ5+FVqdw>88+ zl5VBX!+4eSfx8yQQ=)AcZb-;n?Mwjh#oAN5sasJ<>useHs?^D4+Eec#R4=F`7z zc$L0BpI+Yf)C_Uc;~<}QMjGW1!&P~8G^o);1S$=Il3xFDy`>BhJw^gAgW5fhkg!XB zNAjw=P?s`in@{(5sqX56r>3_bUMf}9<5I4O#T|2FYc%)JL(ZhIf?EB&GP6=S+}h^T zJ^uP1?LJ={BmNR(UCqy@zrA^{uTY|u>OV<%sVl0@8CsC!g{H%eB?R7_qD);2TAvAQ z3`+^M1S2e=^$DYWcF*J2+rYmD_;@3;{F~?i0000 + + diff --git a/assets/icons/ztoa_icon.png b/assets/icons/ztoa_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..003e572526ff10c21a3b27befd50e025ed515491 GIT binary patch literal 1513 zcmVv%6j|<|8J68Y(pyRXMbkT9R;R4yBINN)Rck+x`L2V)$kF|HEZ+2OG4ZGf1aHyM~wBpUo?(*B2-+S}s z8Q^~iqUUl$gs}u15gRW6in{yQlc%|bv9bOX1i)uw-%SG}QEpeL4qID)IYvq9-s(3C zob=yFItE9?#*R1X$B*_>L}r&oi6=(CpCCYP+ik-r!{+wmx_bzv=cVHi7C?*{E}#^IK$-Zx4*1SEk0SW$39Cg?E35Of z&T-dy>h1z))9?#@2+1-)W-qWzI6P;Z%L2n*RThlOmk&PdI@5gFBVmB{w#JvHLPJC% zG6)cl)e|xan|&BP>SST#rA7}SFu4Baxz1T`9^PE^)aJHhMUz|u3#wBPD3<7LITrGGTYm#tXz2ilni$D2YE2L=?TB7z zIMX5gq_dsY?4D9r{&#z7eXkXY!j9N1 zyn6B|Qv%D+t=Kw%G8pKzX7v81YC0*$lQjiTIo|R(qm|)8cLACSAJPC@1#2<-v|8q} zRg|jHW4%ZqQ}ljEhZ0OWpI`Z53Qt3P7fjiGx>Omzba66{=FQ~?^NYSGyhzBJ#gN0< zA`16HX`d7Y*5EB_!*f7A5{cVUS}e_R+qrgV&zc4?H$^o_&QIMQF+F5h9z zbHv5PFVeVu-@${xxa$p!-Qj;)&JnR-gNIs+oD=*+zP$RaxeC6H_;lA=-(HPEUz-)2 zc^@A$?HvtrdG8Z&PuPHgNU(&P6UQ0n=D1DRc_R09#6NdJrp>qUD(Ot9wvDG1-)%f7 zX`A@fk8dTJ1gVw#^GWAeuu{tj5a|pM`_iNzrsU6+8*;IZEr4o)@`!8CCSt~$9h45y@GcT zQe_4`YVf3q7i}txCKzK-lFGkh*PIY(zE%IW7B+2XKE1r5)*NwN709Q9kv3XaVcwLJ@{Qcp{YM*aDJ=|x7t1h*s+xJeFtMWKq(6PAV?QB7F4-P`4Dct?) z`XnnKNdgGu(`P#m|I_~VS|8~wLtbuPKApJo;}F)5hfH#EXn}X(6~#{&$5Z(%cSu)x zXklnNLRdoJmmefbr)(QjG0a2Y=6*9;R4UfTxINevu`Us4s&>4x*a0wyo+;Ji*pAw5 z!hn~7lP$F)@&XO)*QQM{X{PQ5bV!s)w56hlXG-b-9tfsehBS}OP?JW|{vDC2%3wvN z{HHi`M`Q#(RQ4ND_>aJ_x^Ehi9JPOSBItlWEnQ1F@4IwilIPeM3Q6o=ZwhN-4#k|R P00000NkvXXu0mjfgwxI} literal 0 HcmV?d00001 diff --git a/lib/pages/common/custom_dialog.dart b/lib/pages/common/custom_dialog.dart index a40ef10f..9899bda4 100644 --- a/lib/pages/common/custom_dialog.dart +++ b/lib/pages/common/custom_dialog.dart @@ -12,7 +12,7 @@ Future showCustomDialog({ double? iconWidth, VoidCallback? onOkPressed, bool barrierDismissible = false, - required List actions, + List? actions, }) { return showDialog( context: context, diff --git a/lib/pages/roles_and_permission/model/role_type_model.dart b/lib/pages/roles_and_permission/model/role_type_model.dart index 1705e25c..16b24ec5 100644 --- a/lib/pages/roles_and_permission/model/role_type_model.dart +++ b/lib/pages/roles_and_permission/model/role_type_model.dart @@ -16,7 +16,7 @@ class RoleTypeModel { uuid: json['uuid'], createdAt: json['createdAt'], updatedAt: json['updatedAt'], - type: json['type'], + type: json['type'].toString().toLowerCase().replaceAll("_", " "), ); } } diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart similarity index 73% rename from lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index f0e3c9ad..f0ddf01e 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -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 { UsersBloc() : super(UsersInitial()) { - on(_getUsers); - on(_changeUserStatus); on(isCompleteBasicsFun); on(_onLoadCommunityAndSpaces); on(searchTreeNode); @@ -25,6 +24,7 @@ class UsersBloc extends Bloc { on(_sendInvitUser); on(_validateBasicsStep); on(isCompleteRoleFun); + on(checkEmail); } void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { if (formKey.currentState?.validate() ?? false) { @@ -34,74 +34,8 @@ class UsersBloc extends Bloc { } } - List users = []; String roleSelected = ''; - Future _getUsers(GetUsers event, Emitter emit) async { - emit(UsersLoadingState()); - try { - users = [ - RolesUserModel( - id: '1', - userName: 'user 1', - userEmail: 'test1@test.com', - action: '', - createdBy: 'Admin', - creationDate: '25/10/2024', - creationTime: '10:30 AM', - status: 'Invited', - ), - RolesUserModel( - id: '2', - userName: 'user 2', - userEmail: 'test2@test.com', - action: '', - createdBy: 'Admin', - creationDate: '25/10/2024', - creationTime: '10:30 AM', - status: 'Active', - ), - RolesUserModel( - id: '3', - userName: 'user 3', - userEmail: 'test3@test.com', - action: '', - createdBy: 'Admin', - creationDate: '25/10/2024', - creationTime: '10:30 AM', - status: 'Disabled', - ), - ]; - emit(UsersLoadedState(users: users)); - } catch (e) { - emit(ErrorState(e.toString())); - } - } - - void _changeUserStatus(ChangeUserStatus event, Emitter 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(); final TextEditingController firstNameController = TextEditingController(); final TextEditingController lastNameController = TextEditingController(); @@ -109,6 +43,9 @@ class UsersBloc extends Bloc { 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 { int numberSpaces = 0; int numberRole = 0; - // isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { - // emit(UsersLoadingState()); - // isCompleteBasics = firstNameController.text.isNotEmpty && - // lastNameController.text.isNotEmpty && - // emailController.text.isNotEmpty; - // emit(ChangeStatusSteps()); - // return isCompleteBasics; - // } - - bool isCompleteBasicsFun(CheckStepStatus event, Emitter 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 emit) { emit(UsersLoadingState()); @@ -184,7 +97,7 @@ class UsersBloc extends Bloc { ); }).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 { } List selectedIds = []; + List getSelectedIds(List nodes) { List selectedIds = []; for (var node in nodes) { @@ -259,7 +173,7 @@ class UsersBloc extends Bloc { 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 { _sendInvitUser(SendInviteUsers event, Emitter emit) async { try { emit(UsersLoadingState()); - List 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 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: [ + 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 { emit(ChangeStatusSteps()); } + String checkEmailValid = ''; + + Future checkEmail( + CheckEmailEvent event, Emitter emit) async { + emit(UsersLoadingState()); + String? res = await UserPermissionApi().checkEmail( + emailController.text, + ); + checkEmailValid = res!; + emit(ChangeStatusSteps()); + } + + bool isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { + emit(UsersLoadingState()); + add(const CheckEmailEvent()); + final emailRegex = RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + ); + bool isEmailValid = emailRegex.hasMatch(emailController.text); + bool isEmailServerValid = checkEmailValid == 'Valid email'; + isCompleteBasics = firstNameController.text.isNotEmpty && + lastNameController.text.isNotEmpty && + emailController.text.isNotEmpty && + isEmailValid && + isEmailServerValid; + + emit(ChangeStatusSteps()); + emit(ValidateBasics()); + return isCompleteBasics!; + } + void _clearHighlightsRolePermission(List nodes) { for (var node in nodes) { node.isHighlighted = false; diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart similarity index 82% rename from lib/pages/roles_and_permission/users_page/bloc/users_event.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart index 633c1ea5..950726d4 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart @@ -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 get props => []; -} - class SendInviteUsers extends UsersEvent { - const SendInviteUsers(); + final BuildContext context; + const SendInviteUsers({required this.context}); @override - List get props => []; + List get props => [context]; } class CheckSpacesStepStatus extends UsersEvent { @@ -30,7 +26,6 @@ class CheckRoleStepStatus extends UsersEvent { List get props => []; } - class LoadCommunityAndSpacesEvent extends UsersEvent { const LoadCommunityAndSpacesEvent(); @override @@ -57,16 +52,6 @@ class GetBatchStatus extends UsersEvent { List get props => [uuids]; } -class ChangeUserStatus extends UsersEvent { - final String userId; - final String newStatus; - - const ChangeUserStatus({required this.userId, required this.newStatus}); - - @override - List 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 get props => []; } + +class CheckEmailEvent extends UsersEvent { + const CheckEmailEvent(); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart similarity index 88% rename from lib/pages/roles_and_permission/users_page/bloc/users_status.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart index b5fc01d7..c1bf3512 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart @@ -30,11 +30,10 @@ final class SaveState extends UsersState { List get props => []; } -final class UsersLoadedState extends UsersState { - List users = []; - UsersLoadedState({required this.users}); +final class SpacesLoadedState extends UsersState { + SpacesLoadedState(); @override - List get props => [users]; + List get props => []; } final class ErrorState extends UsersState { @@ -77,3 +76,7 @@ class BasicsStepInvalidState extends UsersState { @override List get props => []; } +final class ValidateBasics extends UsersState { + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart new file mode 100644 index 00000000..c476ebb4 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart @@ -0,0 +1,40 @@ +class PermissionOption { + String id; + String title; + bool isChecked; + bool isHighlighted; + List subOptions; + + PermissionOption({ + required this.id, + required this.title, + this.isChecked = false, + this.isHighlighted = false, + this.subOptions = const [], + }); + + factory PermissionOption.fromJson(Map 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 toJson() { + return { + 'id': id, + 'title': title, + 'isChecked': isChecked, + 'isHighlighted': isHighlighted, + 'subOptions': subOptions.map((sub) => sub.toJson()).toList(), + }; + } +} + +enum CheckState { none, some, all } diff --git a/lib/pages/roles_and_permission/users_page/model/tree_node_model.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart similarity index 100% rename from lib/pages/roles_and_permission/users_page/model/tree_node_model.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart similarity index 92% rename from lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart index cae89058..14f7a0fb 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -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 { Expanded( child: Row( children: [ - // Sidebar for Steps Expanded( child: Container( padding: const EdgeInsets.all(20), @@ -75,7 +74,6 @@ class _AddNewUserDialogState extends State { width: 1, color: ColorsManager.grayBorder, ), - // Main content (Form) Expanded( flex: 2, child: Padding( @@ -109,6 +107,8 @@ class _AddNewUserDialogState extends State { ), InkWell( onTap: () { + _blocRole.add(const CheckEmailEvent()); + setState(() { if (currentStep < 3) { currentStep++; @@ -117,11 +117,10 @@ class _AddNewUserDialogState extends State { } 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 { } } + 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 { ); } - 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 { 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 { ), ); } - - 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, - ), - ), - ) - ], - ), - ); - } } diff --git a/lib/pages/roles_and_permission/users_page/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart similarity index 76% rename from lib/pages/roles_and_permission/users_page/view/basics_view.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index 22398ed3..a6ec686b 100644 --- a/lib/pages/roles_and_permission/users_page/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -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: "name@example.com") .copyWith( hintStyle: context.textTheme.bodyMedium?.copyWith( @@ -189,24 +203,24 @@ class BasicsView extends StatelessWidget { if (value == null || value.isEmpty) { return 'Enter Email Address'; } - // Regular expression for email validation final emailRegex = RegExp( r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', ); if (!emailRegex.hasMatch(value)) { return 'Enter a valid Email Address'; } + if (_blocRole.checkEmailValid != "Valid email") { + return _blocRole.checkEmailValid; + } return null; }, ), ), ], ), - SizedBox(height: 10), + const SizedBox(height: 10), Row( children: [ - // Phone Number - Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -227,7 +241,8 @@ class BasicsView extends StatelessWidget { pickerDialogStyle: PickerDialogStyle(), dropdownIconPosition: IconPosition.leading, disableLengthCheck: true, - dropdownTextStyle: TextStyle(color: Colors.black), + dropdownTextStyle: + const TextStyle(color: ColorsManager.blackColor), textInputAction: TextInputAction.done, decoration: inputTextFormDeco( hintText: "05x xxx xxxx", @@ -238,18 +253,39 @@ class BasicsView extends StatelessWidget { color: ColorsManager.textGray), ), initialCountryCode: 'AE', - style: TextStyle(color: Colors.black), - onChanged: (phone) { - print(phone.completeNumber); - }, + countries: const [ + Country( + name: "United Arab Emirates", + nameTranslations: { + "en": "United Arab Emirates", + "ar": "الإمارات العربية المتحدة", + }, + flag: "🇦🇪", + code: "AE", + dialCode: "971", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Saudi Arabia", + nameTranslations: { + "en": "Saudi Arabia", + "ar": "السعودية", + }, + flag: "🇸🇦", + code: "SA", + dialCode: "966", + minLength: 9, + maxLength: 9, + ), + ], + style: const TextStyle(color: Colors.black), + controller: _blocRole.phoneController, ) ], ), ), - - SizedBox(width: 10), - - // Job Title + const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -270,7 +306,8 @@ class BasicsView extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: TextFormField( controller: _blocRole.jobTitleController, - style: TextStyle(color: Colors.black), + style: + const TextStyle(color: ColorsManager.blackColor), decoration: inputTextFormDeco( hintText: "Job Title (Optional)") .copyWith( @@ -287,7 +324,7 @@ class BasicsView extends StatelessWidget { ), ], ), - SizedBox(height: 20), + const SizedBox(height: 20), ], ), ); diff --git a/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart similarity index 96% rename from lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart index f10fd4ed..002b0171 100644 --- a/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart @@ -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 { diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart new file mode 100644 index 00000000..b16cd8d2 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart @@ -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 { + 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(), + ], + ); + }, + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart new file mode 100644 index 00000000..1bc9331e --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart @@ -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 { + 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( + dropdownColor: ColorsManager.whiteColors, + alignment: Alignment.center, + focusColor: Colors.white, + autofocus: true, + value: selectedRole.isNotEmpty ? selectedRole : null, + items: widget.bloc!.roles.map((role) { + return DropdownMenuItem( + 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, + ), + ), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart new file mode 100644 index 00000000..b054a88c --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart @@ -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(builder: (context, state) { + final _blocRole = BlocProvider.of(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, + )))) + ], + ), + ), + ), + ], + ), + ), + ); + }); + } +} diff --git a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart similarity index 98% rename from lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart index 4bc330b2..f942d1d5 100644 --- a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart @@ -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'; 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 new file mode 100644 index 00000000..2a31a91f --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.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 { + UserTableBloc() : super(TableInitial()) { + on(_getUsers); + on(_changeUserStatus); + on(_toggleSortUsersByNameAsc); + on(_toggleSortUsersByNameDesc); + on(_toggleSortUsersByDateOldestToNewest); + on(_toggleSortUsersByDateNewestToOldest); + } + + List users = []; + List initialUsers = []; // Save the initial state + String currentSortOrder = ''; // Keeps track of the current sorting order + String currentSortOrderDate = ''; // Keeps track of the current sorting order + + Future _getUsers(GetUsers event, Emitter emit) async { + emit(UsersLoadingState()); + try { + users = [ + RolesUserModel( + id: '1', + userName: 'b 1', + userEmail: 'test1@test.com', + action: '', + createdBy: 'Admin', + creationDate: '25/10/2024', + creationTime: '10:30 AM', + status: 'Invited', + ), + RolesUserModel( + id: '2', + userName: 'a 2', + userEmail: 'test2@test.com', + action: '', + createdBy: 'Admin', + creationDate: '24/10/2024', + creationTime: '2:30 PM', + status: 'Active', + ), + RolesUserModel( + id: '3', + userName: 'c 3', + userEmail: 'test3@test.com', + action: '', + createdBy: 'Admin', + creationDate: '23/10/2024', + creationTime: '9:00 AM', + status: 'Disabled', + ), + ]; + // Sort users by newest to oldest as default + 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 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 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 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 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 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 '); + } + } +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart new file mode 100644 index 00000000..a6c77bd3 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart @@ -0,0 +1,63 @@ +import 'package:equatable/equatable.dart'; + +sealed class UserTableEvent extends Equatable { + const UserTableEvent(); +} + +class GetRoles extends UserTableEvent { + const GetRoles(); + @override + List get props => []; +} + +class GetUsers extends UserTableEvent { + const GetUsers(); + @override + List get props => []; +} + +class ChangeUserStatus extends UserTableEvent { + final String userId; + final String newStatus; + + const ChangeUserStatus({required this.userId, required this.newStatus}); + + @override + List get props => [userId, newStatus]; +} + +class SortUsersByNameAsc extends UserTableEvent { + const SortUsersByNameAsc(); + + @override + List get props => []; +} + +class SortUsersByNameDesc extends UserTableEvent { + const SortUsersByNameDesc(); + + @override + List get props => []; +} + +class StoreUsersEvent extends UserTableEvent { + const StoreUsersEvent(); + + @override + List get props => []; +} + + +class DateNewestToOldestEvent extends UserTableEvent { + const DateNewestToOldestEvent(); + + @override + List get props => []; +} + +class DateOldestToNewestEvent extends UserTableEvent { + const DateOldestToNewestEvent(); + + @override + List get props => []; +} \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart new file mode 100644 index 00000000..2a132947 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart @@ -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 get props => []; +} + +final class RolesLoadingState extends UserTableState { + @override + List get props => []; +} + +final class UsersLoadingState extends UserTableState { + @override + List get props => []; +} + +final class RolesLoadedState extends UserTableState { + @override + List get props => []; +} + +final class UsersLoadedState extends UserTableState { + List users = []; + UsersLoadedState({required this.users}); + @override + List get props => [users]; +} + +final class ErrorState extends UserTableState { + final String message; + + const ErrorState(this.message); + + @override + List get props => [message]; +} + +/// report state +final class SosReportLoadingState extends UserTableState { + @override + List get props => []; +} + +final class RolesErrorState extends UserTableState { + final String message; + + const RolesErrorState(this.message); + + @override + List get props => [message]; +} + +/// automation reports + +final class SosAutomationReportLoadingState extends UserTableState { + @override + List get props => []; +} + +final class SosAutomationReportErrorState extends UserTableState { + final String message; + + const SosAutomationReportErrorState(this.message); + + @override + List get props => [message]; +} + +final class ChangeTapStatus extends UserTableState { + bool select = true; + + ChangeTapStatus({required this.select}); + + @override + List get props => [select]; +} 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 new file mode 100644 index 00000000..45ebc3ae --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart @@ -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 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: [ + 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; + // }); + }); +} 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 new file mode 100644 index 00000000..e78eae6b --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart @@ -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 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: [ + 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; + // }); + }); +} 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 new file mode 100644 index 00000000..e869e10b --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart @@ -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 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: [ + 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; + // }); + }); +} 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 new file mode 100644 index 00000000..926ac92a --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -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 titles; + final List> 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 + with WidgetsBindingObserver { + late List 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.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.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.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(), + // ], + // ), + // ), + // ), + // ); + // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart similarity index 73% rename from lib/pages/roles_and_permission/users_page/view/users_page.dart rename to lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index 273b8a34..758f062b 100644 --- a/lib/pages/roles_and_permission/users_page/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -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() + .read() .add(ChangeUserStatus(userId: userId, newStatus: newStatus)); }, child: Padding( @@ -104,12 +107,10 @@ class UsersPage extends StatelessWidget { ); } -// return RolesAndPermission(); -// } -// } - return BlocBuilder( + return BlocBuilder( builder: (context, state) { final screenSize = MediaQuery.of(context).size; + final _blocRole = BlocProvider.of(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() + .add(const SortUsersByNameAsc()); + }, + zToaTap: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 4) { + showDateFilterMenu( + context: context, + isSelected: _blocRole.currentSortOrderDate, + aToZTap: () { + context + .read() + .add(const DateNewestToOldestEvent()); + }, + zToaTap: () { + context + .read() + .add(const DateOldestToNewestEvent()); + }, + ); + } + if (columnIndex == 8) { + showDeActivateFilterMenu( + context: context, + isSelected: _blocRole.currentSortOrderDate, + aToZTap: () { + context + .read() + .add(const DateNewestToOldestEvent()); + }, + zToaTap: () { + context + .read() + .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: () {}, diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart deleted file mode 100644 index 9b7ab542..00000000 --- a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart +++ /dev/null @@ -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(builder: (context, state) { - final _blocRole = BlocProvider.of(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 { - 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( - alignment: Alignment.center, - focusColor: Colors.white, - autofocus: true, - value: selectedRole, - items: widget.bloc!.roles.map((role) { - return DropdownMenuItem( - 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 { - 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 subOptions; - - PermissionOption({ - required this.id, - required this.title, - this.isChecked = false, - this.isHighlighted = false, - this.subOptions = const [], - }); - - factory PermissionOption.fromJson(Map 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 toJson() { - return { - 'id': id, - 'title': title, - 'isChecked': isChecked, - 'isHighlighted': isHighlighted, - 'subOptions': subOptions.map((sub) => sub.toJson()).toList(), - }; - } -} diff --git a/lib/pages/roles_and_permission/users_page/view/user_table.dart b/lib/pages/roles_and_permission/users_page/view/user_table.dart deleted file mode 100644 index f951f06e..00000000 --- a/lib/pages/roles_and_permission/users_page/view/user_table.dart +++ /dev/null @@ -1,256 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; - -class DynamicTableScreen extends StatefulWidget { - final List titles; - final List> rows; - - DynamicTableScreen({required this.titles, required this.rows}); - - @override - _DynamicTableScreenState createState() => _DynamicTableScreenState(); -} - -class _DynamicTableScreenState extends State - with WidgetsBindingObserver { - late List 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.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.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.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(), - // ], - // ), - // ), - // ), - // ); - // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart index 05f924ca..da003536 100644 --- a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart +++ b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart @@ -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( listener: (context, state) {}, builder: (context, state) { @@ -77,8 +75,8 @@ class RolesAndPermissionPage extends StatelessWidget { ), ], ), - scaffoldBody: BlocProvider( - create: (context) => UsersBloc()..add(const GetUsers()), + scaffoldBody: BlocProvider( + create: (context) => UserTableBloc()..add(const GetUsers()), child: const UsersPage(), ) // _blocRole.tapSelect == false diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 58dc0d9d..5d2464e6 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -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(); diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 22083191..d5cb8243 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -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 sendInviteUser({ String? firstName, String? lastName, String? email, @@ -42,22 +44,58 @@ class UserPermissionApi { String? roleUuid, List? spaceUuids, }) async { - final response = await _httpService.post( - path: ApiEndpoints.permission, - showServerMessage: true, - body: { + try { + final body = { "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 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(); + } } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 28923538..da746fbd 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -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 } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 8b271d5e..1f4074c4 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -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 From c834ad0670875d3d23b817498fbe0e8e89825614 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 29 Dec 2024 09:52:13 +0300 Subject: [PATCH 009/106] add_new_user_dialog --- .../add_user_dialog/bloc/users_bloc.dart | 1 - .../view/permission_management.dart | 82 ++++++++++++++++--- 2 files changed, 69 insertions(+), 14 deletions(-) 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 f0ddf01e..dbace8ad 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 @@ -199,7 +199,6 @@ class UsersBloc extends Bloc { node.title.toLowerCase().contains(searchTerm.toLowerCase()); bool childMatch = _searchRolePermission(node.subOptions, searchTerm); node.isHighlighted = isMatch || childMatch; - anyMatch = anyMatch || node.isHighlighted; } return anyMatch; diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart index b16cd8d2..266d431e 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart @@ -208,22 +208,52 @@ class _PermissionManagementState extends State { itemCount: subOption.subOptions.length, itemBuilder: (context, index) { final child = subOption.subOptions[index]; - return CheckboxListTile( - selectedTileColor: child.isHighlighted + return Container( + color: option.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), + child: Row( + children: [ + Builder( + builder: (context) { + final checkState = + checkifOneOfthemChecked(PermissionOption( + id: child.id, + title: child.title, + subOptions: [child], + )); + + 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( + 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, ); }, ), @@ -237,3 +267,29 @@ class _PermissionManagementState extends State { ); } } + + + // Container( + // height: 50, + // width: 120, + // child: CheckboxListTile( + // activeColor: ColorsManager.dialogBlueTitle, + // 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, + // ), + // ), \ No newline at end of file From 8f7b46be251c160b6bf99914d23d7255ba60754e Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 29 Dec 2024 11:22:11 +0300 Subject: [PATCH 010/106] fixes checkEmail error --- lib/services/user_permission.dart | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index d5cb8243..91e45073 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -76,7 +76,9 @@ class UserPermissionApi { } } - Future checkEmail(email) async { + + + Future checkEmail(String email) async { try { final response = await _httpService.post( path: ApiEndpoints.checkEmail, @@ -84,16 +86,26 @@ class UserPermissionApi { body: {"email": email}, expectedResponseModel: (json) { if (json['statusCode'] != 400) { - return json["message"]; + var message = json["message"]; + if (message is String) { + return message; + } else { + return 'Unexpected message format'; + } } + return null; }, ); - return response ?? []; + return response ?? 'Unknown error occurred'; } on DioException catch (e) { if (e.response != null) { + print(e.response?.data['error']); final errorMessage = e.response?.data['error']; - return errorMessage; + return errorMessage is String + ? errorMessage + : 'Error occurred while checking email'; } + return 'Error occurred while checking email'; } catch (e) { return e.toString(); } From 9e5cbc285ebd5804382aa5134bbe1c80ea8fb674 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 29 Dec 2024 11:22:45 +0300 Subject: [PATCH 011/106] checkEmail --- lib/services/user_permission.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 91e45073..0e31795a 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -76,8 +76,6 @@ class UserPermissionApi { } } - - Future checkEmail(String email) async { try { final response = await _httpService.post( @@ -99,7 +97,6 @@ class UserPermissionApi { return response ?? 'Unknown error occurred'; } on DioException catch (e) { if (e.response != null) { - print(e.response?.data['error']); final errorMessage = e.response?.data['error']; return errorMessage is String ? errorMessage From edf8bdfdcd106c9efedd50f723391d10dff52cff Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 30 Dec 2024 11:59:37 +0400 Subject: [PATCH 012/106] added buttons in space management page --- .../view/spaces_management_page.dart | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index 8cad58b2..fb893913 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -4,7 +4,6 @@ import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_vie import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart'; @@ -40,6 +39,28 @@ class SpaceManagementPageState extends State { appBarTitle: Text('Space Management', style: Theme.of(context).textTheme.headlineLarge), enableMenuSidebar: false, + centerBody: Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () {}, + child: Text( + 'Community Structure', + style: Theme.of(context).textTheme.bodyLarge, + ), + ), + const SizedBox(width: 20), + GestureDetector( + onTap: () {}, + child: Text( + 'Space Model', + style: Theme.of(context).textTheme.bodyLarge, + ), + ), + ], + ), + ), rightBody: const NavigateHomeGridView(), scaffoldBody: BlocBuilder( builder: (context, state) { From 79358642089a26a95f8cab5c0c77541e0db3ec0f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 31 Dec 2024 10:40:32 +0400 Subject: [PATCH 013/106] fixed community structure --- .../bloc/space_management_bloc.dart | 40 ++++++++ .../bloc/space_management_event.dart | 3 + .../view/spaces_management_page.dart | 94 ++++++++----------- .../bloc/center_body_bloc.dart | 30 ++++++ .../view/center_body_widget.dart | 75 +++++++++++++++ 5 files changed, 185 insertions(+), 57 deletions(-) create mode 100644 lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart create mode 100644 lib/pages/spaces_management/structure_selector/view/center_body_widget.dart diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index ffa5687e..9d6af5a3 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -26,6 +26,7 @@ class SpaceManagementBloc on(_onFetchProducts); on(_onSelectSpace); on(_onNewCommunity); + on(_onBlankState); } void _onUpdateCommunity( @@ -103,6 +104,45 @@ class SpaceManagementBloc } } + Future _onBlankState( + BlankStateEvent event, Emitter emit) async { + try { + final previousState = state; + + if (previousState is SpaceManagementLoaded || + previousState is BlankState) { + final prevCommunities = (previousState as dynamic).communities ?? []; + emit(BlankState( + communities: List.from(prevCommunities), + products: _cachedProducts ?? [], + )); + return; + } + + final communities = await _api.fetchCommunities(); + final updatedCommunities = + await Future.wait(communities.map((community) async { + final spaces = await _fetchSpacesForCommunity(community.uuid); + return CommunityModel( + uuid: community.uuid, + createdAt: community.createdAt, + updatedAt: community.updatedAt, + name: community.name, + description: community.description, + spaces: spaces, + region: community.region, + ); + })); + + emit(BlankState( + communities: updatedCommunities, + products: _cachedProducts ?? [], + )); + } catch (error) { + emit(SpaceManagementError('Error loading communities: $error')); + } + } + void _onLoadCommunityAndSpaces( LoadCommunityAndSpacesEvent event, Emitter emit, diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart index 9e3dcc74..bf79d286 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart @@ -140,3 +140,6 @@ class LoadSpaceHierarchyEvent extends SpaceManagementEvent { @override List get props => [communityId]; } + + +class BlankStateEvent extends SpaceManagementEvent {} diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index fb893913..960350a3 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -1,16 +1,17 @@ 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/spaces_management/structure_selector/bloc/center_body_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/structure_selector/view/center_body_widget.dart'; import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; + class SpaceManagementPage extends StatefulWidget { const SpaceManagementPage({super.key}); @@ -21,70 +22,49 @@ class SpaceManagementPage extends StatefulWidget { class SpaceManagementPageState extends State { final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi(); final ProductApi _productApi = ProductApi(); - Map> communitySpaces = {}; - List products = []; - bool isProductDataLoaded = false; - - @override - void initState() { - super.initState(); - } @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => SpaceManagementBloc(_api, _productApi) - ..add(LoadCommunityAndSpacesEvent()), + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (_) => SpaceManagementBloc(_api, _productApi) + ..add(LoadCommunityAndSpacesEvent()), + ), + BlocProvider( + create: (_) => CenterBodyBloc(), + ), + ], child: WebScaffold( appBarTitle: Text('Space Management', style: Theme.of(context).textTheme.headlineLarge), enableMenuSidebar: false, - centerBody: Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () {}, - child: Text( - 'Community Structure', - style: Theme.of(context).textTheme.bodyLarge, - ), - ), - const SizedBox(width: 20), - GestureDetector( - onTap: () {}, - child: Text( - 'Space Model', - style: Theme.of(context).textTheme.bodyLarge, - ), - ), - ], - ), - ), + centerBody: CenterBodyWidget(), rightBody: const NavigateHomeGridView(), scaffoldBody: BlocBuilder( - builder: (context, state) { - if (state is SpaceManagementLoading) { - return const Center(child: CircularProgressIndicator()); - } else if (state is BlankState) { - return LoadedSpaceView( - communities: state.communities, - selectedCommunity: null, - selectedSpace: null, - products: state.products, - ); - } else if (state is SpaceManagementLoaded) { - return LoadedSpaceView( - communities: state.communities, - selectedCommunity: state.selectedCommunity, - selectedSpace: state.selectedSpace, - products: state.products, - ); - } else if (state is SpaceManagementError) { - return Center(child: Text('Error: ${state.errorMessage}')); - } - return Container(); - }), + builder: (context, state) { + if (state is SpaceManagementLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is BlankState) { + return LoadedSpaceView( + communities: state.communities, + selectedCommunity: null, + selectedSpace: null, + products: state.products, + ); + } else if (state is SpaceManagementLoaded) { + return LoadedSpaceView( + communities: state.communities, + selectedCommunity: state.selectedCommunity, + selectedSpace: state.selectedSpace, + products: state.products, + ); + } else if (state is SpaceManagementError) { + return Center(child: Text('Error: ${state.errorMessage}')); + } + return Container(); + }, + ), ), ); } diff --git a/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart b/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart new file mode 100644 index 00000000..4fe4f413 --- /dev/null +++ b/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart @@ -0,0 +1,30 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +// Define Events +abstract class CenterBodyEvent {} + +class Button1PressedEvent extends CenterBodyEvent {} + +class Button2PressedEvent extends CenterBodyEvent {} + +// Define States +abstract class CenterBodyState {} + +class InitialState extends CenterBodyState {} + +class Button1State extends CenterBodyState {} + +class Button2State extends CenterBodyState {} + +// Bloc Implementation +class CenterBodyBloc extends Bloc { + CenterBodyBloc() : super(InitialState()) { + on((event, emit) { + emit(Button1State()); + }); + + on((event, emit) { + emit(Button2State()); + }); + } +} diff --git a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart new file mode 100644 index 00000000..ffacc29e --- /dev/null +++ b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; +import '../bloc/center_body_bloc.dart'; + +class CenterBodyWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is InitialState) { + context.read().add(Button1PressedEvent()); + } + if (state is Button1State) { + context.read().add(BlankStateEvent()); + } + + return Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () { + context.read().add(Button1PressedEvent()); + }, + child: Text( + 'Community Structure', + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + fontWeight: state is Button1State + ? FontWeight.bold + : FontWeight.normal, + color: state is Button1State + ? Theme.of(context).textTheme.bodyLarge!.color + : Theme.of(context) + .textTheme + .bodyLarge! + .color! + .withOpacity(0.5), + ), + ), + ), + SizedBox(width: 20), + GestureDetector( + onTap: () { + context.read().add(Button2PressedEvent()); + }, + child: Text( + 'Space Model', + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + fontWeight: state is Button2State + ? FontWeight.bold + : FontWeight.normal, + color: state is Button2State + ? Theme.of(context).textTheme.bodyLarge!.color + : Theme.of(context) + .textTheme + .bodyLarge! + .color! + .withOpacity(0.5), + ), + ), + ), + ], + ), + ], + ), + ); + }, + ); + } +} From fa16eaf82f0603194ce6ac57d85f5cad39b47e0b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 31 Dec 2024 11:32:51 +0400 Subject: [PATCH 014/106] rename state --- .../bloc/center_body_bloc.dart | 16 ++++++++-------- .../view/center_body_widget.dart | 18 +++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart b/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart index 4fe4f413..a3148af2 100644 --- a/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart +++ b/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart @@ -3,28 +3,28 @@ import 'package:flutter_bloc/flutter_bloc.dart'; // Define Events abstract class CenterBodyEvent {} -class Button1PressedEvent extends CenterBodyEvent {} +class CommunityStructureSelectedEvent extends CenterBodyEvent {} -class Button2PressedEvent extends CenterBodyEvent {} +class SpaceModelSelectedEvent extends CenterBodyEvent {} // Define States abstract class CenterBodyState {} class InitialState extends CenterBodyState {} -class Button1State extends CenterBodyState {} +class CommunityStructureState extends CenterBodyState {} -class Button2State extends CenterBodyState {} +class SpaceModelState extends CenterBodyState {} // Bloc Implementation class CenterBodyBloc extends Bloc { CenterBodyBloc() : super(InitialState()) { - on((event, emit) { - emit(Button1State()); + on((event, emit) { + emit(CommunityStructureState()); }); - on((event, emit) { - emit(Button2State()); + on((event, emit) { + emit(SpaceModelState()); }); } } diff --git a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart index ffacc29e..3957e4de 100644 --- a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart +++ b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart @@ -10,9 +10,9 @@ class CenterBodyWidget extends StatelessWidget { return BlocBuilder( builder: (context, state) { if (state is InitialState) { - context.read().add(Button1PressedEvent()); + context.read().add(CommunityStructureSelectedEvent()); } - if (state is Button1State) { + if (state is CommunityStructureState) { context.read().add(BlankStateEvent()); } @@ -25,15 +25,15 @@ class CenterBodyWidget extends StatelessWidget { children: [ GestureDetector( onTap: () { - context.read().add(Button1PressedEvent()); + context.read().add(CommunityStructureSelectedEvent()); }, child: Text( 'Community Structure', style: Theme.of(context).textTheme.bodyLarge!.copyWith( - fontWeight: state is Button1State + fontWeight: state is CommunityStructureState ? FontWeight.bold : FontWeight.normal, - color: state is Button1State + color: state is CommunityStructureState ? Theme.of(context).textTheme.bodyLarge!.color : Theme.of(context) .textTheme @@ -43,18 +43,18 @@ class CenterBodyWidget extends StatelessWidget { ), ), ), - SizedBox(width: 20), + const SizedBox(width: 20), GestureDetector( onTap: () { - context.read().add(Button2PressedEvent()); + context.read().add(SpaceModelSelectedEvent()); }, child: Text( 'Space Model', style: Theme.of(context).textTheme.bodyLarge!.copyWith( - fontWeight: state is Button2State + fontWeight: state is SpaceModelState ? FontWeight.bold : FontWeight.normal, - color: state is Button2State + color: state is SpaceModelState ? Theme.of(context).textTheme.bodyLarge!.color : Theme.of(context) .textTheme From b264f6042fb1b2feaff785d9cb0ba5124727727f Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 31 Dec 2024 11:20:32 +0300 Subject: [PATCH 015/106] remove package and change color name --- .../view/roles_and_permission.dart | 4 +-- .../view/spaces_access_view.dart | 4 +-- .../users_table/view/user_table.dart | 2 +- .../roles_and_permission/view/role_card.dart | 2 +- lib/pages/routiens/widgets/dragable_card.dart | 2 +- lib/utils/color_manager.dart | 4 +-- pubspec.lock | 32 ------------------- pubspec.yaml | 1 - 8 files changed, 9 insertions(+), 42 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart index b054a88c..0bc16a94 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart @@ -52,7 +52,7 @@ class RolesAndPermission extends StatelessWidget { flex: 2, child: Container( decoration: const BoxDecoration( - color: ColorsManager.CircleRolesBackground, + color: ColorsManager.circleRolesBackground, borderRadius: BorderRadius.only( topRight: Radius.circular(20), topLeft: Radius.circular(20)), @@ -107,7 +107,7 @@ class RolesAndPermission extends StatelessWidget { Expanded( flex: 7, child: Container( - color: ColorsManager.CircleRolesBackground, + color: ColorsManager.circleRolesBackground, padding: const EdgeInsets.all(8.0), child: Container( color: ColorsManager.whiteColors, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart index f942d1d5..081fab40 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart @@ -51,7 +51,7 @@ class SpacesAccessView extends StatelessWidget { flex: 2, child: Container( decoration: const BoxDecoration( - color: ColorsManager.CircleRolesBackground, + color: ColorsManager.circleRolesBackground, borderRadius: BorderRadius.only( topRight: Radius.circular(20), topLeft: Radius.circular(20)), @@ -105,7 +105,7 @@ class SpacesAccessView extends StatelessWidget { Expanded( flex: 7, child: Container( - color: ColorsManager.CircleRolesBackground, + color: ColorsManager.circleRolesBackground, padding: const EdgeInsets.all(8.0), child: Container( color: ColorsManager.whiteColors, 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 926ac92a..f42c0c03 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 @@ -88,7 +88,7 @@ class _DynamicTableScreenState extends State // Header Row with Resizable Columns Container( decoration: containerDecoration.copyWith( - color: ColorsManager.CircleRolesBackground, + color: ColorsManager.circleRolesBackground, borderRadius: BorderRadius.only( topLeft: Radius.circular(15), topRight: Radius.circular(15))), diff --git a/lib/pages/roles_and_permission/view/role_card.dart b/lib/pages/roles_and_permission/view/role_card.dart index b3f59ee9..b08b14ea 100644 --- a/lib/pages/roles_and_permission/view/role_card.dart +++ b/lib/pages/roles_and_permission/view/role_card.dart @@ -32,7 +32,7 @@ class RoleCard extends StatelessWidget { backgroundColor: ColorsManager.neutralGray, radius: 65, child: CircleAvatar( - backgroundColor: ColorsManager.CircleRolesBackground, + backgroundColor: ColorsManager.circleRolesBackground, radius: 62, child: Icon( Icons.admin_panel_settings, diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index e26d3d12..dd41e1a2 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -86,7 +86,7 @@ class DraggableCard extends StatelessWidget { height: 50, width: 50, decoration: BoxDecoration( - color: ColorsManager.CircleImageBackground, + color: ColorsManager.circleImageBackground, borderRadius: BorderRadius.circular(90), border: Border.all( color: ColorsManager.graysColor, diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 5549b566..91bfe98c 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -54,8 +54,8 @@ abstract class ColorsManager { static const Color neutralGray = Color(0xFFE5E5E5); static const Color warningRed = Color(0xFFFF6465); static const Color borderColor = Color(0xFFE5E5E5); - static const Color CircleImageBackground = Color(0xFFF4F4F4); - static const Color CircleRolesBackground = Color(0xFFF8F8F8); + static const Color circleImageBackground = Color(0xFFF4F4F4); + static const Color circleRolesBackground = Color(0xFFF8F8F8); static const Color activeGreen = Color(0xFF99FF93); static const Color activeGreenText = Color(0xFF008905); static const Color disabledPink = Color(0xFFFF9395); diff --git a/pubspec.lock b/pubspec.lock index 98c62a94..86d132c3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -312,14 +312,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" - intl_phone_number_input: - dependency: "direct main" - description: - name: intl_phone_number_input - sha256: "1c4328713a9503ab26a1fdbb6b00b4cada68c18aac922b35bedbc72eff1297c3" - url: "https://pub.dev" - source: hosted - version: "0.7.4" js: dependency: transitive description: @@ -352,30 +344,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" - libphonenumber_platform_interface: - dependency: transitive - description: - name: libphonenumber_platform_interface - sha256: f801f6c65523f56504b83f0890e6dad584ab3a7507dca65fec0eed640afea40f - url: "https://pub.dev" - source: hosted - version: "0.4.2" - libphonenumber_plugin: - dependency: transitive - description: - name: libphonenumber_plugin - sha256: c615021d9816fbda2b2587881019ed595ecdf54d999652d7e4cce0e1f026368c - url: "https://pub.dev" - source: hosted - version: "0.3.3" - libphonenumber_web: - dependency: transitive - description: - name: libphonenumber_web - sha256: "8186f420dbe97c3132283e52819daff1e55d60d6db46f7ea5ac42f42a28cc2ef" - url: "https://pub.dev" - source: hosted - version: "0.3.2" lints: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c99481d1..6951987c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,7 +53,6 @@ dependencies: uuid: ^4.4.2 time_picker_spinner: ^1.0.0 intl_phone_field: ^3.2.0 - intl_phone_number_input: ^0.7.4 dev_dependencies: flutter_test: From 65ad9c5edf4ea37aab10ebf679f0700155947310 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 2 Jan 2025 17:05:45 +0400 Subject: [PATCH 016/106] space model view --- .../bloc/space_management_bloc.dart | 39 ++++- .../bloc/space_management_event.dart | 2 + .../bloc/space_management_state.dart | 20 +++ .../all_spaces/model/product_model.dart | 1 - .../view/spaces_management_page.dart | 11 +- .../widgets/loaded_space_widget.dart | 26 +++- .../models/space_template_model.dart | 133 ++++++++++++++++++ .../space_model/view/space_model_page.dart | 81 +++++++++++ .../widgets/space_model_card_widget.dart | 123 ++++++++++++++++ .../widgets/subspace_chip_widget.dart | 34 +++++ .../view/center_body_widget.dart | 4 + lib/services/space_model_mang_api.dart | 37 +++++ lib/utils/color_manager.dart | 2 + lib/utils/constants/api_const.dart | 62 +++++--- 14 files changed, 542 insertions(+), 33 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/models/space_template_model.dart create mode 100644 lib/pages/spaces_management/space_model/view/space_model_page.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart create mode 100644 lib/services/space_model_mang_api.dart diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 9d6af5a3..15b014d1 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -4,17 +4,20 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; +import 'package:syncrow_web/services/space_model_mang_api.dart'; class SpaceManagementBloc extends Bloc { final CommunitySpaceManagementApi _api; final ProductApi _productApi; + final SpaceModelManagementApi _spaceModelApi; List? _cachedProducts; - SpaceManagementBloc(this._api, this._productApi) + SpaceManagementBloc(this._api, this._productApi, this._spaceModelApi) : super(SpaceManagementInitial()) { on(_onLoadCommunityAndSpaces); on(_onUpdateSpacePosition); @@ -27,6 +30,7 @@ class SpaceManagementBloc on(_onSelectSpace); on(_onNewCommunity); on(_onBlankState); + on(_onLoadSpaceModel); } void _onUpdateCommunity( @@ -410,4 +414,37 @@ class SpaceManagementBloc } return result.toList(); // Convert back to a list } + + void _onLoadSpaceModel( + SpaceModelLoadEvent event, Emitter emit) async { + emit(SpaceManagementLoading()); + try { + List communities = await _api.fetchCommunities(); + + List updatedCommunities = await Future.wait( + communities.map((community) async { + List spaces = + await _fetchSpacesForCommunity(community.uuid); + return CommunityModel( + uuid: community.uuid, + createdAt: community.createdAt, + updatedAt: community.updatedAt, + name: community.name, + description: community.description, + spaces: spaces, // New spaces list + region: community.region, + ); + }).toList(), + ); + + List spaceModels = + await _spaceModelApi.listSpaceModels(page: 1); + emit(SpaceModelLoaded( + communities: updatedCommunities, + products: _cachedProducts ?? [], + spaceModels: spaceModels)); + } catch (e) { + emit(SpaceManagementError('Error loading communities and spaces: $e')); + } + } } diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart index bf79d286..d25534b4 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart @@ -143,3 +143,5 @@ class LoadSpaceHierarchyEvent extends SpaceManagementEvent { class BlankStateEvent extends SpaceManagementEvent {} + +class SpaceModelLoadEvent extends SpaceManagementEvent {} diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart index eca8c16f..3ceafba9 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart @@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; abstract class SpaceManagementState extends Equatable { const SpaceManagementState(); @@ -27,6 +28,10 @@ class SpaceManagementLoaded extends SpaceManagementState { this.selectedSpace}); } +class SpaceModelManagenetLoaded extends SpaceManagementState { + SpaceModelManagenetLoaded(); +} + class BlankState extends SpaceManagementState { final List communities; final List products; @@ -54,3 +59,18 @@ class SpaceManagementError extends SpaceManagementState { @override List get props => [errorMessage]; } + +class SpaceModelLoaded extends SpaceManagementState { + final List spaceModels; + final List products; + final List communities; + + SpaceModelLoaded({ + required this.communities, + required this.products, + required this.spaceModels, + }); + + @override + List get props => [communities, products, spaceModels]; +} diff --git a/lib/pages/spaces_management/all_spaces/model/product_model.dart b/lib/pages/spaces_management/all_spaces/model/product_model.dart index 5a0e92e1..557c106b 100644 --- a/lib/pages/spaces_management/all_spaces/model/product_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/product_model.dart @@ -19,7 +19,6 @@ class ProductModel { // Factory method to create a Product from JSON factory ProductModel.fromMap(Map json) { - String icon = _mapIconToProduct(json['prodType']); return ProductModel( uuid: json['uuid'], catName: json['catName'], diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index 960350a3..33edceb2 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -9,9 +9,9 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/loaded_sp import 'package:syncrow_web/pages/spaces_management/structure_selector/view/center_body_widget.dart'; import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; +import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; - class SpaceManagementPage extends StatefulWidget { const SpaceManagementPage({super.key}); @@ -22,13 +22,13 @@ class SpaceManagementPage extends StatefulWidget { class SpaceManagementPageState extends State { final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi(); final ProductApi _productApi = ProductApi(); - + final SpaceModelManagementApi _spaceModelApi = SpaceModelManagementApi(); @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ BlocProvider( - create: (_) => SpaceManagementBloc(_api, _productApi) + create: (_) => SpaceManagementBloc(_api, _productApi, _spaceModelApi) ..add(LoadCommunityAndSpacesEvent()), ), BlocProvider( @@ -59,7 +59,10 @@ class SpaceManagementPageState extends State { selectedSpace: state.selectedSpace, products: state.products, ); - } else if (state is SpaceManagementError) { + }else if(state is SpaceModelLoaded){ + return LoadedSpaceView(communities: state.communities, products: state.products, spaceModels: state.spaceModels); + } + else if (state is SpaceManagementError) { return Center(child: Text('Error: ${state.errorMessage}')); } return Container(); diff --git a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart index 7ce56914..6bfd3ee3 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart @@ -5,12 +5,15 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/gradient_canvas_border_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart'; class LoadedSpaceView extends StatefulWidget { final List communities; final CommunityModel? selectedCommunity; final SpaceModel? selectedSpace; final List? products; + final List? spaceModels; const LoadedSpaceView({ super.key, @@ -18,6 +21,7 @@ class LoadedSpaceView extends StatefulWidget { this.selectedCommunity, this.selectedSpace, this.products, + this.spaceModels, }); @override @@ -27,6 +31,9 @@ class LoadedSpaceView extends StatefulWidget { class _LoadedStateViewState extends State { @override Widget build(BuildContext context) { + final bool hasSpaceModels = + widget.spaceModels != null && widget.spaceModels!.isNotEmpty; + return Stack( clipBehavior: Clip.none, children: [ @@ -38,13 +45,18 @@ class _LoadedStateViewState extends State { widget.selectedCommunity?.uuid ?? '', ), - CommunityStructureArea( - selectedCommunity: widget.selectedCommunity, - selectedSpace: widget.selectedSpace, - spaces: widget.selectedCommunity?.spaces ?? [], - products: widget.products, - communities: widget.communities, - ), + hasSpaceModels + ? Expanded( + child: SpaceModelPage( + spaceModels: widget.spaceModels ??[], + )) + : CommunityStructureArea( + selectedCommunity: widget.selectedCommunity, + selectedSpace: widget.selectedSpace, + spaces: widget.selectedCommunity?.spaces ?? [], + products: widget.products, + communities: widget.communities, + ), ], ), const GradientCanvasBorderWidget(), diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart new file mode 100644 index 00000000..3d29c29f --- /dev/null +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -0,0 +1,133 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; + +class SpaceTemplateModel { + final String uuid; + final DateTime createdAt; + final DateTime updatedAt; + final String modelName; + final bool disabled; + final List subspaceModels; + final List tags; + + SpaceTemplateModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.modelName, + required this.disabled, + required this.subspaceModels, + required this.tags, + }); + + factory SpaceTemplateModel.fromJson(Map json) { + return SpaceTemplateModel( + uuid: json['uuid'] ?? '', + createdAt: DateTime.parse(json['createdAt']), + updatedAt: DateTime.parse(json['updatedAt']), + modelName: json['modelName'] ?? '', + disabled: json['disabled'] ?? false, + subspaceModels: (json['subspaceModels'] as List) + .map((item) => SubspaceModel.fromJson(item)) + .toList(), + tags: (json['tags'] as List) + .map((item) => TagModel.fromJson(item)) + .toList(), + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'modelName': modelName, + 'disabled': disabled, + 'subspaceModels': subspaceModels.map((e) => e.toJson()).toList(), + 'tags': tags.map((e) => e.toJson()).toList(), + }; + } +} + +class SubspaceModel { + final String uuid; + final DateTime createdAt; + final DateTime updatedAt; + final String subspaceName; + final bool disabled; + final List tags; + + SubspaceModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.subspaceName, + required this.disabled, + required this.tags, + }); + + factory SubspaceModel.fromJson(Map json) { + return SubspaceModel( + uuid: json['uuid'] ?? '', + createdAt: DateTime.parse(json['createdAt']), + updatedAt: DateTime.parse(json['updatedAt']), + subspaceName: json['subspaceName'] ?? '', + disabled: json['disabled'] ?? false, + tags: (json['tags'] as List) + .map((item) => TagModel.fromJson(item)) + .toList(), + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'subspaceName': subspaceName, + 'disabled': disabled, + 'tags': tags.map((e) => e.toJson()).toList(), + }; + } +} + +class TagModel { + final String uuid; + final DateTime createdAt; + final DateTime updatedAt; + final String tag; + final bool disabled; + final ProductModel? product; + + TagModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.tag, + required this.disabled, + this.product, + }); + + factory TagModel.fromJson(Map json) { + return TagModel( + uuid: json['uuid'] ?? '', + createdAt: DateTime.parse(json['createdAt']), + updatedAt: DateTime.parse(json['updatedAt']), + tag: json['tag'] ?? '', + disabled: json['disabled'] ?? false, + product: json['product'] != null + ? ProductModel.fromMap(json['product']) + : null, + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'tag': tag, + 'disabled': disabled, + 'product': product?.toMap(), + }; + } +} diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart new file mode 100644 index 00000000..736fa2b5 --- /dev/null +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class SpaceModelPage extends StatelessWidget { + final List spaceModels; + + const SpaceModelPage({Key? key, required this.spaceModels}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: ColorsManager.whiteColors, + body: Padding( + padding: const EdgeInsets.fromLTRB(20.0, 16.0, 16.0, 16.0), + child: GridView.builder( + //clipBehavior: Clip.none, + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 14.0, + mainAxisSpacing: 14.0, + childAspectRatio: 3, + ), + itemCount: spaceModels.length + 1, + itemBuilder: (context, index) { + if (index == spaceModels.length) { + return GestureDetector( + onTap: () {}, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(20), + boxShadow: const [ + BoxShadow( + color: ColorsManager.semiTransparentBlackColor, + blurRadius: 15, + offset: Offset(0, 4), + spreadRadius: 0, + ), + BoxShadow( + color: ColorsManager.semiTransparentBlackColor, + blurRadius: 25, + offset: Offset(0, 15), + spreadRadius: -5, + ), + ], + ), + child: Center( + child: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.neutralGray, + border: Border.all( + color: ColorsManager.textFieldGreyColor, + width: 2.0, + ), + ), + child: const Icon( + Icons.add, + size: 40, + color: ColorsManager.spaceColor, // Icon color + ), + ), + ), + ), + ); + } + + final model = spaceModels[index]; + return SpaceModelCardWidget(model: model); + }, + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart new file mode 100644 index 00000000..49128a3f --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class SpaceModelCardWidget extends StatelessWidget { + final SpaceTemplateModel model; + + const SpaceModelCardWidget({Key? key, required this.model}) : super(key: key); + + @override + Widget build(BuildContext context) { + final Map productTagCount = {}; + + for (var tag in model.tags) { + final prodIcon = tag.product?.icon ?? 'Unknown'; + productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + } + + for (var subspace in model.subspaceModels) { + for (var tag in subspace.tags) { + final prodIcon = tag.product?.icon ?? 'Unknown'; + productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + } + } + + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: const Offset(0, 3), + ), + ], + ), + padding: const EdgeInsets.fromLTRB(16.0, 14.0, 8.0, 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: Text( + model.modelName, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(height: 10), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Wrap( + spacing: 3.0, + runSpacing: 3.0, + children: [ + for (var subspace in model.subspaceModels.take(3)) + SubspaceChipWidget(subspace: subspace.subspaceName), + ], + ), + ), + if (productTagCount.isNotEmpty) + Container( + width: 1, + height: double.infinity, + color: ColorsManager.softGray, + margin: const EdgeInsets.symmetric(horizontal: 4.0), + ), + const SizedBox(width: 7), + Expanded( + child: Wrap( + spacing: 4.0, + runSpacing: 4.0, + children: productTagCount.entries.map((entry) { + final prodType = entry.key; + final count = entry.value; + + return Chip( + label: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + prodType, + width: 15, + height: 16, + ), + const SizedBox(width: 4), + Text( + 'x$count', // Product count + style: const TextStyle(fontSize: 12), + ), + ], + ), + backgroundColor: ColorsManager.textFieldGreyColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide( + color: ColorsManager.transparentColor, + width: 0, + ), + ), + ); + }).toList(), + ), + ), + ], + ), + ), + const SizedBox(height: 5), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart new file mode 100644 index 00000000..debe3801 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class SubspaceChipWidget extends StatelessWidget { + final String subspace; + + const SubspaceChipWidget({ + Key? key, + required this.subspace, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Chip( + label: Text( + subspace, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + backgroundColor: ColorsManager.textFieldGreyColor, + labelStyle: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide( + color: Colors.transparent, + width: 0, + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart index 3957e4de..68598081 100644 --- a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart +++ b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart @@ -16,6 +16,10 @@ class CenterBodyWidget extends StatelessWidget { context.read().add(BlankStateEvent()); } + if (state is SpaceModelState) { + context.read().add(SpaceModelLoadEvent()); + } + return Container( child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart new file mode 100644 index 00000000..762f9178 --- /dev/null +++ b/lib/services/space_model_mang_api.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; +import 'package:syncrow_web/utils/constants/temp_const.dart'; + +class SpaceModelManagementApi { + Future> listSpaceModels({int page = 1}) async { + try { + List spaceModels = []; + bool hasNext = true; + while (hasNext) { + await HTTPService().get( + path: ApiEndpoints.listSpaceModels + .replaceAll('{projectId}', TempConst.projectId), + queryParameters: {'page': page}, + expectedResponseModel: (json) { + List jsonData = json['data']; + hasNext = json['hasNext'] ?? false; + int currentPage = json['page'] ?? 1; + List spaceModelList = jsonData.map((jsonItem) { + return SpaceTemplateModel.fromJson(jsonItem); + }).toList(); + + spaceModels.addAll(spaceModelList); + page = currentPage + 1; + return spaceModelList; + }, + ); + } + return spaceModels; + } catch (e) { + debugPrint('Error fetching space models: $e'); + return []; + } + } +} diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 95d0f214..0d49eb24 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -54,5 +54,7 @@ abstract class ColorsManager { static const Color warningRed = Color(0xFFFF6465); static const Color borderColor = Color(0xFFE5E5E5); static const Color CircleImageBackground = Color(0xFFF4F4F4); + static const Color softGray = Color(0xFFD5D5D5); + static const Color semiTransparentBlack = Color(0x19000000) } //background: #background: #5D5D5D; diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index b8d23259..2fdca23a 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -11,12 +11,14 @@ abstract class ApiEndpoints { static const String visitorPassword = '/visitor-password'; static const String getDevices = '/visitor-password/devices'; - static const String sendOnlineOneTime = '/visitor-password/temporary-password/online/one-time'; + static const String sendOnlineOneTime = + '/visitor-password/temporary-password/online/one-time'; static const String sendOnlineMultipleTime = '/visitor-password/temporary-password/online/multiple-time'; //offline Password - static const String sendOffLineOneTime = '/visitor-password/temporary-password/offline/one-time'; + static const String sendOffLineOneTime = + '/visitor-password/temporary-password/offline/one-time'; static const String sendOffLineMultipleTime = '/visitor-password/temporary-password/offline/multiple-time'; @@ -36,30 +38,45 @@ abstract class ApiEndpoints { static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}'; // Space Module - static const String createSpace = '/projects/{projectId}/communities/{communityId}/spaces'; - static const String listSpaces = '/projects/{projectId}/communities/{communityId}/spaces'; - static const String deleteSpace = '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}'; - static const String updateSpace = '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}'; - static const String getSpace = '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}'; - static const String getSpaceHierarchy = '/projects/{projectId}/communities/{communityId}/spaces'; + static const String createSpace = + '/projects/{projectId}/communities/{communityId}/spaces'; + static const String listSpaces = + '/projects/{projectId}/communities/{communityId}/spaces'; + static const String deleteSpace = + '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}'; + static const String updateSpace = + '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}'; + static const String getSpace = + '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}'; + static const String getSpaceHierarchy = + '/projects/{projectId}/communities/{communityId}/spaces'; // Community Module static const String createCommunity = '/projects/{projectId}/communities'; static const String getCommunityList = '/projects/{projectId}/communities'; - static const String getCommunityById = '/projects/{projectId}/communities/{communityId}'; - static const String updateCommunity = '/projects/{projectId}/communities/{communityId}'; - static const String deleteCommunity = '/projects/{projectId}communities/{communityId}'; - static const String getUserCommunities = '/projects/{projectId}/communities/user/{userUuid}'; - static const String createUserCommunity = '/projects/{projectId}/communities/user'; + static const String getCommunityById = + '/projects/{projectId}/communities/{communityId}'; + static const String updateCommunity = + '/projects/{projectId}/communities/{communityId}'; + static const String deleteCommunity = + '/projects/{projectId}communities/{communityId}'; + static const String getUserCommunities = + '/projects/{projectId}/communities/user/{userUuid}'; + static const String createUserCommunity = + '/projects/{projectId}/communities/user'; static const String getDeviceLogsByDate = '/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; static const String scheduleByDeviceId = '/schedule/{deviceUuid}'; - static const String getScheduleByDeviceId = '/schedule/{deviceUuid}?category={category}'; - static const String deleteScheduleByDeviceId = '/schedule/{deviceUuid}/{scheduleUuid}'; - static const String updateScheduleByDeviceId = '/schedule/enable/{deviceUuid}'; + static const String getScheduleByDeviceId = + '/schedule/{deviceUuid}?category={category}'; + static const String deleteScheduleByDeviceId = + '/schedule/{deviceUuid}/{scheduleUuid}'; + static const String updateScheduleByDeviceId = + '/schedule/enable/{deviceUuid}'; static const String factoryReset = '/device/factory/reset/{deviceUuid}'; - static const String powerClamp = '/device/{powerClampUuid}/power-clamp/status'; + static const String powerClamp = + '/device/{powerClampUuid}/power-clamp/status'; //product static const String listProducts = '/products'; @@ -68,13 +85,18 @@ abstract class ApiEndpoints { static const String getIconScene = '/scene/icon'; static const String createScene = '/scene/tap-to-run'; static const String createAutomation = '/automation'; - static const String getUnitScenes = '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; - static const String getAutomationDetails = '/automation/details/{automationId}'; + static const String getUnitScenes = + '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; + static const String getAutomationDetails = + '/automation/details/{automationId}'; static const String getScene = '/scene/tap-to-run/{sceneId}'; static const String deleteScene = '/scene/tap-to-run/{sceneId}'; static const String deleteAutomation = '/automation/{automationId}'; - static const String updateScene = '/scene/tap-to-run/{sceneId}'; + static const String updateScene = '/scene/tap-to-run/{sceneId}'; static const String updateAutomation = '/automation/{automationId}'; + + //space model + static const String listSpaceModels = '/projects/{projectId}/space-models'; } From 80dea5c12db3dbcbbd8a075ff89be09040c6a045 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 2 Jan 2025 17:55:04 +0400 Subject: [PATCH 017/106] fixed block flow --- .../all_spaces/bloc/space_management_bloc.dart | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 15b014d1..2434a32c 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -239,11 +239,15 @@ class SpaceManagementBloc SelectCommunityEvent event, Emitter emit, ) async { - _handleCommunitySpaceStateUpdate( - emit: emit, - selectedCommunity: event.selectedCommunity, - selectedSpace: null, - ); + try { + _handleCommunitySpaceStateUpdate( + emit: emit, + selectedCommunity: event.selectedCommunity, + selectedSpace: null, + ); + } catch (e) { + emit(SpaceManagementError('Error updating state: $e')); + } } void _onSelectSpace( @@ -267,7 +271,8 @@ class SpaceManagementBloc try { if (previousState is SpaceManagementLoaded || - previousState is BlankState) { + previousState is BlankState || + previousState is SpaceModelLoaded) { final communities = List.from( (previousState as dynamic).communities, ); From e12252db969e454887ac829b55c86a2807371b6e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 3 Jan 2025 08:45:34 +0400 Subject: [PATCH 018/106] fixed navigation in between --- .../all_spaces/widgets/sidebar_widget.dart | 4 ++++ .../bloc/center_body_bloc.dart | 22 +++++-------------- .../bloc/center_body_event.dart | 8 +++++++ .../bloc/center_body_state.dart | 9 ++++++++ .../view/center_body_widget.dart | 6 +++-- lib/utils/color_manager.dart | 2 +- 6 files changed, 32 insertions(+), 19 deletions(-) create mode 100644 lib/pages/spaces_management/structure_selector/bloc/center_body_event.dart create mode 100644 lib/pages/spaces_management/structure_selector/bloc/center_body_state.dart diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index a58d73e9..2d557e25 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -8,6 +8,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_m import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_tile.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -186,6 +188,8 @@ class _SidebarWidgetState extends State { _selectedSpaceUuid = null; // Update the selected community }); + context.read().add(CommunitySelectedEvent()); + context.read().add( SelectCommunityEvent(selectedCommunity: community), ); diff --git a/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart b/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart index a3148af2..1de2ae13 100644 --- a/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart +++ b/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart @@ -1,20 +1,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; - -// Define Events -abstract class CenterBodyEvent {} - -class CommunityStructureSelectedEvent extends CenterBodyEvent {} - -class SpaceModelSelectedEvent extends CenterBodyEvent {} - -// Define States -abstract class CenterBodyState {} - -class InitialState extends CenterBodyState {} - -class CommunityStructureState extends CenterBodyState {} - -class SpaceModelState extends CenterBodyState {} +import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart'; +import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_state.dart'; // Bloc Implementation class CenterBodyBloc extends Bloc { @@ -26,5 +12,9 @@ class CenterBodyBloc extends Bloc { on((event, emit) { emit(SpaceModelState()); }); + + on((event, emit) { + emit(CommunitySelectedState()); + }); } } diff --git a/lib/pages/spaces_management/structure_selector/bloc/center_body_event.dart b/lib/pages/spaces_management/structure_selector/bloc/center_body_event.dart new file mode 100644 index 00000000..72cdbd1c --- /dev/null +++ b/lib/pages/spaces_management/structure_selector/bloc/center_body_event.dart @@ -0,0 +1,8 @@ +// Define Events +abstract class CenterBodyEvent {} + +class CommunityStructureSelectedEvent extends CenterBodyEvent {} + +class SpaceModelSelectedEvent extends CenterBodyEvent {} + +class CommunitySelectedEvent extends CenterBodyEvent {} \ No newline at end of file diff --git a/lib/pages/spaces_management/structure_selector/bloc/center_body_state.dart b/lib/pages/spaces_management/structure_selector/bloc/center_body_state.dart new file mode 100644 index 00000000..73428dc5 --- /dev/null +++ b/lib/pages/spaces_management/structure_selector/bloc/center_body_state.dart @@ -0,0 +1,9 @@ +abstract class CenterBodyState {} + +class InitialState extends CenterBodyState {} + +class CommunityStructureState extends CenterBodyState {} + +class SpaceModelState extends CenterBodyState {} + +class CommunitySelectedState extends CenterBodyState {} diff --git a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart index 68598081..45a6aaf7 100644 --- a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart +++ b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; +import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart'; +import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_state.dart'; import '../bloc/center_body_bloc.dart'; class CenterBodyWidget extends StatelessWidget { @@ -34,10 +36,10 @@ class CenterBodyWidget extends StatelessWidget { child: Text( 'Community Structure', style: Theme.of(context).textTheme.bodyLarge!.copyWith( - fontWeight: state is CommunityStructureState + fontWeight: state is CommunityStructureState || state is CommunitySelectedState ? FontWeight.bold : FontWeight.normal, - color: state is CommunityStructureState + color: state is CommunityStructureState || state is CommunitySelectedState ? Theme.of(context).textTheme.bodyLarge!.color : Theme.of(context) .textTheme diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 0d49eb24..8d0aae43 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -55,6 +55,6 @@ abstract class ColorsManager { static const Color borderColor = Color(0xFFE5E5E5); static const Color CircleImageBackground = Color(0xFFF4F4F4); static const Color softGray = Color(0xFFD5D5D5); - static const Color semiTransparentBlack = Color(0x19000000) + static const Color semiTransparentBlack = Color(0x19000000); } //background: #background: #5D5D5D; From e0ff139f3091170d5c4512ab274b4a406f9104b6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 3 Jan 2025 10:13:44 +0400 Subject: [PATCH 019/106] add create space model widget UI --- .../space_model/view/space_model_page.dart | 16 +- .../dialog/create_space_model_dialog.dart | 148 ++++++++++++++++++ 2 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 736fa2b5..432713db 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -20,15 +21,22 @@ class SpaceModelPage extends StatelessWidget { physics: const AlwaysScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, - crossAxisSpacing: 14.0, - mainAxisSpacing: 14.0, - childAspectRatio: 3, + crossAxisSpacing: 13.0, + mainAxisSpacing: 13.0, + childAspectRatio: 3.5, ), itemCount: spaceModels.length + 1, itemBuilder: (context, index) { if (index == spaceModels.length) { return GestureDetector( - onTap: () {}, + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return const CreateSpaceModelDialog(); + }, + ); + }, child: Container( decoration: BoxDecoration( color: ColorsManager.whiteColors, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart new file mode 100644 index 00000000..c253da02 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CreateSpaceModelDialog extends StatelessWidget { + const CreateSpaceModelDialog({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + backgroundColor: ColorsManager.whiteColors, + content: SizedBox( + width: screenWidth * 0.3, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Create New Space Model', + style: Theme.of(context) + .textTheme + .headlineLarge + ?.copyWith(color: ColorsManager.blackColor), + ), + const SizedBox(height: 16), + SizedBox( + width: screenWidth * 0.25, + child: TextField( + style: const TextStyle(color: ColorsManager.blackColor), + decoration: InputDecoration( + filled: true, + fillColor: ColorsManager.textFieldGreyColor, + hintText: 'Please enter the name', + hintStyle: + const TextStyle(color: ColorsManager.lightGrayColor), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric( + vertical: 12.0, + horizontal: 16.0, + ), + ), + ), + ), + const SizedBox(height: 16), + SizedBox( + width: screenWidth * 0.25, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + border: Border.all( + color: ColorsManager.neutralGray, + width: 3.0, + ), + borderRadius: BorderRadius.circular(20), + ), + child: const Padding( + padding: + EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), + child: Row( + children: [ + Icon( + Icons.add, + color: ColorsManager.spaceColor, + ), + SizedBox(width: 10), + Expanded( + child: Text( + 'Create sub space', + style: TextStyle( + color: ColorsManager.blackColor, + fontSize: 16, + ), + ), + ), + ], + ), + ), + ), + ), + const SizedBox(height: 10), + SizedBox( + width: screenWidth * 0.25, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + border: + Border.all(color: ColorsManager.neutralGray, width: 3.0), + borderRadius: BorderRadius.circular(20), + ), + child: const Padding( + padding: + EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), + child: Row( + children: [ + Icon( + Icons.add, + color: ColorsManager.spaceColor, + ), + SizedBox(width: 10), + Expanded( + child: Text( + 'Add devices', + style: TextStyle( + color: ColorsManager.blackColor, + fontSize: 16, + ), + ), + ), + ], + ), + ), + ), + ), + const SizedBox(height: 20), + SizedBox( + width: screenWidth * 0.25, + child: Row( + children: [ + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () => Navigator.of(context).pop(), + )), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + onPressed: () {}, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ], + ), + ) + ], + ), + ), + ); + } +} From 944b981ee03f73b827ae12b43c6458f701a59000 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 3 Jan 2025 14:28:45 +0400 Subject: [PATCH 020/106] added subspace model events --- .../view/spaces_management_page.dart | 10 +- .../space_model/bloc/subspace_model_bloc.dart | 38 ++++ .../models/space_template_model.dart | 38 ++-- .../dialog/create_space_model_dialog.dart | 79 +++++--- .../dialog/create_subspace_model_dialog.dart | 188 ++++++++++++++++++ .../widgets/space_model_card_widget.dart | 8 +- lib/utils/constants/temp_const.dart | 2 +- 7 files changed, 307 insertions(+), 56 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index 33edceb2..77f878b7 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -59,10 +59,12 @@ class SpaceManagementPageState extends State { selectedSpace: state.selectedSpace, products: state.products, ); - }else if(state is SpaceModelLoaded){ - return LoadedSpaceView(communities: state.communities, products: state.products, spaceModels: state.spaceModels); - } - else if (state is SpaceManagementError) { + } else if (state is SpaceModelLoaded) { + return LoadedSpaceView( + communities: state.communities, + products: state.products, + spaceModels: state.spaceModels); + } else if (state is SpaceManagementError) { return Center(child: Text('Error: ${state.errorMessage}')); } return Container(); diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart new file mode 100644 index 00000000..423a5ca5 --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart @@ -0,0 +1,38 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +// Events +abstract class SubSpaceModelEvent {} + +class AddSubSpaceModel extends SubSpaceModelEvent { + final SubspaceTemplateModel subSpace; + AddSubSpaceModel(this.subSpace); +} + +class RemoveSubSpaceModel extends SubSpaceModelEvent { + final SubspaceTemplateModel subSpace; + RemoveSubSpaceModel(this.subSpace); +} + +// State +class SubSpaceModelState { + final List subSpaces; + SubSpaceModelState(this.subSpaces); +} + +// BLoC +class SubSpaceModelBloc extends Bloc { + SubSpaceModelBloc() : super(SubSpaceModelState([])) { + on((event, emit) { + final updatedSubSpaces = List.from(state.subSpaces) + ..add(event.subSpace); + emit(SubSpaceModelState(updatedSubSpaces)); + }); + + on((event, emit) { + final updatedSubSpaces = List.from(state.subSpaces) + ..remove(event.subSpace); + emit(SubSpaceModelState(updatedSubSpaces)); + }); + } +} diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart index 3d29c29f..2107b87f 100644 --- a/lib/pages/spaces_management/space_model/models/space_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -1,4 +1,5 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:uuid/uuid.dart'; class SpaceTemplateModel { final String uuid; @@ -6,28 +7,33 @@ class SpaceTemplateModel { final DateTime updatedAt; final String modelName; final bool disabled; - final List subspaceModels; + final List subspaceModels; final List tags; + String internalId; SpaceTemplateModel({ required this.uuid, + String? internalId, required this.createdAt, required this.updatedAt, required this.modelName, required this.disabled, required this.subspaceModels, required this.tags, - }); + }) : internalId = internalId ?? const Uuid().v4(); factory SpaceTemplateModel.fromJson(Map json) { + final String internalId = json['internalId'] ?? const Uuid().v4(); + return SpaceTemplateModel( uuid: json['uuid'] ?? '', + internalId: internalId, createdAt: DateTime.parse(json['createdAt']), updatedAt: DateTime.parse(json['updatedAt']), modelName: json['modelName'] ?? '', disabled: json['disabled'] ?? false, subspaceModels: (json['subspaceModels'] as List) - .map((item) => SubspaceModel.fromJson(item)) + .map((item) => SubspaceTemplateModel.fromJson(item)) .toList(), tags: (json['tags'] as List) .map((item) => TagModel.fromJson(item)) @@ -48,28 +54,22 @@ class SpaceTemplateModel { } } -class SubspaceModel { - final String uuid; - final DateTime createdAt; - final DateTime updatedAt; +class SubspaceTemplateModel { + final String? uuid; final String subspaceName; final bool disabled; - final List tags; + final List? tags; - SubspaceModel({ - required this.uuid, - required this.createdAt, - required this.updatedAt, + SubspaceTemplateModel({ + this.uuid, required this.subspaceName, required this.disabled, - required this.tags, + this.tags, }); - factory SubspaceModel.fromJson(Map json) { - return SubspaceModel( + factory SubspaceTemplateModel.fromJson(Map json) { + return SubspaceTemplateModel( uuid: json['uuid'] ?? '', - createdAt: DateTime.parse(json['createdAt']), - updatedAt: DateTime.parse(json['updatedAt']), subspaceName: json['subspaceName'] ?? '', disabled: json['disabled'] ?? false, tags: (json['tags'] as List) @@ -81,11 +81,9 @@ class SubspaceModel { Map toJson() { return { 'uuid': uuid, - 'createdAt': createdAt.toIso8601String(), - 'updatedAt': updatedAt.toIso8601String(), 'subspaceName': subspaceName, 'disabled': disabled, - 'tags': tags.map((e) => e.toJson()).toList(), + 'tags': tags?.map((e) => e.toJson()).toList() ?? [], }; } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index c253da02..4f0fff01 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSpaceModelDialog extends StatelessWidget { @@ -48,37 +50,58 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - SizedBox( - width: screenWidth * 0.25, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - border: Border.all( - color: ColorsManager.neutralGray, - width: 3.0, + GestureDetector( + onTap: () async { + final result = await showDialog( + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: 'Create Sub-space', + existingSubSpaces: [ + SubspaceTemplateModel( + subspaceName: "Living Room", + disabled: false, + uuid: "mkmkl,", + ), + ], + ); + }, + ); + if (result == true) {} + }, + child: SizedBox( + width: screenWidth * 0.25, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + border: Border.all( + color: ColorsManager.neutralGray, + width: 3.0, + ), + borderRadius: BorderRadius.circular(20), ), - borderRadius: BorderRadius.circular(20), - ), - child: const Padding( - padding: - EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), - child: Row( - children: [ - Icon( - Icons.add, - color: ColorsManager.spaceColor, - ), - SizedBox(width: 10), - Expanded( - child: Text( - 'Create sub space', - style: TextStyle( - color: ColorsManager.blackColor, - fontSize: 16, + child: const Padding( + padding: + EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), + child: Row( + children: [ + Icon( + Icons.add, + color: ColorsManager.spaceColor, + ), + SizedBox(width: 10), + Expanded( + child: Text( + 'Create sub space', + style: TextStyle( + color: ColorsManager.blackColor, + fontSize: 16, + ), ), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart new file mode 100644 index 00000000..9738f079 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart @@ -0,0 +1,188 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CreateSubSpaceModelDialog extends StatefulWidget { + final bool isEdit; // Flag to determine if it's edit or create + final String dialogTitle; // Title for the dialog + final List? existingSubSpaces; // For edit mode + + const CreateSubSpaceModelDialog({ + super.key, + required this.isEdit, + required this.dialogTitle, + this.existingSubSpaces, + }); + + @override + _CreateSubSpaceModelDialogState createState() => + _CreateSubSpaceModelDialogState(); +} + +class _CreateSubSpaceModelDialogState extends State { + final TextEditingController textController = TextEditingController(); + final FocusNode focusNode = FocusNode(); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + focusNode.requestFocus(); + }); + + if (widget.isEdit) {} + } + + @override + void dispose() { + textController.dispose(); + focusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: BlocProvider( + create: (_) => SubSpaceModelBloc(), + child: BlocBuilder( + builder: (context, state) { + return SizedBox( + width: screenWidth * 0.35, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + widget.dialogTitle, + style: Theme.of(context) + .textTheme + .headlineLarge + ?.copyWith(color: ColorsManager.blackColor), + ), + const SizedBox(height: 16), + Container( + width: screenWidth * 0.35, + padding: const EdgeInsets.symmetric( + vertical: 10.0, horizontal: 16.0), + decoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(10), + ), + child: Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: [ + ...state.subSpaces.map( + (subSpace) => Chip( + label: Text(subSpace.subspaceName, + style: const TextStyle( + color: ColorsManager.spaceColor)), + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide( + color: ColorsManager.transparentColor, + width: 0), + ), + deleteIcon: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager.lightGrayColor, + width: 1.5, + ), + ), + child: const Icon( + Icons.close, + size: 16, + color: ColorsManager.lightGrayColor, + ), + ), + onDeleted: () => context + .read() + .add(RemoveSubSpaceModel(subSpace)), + ), + ), + SizedBox( + width: 200, + child: TextField( + controller: textController, + focusNode: focusNode, + decoration: InputDecoration( + border: InputBorder.none, + hintText: state.subSpaces.isEmpty + ? 'Please enter the name' + : null, + hintStyle: const TextStyle( + color: ColorsManager.lightGrayColor), + ), + onSubmitted: (value) { + if (value.trim().isNotEmpty) { + context.read().add( + AddSubSpaceModel(SubspaceTemplateModel( + subspaceName: value.trim(), + disabled: false))); + textController.clear(); + focusNode.requestFocus(); + } + }, + style: const TextStyle( + color: ColorsManager.blackColor), + ), + ), + ], + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + onPressed: () { + final subSpaces = context + .read() + .state + .subSpaces; + Navigator.of(context).pop(subSpaces); + }, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ], + ), + ], + ), + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart index 49128a3f..550361c7 100644 --- a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart @@ -19,9 +19,11 @@ class SpaceModelCardWidget extends StatelessWidget { } for (var subspace in model.subspaceModels) { - for (var tag in subspace.tags) { - final prodIcon = tag.product?.icon ?? 'Unknown'; - productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + if (subspace.tags != null) { + for (var tag in subspace.tags!) { + final prodIcon = tag.product?.icon ?? 'Unknown'; + productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + } } } diff --git a/lib/utils/constants/temp_const.dart b/lib/utils/constants/temp_const.dart index e5847b98..bcd1a1d6 100644 --- a/lib/utils/constants/temp_const.dart +++ b/lib/utils/constants/temp_const.dart @@ -1,3 +1,3 @@ class TempConst { - static const projectId = '0e62577c-06fa-41b9-8a92-99a21fbaf51c'; + static const projectId = '0685c781-df33-4cbf-bf65-9f4e835eb468'; } From 90e5499f9222029b8e84efc30c8f4a830e9ad4e6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 11:45:05 +0400 Subject: [PATCH 021/106] subspace model --- .env.development | 2 +- .gitignore | 3 + .../bloc/space_management_bloc.dart | 3 +- .../widgets/loaded_space_widget.dart | 3 +- .../bloc/create_space_model_bloc.dart | 26 +++ .../bloc/create_space_model_event.dart | 11 ++ .../bloc/create_space_model_state.dart | 19 ++ .../space_model/bloc/subspace_model_bloc.dart | 40 ++-- .../bloc/subspace_model_event.dart | 14 ++ .../bloc/subspace_model_state.dart | 11 ++ .../models/space_template_model.dart | 109 ++++++++--- .../space_model/view/space_model_page.dart | 7 +- .../dialog/create_space_model_dialog.dart | 174 +++++++++--------- .../dialog/create_subspace_model_dialog.dart | 19 +- .../widgets/space_model_card_widget.dart | 22 ++- lib/utils/constants/action_enum.dart | 31 ++++ 16 files changed, 346 insertions(+), 148 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart create mode 100644 lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart create mode 100644 lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart create mode 100644 lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart create mode 100644 lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart create mode 100644 lib/utils/constants/action_enum.dart diff --git a/.env.development b/.env.development index e77609dc..1fd358ec 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,2 @@ ENV_NAME=development -BASE_URL=https://syncrow-dev.azurewebsites.net \ No newline at end of file +BASE_URL=http://localhost:4001 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 29a3a501..ae866139 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release +lib/utils/constants/temp_const.dart +.env.development +lib/utils/constants/temp_const.dart diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 2434a32c..0c002e7d 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -329,6 +329,7 @@ class SpaceManagementBloc Emitter emit, ) { final communities = List.from(previousState.communities); + for (var community in communities) { if (community.uuid == communityUuid) { @@ -425,7 +426,7 @@ class SpaceManagementBloc emit(SpaceManagementLoading()); try { List communities = await _api.fetchCommunities(); - + List updatedCommunities = await Future.wait( communities.map((community) async { List spaces = diff --git a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart index 6bfd3ee3..5039340c 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart @@ -48,7 +48,8 @@ class _LoadedStateViewState extends State { hasSpaceModels ? Expanded( child: SpaceModelPage( - spaceModels: widget.spaceModels ??[], + spaceModels: widget.spaceModels ?? [], + products: widget.products, )) : CommunityStructureArea( selectedCommunity: widget.selectedCommunity, diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart new file mode 100644 index 00000000..e39d3b3d --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -0,0 +1,26 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +class CreateSpaceModelBloc extends Bloc { + SpaceTemplateModel? _space; + + CreateSpaceModelBloc() : super(CreateSpaceModelInitial()) { + on((event, emit) { + emit(CreateSpaceModelLoading()); + Future.delayed(const Duration(seconds: 1), () { + if (_space != null) { + emit(CreateSpaceModelLoaded(_space!)); + } else { + emit(CreateSpaceModelError("No space template found")); + } + }); + }); + + on((event, emit) { + _space = event.spaceTemplate; + emit(CreateSpaceModelLoaded(_space!)); + }); + } +} \ No newline at end of file diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart new file mode 100644 index 00000000..f3cee617 --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -0,0 +1,11 @@ +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +abstract class CreateSpaceModelEvent {} + +class LoadSpaceTemplate extends CreateSpaceModelEvent {} + +class UpdateSpaceTemplate extends CreateSpaceModelEvent { + final SpaceTemplateModel spaceTemplate; + + UpdateSpaceTemplate(this.spaceTemplate); +} diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart new file mode 100644 index 00000000..1a9f52bb --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart @@ -0,0 +1,19 @@ +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +abstract class CreateSpaceModelState {} + +class CreateSpaceModelInitial extends CreateSpaceModelState {} + +class CreateSpaceModelLoading extends CreateSpaceModelState {} + +class CreateSpaceModelLoaded extends CreateSpaceModelState { + final SpaceTemplateModel space; + + CreateSpaceModelLoaded(this.space); +} + +class CreateSpaceModelError extends CreateSpaceModelState { + final String message; + + CreateSpaceModelError(this.message); +} diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart index 423a5ca5..344d4b95 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart @@ -1,38 +1,34 @@ import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; - -// Events -abstract class SubSpaceModelEvent {} - -class AddSubSpaceModel extends SubSpaceModelEvent { - final SubspaceTemplateModel subSpace; - AddSubSpaceModel(this.subSpace); -} - -class RemoveSubSpaceModel extends SubSpaceModelEvent { - final SubspaceTemplateModel subSpace; - RemoveSubSpaceModel(this.subSpace); -} - -// State -class SubSpaceModelState { - final List subSpaces; - SubSpaceModelState(this.subSpaces); -} +import 'package:syncrow_web/utils/constants/action_enum.dart'; // BLoC class SubSpaceModelBloc extends Bloc { - SubSpaceModelBloc() : super(SubSpaceModelState([])) { + SubSpaceModelBloc() : super(SubSpaceModelState([], [])) { on((event, emit) { final updatedSubSpaces = List.from(state.subSpaces) ..add(event.subSpace); - emit(SubSpaceModelState(updatedSubSpaces)); + emit(SubSpaceModelState(updatedSubSpaces, [])); }); on((event, emit) { final updatedSubSpaces = List.from(state.subSpaces) ..remove(event.subSpace); - emit(SubSpaceModelState(updatedSubSpaces)); + + final updatedSubspaceModels = List.from( + state.updatedSubSpaceModels, + ); + + if (event.subSpace.uuid?.isNotEmpty ?? false) { + updatedSubspaceModels.add(UpdateSubspaceTemplateModel( + action: Action.delete, + uuid: event.subSpace.uuid!, + )); + } + + emit(SubSpaceModelState(updatedSubSpaces, updatedSubspaceModels)); }); } } diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart new file mode 100644 index 00000000..07d85643 --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart @@ -0,0 +1,14 @@ +// Events +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +abstract class SubSpaceModelEvent {} + +class AddSubSpaceModel extends SubSpaceModelEvent { + final SubspaceTemplateModel subSpace; + AddSubSpaceModel(this.subSpace); +} + +class RemoveSubSpaceModel extends SubSpaceModelEvent { + final SubspaceTemplateModel subSpace; + RemoveSubSpaceModel(this.subSpace); +} diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart new file mode 100644 index 00000000..1579f32f --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart @@ -0,0 +1,11 @@ +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +class SubSpaceModelState { + final List subSpaces; + final List updatedSubSpaceModels; + + SubSpaceModelState( + this.subSpaces, + this.updatedSubSpaceModels, + ); +} diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart index 2107b87f..a885ea6c 100644 --- a/lib/pages/spaces_management/space_model/models/space_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -1,25 +1,20 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/utils/constants/action_enum.dart'; import 'package:uuid/uuid.dart'; class SpaceTemplateModel { - final String uuid; - final DateTime createdAt; - final DateTime updatedAt; + final String? uuid; final String modelName; - final bool disabled; - final List subspaceModels; - final List tags; + final List? subspaceModels; + final List? tags; String internalId; SpaceTemplateModel({ - required this.uuid, + this.uuid, String? internalId, - required this.createdAt, - required this.updatedAt, required this.modelName, - required this.disabled, - required this.subspaceModels, - required this.tags, + this.subspaceModels, + this.tags, }) : internalId = internalId ?? const Uuid().v4(); factory SpaceTemplateModel.fromJson(Map json) { @@ -28,28 +23,22 @@ class SpaceTemplateModel { return SpaceTemplateModel( uuid: json['uuid'] ?? '', internalId: internalId, - createdAt: DateTime.parse(json['createdAt']), - updatedAt: DateTime.parse(json['updatedAt']), modelName: json['modelName'] ?? '', - disabled: json['disabled'] ?? false, subspaceModels: (json['subspaceModels'] as List) - .map((item) => SubspaceTemplateModel.fromJson(item)) - .toList(), + .map((item) => SubspaceTemplateModel.fromJson(item)) + .toList(), tags: (json['tags'] as List) - .map((item) => TagModel.fromJson(item)) - .toList(), + .map((item) => TagModel.fromJson(item)) + .toList(), ); } Map toJson() { return { 'uuid': uuid, - 'createdAt': createdAt.toIso8601String(), - 'updatedAt': updatedAt.toIso8601String(), 'modelName': modelName, - 'disabled': disabled, - 'subspaceModels': subspaceModels.map((e) => e.toJson()).toList(), - 'tags': tags.map((e) => e.toJson()).toList(), + 'subspaceModels': subspaceModels?.map((e) => e.toJson()).toList(), + 'tags': tags?.map((e) => e.toJson()).toList(), }; } } @@ -129,3 +118,75 @@ class TagModel { }; } } + +class UpdateSubspaceTemplateModel { + final String uuid; + final Action action; + final String? subspaceName; + final List? tags; + + UpdateSubspaceTemplateModel({ + required this.action, + required this.uuid, + this.subspaceName, + this.tags, + }); + + factory UpdateSubspaceTemplateModel.fromJson(Map json) { + return UpdateSubspaceTemplateModel( + action: ActionExtension.fromValue(json['action']), + uuid: json['uuid'] ?? '', + subspaceName: json['subspaceName'] ?? '', + tags: (json['tags'] as List) + .map((item) => UpdateTagModel.fromJson(item)) + .toList(), + ); + } + + Map toJson() { + return { + 'action': action.value, + 'uuid': uuid, + 'subspaceName': subspaceName, + 'tags': tags?.map((e) => e.toJson()).toList() ?? [], + }; + } +} + +class UpdateTagModel { + final Action action; + final String? uuid; + final String tag; + final bool disabled; + final ProductModel? product; + + UpdateTagModel({ + required this.action, + this.uuid, + required this.tag, + required this.disabled, + this.product, + }); + + factory UpdateTagModel.fromJson(Map json) { + return UpdateTagModel( + action: ActionExtension.fromValue(json['action']), + uuid: json['uuid'] ?? '', + tag: json['tag'] ?? '', + disabled: json['disabled'] ?? false, + product: json['product'] != null + ? ProductModel.fromMap(json['product']) + : null, + ); + } + + Map toJson() { + return { + 'action': action.value, + 'uuid': uuid, + 'tag': tag, + 'disabled': disabled, + 'product': product?.toMap(), + }; + } +} diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 432713db..dd36350a 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; @@ -6,8 +7,10 @@ import 'package:syncrow_web/utils/color_manager.dart'; class SpaceModelPage extends StatelessWidget { final List spaceModels; + final List? products; - const SpaceModelPage({Key? key, required this.spaceModels}) : super(key: key); + const SpaceModelPage({Key? key, required this.spaceModels, this.products}) + : super(key: key); @override Widget build(BuildContext context) { @@ -33,7 +36,7 @@ class SpaceModelPage extends StatelessWidget { showDialog( context: context, builder: (BuildContext context) { - return const CreateSpaceModelDialog(); + return CreateSpaceModelDialog(products: products); }, ); }, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 4f0fff01..2d14af22 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -1,12 +1,15 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSpaceModelDialog extends StatelessWidget { - const CreateSpaceModelDialog({Key? key}) : super(key: key); + final List? products; + + const CreateSpaceModelDialog({Key? key, this.products}) : super(key: key); @override Widget build(BuildContext context) { @@ -50,94 +53,54 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - GestureDetector( - onTap: () async { - final result = await showDialog( + TextButton( + onPressed: () async { + final result = await showDialog( + barrierDismissible: false, context: context, builder: (BuildContext context) { - return CreateSubSpaceModelDialog( + return const CreateSubSpaceModelDialog( isEdit: true, dialogTitle: 'Create Sub-space', - existingSubSpaces: [ - SubspaceTemplateModel( - subspaceName: "Living Room", - disabled: false, - uuid: "mkmkl,", - ), - ], ); }, ); - if (result == true) {} + if (result != null && result.isNotEmpty) { + // Handle the result if necessary + print('Subspace created: $result'); + } }, - child: SizedBox( - width: screenWidth * 0.25, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - border: Border.all( - color: ColorsManager.neutralGray, - width: 3.0, - ), - borderRadius: BorderRadius.circular(20), - ), - child: const Padding( - padding: - EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), - child: Row( - children: [ - Icon( - Icons.add, - color: ColorsManager.spaceColor, - ), - SizedBox(width: 10), - Expanded( - child: Text( - 'Create sub space', - style: TextStyle( - color: ColorsManager.blackColor, - fontSize: 16, - ), - ), - ), - ], - ), - ), - ), + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + ), + child: _buildButtonContent( + context, + icon: Icons.add, + label: 'Create Sub Space', ), ), const SizedBox(height: 10), - SizedBox( - width: screenWidth * 0.25, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - border: - Border.all(color: ColorsManager.neutralGray, width: 3.0), - borderRadius: BorderRadius.circular(20), - ), - child: const Padding( - padding: - EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), - child: Row( - children: [ - Icon( - Icons.add, - color: ColorsManager.spaceColor, - ), - SizedBox(width: 10), - Expanded( - child: Text( - 'Add devices', - style: TextStyle( - color: ColorsManager.blackColor, - fontSize: 16, - ), - ), - ), - ], + TextButton( + onPressed: () async { + final result = await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AddDeviceWidget( + products: products, ), - ), + ); + if (result == true) { + // Handle the result if necessary + print('Devices added successfully'); + } + }, + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + ), + child: _buildButtonContent( + context, + icon: Icons.add, + label: 'Add Devices', ), ), const SizedBox(height: 20), @@ -146,14 +109,17 @@ class CreateSpaceModelDialog extends StatelessWidget { child: Row( children: [ Expanded( - child: CancelButton( - label: 'Cancel', - onPressed: () => Navigator.of(context).pop(), - )), + child: CancelButton( + label: 'Cancel', + onPressed: () => Navigator.of(context).pop(), + ), + ), const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: () {}, + onPressed: () { + Navigator.of(context).pop(); + }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, foregroundColor: ColorsManager.whiteColors, @@ -162,10 +128,50 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ], ), - ) + ), ], ), ), ); } + + Widget _buildButtonContent(BuildContext context, + {required IconData icon, required String label}) { + final screenWidth = MediaQuery.of(context).size.width; + + return SizedBox( + width: screenWidth * 0.25, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + border: Border.all( + color: ColorsManager.neutralGray, + width: 3.0, + ), + borderRadius: BorderRadius.circular(20), + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), + child: Row( + children: [ + Icon( + icon, + color: ColorsManager.spaceColor, + ), + const SizedBox(width: 10), + Expanded( + child: Text( + label, + style: const TextStyle( + color: ColorsManager.blackColor, + fontSize: 16, + ), + ), + ), + ], + ), + ), + ), + ); + } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart index 9738f079..c4fec906 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart @@ -3,13 +3,15 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSubSpaceModelDialog extends StatefulWidget { - final bool isEdit; // Flag to determine if it's edit or create - final String dialogTitle; // Title for the dialog - final List? existingSubSpaces; // For edit mode + final bool isEdit; + final String dialogTitle; + final List? existingSubSpaces; const CreateSubSpaceModelDialog({ super.key, @@ -52,8 +54,17 @@ class _CreateSubSpaceModelDialogState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), + child: BlocProvider( - create: (_) => SubSpaceModelBloc(), + create: (_) { + final bloc = SubSpaceModelBloc(); + if (widget.existingSubSpaces != null) { + for (var subSpace in widget.existingSubSpaces!) { + bloc.add(AddSubSpaceModel(subSpace)); + } + } + return bloc; + }, child: BlocBuilder( builder: (context, state) { return SizedBox( diff --git a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart index 550361c7..92d5735d 100644 --- a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart @@ -13,16 +13,20 @@ class SpaceModelCardWidget extends StatelessWidget { Widget build(BuildContext context) { final Map productTagCount = {}; - for (var tag in model.tags) { - final prodIcon = tag.product?.icon ?? 'Unknown'; - productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + if (model.tags != null) { + for (var tag in model.tags!) { + final prodIcon = tag.product?.icon ?? 'Unknown'; + productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + } } - for (var subspace in model.subspaceModels) { - if (subspace.tags != null) { - for (var tag in subspace.tags!) { - final prodIcon = tag.product?.icon ?? 'Unknown'; - productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + if (model.subspaceModels != null) { + for (var subspace in model.subspaceModels!) { + if (subspace.tags != null) { + for (var tag in subspace.tags!) { + final prodIcon = tag.product?.icon ?? 'Unknown'; + productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + } } } } @@ -65,7 +69,7 @@ class SpaceModelCardWidget extends StatelessWidget { spacing: 3.0, runSpacing: 3.0, children: [ - for (var subspace in model.subspaceModels.take(3)) + for (var subspace in model.subspaceModels!.take(3)) SubspaceChipWidget(subspace: subspace.subspaceName), ], ), diff --git a/lib/utils/constants/action_enum.dart b/lib/utils/constants/action_enum.dart new file mode 100644 index 00000000..2cfe53de --- /dev/null +++ b/lib/utils/constants/action_enum.dart @@ -0,0 +1,31 @@ +enum Action { + update, + add, + delete, +} + +extension ActionExtension on Action { + String get value { + switch (this) { + case Action.update: + return 'update'; + case Action.add: + return 'add'; + case Action.delete: + return 'delete'; + } + } + + static Action fromValue(String value) { + switch (value) { + case 'update': + return Action.update; + case 'add': + return Action.add; + case 'delete': + return Action.delete; + default: + throw ArgumentError('Invalid action: $value'); + } + } +} From 67ad986fcc49b97967688cec94e290f0f39a294a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 11:59:12 +0400 Subject: [PATCH 022/106] fixed issues in create space model --- .../space_model/bloc/subspace_model_bloc.dart | 22 ++++++- .../bloc/subspace_model_event.dart | 6 +- .../widgets/button_content_widget.dart | 53 +++++++++++++++ .../dialog/create_space_model_dialog.dart | 65 +++++-------------- .../dialog/create_subspace_model_dialog.dart | 55 +++++----------- 5 files changed, 109 insertions(+), 92 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/widgets/button_content_widget.dart diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart index 344d4b95..41c1d76f 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart @@ -10,7 +10,7 @@ class SubSpaceModelBloc extends Bloc { on((event, emit) { final updatedSubSpaces = List.from(state.subSpaces) ..add(event.subSpace); - emit(SubSpaceModelState(updatedSubSpaces, [])); + emit(SubSpaceModelState(updatedSubSpaces, state.updatedSubSpaceModels)); }); on((event, emit) { @@ -30,5 +30,25 @@ class SubSpaceModelBloc extends Bloc { emit(SubSpaceModelState(updatedSubSpaces, updatedSubspaceModels)); }); + + on((event, emit) { + final updatedSubSpaces = state.subSpaces.map((subSpace) { + if (subSpace.uuid == event.updatedSubSpace.uuid) { + return event.updatedSubSpace; + } + return subSpace; + }).toList(); + + final updatedSubspaceModels = List.from( + state.updatedSubSpaceModels, + ); + + updatedSubspaceModels.add(UpdateSubspaceTemplateModel( + action: Action.update, + uuid: event.updatedSubSpace.uuid!, + )); + + emit(SubSpaceModelState(updatedSubSpaces, updatedSubspaceModels)); + }); } } diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart index 07d85643..2147d128 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart @@ -1,4 +1,3 @@ -// Events import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; abstract class SubSpaceModelEvent {} @@ -12,3 +11,8 @@ class RemoveSubSpaceModel extends SubSpaceModelEvent { final SubspaceTemplateModel subSpace; RemoveSubSpaceModel(this.subSpace); } + +class UpdateSubSpaceModel extends SubSpaceModelEvent { + final SubspaceTemplateModel updatedSubSpace; + UpdateSubSpaceModel(this.updatedSubSpace); +} diff --git a/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart b/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart new file mode 100644 index 00000000..81ecb674 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class ButtonContentWidget extends StatelessWidget { + final IconData icon; + final String label; + + const ButtonContentWidget({ + Key? key, + required this.icon, + required this.label, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return SizedBox( + width: screenWidth * 0.25, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + border: Border.all( + color: ColorsManager.neutralGray, + width: 3.0, + ), + borderRadius: BorderRadius.circular(20), + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), + child: Row( + children: [ + Icon( + icon, + color: ColorsManager.spaceColor, + ), + const SizedBox(width: 10), + Expanded( + child: Text( + label, + style: const TextStyle( + color: ColorsManager.blackColor, + fontSize: 16, + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 2d14af22..77ca4e6d 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -3,6 +3,8 @@ import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -14,6 +16,7 @@ class CreateSpaceModelDialog extends StatelessWidget { @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; + List? subspaces = []; // Store subspaces here return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), @@ -55,26 +58,28 @@ class CreateSpaceModelDialog extends StatelessWidget { const SizedBox(height: 16), TextButton( onPressed: () async { - final result = await showDialog( + final result = await showDialog>( barrierDismissible: false, context: context, builder: (BuildContext context) { - return const CreateSubSpaceModelDialog( + return CreateSubSpaceModelDialog( isEdit: true, dialogTitle: 'Create Sub-space', + existingSubSpaces: subspaces, ); }, ); - if (result != null && result.isNotEmpty) { - // Handle the result if necessary - print('Subspace created: $result'); + + if (result != null) { + // Update the subspaces + subspaces = result; + print('Updated Subspaces: $subspaces'); } }, style: TextButton.styleFrom( padding: EdgeInsets.zero, ), - child: _buildButtonContent( - context, + child: const ButtonContentWidget( icon: Icons.add, label: 'Create Sub Space', ), @@ -97,8 +102,7 @@ class CreateSpaceModelDialog extends StatelessWidget { style: TextButton.styleFrom( padding: EdgeInsets.zero, ), - child: _buildButtonContent( - context, + child: ButtonContentWidget( icon: Icons.add, label: 'Add Devices', ), @@ -118,7 +122,8 @@ class CreateSpaceModelDialog extends StatelessWidget { Expanded( child: DefaultButton( onPressed: () { - Navigator.of(context).pop(); + // Return data when OK is pressed + Navigator.of(context).pop(subspaces); }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, @@ -134,44 +139,4 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ); } - - Widget _buildButtonContent(BuildContext context, - {required IconData icon, required String label}) { - final screenWidth = MediaQuery.of(context).size.width; - - return SizedBox( - width: screenWidth * 0.25, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - border: Border.all( - color: ColorsManager.neutralGray, - width: 3.0, - ), - borderRadius: BorderRadius.circular(20), - ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), - child: Row( - children: [ - Icon( - icon, - color: ColorsManager.spaceColor, - ), - const SizedBox(width: 10), - Expanded( - child: Text( - label, - style: const TextStyle( - color: ColorsManager.blackColor, - fontSize: 16, - ), - ), - ), - ], - ), - ), - ), - ); - } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart index c4fec906..31a1f0d0 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart @@ -8,58 +8,32 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_mo import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class CreateSubSpaceModelDialog extends StatefulWidget { +class CreateSubSpaceModelDialog extends StatelessWidget { final bool isEdit; final String dialogTitle; final List? existingSubSpaces; const CreateSubSpaceModelDialog({ - super.key, + Key? key, required this.isEdit, required this.dialogTitle, this.existingSubSpaces, - }); - - @override - _CreateSubSpaceModelDialogState createState() => - _CreateSubSpaceModelDialogState(); -} - -class _CreateSubSpaceModelDialogState extends State { - final TextEditingController textController = TextEditingController(); - final FocusNode focusNode = FocusNode(); - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) { - focusNode.requestFocus(); - }); - - if (widget.isEdit) {} - } - - @override - void dispose() { - textController.dispose(); - focusNode.dispose(); - super.dispose(); - } + }) : super(key: key); @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; + final textController = TextEditingController(); return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), - child: BlocProvider( create: (_) { final bloc = SubSpaceModelBloc(); - if (widget.existingSubSpaces != null) { - for (var subSpace in widget.existingSubSpaces!) { + if (existingSubSpaces != null) { + for (var subSpace in existingSubSpaces!) { bloc.add(AddSubSpaceModel(subSpace)); } } @@ -76,7 +50,7 @@ class _CreateSubSpaceModelDialogState extends State { mainAxisSize: MainAxisSize.min, children: [ Text( - widget.dialogTitle, + dialogTitle, style: Theme.of(context) .textTheme .headlineLarge @@ -97,15 +71,18 @@ class _CreateSubSpaceModelDialogState extends State { children: [ ...state.subSpaces.map( (subSpace) => Chip( - label: Text(subSpace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor)), + label: Text( + subSpace.subspaceName, + style: const TextStyle( + color: ColorsManager.spaceColor), + ), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: const BorderSide( - color: ColorsManager.transparentColor, - width: 0), + color: ColorsManager.transparentColor, + width: 0, + ), ), deleteIcon: Container( width: 24, @@ -132,7 +109,6 @@ class _CreateSubSpaceModelDialogState extends State { width: 200, child: TextField( controller: textController, - focusNode: focusNode, decoration: InputDecoration( border: InputBorder.none, hintText: state.subSpaces.isEmpty @@ -148,7 +124,6 @@ class _CreateSubSpaceModelDialogState extends State { subspaceName: value.trim(), disabled: false))); textController.clear(); - focusNode.requestFocus(); } }, style: const TextStyle( From 1d35377137ce61689e9c5856db93fb80b7e3bb9b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 12:07:48 +0400 Subject: [PATCH 023/106] added white background color to container --- .../space_model/bloc/subspace_model_bloc.dart | 44 +++- .../bloc/subspace_model_state.dart | 14 + .../dialog/create_space_model_dialog.dart | 2 +- .../dialog/create_subspace_model_dialog.dart | 246 +++++++++--------- 4 files changed, 182 insertions(+), 124 deletions(-) diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart index 41c1d76f..7455d8a5 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart @@ -4,15 +4,36 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_mo import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; -// BLoC class SubSpaceModelBloc extends Bloc { - SubSpaceModelBloc() : super(SubSpaceModelState([], [])) { + SubSpaceModelBloc() : super(SubSpaceModelState([], [], '')) { + // Handle AddSubSpaceModel Event on((event, emit) { - final updatedSubSpaces = List.from(state.subSpaces) - ..add(event.subSpace); - emit(SubSpaceModelState(updatedSubSpaces, state.updatedSubSpaceModels)); + // Check for duplicate names (case-insensitive) + final existingNames = + state.subSpaces.map((e) => e.subspaceName.toLowerCase()).toSet(); + + if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) { + // Emit state with an error message if duplicate name exists + emit(SubSpaceModelState( + state.subSpaces, + state.updatedSubSpaceModels, + 'Subspace name already exists.', + )); + } else { + // Add subspace if no duplicate exists + final updatedSubSpaces = + List.from(state.subSpaces) + ..add(event.subSpace); + + emit(SubSpaceModelState( + updatedSubSpaces, + state.updatedSubSpaceModels, + '', // Clear error message + )); + } }); + // Handle RemoveSubSpaceModel Event on((event, emit) { final updatedSubSpaces = List.from(state.subSpaces) ..remove(event.subSpace); @@ -28,9 +49,14 @@ class SubSpaceModelBloc extends Bloc { )); } - emit(SubSpaceModelState(updatedSubSpaces, updatedSubspaceModels)); + emit(SubSpaceModelState( + updatedSubSpaces, + updatedSubspaceModels, + '', // Clear error message + )); }); + // Handle UpdateSubSpaceModel Event on((event, emit) { final updatedSubSpaces = state.subSpaces.map((subSpace) { if (subSpace.uuid == event.updatedSubSpace.uuid) { @@ -48,7 +74,11 @@ class SubSpaceModelBloc extends Bloc { uuid: event.updatedSubSpace.uuid!, )); - emit(SubSpaceModelState(updatedSubSpaces, updatedSubspaceModels)); + emit(SubSpaceModelState( + updatedSubSpaces, + updatedSubspaceModels, + '', // Clear error message + )); }); } } diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart index 1579f32f..9026cb06 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart @@ -3,9 +3,23 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_tem class SubSpaceModelState { final List subSpaces; final List updatedSubSpaceModels; + final String errorMessage; SubSpaceModelState( this.subSpaces, this.updatedSubSpaceModels, + this.errorMessage, ); + + SubSpaceModelState copyWith({ + List? subSpaces, + List? updatedSubSpaceModels, + String? errorMessage, + }) { + return SubSpaceModelState( + subSpaces ?? this.subSpaces, + updatedSubSpaceModels ?? this.updatedSubSpaceModels, + errorMessage ?? this.errorMessage, + ); + } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 77ca4e6d..cdb4ab60 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -102,7 +102,7 @@ class CreateSpaceModelDialog extends StatelessWidget { style: TextButton.styleFrom( padding: EdgeInsets.zero, ), - child: ButtonContentWidget( + child: const ButtonContentWidget( icon: Icons.add, label: 'Add Devices', ), diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart index 31a1f0d0..c603b025 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart @@ -41,131 +41,145 @@ class CreateSubSpaceModelDialog extends StatelessWidget { }, child: BlocBuilder( builder: (context, state) { - return SizedBox( - width: screenWidth * 0.35, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - dialogTitle, - style: Theme.of(context) - .textTheme - .headlineLarge - ?.copyWith(color: ColorsManager.blackColor), - ), - const SizedBox(height: 16), - Container( - width: screenWidth * 0.35, - padding: const EdgeInsets.symmetric( - vertical: 10.0, horizontal: 16.0), - decoration: BoxDecoration( - color: ColorsManager.boxColor, - borderRadius: BorderRadius.circular(10), - ), - child: Wrap( - spacing: 8.0, - runSpacing: 8.0, - children: [ - ...state.subSpaces.map( - (subSpace) => Chip( - label: Text( - subSpace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor), - ), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: const BorderSide( - color: ColorsManager.transparentColor, - width: 0, + return Container( + color: ColorsManager.whiteColors, + child: SizedBox( + width: screenWidth * 0.35, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + dialogTitle, + style: Theme.of(context) + .textTheme + .headlineLarge + ?.copyWith(color: ColorsManager.blackColor), + ), + const SizedBox(height: 16), + Container( + width: screenWidth * 0.35, + padding: const EdgeInsets.symmetric( + vertical: 10.0, horizontal: 16.0), + decoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(10), + ), + child: Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: [ + ...state.subSpaces.map( + (subSpace) => Chip( + label: Text( + subSpace.subspaceName, + style: const TextStyle( + color: ColorsManager.spaceColor), + ), + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide( + color: ColorsManager.transparentColor, + width: 0, + ), + ), + deleteIcon: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager.lightGrayColor, + width: 1.5, + ), + ), + child: const Icon( + Icons.close, + size: 16, + color: ColorsManager.lightGrayColor, + ), + ), + onDeleted: () => context + .read() + .add(RemoveSubSpaceModel(subSpace)), ), ), - deleteIcon: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: ColorsManager.lightGrayColor, - width: 1.5, + SizedBox( + width: 200, + child: TextField( + controller: textController, + decoration: InputDecoration( + border: InputBorder.none, + hintText: state.subSpaces.isEmpty + ? 'Please enter the name' + : null, + hintStyle: const TextStyle( + color: ColorsManager.lightGrayColor), + ), + onSubmitted: (value) { + if (value.trim().isNotEmpty) { + context.read().add( + AddSubSpaceModel( + SubspaceTemplateModel( + subspaceName: value.trim(), + disabled: false))); + textController.clear(); + } + }, + style: const TextStyle( + color: ColorsManager.blackColor), + ), + ), + if (state.errorMessage.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + state.errorMessage, + style: const TextStyle( + color: ColorsManager.warningRed, + fontSize: 12, + ), ), ), - child: const Icon( - Icons.close, - size: 16, - color: ColorsManager.lightGrayColor, - ), - ), - onDeleted: () => context - .read() - .add(RemoveSubSpaceModel(subSpace)), - ), - ), - SizedBox( - width: 200, - child: TextField( - controller: textController, - decoration: InputDecoration( - border: InputBorder.none, - hintText: state.subSpaces.isEmpty - ? 'Please enter the name' - : null, - hintStyle: const TextStyle( - color: ColorsManager.lightGrayColor), - ), - onSubmitted: (value) { - if (value.trim().isNotEmpty) { - context.read().add( - AddSubSpaceModel(SubspaceTemplateModel( - subspaceName: value.trim(), - disabled: false))); - textController.clear(); - } - }, - style: const TextStyle( - color: ColorsManager.blackColor), - ), - ), - ], - ), - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: CancelButton( - label: 'Cancel', - onPressed: () { - Navigator.of(context).pop(); - }, + ], ), ), - const SizedBox(width: 10), - Expanded( - child: DefaultButton( - onPressed: () { - final subSpaces = context - .read() - .state - .subSpaces; - Navigator.of(context).pop(subSpaces); - }, - backgroundColor: ColorsManager.secondaryColor, - borderRadius: 10, - foregroundColor: ColorsManager.whiteColors, - child: const Text('OK'), - ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + onPressed: () { + final subSpaces = context + .read() + .state + .subSpaces; + Navigator.of(context).pop(subSpaces); + }, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ], ), ], ), - ], - ), - ), - ); + ), + )); }, ), ), From 1c256cc55c697b4929e024663087ff53b046f43d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 12:25:34 +0400 Subject: [PATCH 024/106] added event for creating subspace --- .../bloc/create_space_model_bloc.dart | 20 ++++++++++++++-- .../bloc/create_space_model_event.dart | 6 +++++ .../models/space_template_model.dart | 24 +++++++++++++++---- .../dialog/create_space_model_dialog.dart | 8 ++++++- 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index e39d3b3d..25d7c731 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -3,7 +3,8 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; -class CreateSpaceModelBloc extends Bloc { +class CreateSpaceModelBloc + extends Bloc { SpaceTemplateModel? _space; CreateSpaceModelBloc() : super(CreateSpaceModelInitial()) { @@ -22,5 +23,20 @@ class CreateSpaceModelBloc extends Bloc((event, emit) { + if (_space != null) { + final updatedSpace = _space!.copyWith( + subspaceModels: [ + ...(_space!.subspaceModels ?? []), + ...event.subspaces, + ], + ); + _space = updatedSpace; + emit(CreateSpaceModelLoaded(updatedSpace)); + } else { + emit(CreateSpaceModelError("Space template not initialized")); + } + }); } -} \ No newline at end of file +} diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index f3cee617..d3fe8f93 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -9,3 +9,9 @@ class UpdateSpaceTemplate extends CreateSpaceModelEvent { UpdateSpaceTemplate(this.spaceTemplate); } + +class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { + final List subspaces; + + AddSubspacesToSpaceTemplate(this.subspaces); +} diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart index a885ea6c..d6f8090e 100644 --- a/lib/pages/spaces_management/space_model/models/space_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -25,11 +25,11 @@ class SpaceTemplateModel { internalId: internalId, modelName: json['modelName'] ?? '', subspaceModels: (json['subspaceModels'] as List) - .map((item) => SubspaceTemplateModel.fromJson(item)) - .toList(), + .map((item) => SubspaceTemplateModel.fromJson(item)) + .toList(), tags: (json['tags'] as List) - .map((item) => TagModel.fromJson(item)) - .toList(), + .map((item) => TagModel.fromJson(item)) + .toList(), ); } @@ -41,6 +41,22 @@ class SpaceTemplateModel { 'tags': tags?.map((e) => e.toJson()).toList(), }; } + + SpaceTemplateModel copyWith({ + String? uuid, + String? modelName, + List? subspaceModels, + List? tags, + String? internalId, + }) { + return SpaceTemplateModel( + uuid: uuid ?? this.uuid, + modelName: modelName ?? this.modelName, + subspaceModels: subspaceModels ?? this.subspaceModels, + tags: tags ?? this.tags, + internalId: internalId ?? this.internalId, + ); + } } class SubspaceTemplateModel { diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index cdb4ab60..c152fcc8 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -3,6 +3,8 @@ import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; @@ -73,7 +75,11 @@ class CreateSpaceModelDialog extends StatelessWidget { if (result != null) { // Update the subspaces subspaces = result; - print('Updated Subspaces: $subspaces'); + if (result.isNotEmpty) { + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } } }, style: TextButton.styleFrom( From 819670867d58537513e311aef461a9e4c23c74b1 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 12:54:35 +0400 Subject: [PATCH 025/106] separate widget --- .../space_model/view/space_model_page.dart | 7 +- .../dialog/create_space_model_dialog.dart | 127 +++++++++++++----- 2 files changed, 101 insertions(+), 33 deletions(-) diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index dd36350a..9eb72d89 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; @@ -36,7 +38,10 @@ class SpaceModelPage extends StatelessWidget { showDialog( context: context, builder: (BuildContext context) { - return CreateSpaceModelDialog(products: products); + return BlocProvider( + create: (_) => CreateSpaceModelBloc(), + child: CreateSpaceModelDialog(products: products), + ); }, ); }, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index c152fcc8..a5a27923 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; @@ -58,38 +59,7 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - TextButton( - onPressed: () async { - final result = await showDialog>( - barrierDismissible: false, - context: context, - builder: (BuildContext context) { - return CreateSubSpaceModelDialog( - isEdit: true, - dialogTitle: 'Create Sub-space', - existingSubSpaces: subspaces, - ); - }, - ); - - if (result != null) { - // Update the subspaces - subspaces = result; - if (result.isNotEmpty) { - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } - } - }, - style: TextButton.styleFrom( - padding: EdgeInsets.zero, - ), - child: const ButtonContentWidget( - icon: Icons.add, - label: 'Create Sub Space', - ), - ), + _buildSubspacesSection(context, subspaces), const SizedBox(height: 10), TextButton( onPressed: () async { @@ -145,4 +115,97 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ); } + + Widget _buildSubspacesSection( + BuildContext context, List subspaces) { + return Container( + child: subspaces.isEmpty + ? TextButton( + style: TextButton.styleFrom( + overlayColor: Colors.transparent, + ), + onPressed: () async { + final result = await showDialog>( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: subspaces.isEmpty? 'Create Sub-space': 'Edit Sub-space', + existingSubSpaces: subspaces, + ); + }, + ); + + if (result != null) { + subspaces.clear(); + subspaces.addAll(result); + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + }, + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Create Sub Space', + ), + ) + : Row( + children: [ + Expanded( + child: TextField( + readOnly: true, + decoration: InputDecoration( + filled: true, + fillColor: ColorsManager.whiteColors, + hintText: subspaces.map((e) => e.subspaceName).join(", "), + hintStyle: + const TextStyle(color: ColorsManager.spaceColor), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide.none, + ), + ), + ), + ), + const SizedBox(width: 10), + TextButton( + onPressed: () async { + final result = + await showDialog>( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: 'Edit Sub-space', + existingSubSpaces: subspaces, + ); + }, + ); + + if (result != null) { + subspaces.clear(); + subspaces.addAll(result); + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + }, + style: TextButton.styleFrom( + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(color: ColorsManager.spaceColor), + ), + ), + child: const Text( + 'Edit', + style: TextStyle(color: ColorsManager.spaceColor), + ), + ), + ], + ), + ); + } } From 691beb2e86843835306f7e7cffe8d7aea3cf96aa Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 13:08:53 +0400 Subject: [PATCH 026/106] added bloc to widget --- .../space_model/view/space_model_page.dart | 5 +- .../dialog/create_space_model_dialog.dart | 182 ++++++++++-------- 2 files changed, 98 insertions(+), 89 deletions(-) diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 9eb72d89..3c601e47 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -38,10 +38,7 @@ class SpaceModelPage extends StatelessWidget { showDialog( context: context, builder: (BuildContext context) { - return BlocProvider( - create: (_) => CreateSpaceModelBloc(), - child: CreateSpaceModelDialog(products: products), - ); + return CreateSpaceModelDialog(products: products); }, ); }, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index a5a27923..aa4482d3 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; @@ -25,94 +26,103 @@ class CreateSpaceModelDialog extends StatelessWidget { shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), backgroundColor: ColorsManager.whiteColors, content: SizedBox( - width: screenWidth * 0.3, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Create New Space Model', - style: Theme.of(context) - .textTheme - .headlineLarge - ?.copyWith(color: ColorsManager.blackColor), - ), - const SizedBox(height: 16), - SizedBox( - width: screenWidth * 0.25, - child: TextField( - style: const TextStyle(color: ColorsManager.blackColor), - decoration: InputDecoration( - filled: true, - fillColor: ColorsManager.textFieldGreyColor, - hintText: 'Please enter the name', - hintStyle: - const TextStyle(color: ColorsManager.lightGrayColor), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide.none, - ), - contentPadding: const EdgeInsets.symmetric( - vertical: 12.0, - horizontal: 16.0, - ), - ), - ), - ), - const SizedBox(height: 16), - _buildSubspacesSection(context, subspaces), - const SizedBox(height: 10), - TextButton( - onPressed: () async { - final result = await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AddDeviceWidget( - products: products, - ), - ); - if (result == true) { - // Handle the result if necessary - print('Devices added successfully'); - } - }, - style: TextButton.styleFrom( - padding: EdgeInsets.zero, - ), - child: const ButtonContentWidget( - icon: Icons.add, - label: 'Add Devices', - ), - ), - const SizedBox(height: 20), - SizedBox( - width: screenWidth * 0.25, - child: Row( - children: [ - Expanded( - child: CancelButton( - label: 'Cancel', - onPressed: () => Navigator.of(context).pop(), + width: screenWidth * 0.3, + child: BlocProvider( + create: (_) { + final bloc = CreateSpaceModelBloc(); + return bloc; + }, + child: BlocBuilder( + builder: (context, state) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Create New Space Model', + style: Theme.of(context) + .textTheme + .headlineLarge + ?.copyWith(color: ColorsManager.blackColor), ), - ), - const SizedBox(width: 10), - Expanded( - child: DefaultButton( - onPressed: () { - // Return data when OK is pressed - Navigator.of(context).pop(subspaces); + const SizedBox(height: 16), + SizedBox( + width: screenWidth * 0.25, + child: TextField( + style: const TextStyle(color: ColorsManager.blackColor), + decoration: InputDecoration( + filled: true, + fillColor: ColorsManager.textFieldGreyColor, + hintText: 'Please enter the name', + hintStyle: const TextStyle( + color: ColorsManager.lightGrayColor), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric( + vertical: 12.0, + horizontal: 16.0, + ), + ), + ), + ), + const SizedBox(height: 16), + _buildSubspacesSection(context, subspaces), + const SizedBox(height: 10), + TextButton( + onPressed: () async { + final result = await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AddDeviceWidget( + products: products, + ), + ); + if (result == true) { + // Handle the result if necessary + print('Devices added successfully'); + } }, - backgroundColor: ColorsManager.secondaryColor, - borderRadius: 10, - foregroundColor: ColorsManager.whiteColors, - child: const Text('OK'), + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + ), + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Add Devices', + ), ), - ), - ], - ), + const SizedBox(height: 20), + SizedBox( + width: screenWidth * 0.25, + child: Row( + children: [ + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () => Navigator.of(context).pop(), + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + onPressed: () { + // Return data when OK is pressed + Navigator.of(context).pop(subspaces); + }, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ], + ), + ), + ], + ); + }, ), - ], - ), - ), + )), ); } @@ -131,7 +141,9 @@ class CreateSpaceModelDialog extends StatelessWidget { builder: (BuildContext context) { return CreateSubSpaceModelDialog( isEdit: true, - dialogTitle: subspaces.isEmpty? 'Create Sub-space': 'Edit Sub-space', + dialogTitle: subspaces.isEmpty + ? 'Create Sub-space' + : 'Edit Sub-space', existingSubSpaces: subspaces, ); }, From d2d5e76102efca2f8779cc9f6bbe541330069551 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 14:06:46 +0400 Subject: [PATCH 027/106] subspace model and view --- .../dialog/create_space_model_dialog.dart | 195 ++++++++++-------- 1 file changed, 110 insertions(+), 85 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index aa4482d3..23fce4cc 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -128,96 +128,121 @@ class CreateSpaceModelDialog extends StatelessWidget { Widget _buildSubspacesSection( BuildContext context, List subspaces) { + final screenWidth = MediaQuery.of(context).size.width; + return Container( - child: subspaces.isEmpty - ? TextButton( - style: TextButton.styleFrom( - overlayColor: Colors.transparent, - ), - onPressed: () async { - final result = await showDialog>( - barrierDismissible: false, - context: context, - builder: (BuildContext context) { - return CreateSubSpaceModelDialog( - isEdit: true, - dialogTitle: subspaces.isEmpty - ? 'Create Sub-space' - : 'Edit Sub-space', - existingSubSpaces: subspaces, - ); - }, - ); + child: subspaces.isEmpty + ? TextButton( + style: TextButton.styleFrom( + overlayColor: Colors.transparent, + ), + onPressed: () async { + final result = await showDialog>( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: subspaces.isEmpty + ? 'Create Sub-space' + : 'Edit Sub-space', + existingSubSpaces: subspaces, + ); + }, + ); - if (result != null) { - subspaces.clear(); - subspaces.addAll(result); - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } - }, - child: const ButtonContentWidget( - icon: Icons.add, - label: 'Create Sub Space', - ), - ) - : Row( - children: [ - Expanded( - child: TextField( - readOnly: true, - decoration: InputDecoration( - filled: true, - fillColor: ColorsManager.whiteColors, - hintText: subspaces.map((e) => e.subspaceName).join(", "), - hintStyle: - const TextStyle(color: ColorsManager.spaceColor), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide.none, + if (result != null) { + subspaces.clear(); + subspaces.addAll(result); + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + }, + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Create Sub Space', + ), + ) + : SizedBox( + width: screenWidth * 0.25, // Set the desired width + child: Container( + padding: const EdgeInsets.all( + 8.0), // Add padding around the content + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, // Background color + borderRadius: BorderRadius.circular(15), // Rounded corners + border: Border.all( + color: ColorsManager.textFieldGreyColor, // Border color + width: 3.0, // Border width + ), + ), + child: Wrap( + spacing: 8.0, // Spacing between chips + runSpacing: 8.0, // Spacing between rows of chips + children: [ + ...subspaces.map( + (subspace) => Chip( + label: Text( + subspace.subspaceName, + style: const TextStyle( + color: ColorsManager.spaceColor), // Text color + ), + backgroundColor: + Colors.white, // Chip background color + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(16), // Rounded chip + side: const BorderSide( + color: + ColorsManager.spaceColor), // Border color + ), + ), ), - ), - ), - ), - const SizedBox(width: 10), - TextButton( - onPressed: () async { - final result = - await showDialog>( - barrierDismissible: false, - context: context, - builder: (BuildContext context) { - return CreateSubSpaceModelDialog( - isEdit: true, - dialogTitle: 'Edit Sub-space', - existingSubSpaces: subspaces, - ); - }, - ); + GestureDetector( + onTap: () async { + final result = + await showDialog>( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: 'Edit Sub-space', + existingSubSpaces: subspaces, + ); + }, + ); - if (result != null) { - subspaces.clear(); - subspaces.addAll(result); - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } - }, - style: TextButton.styleFrom( - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: BorderSide(color: ColorsManager.spaceColor), - ), - ), - child: const Text( - 'Edit', - style: TextStyle(color: ColorsManager.spaceColor), + if (result != null) { + subspaces.clear(); + subspaces.addAll(result); + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + }, + child: Chip( + label: const Text( + 'Edit', + style: TextStyle( + color: ColorsManager + .spaceColor), // Text color for "Edit" + ), + backgroundColor: + Colors.white, // Background color for "Edit" + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(16), // Rounded chip + side: const BorderSide( + color: + ColorsManager.spaceColor), // Border color + ), + ), + ), + ], ), ), - ], - ), - ); + )); } } From 69092823a2c593f1db4317d4ae2a560c0544b06c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 14:35:43 +0400 Subject: [PATCH 028/106] separation of widget --- .../dialog/create_space_model_dialog.dart | 126 +---------------- .../widgets/subspace_model_create_widget.dart | 133 ++++++++++++++++++ 2 files changed, 136 insertions(+), 123 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 23fce4cc..1e497f3d 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -5,11 +5,11 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; + +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSpaceModelDialog extends StatelessWidget { @@ -67,7 +67,7 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - _buildSubspacesSection(context, subspaces), + SubspaceModelCreate(context, subspaces: subspaces), const SizedBox(height: 10), TextButton( onPressed: () async { @@ -125,124 +125,4 @@ class CreateSpaceModelDialog extends StatelessWidget { )), ); } - - Widget _buildSubspacesSection( - BuildContext context, List subspaces) { - final screenWidth = MediaQuery.of(context).size.width; - - return Container( - child: subspaces.isEmpty - ? TextButton( - style: TextButton.styleFrom( - overlayColor: Colors.transparent, - ), - onPressed: () async { - final result = await showDialog>( - barrierDismissible: false, - context: context, - builder: (BuildContext context) { - return CreateSubSpaceModelDialog( - isEdit: true, - dialogTitle: subspaces.isEmpty - ? 'Create Sub-space' - : 'Edit Sub-space', - existingSubSpaces: subspaces, - ); - }, - ); - - if (result != null) { - subspaces.clear(); - subspaces.addAll(result); - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } - }, - child: const ButtonContentWidget( - icon: Icons.add, - label: 'Create Sub Space', - ), - ) - : SizedBox( - width: screenWidth * 0.25, // Set the desired width - child: Container( - padding: const EdgeInsets.all( - 8.0), // Add padding around the content - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, // Background color - borderRadius: BorderRadius.circular(15), // Rounded corners - border: Border.all( - color: ColorsManager.textFieldGreyColor, // Border color - width: 3.0, // Border width - ), - ), - child: Wrap( - spacing: 8.0, // Spacing between chips - runSpacing: 8.0, // Spacing between rows of chips - children: [ - ...subspaces.map( - (subspace) => Chip( - label: Text( - subspace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor), // Text color - ), - backgroundColor: - Colors.white, // Chip background color - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16), // Rounded chip - side: const BorderSide( - color: - ColorsManager.spaceColor), // Border color - ), - ), - ), - GestureDetector( - onTap: () async { - final result = - await showDialog>( - barrierDismissible: false, - context: context, - builder: (BuildContext context) { - return CreateSubSpaceModelDialog( - isEdit: true, - dialogTitle: 'Edit Sub-space', - existingSubSpaces: subspaces, - ); - }, - ); - - if (result != null) { - subspaces.clear(); - subspaces.addAll(result); - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } - }, - child: Chip( - label: const Text( - 'Edit', - style: TextStyle( - color: ColorsManager - .spaceColor), // Text color for "Edit" - ), - backgroundColor: - Colors.white, // Background color for "Edit" - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16), // Rounded chip - side: const BorderSide( - color: - ColorsManager.spaceColor), // Border color - ), - ), - ), - ], - ), - ), - )); - } } diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart new file mode 100644 index 00000000..df37810a --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; + +class SubspaceModelCreate extends StatelessWidget { + final List subspaces; + + const SubspaceModelCreate(BuildContext context, { + Key? key, + required this.subspaces, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return Container( + child: subspaces.isEmpty + ? TextButton( + style: TextButton.styleFrom( + overlayColor: ColorsManager.transparentColor, + ), + onPressed: () async { + final result = await showDialog>( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: subspaces.isEmpty + ? 'Create Sub-space' + : 'Edit Sub-space', + existingSubSpaces: subspaces, + ); + }, + ); + + if (result != null) { + subspaces.clear(); + subspaces.addAll(result); + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + }, + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Create Sub Space', + ), + ) + : SizedBox( + width: screenWidth * 0.25, // Set the desired width + child: Container( + padding: const EdgeInsets.all(8.0), // Add padding + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, // Background color + borderRadius: BorderRadius.circular(15), // Rounded corners + border: Border.all( + color: ColorsManager.textFieldGreyColor, // Border color + width: 3.0, // Border width + ), + ), + child: Wrap( + spacing: 8.0, // Spacing between chips + runSpacing: 8.0, // Spacing between rows of chips + children: [ + ...subspaces.map( + (subspace) => Chip( + label: Text( + subspace.subspaceName, + style: const TextStyle( + color: ColorsManager.spaceColor), // Text color + ), + backgroundColor: Colors.white, // Chip background color + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(16), // Rounded chip + side: const BorderSide( + color: ColorsManager.spaceColor), // Border color + ), + ), + ), + GestureDetector( + onTap: () async { + final result = + await showDialog>( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: 'Edit Sub-space', + existingSubSpaces: subspaces, + ); + }, + ); + + if (result != null) { + subspaces.clear(); + subspaces.addAll(result); + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + }, + child: Chip( + label: const Text( + 'Edit', + style: TextStyle( + color: ColorsManager.spaceColor), // Text color + ), + backgroundColor: + Colors.white, // Background color for "Edit" + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(16), // Rounded chip + side: const BorderSide( + color: ColorsManager.spaceColor), // Border color + ), + ), + ), + ], + ), + ), + ), + ); + } +} From 0bed2573a02a94cb63f9658b94d9351a33935071 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 15:31:32 +0400 Subject: [PATCH 029/106] separation of models into different files --- .../bloc/create_space_model_event.dart | 1 + .../space_model/bloc/subspace_model_bloc.dart | 1 + .../bloc/subspace_model_event.dart | 2 +- .../bloc/subspace_model_state.dart | 1 + .../models/space_template_model.dart | 77 +----- .../models/subspace_template_model.dart | 35 +++ .../space_model/models/tag_model.dart | 44 ++++ .../dialog/add_device_type_model_widget.dart | 223 ++++++++++++++++++ .../dialog/create_space_model_dialog.dart | 6 +- .../dialog/create_subspace_model_dialog.dart | 2 +- .../widgets/subspace_model_create_widget.dart | 2 +- 11 files changed, 314 insertions(+), 80 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/models/subspace_template_model.dart create mode 100644 lib/pages/spaces_management/space_model/models/tag_model.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index d3fe8f93..6398c7ec 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -1,4 +1,5 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; abstract class CreateSpaceModelEvent {} diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart index 7455d8a5..4aa3dd82 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart @@ -2,6 +2,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; class SubSpaceModelBloc extends Bloc { diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart index 2147d128..63629472 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart @@ -1,4 +1,4 @@ -import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; abstract class SubSpaceModelEvent {} diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart index 9026cb06..ab60a813 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart @@ -1,4 +1,5 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; class SubSpaceModelState { final List subSpaces; diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart index d6f8090e..51804178 100644 --- a/lib/pages/spaces_management/space_model/models/space_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -1,4 +1,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; import 'package:uuid/uuid.dart'; @@ -59,81 +61,6 @@ class SpaceTemplateModel { } } -class SubspaceTemplateModel { - final String? uuid; - final String subspaceName; - final bool disabled; - final List? tags; - - SubspaceTemplateModel({ - this.uuid, - required this.subspaceName, - required this.disabled, - this.tags, - }); - - factory SubspaceTemplateModel.fromJson(Map json) { - return SubspaceTemplateModel( - uuid: json['uuid'] ?? '', - subspaceName: json['subspaceName'] ?? '', - disabled: json['disabled'] ?? false, - tags: (json['tags'] as List) - .map((item) => TagModel.fromJson(item)) - .toList(), - ); - } - - Map toJson() { - return { - 'uuid': uuid, - 'subspaceName': subspaceName, - 'disabled': disabled, - 'tags': tags?.map((e) => e.toJson()).toList() ?? [], - }; - } -} - -class TagModel { - final String uuid; - final DateTime createdAt; - final DateTime updatedAt; - final String tag; - final bool disabled; - final ProductModel? product; - - TagModel({ - required this.uuid, - required this.createdAt, - required this.updatedAt, - required this.tag, - required this.disabled, - this.product, - }); - - factory TagModel.fromJson(Map json) { - return TagModel( - uuid: json['uuid'] ?? '', - createdAt: DateTime.parse(json['createdAt']), - updatedAt: DateTime.parse(json['updatedAt']), - tag: json['tag'] ?? '', - disabled: json['disabled'] ?? false, - product: json['product'] != null - ? ProductModel.fromMap(json['product']) - : null, - ); - } - - Map toJson() { - return { - 'uuid': uuid, - 'createdAt': createdAt.toIso8601String(), - 'updatedAt': updatedAt.toIso8601String(), - 'tag': tag, - 'disabled': disabled, - 'product': product?.toMap(), - }; - } -} class UpdateSubspaceTemplateModel { final String uuid; diff --git a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart new file mode 100644 index 00000000..8f9d4d4d --- /dev/null +++ b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart @@ -0,0 +1,35 @@ +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; + +class SubspaceTemplateModel { + final String? uuid; + final String subspaceName; + final bool disabled; + final List? tags; + + SubspaceTemplateModel({ + this.uuid, + required this.subspaceName, + required this.disabled, + this.tags, + }); + + factory SubspaceTemplateModel.fromJson(Map json) { + return SubspaceTemplateModel( + uuid: json['uuid'] ?? '', + subspaceName: json['subspaceName'] ?? '', + disabled: json['disabled'] ?? false, + tags: (json['tags'] as List) + .map((item) => TagModel.fromJson(item)) + .toList(), + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'subspaceName': subspaceName, + 'disabled': disabled, + 'tags': tags?.map((e) => e.toJson()).toList() ?? [], + }; + } +} diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart new file mode 100644 index 00000000..356368fc --- /dev/null +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -0,0 +1,44 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; + +class TagModel { + final String uuid; + final DateTime createdAt; + final DateTime updatedAt; + final String tag; + final bool disabled; + final ProductModel? product; + + TagModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.tag, + required this.disabled, + this.product, + }); + + factory TagModel.fromJson(Map json) { + return TagModel( + uuid: json['uuid'] ?? '', + createdAt: DateTime.parse(json['createdAt']), + updatedAt: DateTime.parse(json['updatedAt']), + tag: json['tag'] ?? '', + disabled: json['disabled'] ?? false, + product: json['product'] != null + ? ProductModel.fromMap(json['product']) + : null, + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'tag': tag, + 'disabled': disabled, + 'product': product?.toMap(), + }; + } +} + diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart b/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart new file mode 100644 index 00000000..85285502 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart @@ -0,0 +1,223 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.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 AddDeviceTypeModelWidget extends StatefulWidget { + final List? products; + final ValueChanged>? onProductsSelected; + final List? initialSelectedProducts; + + const AddDeviceTypeModelWidget({ + super.key, + this.products, + this.initialSelectedProducts, + this.onProductsSelected, + }); + + @override + _AddDeviceWidgetState createState() => _AddDeviceWidgetState(); +} + +class _AddDeviceWidgetState extends State { + late final ScrollController _scrollController; + late List productCounts; + + @override + void initState() { + super.initState(); + _scrollController = ScrollController(); + productCounts = widget.initialSelectedProducts != null + ? List.from(widget.initialSelectedProducts!) + : []; + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + + // Adjust the GridView properties based on screen width + final crossAxisCount = size.width > 1200 + ? 8 + : size.width > 800 + ? 5 + : 3; + + return AlertDialog( + title: const Text('Add Devices'), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Container( + width: size.width * 0.9, + height: size.height * 0.65, + color: ColorsManager.textFieldGreyColor, + child: Column( + children: [ + const SizedBox(height: 16), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Scrollbar( + controller: _scrollController, + thumbVisibility: false, + child: GridView.builder( + shrinkWrap: true, + controller: _scrollController, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + mainAxisSpacing: 6, + crossAxisSpacing: 4, + childAspectRatio: .8, + ), + itemCount: widget.products?.length ?? 0, + itemBuilder: (context, index) { + final product = widget.products![index]; + return _buildDeviceTypeTile(product, size); + }, + ), + ), + ), + ), + ], + ), + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildActionButton( + 'Cancel', ColorsManager.boxColor, ColorsManager.blackColor, () { + Navigator.of(context).pop(); + }), + _buildActionButton( + 'Continue', ColorsManager.secondaryColor, Colors.white, () { + Navigator.of(context).pop(); + if (widget.onProductsSelected != null) { + widget.onProductsSelected!(productCounts); + } + }), + ], + ), + ], + ); + } + + Widget _buildDeviceTypeTile(ProductModel product, Size size) { + final selectedProduct = productCounts.firstWhere( + (p) => p.productId == product.uuid, + orElse: () => SelectedProduct(productId: product.uuid, count: 0), + ); + + return SizedBox( + width: size.width * 0.12, + height: size.height * 0.15, + child: Card( + elevation: 2, + color: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildDeviceIcon(product, size), + const SizedBox(height: 4), + _buildDeviceName(product, size), + const SizedBox(height: 4), + CounterWidget( + initialCount: selectedProduct.count, + onCountChanged: (newCount) { + setState(() { + if (newCount > 0) { + if (!productCounts.contains(selectedProduct)) { + productCounts.add(SelectedProduct( + productId: product.uuid, count: newCount)); + } else { + selectedProduct.count = newCount; + } + } else { + productCounts + .removeWhere((p) => p.productId == product.uuid); + } + + if (widget.onProductsSelected != null) { + widget.onProductsSelected!(productCounts); + } + }); + }, + ), + ], + ), + ), + ), + ); + } + + Widget _buildDeviceIcon(ProductModel product, Size size) { + return Container( + height: size.width > 800 ? 50 : 40, + width: size.width > 800 ? 50 : 40, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.textFieldGreyColor, + border: Border.all( + color: ColorsManager.neutralGray, + width: 2, + ), + ), + child: Center( + child: SvgPicture.asset( + product.icon ?? Assets.sensors, + width: size.width > 800 ? 30 : 20, + height: size.width > 800 ? 30 : 20, + ), + ), + ); + } + + Widget _buildDeviceName(ProductModel product, Size size) { + return SizedBox( + height: size.width > 800 ? 35 : 25, + child: Text( + product.name ?? '', + style: context.textTheme.bodySmall + ?.copyWith(color: ColorsManager.blackColor), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ); + } + + Widget _buildActionButton( + String label, + Color backgroundColor, + Color foregroundColor, + VoidCallback onPressed, + ) { + return SizedBox( + width: 120, + child: DefaultButton( + onPressed: onPressed, + backgroundColor: backgroundColor, + foregroundColor: foregroundColor, + child: Text(label), + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 1e497f3d..e2095703 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -3,15 +3,17 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import '../../models/subspace_template_model.dart'; + class CreateSpaceModelDialog extends StatelessWidget { final List? products; @@ -74,7 +76,7 @@ class CreateSpaceModelDialog extends StatelessWidget { final result = await showDialog( barrierDismissible: false, context: context, - builder: (context) => AddDeviceWidget( + builder: (context) => AddDeviceTypeModelWidget( products: products, ), ); diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart index c603b025..65c85efa 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart @@ -5,7 +5,7 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_state.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSubSpaceModelDialog extends StatelessWidget { diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index df37810a..0ccd929e 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; From ed06b0ebd6e6f25fba00eb30ee573947d062454f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 15:37:20 +0400 Subject: [PATCH 030/106] converted to stateless --- .../bloc/add_device_model_bloc.dart | 30 +++ .../dialog/add_device_type_model_widget.dart | 173 ++++++++---------- 2 files changed, 106 insertions(+), 97 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/bloc/add_device_model_bloc.dart diff --git a/lib/pages/spaces_management/space_model/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/add_device_model_bloc.dart new file mode 100644 index 00000000..31c99dc4 --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/add_device_model_bloc.dart @@ -0,0 +1,30 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; + +class AddDeviceTypeModelBloc extends Cubit> { + AddDeviceTypeModelBloc(List initialProducts) + : super(initialProducts); + + void updateProductCount(String productId, int count) { + final existingProduct = state.firstWhere( + (p) => p.productId == productId, + orElse: () => SelectedProduct(productId: productId, count: 0), + ); + + if (count > 0) { + if (!state.contains(existingProduct)) { + emit([...state, SelectedProduct(productId: productId, count: count)]); + } else { + final updatedList = state.map((p) { + if (p.productId == productId) { + return SelectedProduct(productId: p.productId, count: count); + } + return p; + }).toList(); + emit(updatedList); + } + } else { + emit(state.where((p) => p.productId != productId).toList()); + } + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart b/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart index 85285502..06ec6bf4 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart @@ -1,14 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/add_device_model_bloc.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 AddDeviceTypeModelWidget extends StatefulWidget { +class AddDeviceTypeModelWidget extends StatelessWidget { final List? products; final ValueChanged>? onProductsSelected; final List? initialSelectedProducts; @@ -20,101 +21,93 @@ class AddDeviceTypeModelWidget extends StatefulWidget { this.onProductsSelected, }); - @override - _AddDeviceWidgetState createState() => _AddDeviceWidgetState(); -} - -class _AddDeviceWidgetState extends State { - late final ScrollController _scrollController; - late List productCounts; - - @override - void initState() { - super.initState(); - _scrollController = ScrollController(); - productCounts = widget.initialSelectedProducts != null - ? List.from(widget.initialSelectedProducts!) - : []; - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; - - // Adjust the GridView properties based on screen width final crossAxisCount = size.width > 1200 ? 8 : size.width > 800 ? 5 : 3; - return AlertDialog( - title: const Text('Add Devices'), - backgroundColor: ColorsManager.whiteColors, - content: SingleChildScrollView( - child: Container( - width: size.width * 0.9, - height: size.height * 0.65, - color: ColorsManager.textFieldGreyColor, - child: Column( - children: [ - const SizedBox(height: 16), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Scrollbar( - controller: _scrollController, - thumbVisibility: false, - child: GridView.builder( - shrinkWrap: true, - controller: _scrollController, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: crossAxisCount, - mainAxisSpacing: 6, - crossAxisSpacing: 4, - childAspectRatio: .8, + return BlocProvider( + create: (_) => AddDeviceTypeModelBloc( + initialSelectedProducts ?? []), // Initialize with initial products + child: AlertDialog( + title: const Text('Add Devices'), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Container( + width: size.width * 0.9, + height: size.height * 0.65, + color: ColorsManager.textFieldGreyColor, + child: Column( + children: [ + const SizedBox(height: 16), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Scrollbar( + thumbVisibility: false, + child: BlocBuilder>( + builder: (context, productCounts) { + return GridView.builder( + shrinkWrap: true, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + mainAxisSpacing: 6, + crossAxisSpacing: 4, + childAspectRatio: .8, + ), + itemCount: products?.length ?? 0, + itemBuilder: (context, index) { + final product = products![index]; + return _buildDeviceTypeTile( + context, product, size, productCounts); + }, + ); + }, ), - itemCount: widget.products?.length ?? 0, - itemBuilder: (context, index) { - final product = widget.products![index]; - return _buildDeviceTypeTile(product, size); - }, ), ), ), + ], + ), + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildActionButton( + 'Cancel', + ColorsManager.boxColor, + ColorsManager.blackColor, + () => Navigator.of(context).pop(), + ), + _buildActionButton( + 'Continue', + ColorsManager.secondaryColor, + ColorsManager.whiteColors, + () { + Navigator.of(context).pop(); + if (onProductsSelected != null) { + final selectedProducts = + context.read().state; + onProductsSelected!(selectedProducts); + } + }, ), ], ), - ), + ], ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildActionButton( - 'Cancel', ColorsManager.boxColor, ColorsManager.blackColor, () { - Navigator.of(context).pop(); - }), - _buildActionButton( - 'Continue', ColorsManager.secondaryColor, Colors.white, () { - Navigator.of(context).pop(); - if (widget.onProductsSelected != null) { - widget.onProductsSelected!(productCounts); - } - }), - ], - ), - ], ); } - Widget _buildDeviceTypeTile(ProductModel product, Size size) { + Widget _buildDeviceTypeTile(BuildContext context, ProductModel product, + Size size, List productCounts) { final selectedProduct = productCounts.firstWhere( (p) => p.productId == product.uuid, orElse: () => SelectedProduct(productId: product.uuid, count: 0), @@ -137,28 +130,14 @@ class _AddDeviceWidgetState extends State { children: [ _buildDeviceIcon(product, size), const SizedBox(height: 4), - _buildDeviceName(product, size), + _buildDeviceName(context,product, size), const SizedBox(height: 4), CounterWidget( initialCount: selectedProduct.count, onCountChanged: (newCount) { - setState(() { - if (newCount > 0) { - if (!productCounts.contains(selectedProduct)) { - productCounts.add(SelectedProduct( - productId: product.uuid, count: newCount)); - } else { - selectedProduct.count = newCount; - } - } else { - productCounts - .removeWhere((p) => p.productId == product.uuid); - } - - if (widget.onProductsSelected != null) { - widget.onProductsSelected!(productCounts); - } - }); + context + .read() + .updateProductCount(product.uuid, newCount); }, ), ], @@ -190,12 +169,12 @@ class _AddDeviceWidgetState extends State { ); } - Widget _buildDeviceName(ProductModel product, Size size) { + Widget _buildDeviceName(BuildContext context, product, Size size) { return SizedBox( height: size.width > 800 ? 35 : 25, child: Text( product.name ?? '', - style: context.textTheme.bodySmall + style: Theme.of(context).textTheme.bodySmall ?.copyWith(color: ColorsManager.blackColor), textAlign: TextAlign.center, maxLines: 2, From b59e7e4836f311c54f8063afae4ea333b425dc68 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 15:41:03 +0400 Subject: [PATCH 031/106] reassign folders --- .../space_model/widgets/subspace_model_create_widget.dart | 2 +- .../bloc/subspace_model_bloc.dart | 4 ++-- .../bloc/subspace_model_event.dart | 0 .../bloc/subspace_model_state.dart | 0 .../views}/create_subspace_model_dialog.dart | 6 +++--- 5 files changed, 6 insertions(+), 6 deletions(-) rename lib/pages/spaces_management/{space_model => subspace_model}/bloc/subspace_model_bloc.dart (93%) rename lib/pages/spaces_management/{space_model => subspace_model}/bloc/subspace_model_event.dart (100%) rename lib/pages/spaces_management/{space_model => subspace_model}/bloc/subspace_model_state.dart (100%) rename lib/pages/spaces_management/{space_model/widgets/dialog => subspace_model/views}/create_subspace_model_dialog.dart (96%) diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 0ccd929e..4706fece 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/subspace_model/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/subspace_model/bloc/subspace_model_bloc.dart similarity index 93% rename from lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart rename to lib/pages/spaces_management/subspace_model/bloc/subspace_model_bloc.dart index 4aa3dd82..7feadc4f 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/subspace_model/bloc/subspace_model_bloc.dart @@ -1,6 +1,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_event.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart b/lib/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart similarity index 100% rename from lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart rename to lib/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart b/lib/pages/spaces_management/subspace_model/bloc/subspace_model_state.dart similarity index 100% rename from lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart rename to lib/pages/spaces_management/subspace_model/bloc/subspace_model_state.dart diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart b/lib/pages/spaces_management/subspace_model/views/create_subspace_model_dialog.dart similarity index 96% rename from lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart rename to lib/pages/spaces_management/subspace_model/views/create_subspace_model_dialog.dart index 65c85efa..54981975 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/subspace_model/views/create_subspace_model_dialog.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_event.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; From ae09cbda1e422907802ca52457cad16d57f04633 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 15:59:11 +0400 Subject: [PATCH 032/106] separating folder --- .../dialog/add_device_type_model_widget.dart | 202 ------------------ .../dialog/create_space_model_dialog.dart | 2 +- .../bloc/add_device_model_bloc.dart | 0 .../views/add_device_type_model_widget.dart | 87 ++++++++ .../widgets/action_button_widget.dart | 30 +++ .../tag_model/widgets/device_icon_widget.dart | 36 ++++ .../tag_model/widgets/device_name_widget.dart | 28 +++ .../widgets/device_type_tile_widget.dart | 58 +++++ .../widgets/scrollable_grid_view_widget.dart | 49 +++++ 9 files changed, 289 insertions(+), 203 deletions(-) delete mode 100644 lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart rename lib/pages/spaces_management/{space_model => tag_model}/bloc/add_device_model_bloc.dart (100%) create mode 100644 lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart create mode 100644 lib/pages/spaces_management/tag_model/widgets/action_button_widget.dart create mode 100644 lib/pages/spaces_management/tag_model/widgets/device_icon_widget.dart create mode 100644 lib/pages/spaces_management/tag_model/widgets/device_name_widget.dart create mode 100644 lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart create mode 100644 lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart b/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart deleted file mode 100644 index 06ec6bf4..00000000 --- a/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart +++ /dev/null @@ -1,202 +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/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/add_device_model_bloc.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/constants/assets.dart'; - -class AddDeviceTypeModelWidget extends StatelessWidget { - final List? products; - final ValueChanged>? onProductsSelected; - final List? initialSelectedProducts; - - const AddDeviceTypeModelWidget({ - super.key, - this.products, - this.initialSelectedProducts, - this.onProductsSelected, - }); - - @override - Widget build(BuildContext context) { - final size = MediaQuery.of(context).size; - final crossAxisCount = size.width > 1200 - ? 8 - : size.width > 800 - ? 5 - : 3; - - return BlocProvider( - create: (_) => AddDeviceTypeModelBloc( - initialSelectedProducts ?? []), // Initialize with initial products - child: AlertDialog( - title: const Text('Add Devices'), - backgroundColor: ColorsManager.whiteColors, - content: SingleChildScrollView( - child: Container( - width: size.width * 0.9, - height: size.height * 0.65, - color: ColorsManager.textFieldGreyColor, - child: Column( - children: [ - const SizedBox(height: 16), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Scrollbar( - thumbVisibility: false, - child: BlocBuilder>( - builder: (context, productCounts) { - return GridView.builder( - shrinkWrap: true, - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: crossAxisCount, - mainAxisSpacing: 6, - crossAxisSpacing: 4, - childAspectRatio: .8, - ), - itemCount: products?.length ?? 0, - itemBuilder: (context, index) { - final product = products![index]; - return _buildDeviceTypeTile( - context, product, size, productCounts); - }, - ); - }, - ), - ), - ), - ), - ], - ), - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildActionButton( - 'Cancel', - ColorsManager.boxColor, - ColorsManager.blackColor, - () => Navigator.of(context).pop(), - ), - _buildActionButton( - 'Continue', - ColorsManager.secondaryColor, - ColorsManager.whiteColors, - () { - Navigator.of(context).pop(); - if (onProductsSelected != null) { - final selectedProducts = - context.read().state; - onProductsSelected!(selectedProducts); - } - }, - ), - ], - ), - ], - ), - ); - } - - Widget _buildDeviceTypeTile(BuildContext context, ProductModel product, - Size size, List productCounts) { - final selectedProduct = productCounts.firstWhere( - (p) => p.productId == product.uuid, - orElse: () => SelectedProduct(productId: product.uuid, count: 0), - ); - - return SizedBox( - width: size.width * 0.12, - height: size.height * 0.15, - child: Card( - elevation: 2, - color: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - child: Padding( - padding: const EdgeInsets.all(4.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - _buildDeviceIcon(product, size), - const SizedBox(height: 4), - _buildDeviceName(context,product, size), - const SizedBox(height: 4), - CounterWidget( - initialCount: selectedProduct.count, - onCountChanged: (newCount) { - context - .read() - .updateProductCount(product.uuid, newCount); - }, - ), - ], - ), - ), - ), - ); - } - - Widget _buildDeviceIcon(ProductModel product, Size size) { - return Container( - height: size.width > 800 ? 50 : 40, - width: size.width > 800 ? 50 : 40, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: ColorsManager.textFieldGreyColor, - border: Border.all( - color: ColorsManager.neutralGray, - width: 2, - ), - ), - child: Center( - child: SvgPicture.asset( - product.icon ?? Assets.sensors, - width: size.width > 800 ? 30 : 20, - height: size.width > 800 ? 30 : 20, - ), - ), - ); - } - - Widget _buildDeviceName(BuildContext context, product, Size size) { - return SizedBox( - height: size.width > 800 ? 35 : 25, - child: Text( - product.name ?? '', - style: Theme.of(context).textTheme.bodySmall - ?.copyWith(color: ColorsManager.blackColor), - textAlign: TextAlign.center, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ); - } - - Widget _buildActionButton( - String label, - Color backgroundColor, - Color foregroundColor, - VoidCallback onPressed, - ) { - return SizedBox( - width: 120, - child: DefaultButton( - onPressed: onPressed, - backgroundColor: backgroundColor, - foregroundColor: foregroundColor, - child: Text(label), - ), - ); - } -} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index e2095703..a84b6409 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -7,7 +7,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; diff --git a/lib/pages/spaces_management/space_model/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart similarity index 100% rename from lib/pages/spaces_management/space_model/bloc/add_device_model_bloc.dart rename to lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart new file mode 100644 index 00000000..ad71116a --- /dev/null +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class AddDeviceTypeModelWidget extends StatelessWidget { + final List? products; + final ValueChanged>? onProductsSelected; + final List? initialSelectedProducts; + + const AddDeviceTypeModelWidget({ + super.key, + this.products, + this.initialSelectedProducts, + this.onProductsSelected, + }); + + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + final crossAxisCount = size.width > 1200 + ? 8 + : size.width > 800 + ? 5 + : 3; + + return BlocProvider( + create: (_) => AddDeviceTypeModelBloc(initialSelectedProducts ?? []), + child: AlertDialog( + title: const Text('Add Devices'), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Container( + width: size.width * 0.9, + height: size.height * 0.65, + color: ColorsManager.textFieldGreyColor, + child: Column( + children: [ + const SizedBox(height: 16), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: ScrollableGridViewWidget( + products: products, + crossAxisCount: crossAxisCount, + ), + ), + ), + ], + ), + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ActionButton( + label: 'Cancel', + backgroundColor: ColorsManager.boxColor, + foregroundColor: ColorsManager.blackColor, + onPressed: () => Navigator.of(context).pop(), + ), + ActionButton( + label: 'Continue', + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: ColorsManager.whiteColors, + onPressed: () { + Navigator.of(context).pop(); + if (onProductsSelected != null) { + final selectedProducts = + context.read().state; + onProductsSelected!(selectedProducts); + } + }, + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/tag_model/widgets/action_button_widget.dart b/lib/pages/spaces_management/tag_model/widgets/action_button_widget.dart new file mode 100644 index 00000000..8d10a4de --- /dev/null +++ b/lib/pages/spaces_management/tag_model/widgets/action_button_widget.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; + +class ActionButton extends StatelessWidget { + final String label; + final Color backgroundColor; + final Color foregroundColor; + final VoidCallback onPressed; + + const ActionButton({ + super.key, + required this.label, + required this.backgroundColor, + required this.foregroundColor, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 120, + child: DefaultButton( + onPressed: onPressed, + backgroundColor: backgroundColor, + foregroundColor: foregroundColor, + child: Text(label), + ), + ); + } +} diff --git a/lib/pages/spaces_management/tag_model/widgets/device_icon_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_icon_widget.dart new file mode 100644 index 00000000..4e8adbd9 --- /dev/null +++ b/lib/pages/spaces_management/tag_model/widgets/device_icon_widget.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class DeviceIconWidget extends StatelessWidget { + final String? icon; + + const DeviceIconWidget({ + super.key, + required this.icon, + }); + + @override + Widget build(BuildContext context) { + return Container( + height: 50, + width: 50, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.textFieldGreyColor, + border: Border.all( + color: ColorsManager.neutralGray, + width: 2, + ), + ), + child: Center( + child: SvgPicture.asset( + icon ?? Assets.sensors, + width: 30, + height: 30, + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/tag_model/widgets/device_name_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_name_widget.dart new file mode 100644 index 00000000..11a102a8 --- /dev/null +++ b/lib/pages/spaces_management/tag_model/widgets/device_name_widget.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DeviceNameWidget extends StatelessWidget { + final String? name; + + const DeviceNameWidget({ + super.key, + required this.name, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 35, + child: Text( + name ?? '', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.blackColor), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ); + } +} diff --git a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart new file mode 100644 index 00000000..13631448 --- /dev/null +++ b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_icon_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_name_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class DeviceTypeTileWidget extends StatelessWidget { + final ProductModel product; + final List productCounts; + + const DeviceTypeTileWidget({ + super.key, + required this.product, + required this.productCounts, + }); + + @override + Widget build(BuildContext context) { + final selectedProduct = productCounts.firstWhere( + (p) => p.productId == product.uuid, + orElse: () => SelectedProduct(productId: product.uuid, count: 0), + ); + + return Card( + elevation: 2, + color: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + DeviceIconWidget(icon: product.icon ?? Assets.doorSensor), + const SizedBox(height: 4), + DeviceNameWidget(name: product.name), + const SizedBox(height: 4), + CounterWidget( + initialCount: selectedProduct.count, + onCountChanged: (newCount) { + context + .read() + .updateProductCount(product.uuid, newCount); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart new file mode 100644 index 00000000..e05c6711 --- /dev/null +++ b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart'; + +class ScrollableGridViewWidget extends StatelessWidget { + final List? products; + final int crossAxisCount; + + const ScrollableGridViewWidget({ + super.key, + required this.products, + required this.crossAxisCount, + }); + + @override + Widget build(BuildContext context) { + final scrollController = ScrollController(); + + return Scrollbar( + controller: scrollController, + thumbVisibility: true, + child: BlocBuilder>( + builder: (context, productCounts) { + return GridView.builder( + controller: scrollController, + shrinkWrap: true, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + mainAxisSpacing: 6, + crossAxisSpacing: 4, + childAspectRatio: .8, + ), + itemCount: products?.length ?? 0, + itemBuilder: (context, index) { + final product = products![index]; + return DeviceTypeTileWidget( + product: product, + productCounts: productCounts, + ); + }, + ); + }, + ), + ); + } +} From 58b469b92af0c05737695323febc90d48b2cddc4 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 16:15:35 +0400 Subject: [PATCH 033/106] refactor --- .../tag_model/bloc/add_device_model_bloc.dart | 28 ++++++++++++------- .../bloc/add_device_type_model_event.dart | 16 +++++++++++ .../widgets/device_type_tile_widget.dart | 7 +++-- 3 files changed, 38 insertions(+), 13 deletions(-) create mode 100644 lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart index 31c99dc4..1352e97c 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart @@ -1,30 +1,38 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart'; -class AddDeviceTypeModelBloc extends Cubit> { +class AddDeviceTypeModelBloc + extends Bloc> { AddDeviceTypeModelBloc(List initialProducts) - : super(initialProducts); + : super(initialProducts) { + on(_onUpdateProductCount); + } - void updateProductCount(String productId, int count) { + void _onUpdateProductCount( + UpdateProductCountEvent event, Emitter> emit) { final existingProduct = state.firstWhere( - (p) => p.productId == productId, - orElse: () => SelectedProduct(productId: productId, count: 0), + (p) => p.productId == event.productId, + orElse: () => SelectedProduct(productId: event.productId, count: 0), ); - if (count > 0) { + if (event.count > 0) { if (!state.contains(existingProduct)) { - emit([...state, SelectedProduct(productId: productId, count: count)]); + emit([ + ...state, + SelectedProduct(productId: event.productId, count: event.count) + ]); } else { final updatedList = state.map((p) { - if (p.productId == productId) { - return SelectedProduct(productId: p.productId, count: count); + if (p.productId == event.productId) { + return SelectedProduct(productId: p.productId, count: event.count); } return p; }).toList(); emit(updatedList); } } else { - emit(state.where((p) => p.productId != productId).toList()); + emit(state.where((p) => p.productId != event.productId).toList()); } } } diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart new file mode 100644 index 00000000..a3feaad8 --- /dev/null +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart @@ -0,0 +1,16 @@ +import 'package:equatable/equatable.dart'; + +abstract class AddDeviceTypeModelEvent extends Equatable { + @override + List get props => []; +} + +class UpdateProductCountEvent extends AddDeviceTypeModelEvent { + final String productId; + final int count; + + UpdateProductCountEvent({required this.productId, required this.count}); + + @override + List get props => [productId, count]; +} diff --git a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart index 13631448..1190bc5c 100644 --- a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart @@ -45,9 +45,10 @@ class DeviceTypeTileWidget extends StatelessWidget { CounterWidget( initialCount: selectedProduct.count, onCountChanged: (newCount) { - context - .read() - .updateProductCount(product.uuid, newCount); + context.read().add( + UpdateProductCountEvent( + productId: product.uuid, count: newCount), + ); }, ), ], From 3876909beade9c20ef85842367554c1e8547256b Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 5 Jan 2025 17:27:26 +0300 Subject: [PATCH 034/106] edit_user and pagination and search and filter --- .../model/edit_user_model.dart | 267 +++++++++++++ .../model/roles_user_model.dart | 68 +++- .../add_user_dialog/bloc/users_bloc.dart | 232 ++++++++++- .../add_user_dialog/bloc/users_event.dart | 90 ++++- .../add_user_dialog/bloc/users_status.dart | 8 + .../add_user_dialog/view/add_user_dialog.dart | 15 +- .../add_user_dialog/view/basics_view.dart | 8 +- .../add_user_dialog/view/build_tree_view.dart | 146 +++++++ .../view/delete_user_dialog.dart | 129 +++++-- .../view/edit_user_dialog.dart | 364 ++++++++++++++++++ .../view/popup_menu_filter.dart | 108 ++++++ .../view/spaces_access_view.dart | 161 +------- .../users_table/bloc/user_table_bloc.dart | 259 +++++++++---- .../users_table/bloc/user_table_event.dart | 62 ++- .../users_table/view/user_table.dart | 136 ++++--- .../users_table/view/users_page.dart | 245 ++++++++++-- .../view/roles_and_permission_page.dart | 2 +- lib/services/space_mana_api.dart | 1 + lib/services/user_permission.dart | 114 ++++++ lib/utils/constants/api_const.dart | 8 +- macos/Podfile.lock | 36 ++ macos/Runner.xcodeproj/project.pbxproj | 98 ++++- .../contents.xcworkspacedata | 3 + pubspec.lock | 8 + pubspec.yaml | 1 + 25 files changed, 2156 insertions(+), 413 deletions(-) create mode 100644 lib/pages/roles_and_permission/model/edit_user_model.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart create mode 100644 macos/Podfile.lock diff --git a/lib/pages/roles_and_permission/model/edit_user_model.dart b/lib/pages/roles_and_permission/model/edit_user_model.dart new file mode 100644 index 00000000..17fba1a4 --- /dev/null +++ b/lib/pages/roles_and_permission/model/edit_user_model.dart @@ -0,0 +1,267 @@ +// import 'dart:convert'; + +// // Model for Space +// class UserSpaceModel { +// final String uuid; +// final DateTime createdAt; +// final DateTime updatedAt; + +// UserSpaceModel({ +// required this.uuid, +// required this.createdAt, +// required this.updatedAt, +// }); + +// factory UserSpaceModel.fromJson(Map json) { +// return UserSpaceModel( +// uuid: json['uuid'], +// createdAt: DateTime.parse(json['createdAt']), +// updatedAt: DateTime.parse(json['updatedAt']), +// ); +// } + +// Map toJson() { +// return { +// 'uuid': uuid, +// 'createdAt': createdAt.toIso8601String(), +// 'updatedAt': updatedAt.toIso8601String(), +// }; +// } +// } + +// // Model for User +// class EditUserModel { +// final String uuid; +// final DateTime createdAt; +// final dynamic email; +// final dynamic? jobTitle; +// final dynamic status; +// final String firstName; +// final String lastName; +// final String? phoneNumber; +// final bool isEnabled; +// final dynamic invitedBy; +// final dynamic roleType; +// final List spaces; +// final String createdDate; +// final String createdTime; + +// EditUserModel({ +// required this.uuid, +// required this.createdAt, +// required this.email, +// this.jobTitle, +// required this.status, +// required this.firstName, +// required this.lastName, +// this.phoneNumber, +// required this.isEnabled, +// required this.invitedBy, +// required this.roleType, +// required this.spaces, +// required this.createdDate, +// required this.createdTime, +// }); + +// factory EditUserModel.fromJson(Map json) { +// var spacesList = (json['spaces'] as List) +// .map((spaceJson) => UserSpaceModel.fromJson(spaceJson)) +// .toList(); + +// return EditUserModel( +// uuid: json['uuid'], +// createdAt: DateTime.parse(json['createdAt']), +// email: json['email'], +// jobTitle: json['jobTitle'], +// status: json['status'], +// firstName: json['firstName'], +// lastName: json['lastName'], +// phoneNumber: json['phoneNumber'], +// isEnabled: json['isEnabled'], +// invitedBy: json['invitedBy'], +// roleType: json['roleType'], +// spaces: spacesList, +// createdDate: json['createdDate'], +// createdTime: json['createdTime'], +// ); +// } + +// Map toJson() { +// return { +// 'uuid': uuid, +// 'createdAt': createdAt.toIso8601String(), +// 'email': email, +// 'jobTitle': jobTitle, +// 'status': status, +// 'firstName': firstName, +// 'lastName': lastName, +// 'phoneNumber': phoneNumber, +// 'isEnabled': isEnabled, +// 'invitedBy': invitedBy, +// 'roleType': roleType, +// 'spaces': spaces.map((space) => space.toJson()).toList(), +// 'createdDate': createdDate, +// 'createdTime': createdTime, +// }; +// } +// } + +class UserProjectResponse { + final int statusCode; + final String message; + final EditUserModel data; + final bool success; + + UserProjectResponse({ + required this.statusCode, + required this.message, + required this.data, + required this.success, + }); + + /// Create a [UserProjectResponse] from JSON data + factory UserProjectResponse.fromJson(Map json) { + return UserProjectResponse( + statusCode: json['statusCode'] as int, + message: json['message'] as String, + data: EditUserModel.fromJson(json['data'] as Map), + success: json['success'] as bool, + ); + } + + /// Convert the [UserProjectResponse] to JSON + Map toJson() { + return { + 'statusCode': statusCode, + 'message': message, + 'data': data.toJson(), + 'success': success, + }; + } +} + +class EditUserModel { + final String uuid; + final String firstName; + final String lastName; + final String email; + final String createdDate; // e.g. "1/3/2025" + final String createdTime; // e.g. "8:41:43 AM" + final String status; // e.g. "invited" + final String invitedBy; // e.g. "SUPER_ADMIN" + final String phoneNumber; // can be empty + final String jobTitle; // can be empty + final String roleType; // e.g. "ADMIN" + final List spaces; + + EditUserModel({ + required this.uuid, + required this.firstName, + required this.lastName, + required this.email, + required this.createdDate, + required this.createdTime, + required this.status, + required this.invitedBy, + required this.phoneNumber, + required this.jobTitle, + required this.roleType, + required this.spaces, + }); + + /// Create a [UserData] from JSON data + factory EditUserModel.fromJson(Map json) { + return EditUserModel( + uuid: json['uuid'] as String, + firstName: json['firstName'] as String, + lastName: json['lastName'] as String, + email: json['email'] as String, + createdDate: json['createdDate'] as String, + createdTime: json['createdTime'] as String, + status: json['status'] as String, + invitedBy: json['invitedBy'] as String, + phoneNumber: json['phoneNumber'] ?? '', + jobTitle: json['jobTitle'] ?? '', + roleType: json['roleType'] as String, + spaces: (json['spaces'] as List) + .map((e) => UserSpaceModel.fromJson(e as Map)) + .toList(), + ); + } + + /// Convert the [UserData] to JSON + Map toJson() { + return { + 'uuid': uuid, + 'firstName': firstName, + 'lastName': lastName, + 'email': email, + 'createdDate': createdDate, + 'createdTime': createdTime, + 'status': status, + 'invitedBy': invitedBy, + 'phoneNumber': phoneNumber, + 'jobTitle': jobTitle, + 'roleType': roleType, + 'spaces': spaces.map((e) => e.toJson()).toList(), + }; + } +} + +class UserSpaceModel { + final String uuid; + final String createdAt; // e.g. "2024-11-04T07:20:35.940Z" + final String updatedAt; // e.g. "2024-11-28T18:47:29.736Z" + final dynamic spaceTuyaUuid; + final dynamic spaceName; + final dynamic invitationCode; + final bool disabled; + final double x; + final double y; + final String icon; + + UserSpaceModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.spaceTuyaUuid, + required this.spaceName, + required this.invitationCode, + required this.disabled, + required this.x, + required this.y, + required this.icon, + }); + + /// Create a [UserSpaceModel] from JSON data + factory UserSpaceModel.fromJson(Map json) { + return UserSpaceModel( + uuid: json['uuid'] as String, + createdAt: json['createdAt'] as String, + updatedAt: json['updatedAt'] as String, + spaceTuyaUuid: json['spaceTuyaUuid'] as String?, + spaceName: json['spaceName'] as String, + invitationCode: json['invitationCode'] as String?, + disabled: json['disabled'] as bool, + x: (json['x'] as num).toDouble(), + y: (json['y'] as num).toDouble(), + icon: json['icon'] as String, + ); + } + + /// Convert the [UserSpaceModel] to JSON + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt, + 'updatedAt': updatedAt, + 'spaceTuyaUuid': spaceTuyaUuid, + 'spaceName': spaceName, + 'invitationCode': invitationCode, + 'disabled': disabled, + 'x': x, + 'y': y, + 'icon': icon, + }; + } +} diff --git a/lib/pages/roles_and_permission/model/roles_user_model.dart b/lib/pages/roles_and_permission/model/roles_user_model.dart index 244c58de..6298dbe6 100644 --- a/lib/pages/roles_and_permission/model/roles_user_model.dart +++ b/lib/pages/roles_and_permission/model/roles_user_model.dart @@ -1,22 +1,50 @@ class RolesUserModel { - String? id; - String? userName; - String? userEmail; - String? userRole; - String? creationDate; - String? creationTime; - String? createdBy; - String? status; - String? action; - RolesUserModel( - {this.id, - this.userName, - this.userEmail, - this.userRole, - this.creationDate, - this.creationTime, - this.status, - this.action, - this.createdBy, - }); + final String uuid; + final DateTime createdAt; + final String email; + final dynamic firstName; + final dynamic lastName; + final dynamic roleType; + final dynamic status; + final bool isEnabled; + final String invitedBy; + final dynamic phoneNumber; + final dynamic jobTitle; + final dynamic createdDate; + final dynamic createdTime; + + RolesUserModel({ + required this.uuid, + required this.createdAt, + required this.email, + required this.firstName, + required this.lastName, + required this.roleType, + required this.status, + required this.isEnabled, + required this.invitedBy, + this.phoneNumber, + this.jobTitle, + required this.createdDate, + required this.createdTime, + }); + + factory RolesUserModel.fromJson(Map json) { + return RolesUserModel( + uuid: json['uuid'], + createdAt: DateTime.parse(json['createdAt']), + email: json['email'], + firstName: json['firstName'], + lastName: json['lastName'], + roleType: json['roleType'].toString().toLowerCase().replaceAll("_", " "), + status: json['status'], + isEnabled: json['isEnabled'], + invitedBy: + json['invitedBy'].toString().toLowerCase().replaceAll("_", " "), + phoneNumber: json['phoneNumber'], + jobTitle: json['jobTitle'].toString(), + createdDate: json['createdDate'], + createdTime: json['createdTime'], + ); + } } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index dbace8ad..730825cb 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -1,6 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/common/custom_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/edit_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; @@ -25,6 +26,11 @@ class UsersBloc extends Bloc { on(_validateBasicsStep); on(isCompleteRoleFun); on(checkEmail); + on(getUserById); + on(_onToggleNodeExpansion); + on(_onToggleNodeCheck); + on(_editInvitUser); + } void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { if (formKey.currentState?.validate() ?? false) { @@ -42,9 +48,7 @@ class UsersBloc extends Bloc { final TextEditingController emailController = TextEditingController(); final TextEditingController phoneController = TextEditingController(); final TextEditingController jobTitleController = TextEditingController(); - final TextEditingController roleSearchController = TextEditingController(); - // final TextEditingController jobTitleController = TextEditingController(); bool? isCompleteBasics; bool? isCompleteRolePermissions; @@ -84,6 +88,7 @@ class UsersBloc extends Bloc { await CommunitySpaceManagementApi().fetchCommunities(); updatedCommunities = await Future.wait( communities.map((community) async { + print(community.uuid); List spaces = await _fetchSpacesForCommunity(community.uuid); spacesNodes = _buildTreeNodes(spaces); @@ -228,8 +233,8 @@ class UsersBloc extends Bloc { actions: [ TextButton( onPressed: () { - Navigator.of(event.context).pop(); - Navigator.of(event.context).pop(); + Navigator.of(event.context).pop(true); + Navigator.of(event.context).pop(true); }, child: const Text('OK'), ), @@ -244,6 +249,48 @@ class UsersBloc extends Bloc { } } + _editInvitUser(EditInviteUsers event, Emitter emit) async { + try { + emit(UsersLoadingState()); + List selectedIds = getSelectedIds(updatedCommunities) ?? []; + bool res = await UserPermissionApi().editInviteUser( + userId: event.userId, + firstName: firstNameController.text, + jobTitle: jobTitleController.text, + lastName: lastNameController.text, + phoneNumber: phoneController.text, + roleUuid: roleSelected, + spaceUuids: selectedIds, + ); + if (res == true) { + showCustomDialog( + barrierDismissible: false, + context: event.context, + message: "The invite was sent successfully.", + iconPath: Assets.deviceNoteIcon, + title: "Invite Success", + dialogHeight: MediaQuery.of(event.context).size.height * 0.3, + actions: [ + TextButton( + onPressed: () { + Navigator.of(event.context).pop(true); + Navigator.of(event.context).pop(true); + }, + child: const Text('OK'), + ), + ], + ).then( + (value) {}, + ); + } else { + emit(const ErrorState('Failed to send invite.')); + } + emit(SaveState()); + } catch (e) { + emit(ErrorState('Failed to send invite: ${e.toString()}')); + } + } + void searchRolePermission(SearchPermission event, Emitter emit) { emit(UsersLoadingState()); if (event.searchTerm!.isEmpty) { @@ -268,18 +315,22 @@ class UsersBloc extends Bloc { bool isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { emit(UsersLoadingState()); - add(const CheckEmailEvent()); - final emailRegex = RegExp( - r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', - ); - bool isEmailValid = emailRegex.hasMatch(emailController.text); - bool isEmailServerValid = checkEmailValid == 'Valid email'; - isCompleteBasics = firstNameController.text.isNotEmpty && - lastNameController.text.isNotEmpty && - emailController.text.isNotEmpty && - isEmailValid && - isEmailServerValid; - + if (event.isEditUser == false) { + add(const CheckEmailEvent()); + final emailRegex = RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + ); + bool isEmailValid = emailRegex.hasMatch(emailController.text); + bool isEmailServerValid = checkEmailValid == 'Valid email'; + isCompleteBasics = firstNameController.text.isNotEmpty && + lastNameController.text.isNotEmpty && + emailController.text.isNotEmpty && + isEmailValid && + isEmailServerValid; + } else { + isCompleteBasics = firstNameController.text.isNotEmpty && + lastNameController.text.isNotEmpty; + } emit(ChangeStatusSteps()); emit(ValidateBasics()); return isCompleteBasics!; @@ -293,4 +344,153 @@ class UsersBloc extends Bloc { } } } + + EditUserModel? res = EditUserModel( + spaces: [], + jobTitle: '', + phoneNumber: '', + uuid: '', + email: '', + firstName: '', + lastName: '', + roleType: '', + status: '', + invitedBy: '', + createdDate: '', + createdTime: ''); + + Future getUserById( + GetUserByIdEvent event, + Emitter emit, + ) async { + emit(UsersLoadingState()); + + try { + if (event.uuid?.isNotEmpty ?? false) { + final res = await UserPermissionApi().fetchUserById(event.uuid); + + if (res != null) { + // Populate the text controllers + firstNameController.text = res.firstName; + lastNameController.text = res.lastName; + emailController.text = res.email; + phoneController.text = res.phoneNumber ?? ''; + jobTitleController.text = res.jobTitle ?? ''; + res.roleType; + if (updatedCommunities.isNotEmpty) { + // Create a list of UUIDs to mark + final uuidsToMark = res.spaces.map((space) => space.uuid).toList(); + // Print all IDs and mark nodes in updatedCommunities + print('Printing and marking nodes in updatedCommunities:'); + _printAndMarkNodes(updatedCommunities, uuidsToMark); + } else { + print('updatedCommunities is empty!'); + } + final roleId = roles + .firstWhere((element) => + element.type == + res.roleType.toString().toLowerCase().replaceAll("_", " ")) + .uuid; + print('Role ID: $roleId'); + roleSelected = roleId; + add(PermissionEvent(roleUuid: roleSelected)); + emit(ChangeStatusSteps()); + } else { + // emit(UsersErrorState("User not found")); + } + } else { + // emit(UsersErrorState("Invalid user ID")); + } + } catch (e) { + print("Failed to fetch user data: $e"); + // emit(UsersErrorState("Failed to fetch user data: $e")); + } + } + + /// Recursively print all the node IDs, including nested children. + /// Recursively print all node IDs and mark nodes as `isChecked` if their UUID exists in the list. + void _printAndMarkNodes(List nodes, List uuidsToMark, + [int level = 0]) { + for (final node in nodes) { + // Check if the current node's UUID exists in the list of UUIDs to mark. + if (uuidsToMark.contains(node.uuid)) { + node.isChecked = true; // Mark the node as checked. + print( + '${' ' * level}MATCH FOUND: Node ID: ${node.uuid}, Title: ${node.title} is marked as checked.'); + } else { + print('${' ' * level}Node ID: ${node.uuid}, Title: ${node.title}'); + } + if (node.children.isNotEmpty) { + _printAndMarkNodes(node.children, uuidsToMark, level + 1); + } + } + } + + void _onToggleNodeExpansion( + ToggleNodeExpansion event, + Emitter emit, + ) { + emit(UsersLoadingState()); + event.node.isExpanded = !event.node.isExpanded; + emit(ChangeStatusSteps()); + } + + void _onToggleNodeCheck( + ToggleNodeCheck event, + Emitter emit, + ) { + emit(UsersLoadingState()); + //Toggle node's checked state + event.node.isChecked = !event.node.isChecked; + debugPrint( + 'Node toggled. ID: ${event.node.uuid}, isChecked: ${event.node.isChecked}', + ); + // Update children and parent + _updateChildrenCheckStatus(event.node, event.node.isChecked); + _updateParentCheckStatus(event.node); + + // Finally, emit a new state + emit(ChangeStatusSteps()); + } + +// Existing methods that remain in the BLoC: + + void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { + for (var child in node.children) { + child.isChecked = isChecked; + _updateChildrenCheckStatus(child, isChecked); + } + } + + void _updateParentCheckStatus(TreeNode node) { + TreeNode? parent = _findParent(updatedCommunities, node); + if (parent != null) { + parent.isChecked = _areAllChildrenChecked(parent); + _updateParentCheckStatus(parent); + } + } + + bool _areAllChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.every((child) => + child.isChecked && + (child.children.isEmpty || _areAllChildrenChecked(child))); + } + +// Private helper method to find the parent of a given node. + TreeNode? _findParent(List nodes, TreeNode target) { + for (var node in nodes) { + if (node.children.contains(target)) { + return node; + } + final parent = _findParent(node.children, target); + if (parent != null) { + return parent; + } + } + return null; + } + + + } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart index 950726d4..0f4631ff 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart @@ -14,6 +14,14 @@ class SendInviteUsers extends UsersEvent { List get props => [context]; } +class EditInviteUsers extends UsersEvent { + final BuildContext context; + final String userId; + const EditInviteUsers({required this.context, required this.userId}); + @override + List get props => [context, userId]; +} + class CheckSpacesStepStatus extends UsersEvent { const CheckSpacesStepStatus(); @override @@ -52,9 +60,11 @@ class GetBatchStatus extends UsersEvent { List get props => [uuids]; } +//isEditUser:widget.userId!=''? false:true class CheckStepStatus extends UsersEvent { final int? steps; - const CheckStepStatus({this.steps}); + bool? isEditUser = false; + CheckStepStatus({this.steps, required this.isEditUser}); @override List get props => [steps]; } @@ -85,7 +95,7 @@ class SelecteId extends UsersEvent { } class ValidateBasicsStep extends UsersEvent { - const ValidateBasicsStep(); + ValidateBasicsStep(); @override List get props => []; } @@ -95,3 +105,79 @@ class CheckEmailEvent extends UsersEvent { @override List get props => []; } + +class GetUserByIdEvent extends UsersEvent { + final String? uuid; + const GetUserByIdEvent({this.uuid}); + @override + List get props => [uuid]; +} + +class ToggleNodeExpansion extends UsersEvent { + final TreeNode node; + + ToggleNodeExpansion({required this.node}); + + @override + List get props => [node]; +} + +class UpdateNodeCheckStatus extends UsersEvent { + final TreeNode node; + + UpdateNodeCheckStatus({required this.node}); + @override + List get props => [node]; +} + +// Define new events +class ToggleNodeHighlightEvent extends UsersEvent { + final TreeNode node; + + ToggleNodeHighlightEvent(this.node); + @override + List get props => [node]; +} + +class ExpandAllNodesEvent extends UsersEvent { + @override + List get props => []; +} + +class CollapseAllNodesEvent extends UsersEvent { + @override + List get props => []; +} + +class ClearSelectionsEvent extends UsersEvent { + @override + List get props => []; +} + +class ToggleNodeCheckEvent extends UsersEvent { + final TreeNode node; + + ToggleNodeCheckEvent(this.node); + @override + List get props => []; +} + +// users_event.dart + +// 1. Extend UsersEvent +class ToggleNodeCheck extends UsersEvent { + final TreeNode node; + + // 2. Add a constructor that takes the node to toggle + ToggleNodeCheck(this.node); + @override + List get props => []; +} + +class EditUserEvent extends UsersEvent { + const EditUserEvent(); + @override + List get props => []; +} + + diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart index c1bf3512..646dccfd 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; sealed class UsersState extends Equatable { const UsersState(); @@ -80,3 +81,10 @@ final class ValidateBasics extends UsersState { @override List get props => []; } +class UsersLoadedState extends UsersState { + final List updatedCommunities; + + UsersLoadedState({required this.updatedCommunities}); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart index 14f7a0fb..48df801a 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -113,7 +113,8 @@ class _AddNewUserDialogState extends State { if (currentStep < 3) { currentStep++; if (currentStep == 2) { - _blocRole.add(const CheckStepStatus()); + _blocRole.add( + CheckStepStatus(isEditUser: false)); } else if (currentStep == 3) { _blocRole .add(const CheckSpacesStepStatus()); @@ -150,9 +151,11 @@ class _AddNewUserDialogState extends State { Widget _getFormContent() { switch (currentStep) { case 1: - return const BasicsView(); + return BasicsView( + userId: '', + ); case 2: - return const SpacesAccessView(); + return SpacesAccessView(); case 3: return const RolesAndPermission(); default: @@ -169,7 +172,7 @@ class _AddNewUserDialogState extends State { bloc.add(const CheckSpacesStepStatus()); currentStep = step; Future.delayed(const Duration(milliseconds: 500), () { - bloc.add(const ValidateBasicsStep()); + bloc.add(ValidateBasicsStep()); }); }); @@ -234,7 +237,7 @@ class _AddNewUserDialogState extends State { onTap: () { setState(() { currentStep = step; - bloc.add(const CheckStepStatus()); + bloc.add(CheckStepStatus(isEditUser: false)); if (step3 == 3) { bloc.add(const CheckRoleStepStatus()); } @@ -299,7 +302,7 @@ class _AddNewUserDialogState extends State { currentStep = step; step3 = step; bloc.add(const CheckSpacesStepStatus()); - bloc.add(const CheckStepStatus()); + bloc.add(CheckStepStatus(isEditUser: false)); }); }, child: Column( diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index a6ec686b..bbca9aaa 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -11,7 +11,8 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class BasicsView extends StatelessWidget { - const BasicsView({super.key}); + String? userId = ''; + BasicsView({super.key, this.userId}); @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { @@ -184,10 +185,11 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( + enabled: userId!=''? false:true, onChanged: (value) { Future.delayed(const Duration(milliseconds: 200), () { - _blocRole.add(const CheckStepStatus()); - _blocRole.add(ValidateBasicsStep()); + _blocRole.add(CheckStepStatus(isEditUser:userId!=''? false:true)); + _blocRole.add( ValidateBasicsStep()); }); }, controller: _blocRole.emailController, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart new file mode 100644 index 00000000..b7fc1085 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart @@ -0,0 +1,146 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class TreeView extends StatelessWidget { + final String? userId; + + const TreeView({ + super.key, + this.userId, + }); + + @override + Widget build(BuildContext context) { + final _blocRole = BlocProvider.of(context); + debugPrint('TreeView constructed with userId = $userId'); + return BlocProvider( + create: (_) => UsersBloc(), + // ..add(const LoadCommunityAndSpacesEvent()), + child: BlocConsumer( + listener: (context, state) { + // if (state is SpacesLoadedState) { + // _blocRole.add(GetUserByIdEvent(uuid: userId)); + // } + }, + builder: (context, state) { + if (state is UsersLoadingState) { + return const Center(child: CircularProgressIndicator()); + } + return SingleChildScrollView( + child: _buildTree(_blocRole.updatedCommunities, _blocRole), + ); + }, + ), + ); + } + + Widget _buildTree( + List nodes, + UsersBloc bloc, { + int level = 0, + }) { + return Column( + children: nodes.map((node) { + return Container( + color: node.isHighlighted ? Colors.blue.shade50 : Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + /// Checkbox (GestureDetector) + GestureDetector( + onTap: () { + bloc.add(ToggleNodeCheck(node)); + }, + child: Image.asset( + _getCheckBoxImage(node), + width: 20, + height: 20, + ), + ), + const SizedBox(width: 15), + Expanded( + child: Padding( + padding: EdgeInsets.only(left: level * 10.0), + child: Row( + children: [ + GestureDetector( + onTap: () { + bloc.add(ToggleNodeExpansion(node: node)); + }, + child: node.children.isNotEmpty + ? SvgPicture.asset( + node.isExpanded + ? Assets.arrowDown + : Assets.arrowForward, + fit: BoxFit.none, + ) + : const SizedBox(width: 16), + ), + const SizedBox(width: 20), + Text( + node.title, + style: TextStyle( + fontSize: 16, + color: node.isHighlighted + ? ColorsManager.blackColor + : ColorsManager.textGray, + ), + ), + ], + ), + ), + ), + ], + ), + ), + if (node.isExpanded) + _buildTree( + node.children, + bloc, + level: level + 1, + ), + ], + ), + ); + }).toList(), + ); + } + + String _getCheckBoxImage(TreeNode node) { + if (node.children.isEmpty) { + return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox; + } + if (_areAllChildrenChecked(node)) { + return Assets.CheckBoxChecked; + } else if (_areSomeChildrenChecked(node)) { + return Assets.rectangleCheckBox; + } else { + return Assets.emptyBox; + } + } + + bool _areAllChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.every((child) => + child.isChecked && + (child.children.isEmpty || _areAllChildrenChecked(child))); + } + + bool _areSomeChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.any((child) => + child.isChecked || + (child.children.isNotEmpty && _areSomeChildrenChecked(child))); + } +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart index 002b0171..837ee62c 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class DeleteUserDialog extends StatefulWidget { - const DeleteUserDialog({super.key}); + final Function()? onTapDelete; + DeleteUserDialog({super.key, this.onTapDelete}); @override _DeleteUserDialogState createState() => _DeleteUserDialogState(); @@ -17,47 +15,94 @@ class _DeleteUserDialogState extends State { @override Widget build(BuildContext context) { - return BlocProvider( - create: (BuildContext context) => UsersBloc(), - child: BlocConsumer( - listener: (context, state) {}, - builder: (context, state) { - final _blocRole = BlocProvider.of(context); - - return Dialog( - child: Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(20))), - child: const Column( - children: [ - Padding( - padding: EdgeInsets.all(8.0), - child: SizedBox( - child: Text( - "Delete User", - style: TextStyle( - color: ColorsManager.red, - fontSize: 18, - fontWeight: FontWeight.bold), + return Dialog( + child: Container( + height: 160, + width: 200, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + child: Column( + children: [ + const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Delete User", + style: TextStyle( + color: ColorsManager.red, + fontSize: 18, + fontWeight: FontWeight.bold), + ), + ), + ), + const Padding( + padding: EdgeInsets.only( + left: 25, + right: 25, + ), + child: Divider(), + ), + const Expanded( + child: Padding( + padding: EdgeInsets.only(left: 25, right: 25, top: 10, bottom: 10), + child: Text( + "Are you sure you want to delete this user?", + textAlign: TextAlign.center, + ), + )), + Row( + children: [ + Expanded( + child: InkWell( + onTap: () { + Navigator.of(context).pop(true); + }, + child: Container( + padding: const EdgeInsets.all(10), + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.grayBorder, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.grayBorder, + width: 1, ), ), ), - Divider(), - Expanded( + child: const Center(child: Text('Cancel'))), + )), + Expanded( + child: InkWell( + onTap: widget.onTapDelete, + child: Container( + padding: const EdgeInsets.all(10), + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.grayBorder, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.grayBorder, + width: 1, + ), + ), + ), + child: const Center( child: Text( - "Are you sure you want to delete this user?", - textAlign: TextAlign.center, - )), - Row( - children: [ - Expanded(child: Text('Cancel')), - Expanded(child: Text('Delete')), - ], - ) - ], - ), - )); - })); + 'Delete', + style: TextStyle( + color: ColorsManager.red, + ), + ))), + )), + ], + ) + ], + ), + )); } } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart new file mode 100644 index 00000000..8b6600e0 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart @@ -0,0 +1,364 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class EditUserDialog extends StatefulWidget { + final String? userId; + const EditUserDialog({super.key, this.userId}); + + @override + _EditUserDialogState createState() => _EditUserDialogState(); +} + +class _EditUserDialogState extends State { + int currentStep = 1; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => UsersBloc() + ..add(const LoadCommunityAndSpacesEvent()) + ..add(const RoleEvent()) + ..add(GetUserByIdEvent(uuid: widget.userId)), + child: BlocConsumer(listener: (context, state) { + if (state is SpacesLoadedState) { + BlocProvider.of(context) + .add(GetUserByIdEvent(uuid: widget.userId)); + } + }, builder: (context, state) { + final _blocRole = BlocProvider.of(context); + + return Dialog( + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + width: 900, + child: Column( + children: [ + // Title + const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Edit User", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: ColorsManager.secondaryColor), + ), + ), + ), + const Divider(), + Expanded( + child: Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildStep1Indicator(1, "Basics", _blocRole), + _buildStep2Indicator(2, "Spaces", _blocRole), + _buildStep3Indicator( + 3, "Role & Permissions", _blocRole), + ], + ), + ), + ), + Container( + width: 1, + color: ColorsManager.grayBorder, + ), + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + Expanded( + child: _getFormContent(widget.userId), + ), + const SizedBox(height: 20), + ], + ), + ), + ), + ], + ), + ), + const Divider(), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + InkWell( + onTap: () { + // _blocRole.add(const CheckEmailEvent()); + + setState(() { + if (currentStep < 3) { + currentStep++; + if (currentStep == 2) { + _blocRole + .add(CheckStepStatus(isEditUser: true)); + } else if (currentStep == 3) { + _blocRole.add(const CheckSpacesStepStatus()); + } + } else { + _blocRole.add(EditInviteUsers( + context: context, + userId: widget.userId!)); + } + }); + }, + child: Text( + currentStep < 3 ? "Next" : "Save", + style: TextStyle( + color: (_blocRole.isCompleteSpaces == false || + _blocRole.isCompleteBasics == false || + _blocRole.isCompleteRolePermissions == + false) && + currentStep == 3 + ? ColorsManager.grayColor + : ColorsManager.secondaryColor), + ), + ), + ], + ), + ), + ], + ), + )); + })); + } + + Widget _getFormContent(userid) { + switch (currentStep) { + case 1: + return BasicsView( + userId: userid, + ); + case 2: + return SpacesAccessView( + userId: userid, + ); + case 3: + return const RolesAndPermission(); + default: + return Container(); + } + } + + int step3 = 0; + + Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + bloc.add(const CheckSpacesStepStatus()); + currentStep = step; + Future.delayed(const Duration(milliseconds: 500), () { + bloc.add(ValidateBasicsStep()); + }); + }); + + if (step3 == 3) { + bloc.add(const CheckRoleStepStatus()); + } + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + SvgPicture.asset( + currentStep == step + ? Assets.currentProcessIcon + : bloc.isCompleteBasics == false + ? Assets.wrongProcessIcon + : bloc.isCompleteBasics == true + ? Assets.completeProcessIcon + : Assets.uncomplete_ProcessIcon, + width: 25, + height: 25, + ), + const SizedBox(width: 10), + Text( + label, + style: TextStyle( + fontSize: 16, + color: currentStep == step + ? ColorsManager.blackColor + : ColorsManager.greyColor, + fontWeight: currentStep == step + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + ), + if (step != 3) + Padding( + padding: const EdgeInsets.all(5.0), + child: Padding( + padding: const EdgeInsets.only(left: 12), + child: Container( + height: 60, + width: 1, + color: Colors.grey, + ), + ), + ) + ], + ), + ); + } + + Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + currentStep = step; + bloc.add(CheckStepStatus(isEditUser: true)); + if (step3 == 3) { + bloc.add(const CheckRoleStepStatus()); + } + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + SvgPicture.asset( + currentStep == step + ? Assets.currentProcessIcon + : bloc.isCompleteSpaces == false + ? Assets.wrongProcessIcon + : bloc.isCompleteSpaces == true + ? Assets.completeProcessIcon + : Assets.uncomplete_ProcessIcon, + width: 25, + height: 25, + ), + const SizedBox(width: 10), + Text( + label, + style: TextStyle( + fontSize: 16, + color: currentStep == step + ? ColorsManager.blackColor + : ColorsManager.greyColor, + fontWeight: currentStep == step + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + ), + if (step != 3) + Padding( + padding: const EdgeInsets.all(5.0), + child: Padding( + padding: const EdgeInsets.only(left: 12), + child: Container( + height: 60, + width: 1, + color: Colors.grey, + ), + ), + ) + ], + ), + ); + } + + Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + currentStep = step; + step3 = step; + bloc.add(const CheckSpacesStepStatus()); + bloc.add(CheckStepStatus(isEditUser: true)); + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + SvgPicture.asset( + currentStep == step + ? Assets.currentProcessIcon + : bloc.isCompleteRolePermissions == false + ? Assets.wrongProcessIcon + : bloc.isCompleteRolePermissions == true + ? Assets.completeProcessIcon + : Assets.uncomplete_ProcessIcon, + width: 25, + height: 25, + ), + const SizedBox(width: 10), + Text( + label, + style: TextStyle( + fontSize: 16, + color: currentStep == step + ? ColorsManager.blackColor + : ColorsManager.greyColor, + fontWeight: currentStep == step + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + ), + if (step != 3) + Padding( + padding: const EdgeInsets.all(5.0), + child: Padding( + padding: const EdgeInsets.only(left: 12), + child: Container( + height: 60, + width: 1, + color: Colors.grey, + ), + ), + ) + ], + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart new file mode 100644 index 00000000..02eac036 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +Future showPopUpFilterMenu({ + required BuildContext context, + Function()? onSortAtoZ, + Function()? onSortZtoA, + Function()? cancelButton, + required Map checkboxStates, + Function()? onOkPressed, + List? list, +}) async { + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; + + await showMenu( + context: context, + position: RelativeRect.fromLTRB( + overlay.size.width / 4, + 240, + overlay.size.width / 4, + 0, + ), + color: ColorsManager.whiteColors, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + items: [ + PopupMenuItem( + onTap: onSortAtoZ, + child: ListTile( + leading: Image.asset( + Assets.AtoZIcon, + width: 25, + ), + title: const Text( + "Sort A to Z", + style: TextStyle(color: Colors.blueGrey), + ), + ), + ), + PopupMenuItem( + onTap: onSortZtoA, + child: ListTile( + leading: Image.asset( + Assets.ZtoAIcon, + width: 25, + ), + title: const Text( + "Sort Z to A", + style: TextStyle(color: Colors.blueGrey), + ), + ), + ), + const PopupMenuDivider(), + const PopupMenuItem( + child: Text( + "Filter by Status", + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + PopupMenuItem( + child: SizedBox( + height: 200, + width: 400, + child: ListView.builder( + itemCount: list?.length ?? 0, + itemBuilder: (context, index) { + final item = list![index]; + return CheckboxListTile( + title: Text(item), + value: checkboxStates[item], + onChanged: (bool? newValue) { + checkboxStates[item] = newValue ?? false; + (context as Element).markNeedsBuild(); + }, + ); + }, + ), + ), + ), + PopupMenuItem( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); // Close the menu + }, + child: const Text("Cancel"), + ), + GestureDetector( + onTap: onOkPressed, + child: const Text( + "OK", + style: TextStyle( + color: ColorsManager.spaceColor, + ), + ), + ), + ], + ), + ), + ], + ); +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart index 081fab40..dc2eefc3 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart @@ -4,14 +4,15 @@ import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class SpacesAccessView extends StatelessWidget { - const SpacesAccessView({super.key}); + String? userId = ''; + SpacesAccessView({super.key, this.userId}); @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; @@ -109,9 +110,7 @@ class SpacesAccessView extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: Container( color: ColorsManager.whiteColors, - child: TreeView( - bloc: _blocRole, - )))) + child: TreeView(userId: userId)))) ], ), ), @@ -123,155 +122,3 @@ class SpacesAccessView extends StatelessWidget { }); } } - -// ignore: must_be_immutable - -class TreeView extends StatefulWidget { - UsersBloc? bloc; - TreeView({super.key, this.bloc}); - @override - _TreeViewState createState() => _TreeViewState(); -} - -class _TreeViewState extends State { - Widget _buildTree(List nodes, {int level = 0}) { - return Column( - children: nodes.map((node) => _buildNode(node, level: level)).toList(), - ); - } - - Widget _buildNode(TreeNode node, {int level = 0}) { - return Container( - color: node.isHighlighted ? Colors.blue.shade50 : Colors.transparent, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(5.0), - child: Row( - children: [ - GestureDetector( - onTap: () { - setState(() { - node.isChecked = !node.isChecked; - _updateChildrenCheckStatus(node, node.isChecked); - _updateParentCheckStatus(node); - // widget.bloc!.add( - // SelecteId(nodes: widget.bloc!.updatedCommunities)); - }); - }, - child: Image.asset( - _getCheckBoxImage(node), - width: 20, - height: 20, - ), - ), - const SizedBox(width: 15), - Expanded( - child: Padding( - padding: EdgeInsets.only(left: level * 10.0), - child: Row( - children: [ - GestureDetector( - onTap: () { - setState(() { - node.isExpanded = !node.isExpanded; - }); - }, - child: SizedBox( - child: SvgPicture.asset( - node.children.isNotEmpty - ? (node.isExpanded - ? Assets.arrowDown - : Assets.arrowForward) - : Assets.arrowForward, - fit: BoxFit.none, - ), - ), - ), - const SizedBox(width: 20), - Text( - node.title, - style: TextStyle( - fontSize: 16, - color: node.isHighlighted - ? ColorsManager.blackColor - : ColorsManager.textGray, - ), - ), - ], - ), - ), - ), - ], - ), - ), - if (node.isExpanded && node.children.isNotEmpty) - _buildTree(node.children, level: level + 1), - ], - ), - ); - } - - String _getCheckBoxImage(TreeNode node) { - if (node.children.isEmpty) { - return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox; - } - if (_areAllChildrenChecked(node)) { - return Assets.CheckBoxChecked; - } else if (_areSomeChildrenChecked(node)) { - return Assets.rectangleCheckBox; - } else { - return Assets.emptyBox; - } - } - - bool _areAllChildrenChecked(TreeNode node) { - return node.children.isNotEmpty && - node.children.every((child) => - child.isChecked && - (child.children.isEmpty || _areAllChildrenChecked(child))); - } - - bool _areSomeChildrenChecked(TreeNode node) { - return node.children.isNotEmpty && - node.children.any((child) => - child.isChecked || - (child.children.isNotEmpty && _areSomeChildrenChecked(child))); - } - - void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { - for (var child in node.children) { - child.isChecked = isChecked; - _updateChildrenCheckStatus(child, isChecked); - } - } - - void _updateParentCheckStatus(TreeNode node) { - TreeNode? parent = _findParent(widget.bloc!.updatedCommunities, node); - if (parent != null) { - setState(() { - parent.isChecked = _areAllChildrenChecked(parent); - _updateParentCheckStatus(parent); - }); - } - } - - TreeNode? _findParent(List nodes, TreeNode target) { - for (var node in nodes) { - if (node.children.contains(target)) { - return node; - } - var parent = _findParent(node.children, target); - if (parent != null) return parent; - } - return null; - } - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: _buildTree(widget.bloc!.updatedCommunities), - ); - } -} diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart index 2a31a91f..82212103 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart'; +import 'package:syncrow_web/services/user_permission.dart'; class UserTableBloc extends Bloc { UserTableBloc() : super(TableInitial()) { @@ -12,79 +14,108 @@ class UserTableBloc extends Bloc { on(_toggleSortUsersByNameDesc); on(_toggleSortUsersByDateOldestToNewest); on(_toggleSortUsersByDateNewestToOldest); + on(_searchUsers); + on(_handlePageChange); + on(_filterUsersByRole); + on(_filterUsersByJobTitle); + on(_filterUsersByCreated); + on(_filterUserActevate); + on(_deleteUser); } - + int itemsPerPage = 10; + int currentPage = 1; List users = []; - List initialUsers = []; // Save the initial state - String currentSortOrder = ''; // Keeps track of the current sorting order - String currentSortOrderDate = ''; // Keeps track of the current sorting order + List initialUsers = []; + String currentSortOrder = ''; + String currentSortOrderDate = ''; + List roleTypes = []; + List jobTitle = []; + List createdBy = []; + List deActivate = []; Future _getUsers(GetUsers event, Emitter emit) async { emit(UsersLoadingState()); try { - users = [ - RolesUserModel( - id: '1', - userName: 'b 1', - userEmail: 'test1@test.com', - action: '', - createdBy: 'Admin', - creationDate: '25/10/2024', - creationTime: '10:30 AM', - status: 'Invited', - ), - RolesUserModel( - id: '2', - userName: 'a 2', - userEmail: 'test2@test.com', - action: '', - createdBy: 'Admin', - creationDate: '24/10/2024', - creationTime: '2:30 PM', - status: 'Active', - ), - RolesUserModel( - id: '3', - userName: 'c 3', - userEmail: 'test3@test.com', - action: '', - createdBy: 'Admin', - creationDate: '23/10/2024', - creationTime: '9:00 AM', - status: 'Disabled', - ), - ]; - // Sort users by newest to oldest as default + roleTypes.clear(); + jobTitle.clear(); + createdBy.clear(); + deActivate.clear(); + users = await UserPermissionApi().fetchUsers(); + for (var user in users) { + roleTypes.add(user.roleType.toString()); + } + for (var user in users) { + jobTitle.add(user.jobTitle.toString()); + } + for (var user in users) { + createdBy.add(user.invitedBy.toString()); + } + for (var user in users) { + deActivate.add(user.status.toString()); + } + roleTypes = roleTypes.toSet().toList(); + jobTitle = jobTitle.toSet().toList(); + createdBy = createdBy.toSet().toList(); + deActivate = deActivate.toSet().toList(); + users.sort((a, b) { - final dateA = _parseDateTime(a.creationDate!); - final dateB = _parseDateTime(b.creationDate!); - return dateB.compareTo(dateA); // Newest to oldest + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateB.compareTo(dateA); }); - initialUsers = List.from(users); // Save the initial state + initialUsers = List.from(users); + _handlePageChange(ChangePage(1), emit); emit(UsersLoadedState(users: users)); } catch (e) { emit(ErrorState(e.toString())); } } - void _changeUserStatus(ChangeUserStatus event, Emitter emit) { + Future _deleteUser( + DeleteUserEvent event, Emitter emit) async { + emit(UsersLoadingState()); try { - users = users.map((user) { - if (user.id == event.userId) { - return RolesUserModel( - id: user.id, - userName: user.userName, - userEmail: user.userEmail, - createdBy: user.createdBy, - creationDate: user.creationDate, - creationTime: user.creationTime, - status: event.newStatus, - action: user.action, - ); - } - return user; - }).toList(); + bool res = await UserPermissionApi().deleteUserById(event.userId); + if (res == true) { + Navigator.of(event.context).pop(true); + } else { + emit(const ErrorState('Something error')); + } + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + Future _changeUserStatus( + ChangeUserStatus event, Emitter emit) async { + try { + emit(UsersLoadingState()); + bool res = await UserPermissionApi().changeUserStatusById( + event.userId, event.newStatus == "disabled" ? true : false); + if (res == true) { + add(const GetUsers()); + // users = users.map((user) { + // if (user.uuid == event.userId) { + // return RolesUserModel( + // uuid: user.uuid, + // createdAt: user.createdAt, + // email: user.email, + // firstName: user.firstName, + // lastName: user.lastName, + // roleType: user.roleType, + // status: event.newStatus, + // isEnabled: event.newStatus == "disabled" ? false : true, + // invitedBy: user.invitedBy, + // phoneNumber: user.phoneNumber, + // jobTitle: user.jobTitle, + // createdDate: user.createdDate, + // createdTime: user.createdTime, + // ); + // } + // return user; + // }).toList(); + } emit(UsersLoadedState(users: users)); } catch (e) { emit(ErrorState(e.toString())); @@ -94,16 +125,14 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByNameAsc( SortUsersByNameAsc event, Emitter emit) { if (currentSortOrder == "Asc") { - // If already sorted ascending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; - users = List.from(initialUsers); // Reset to saved initial state + users = List.from(initialUsers); emit(UsersLoadedState(users: users)); } else { - // Sort ascending emit(UsersLoadingState()); currentSortOrder = "Asc"; - users.sort((a, b) => a.userName!.compareTo(b.userName!)); + users.sort((a, b) => a.firstName!.compareTo(b.firstName!)); emit(UsersLoadedState(users: users)); } } @@ -111,7 +140,6 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByNameDesc( SortUsersByNameDesc event, Emitter emit) { if (currentSortOrder == "Desc") { - // If already sorted descending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; users = List.from(initialUsers); // Reset to saved initial state @@ -120,7 +148,7 @@ class UserTableBloc extends Bloc { // Sort descending emit(UsersLoadingState()); currentSortOrder = "Desc"; - users.sort((a, b) => b.userName!.compareTo(a.userName!)); + users.sort((a, b) => b.firstName!.compareTo(a.firstName!)); emit(UsersLoadedState(users: users)); } } @@ -128,19 +156,17 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByDateNewestToOldest( DateNewestToOldestEvent event, Emitter emit) { if (currentSortOrderDate == "NewestToOldest") { - // If already sorted ascending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; currentSortOrderDate = ""; - users = List.from(initialUsers); // Reset to saved initial state + users = List.from(initialUsers); emit(UsersLoadedState(users: users)); } else { - // Sort ascending emit(UsersLoadingState()); users.sort((a, b) { - final dateA = _parseDateTime(a.creationDate!); - final dateB = _parseDateTime(b.creationDate!); - return dateB.compareTo(dateA); // Newest to oldest + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateB.compareTo(dateA); }); emit(UsersLoadedState(users: users)); } @@ -149,19 +175,17 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByDateOldestToNewest( DateOldestToNewestEvent event, Emitter emit) { if (currentSortOrderDate == "OldestToNewest") { - // If already sorted ascending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; currentSortOrderDate = ""; - users = List.from(initialUsers); // Reset to saved initial state + users = List.from(initialUsers); emit(UsersLoadedState(users: users)); } else { - // Sort ascending emit(UsersLoadingState()); users.sort((a, b) { - final dateA = _parseDateTime(a.creationDate!); - final dateB = _parseDateTime(b.creationDate!); - return dateA.compareTo(dateB); // Newest to oldest + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateA.compareTo(dateB); }); emit(UsersLoadedState(users: users)); } @@ -169,17 +193,94 @@ class UserTableBloc extends Bloc { DateTime _parseDateTime(String date) { try { - // Split the date into day, month, and year final dateParts = date.split('/'); final day = int.parse(dateParts[0]); final month = int.parse(dateParts[1]); final year = int.parse(dateParts[2]); - - // Split the time into hours and minutes - return DateTime(year, month, day); } catch (e) { throw FormatException('Invalid date or time format: $date '); } } + + Future _searchUsers( + SearchUsers event, Emitter emit) async { + try { + final query = event.query.toLowerCase(); + final filteredUsers = initialUsers.where((user) { + final fullName = "${user.firstName} ${user.lastName}".toLowerCase(); + final email = user.email?.toLowerCase() ?? ""; + return fullName.contains(query) || email.contains(query); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + void _paginateUsers( + int pageNumber, int itemsPerPage, Emitter emit) { + final startIndex = (pageNumber - 1) * itemsPerPage; + final endIndex = startIndex + itemsPerPage; + if (startIndex >= users.length) { + emit(UsersLoadedState(users: const [])); + return; + } + final paginatedUsers = users.sublist( + startIndex, + endIndex > users.length ? users.length : endIndex, + ); + emit(UsersLoadedState(users: paginatedUsers)); + } + + void _handlePageChange(ChangePage event, Emitter emit) { + final itemsPerPage = 10; + final startIndex = (event.pageNumber - 1) * itemsPerPage; + final endIndex = startIndex + itemsPerPage; + if (startIndex >= users.length) { + emit(UsersLoadedState(users: [])); + return; + } + final paginatedUsers = users.sublist( + startIndex, + endIndex > users.length ? users.length : endIndex, + ); + emit(UsersLoadedState(users: paginatedUsers)); + } + + void _filterUsersByRole( + FilterUsersByRoleEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = initialUsers.where((user) { + return event.selectedRoles.contains(user.roleType); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } + + void _filterUsersByJobTitle( + FilterUsersByJobEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = users.where((user) { + return event.selectedJob.contains(user.jobTitle); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } + + void _filterUsersByCreated( + FilterUsersByCreatedEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = initialUsers.where((user) { + return event.selectedCreatedBy.contains(user.invitedBy); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } + + void _filterUserActevate( + FilterUsersByDeActevateEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = initialUsers.where((user) { + return event.selectedActivate.contains(user.status); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } } diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart index a6c77bd3..dbcd9a26 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; sealed class UserTableEvent extends Equatable { const UserTableEvent(); @@ -47,7 +48,6 @@ class StoreUsersEvent extends UserTableEvent { List get props => []; } - class DateNewestToOldestEvent extends UserTableEvent { const DateNewestToOldestEvent(); @@ -60,4 +60,62 @@ class DateOldestToNewestEvent extends UserTableEvent { @override List get props => []; -} \ No newline at end of file +} + +class SearchUsers extends UserTableEvent { + final String query; + SearchUsers(this.query); + @override + List get props => []; +} + +class ChangePage extends UserTableEvent { + final int pageNumber; + + ChangePage(this.pageNumber); + + @override + List get props => [pageNumber]; +} + +class DeleteUserEvent extends UserTableEvent { + final String userId; + final BuildContext context; + + const DeleteUserEvent(this.userId, this.context); + + @override + List get props => [userId, context]; +} + +class FilterUsersByRoleEvent extends UserTableEvent { + final List selectedRoles; + + FilterUsersByRoleEvent(this.selectedRoles); + @override + List get props => [selectedRoles]; +} + +class FilterUsersByJobEvent extends UserTableEvent { + final List selectedJob; + + FilterUsersByJobEvent(this.selectedJob); + @override + List get props => [selectedJob]; +} + +class FilterUsersByCreatedEvent extends UserTableEvent { + final List selectedCreatedBy; + + FilterUsersByCreatedEvent(this.selectedCreatedBy); + @override + List get props => [selectedCreatedBy]; +} + +class FilterUsersByDeActevateEvent extends UserTableEvent { + final List selectedActivate; + + FilterUsersByDeActevateEvent(this.selectedActivate); + @override + List get props => [selectedActivate]; +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart index f42c0c03..4f777ee3 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -81,15 +81,17 @@ class _DynamicTableScreenState extends State scrollDirection: Axis.horizontal, child: Container( decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, borderRadius: const BorderRadius.all(Radius.circular(20))), child: FittedBox( child: Column( children: [ // Header Row with Resizable Columns Container( + width: MediaQuery.of(context).size.width, decoration: containerDecoration.copyWith( color: ColorsManager.circleRolesBackground, - borderRadius: BorderRadius.only( + borderRadius: const BorderRadius.only( topLeft: Radius.circular(15), topRight: Radius.circular(15))), child: Row( @@ -167,56 +169,96 @@ class _DynamicTableScreenState extends State ), ), // Data Rows with Dividers - Container( - decoration: containerDecoration.copyWith( - color: ColorsManager.whiteColors, - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(15), - bottomRight: Radius.circular(15))), - child: Column( - children: widget.rows.map((row) { - int rowIndex = widget.rows.indexOf(row); - return Column( - children: [ - Container( - child: Padding( - padding: const EdgeInsets.only( - left: 5, top: 10, right: 5, bottom: 10), - child: Row( - children: List.generate(row.length, (index) { - return SizedBox( - width: columnWidths[index], - child: SizedBox( - child: Padding( - padding: const EdgeInsets.only( - left: 15, right: 10), - child: row[index], - ), + widget.rows.isEmpty + ? Container( + child: SizedBox( + height: MediaQuery.of(context).size.height / 2, + child: Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + SvgPicture.asset(Assets.emptyTable), + const SizedBox( + height: 15, ), - ); - }), - ), + const Text( + 'No Users', + style: TextStyle( + color: ColorsManager.lightGrayColor, + fontSize: 16, + fontWeight: FontWeight.w700), + ) + ], + ), + ], ), ), - if (rowIndex < widget.rows.length - 1) - Row( - children: List.generate(widget.titles.length, - (index) { - return SizedBox( - width: columnWidths[index], - child: const Divider( - color: ColorsManager.boxDivider, - thickness: 1, - height: 1, - ), + ), + ) + : Center( + child: Container( + // height: MediaQuery.of(context).size.height * 0.59, + width: MediaQuery.of(context).size.width, + decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(15), + bottomRight: Radius.circular(15))), + child: ListView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: widget.rows.length, + itemBuilder: (context, rowIndex) { + final row = widget.rows[rowIndex]; + return Column( + children: [ + Container( + child: Padding( + padding: const EdgeInsets.only( + left: 5, + top: 10, + right: 5, + bottom: 10), + child: Row( + children: + List.generate(row.length, (index) { + return SizedBox( + width: columnWidths[index], + child: SizedBox( + child: Padding( + padding: const EdgeInsets.only( + left: 15, right: 10), + child: row[index], + ), + ), + ); + }), + ), + ), + ), + if (rowIndex < widget.rows.length - 1) + Row( + children: List.generate( + widget.titles.length, (index) { + return SizedBox( + width: columnWidths[index], + child: const Divider( + color: ColorsManager.boxDivider, + thickness: 1, + height: 1, + ), + ); + }), + ), + ], ); - })) - // Add a Divider below each row except the last one - ], - ); - }).toList(), - ), - ), + }, + ), + ), + ), ], ), ), diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index 758f062b..8d838ba2 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:number_pagination/number_pagination.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart'; @@ -15,7 +19,8 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class UsersPage extends StatelessWidget { - const UsersPage({super.key}); + UsersPage({super.key}); + @override Widget build(BuildContext context) { final TextEditingController searchController = TextEditingController(); @@ -44,9 +49,9 @@ class UsersPage extends StatelessWidget { padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(20)), - color: status == "Invited" + color: status == "invited" ? ColorsManager.invitedOrange.withOpacity(0.5) - : status == "Active" + : status == "active" ? ColorsManager.activeGreen.withOpacity(0.5) : ColorsManager.disabledPink.withOpacity(0.5), ), @@ -60,9 +65,9 @@ class UsersPage extends StatelessWidget { Text( status, style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: status == "Invited" + color: status == "invited" ? ColorsManager.invitedOrangeText - : status == "Active" + : status == "active" ? ColorsManager.activeGreenText : ColorsManager.disabledRedText, fontWeight: FontWeight.w400, @@ -81,23 +86,14 @@ class UsersPage extends StatelessWidget { required Function()? onTap}) { return Center( child: InkWell( - onTap: () { - final newStatus = status == 'Active' - ? 'Disabled' - : status == 'Disabled' - ? 'Invited' - : 'Active'; - context - .read() - .add(ChangeUserStatus(userId: userId, newStatus: newStatus)); - }, + onTap: onTap, child: Padding( padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), child: SvgPicture.asset( - status == "Invited" + status == "invited" ? Assets.invitedIcon - : status == "Active" + : status == "active" ? Assets.activeUser : Assets.deActiveUser, height: 35, @@ -113,12 +109,14 @@ class UsersPage extends StatelessWidget { final _blocRole = BlocProvider.of(context); if (state is UsersLoadingState) { + _blocRole.add(ChangePage(_blocRole.currentPage)); + return const Center(child: CircularProgressIndicator()); } else if (state is UsersLoadedState) { return Padding( padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: ListView( + shrinkWrap: true, children: [ Row( children: [ @@ -131,6 +129,9 @@ class UsersPage extends StatelessWidget { width: screenSize.width * 0.4, child: TextFormField( controller: searchController, + onChanged: (value) { + context.read().add(SearchUsers(value)); + }, style: const TextStyle(color: Colors.black), decoration: textBoxDecoration(radios: 15)!.copyWith( fillColor: ColorsManager.whiteColors, @@ -158,9 +159,9 @@ class UsersPage extends StatelessWidget { builder: (BuildContext context) { return const AddNewUserDialog(); }, - ).then((listDevice) { - if (listDevice != null) { - + ).then((v) { + if (v != null) { + _blocRole.add(const GetUsers()); } }); }, @@ -201,6 +202,66 @@ class UsersPage extends StatelessWidget { }, ); } + if (columnIndex == 2) { + final Map checkboxStates = { + for (var item in _blocRole.jobTitle) + item: false, // Initialize with false + }; + + showPopUpFilterMenu( + list: _blocRole.jobTitle, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole.add(FilterUsersByJobEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 3) { + final Map checkboxStates = { + for (var item in _blocRole.roleTypes) + item: false, // Initialize with false + }; + + showPopUpFilterMenu( + list: _blocRole.roleTypes, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole.add(FilterUsersByRoleEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } if (columnIndex == 4) { showDateFilterMenu( context: context, @@ -217,6 +278,37 @@ class UsersPage extends StatelessWidget { }, ); } + if (columnIndex == 6) { + final Map checkboxStates = { + for (var item in _blocRole.createdBy) + item: false, // Initialize with false + }; + + showPopUpFilterMenu( + list: _blocRole.createdBy, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole + .add(FilterUsersByCreatedEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } if (columnIndex == 8) { showDeActivateFilterMenu( context: context, @@ -248,19 +340,37 @@ class UsersPage extends StatelessWidget { ], rows: state.users.map((user) { return [ - Text(user.userName!), - Text(user.userEmail!), - const Text("Test"), - const Text("Member"), - Text(user.creationDate!), - Text(user.creationTime!), - Text(user.createdBy!), + Text('${user.firstName} ${user.lastName}'), + Text(user.email ?? ''), + Text(user.jobTitle ?? ''), + Text(user.roleType ?? ''), + Text(user.createdDate ?? ''), + Text(user.createdTime ?? ''), + Text(user.invitedBy), changeIconStatus( - status: user.status!, - userId: user.id!, - onTap: () {}, + status: + user.isEnabled == false ? 'disabled' : user.status, + userId: user.uuid, + onTap: user.status != "invited" + ? () { + final newStatus = user.status == 'active' + ? 'disabled' + : user.status == 'disabled' + ? 'invited' + : 'active'; + context.read().add( + ChangeUserStatus( + userId: user.uuid, + newStatus: user.isEnabled == false + ? 'disabled' + : user.status)); + } + : null, + ), + status( + status: + user.isEnabled == false ? 'disabled' : user.status, ), - status(status: user.status!), Row( children: [ // actionButton( @@ -269,17 +379,80 @@ class UsersPage extends StatelessWidget { // ), actionButton( title: "Edit", - onTap: () {}, + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return EditUserDialog(userId: user.uuid); + }, + ).then((v) { + if (v != null) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + } + }); + }, ), actionButton( title: "Delete", - onTap: () {}, + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return DeleteUserDialog( + onTapDelete: () { + _blocRole.add( + DeleteUserEvent(user.uuid, context)); + }, + ); + }, + ).then((v) { + if (v != null) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + } + }); + }, ), ], ), ]; }).toList(), ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + width: 500, + child: NumberPagination( + buttonRadius: 10, + selectedButtonColor: ColorsManager.secondaryColor, + buttonUnSelectedBorderColor: ColorsManager.grayBorder, + lastPageIcon: + const Icon(Icons.keyboard_double_arrow_right), + firstPageIcon: + const Icon(Icons.keyboard_double_arrow_left), + totalPages: + (_blocRole.users.length / _blocRole.itemsPerPage) + .ceil(), + currentPage: _blocRole.currentPage, + onPageChanged: (int pageNumber) { + _blocRole.currentPage = pageNumber; + context + .read() + .add(ChangePage(pageNumber)); + }, + ), + ), + ], + ), + ), ], ), ); diff --git a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart index da003536..44944ed3 100644 --- a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart +++ b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart @@ -77,7 +77,7 @@ class RolesAndPermissionPage extends StatelessWidget { ), scaffoldBody: BlocProvider( create: (context) => UserTableBloc()..add(const GetUsers()), - child: const UsersPage(), + child: UsersPage(), ) // _blocRole.tapSelect == false // ? UsersPage( diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 5d2464e6..abf151fb 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -252,6 +252,7 @@ class CommunitySpaceManagementApi { path: ApiEndpoints.getSpaceHierarchy .replaceAll('{communityId}', communityId), expectedResponseModel: (json) { + print(json); final spaceModels = (json['data'] as List) .map((spaceJson) => SpaceModel.fromJson(spaceJson)) .toList(); diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 0e31795a..f62437ac 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -1,6 +1,9 @@ import 'dart:convert'; import 'package:dio/dio.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/edit_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -8,6 +11,25 @@ import 'package:syncrow_web/utils/constants/api_const.dart'; class UserPermissionApi { static final HTTPService _httpService = HTTPService(); + Future> fetchUsers() async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getUsers, + showServerMessage: true, + expectedResponseModel: (json) { + debugPrint('fetchUsers Response: $json'); + final List data = + json['data'] ?? []; // Default to an empty list if no data + return data.map((item) => RolesUserModel.fromJson(item)).toList(); + }, + ); + return response; + } catch (e, stackTrace) { + debugPrint('Error in fetchUsers: $e'); + rethrow; + } + } + fetchRoles() async { final response = await _httpService.get( path: ApiEndpoints.roleTypes, @@ -67,6 +89,7 @@ class UserPermissionApi { } }, ); + print('sendInviteUser=$body'); return response ?? []; } on DioException catch (e) { @@ -107,4 +130,95 @@ class UserPermissionApi { return e.toString(); } } + + Future fetchUserById(userUuid) async { + final response = await _httpService.get( + path: ApiEndpoints.getUserById.replaceAll("{userUuid}", userUuid), + showServerMessage: true, + expectedResponseModel: (json) { + EditUserModel res = EditUserModel.fromJson(json['data']); + return res; + }, + ); + return response; + } + + Future editInviteUser({ + String? firstName, + String? userId, + String? lastName, + String? jobTitle, + String? phoneNumber, + String? roleUuid, + List? spaceUuids, + }) async { + try { + final body = { + "firstName": firstName, + "lastName": lastName, + "jobTitle": jobTitle != '' ? jobTitle : " ", + "phoneNumber": phoneNumber != '' ? phoneNumber : " ", + "roleUuid": roleUuid, + "projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c", + "spaceUuids": spaceUuids, + }; + final response = await _httpService.put( + path: ApiEndpoints.editUser.replaceAll('{inviteUserUuid}', userId!), + body: jsonEncode(body), + expectedResponseModel: (json) { + if (json['statusCode'] != 400) { + return json["success"]; + } else { + return false; + } + }, + ); + return response; + } on DioException catch (e) { + return false; + } catch (e) { + return false; + } + } + + Future deleteUserById(userUuid) async { + try { + final response = await _httpService.delete( + path: ApiEndpoints.deleteUser.replaceAll("{inviteUserUuid}", userUuid), + showServerMessage: true, + expectedResponseModel: (json) { + return json['success']; + }, + ); + return response; + } catch (e) { + return false; + } + } + + Future changeUserStatusById(userUuid, status) async { + try { + Map bodya = { + "disable": status, + "projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c" + }; + print('changeUserStatusById==$bodya'); + print('changeUserStatusById==$userUuid'); + + final response = await _httpService.put( + path: ApiEndpoints.changeUserStatus + .replaceAll("{invitedUserUuid}", userUuid), + body: bodya, + expectedResponseModel: (json) { + print('changeUserStatusById==${json['success']}'); + return json['success']; + }, + ); + + return response; + } catch (e) { + return false; + print(e); + } + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index da746fbd..a229c5f6 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -100,7 +100,13 @@ abstract class ApiEndpoints { static const String roleTypes = '/role/types'; static const String permission = '/permission/{roleUuid}'; static const String inviteUser = '/invite-user'; + static const String checkEmail = '/invite-user/check-email'; + static const String getUsers = '/projects/${projectUuid}/user'; + static const String getUserById = '/projects/${projectUuid}/user/{userUuid}'; + static const String editUser = '/invite-user/{inviteUserUuid}'; + static const String deleteUser = '/invite-user/{inviteUserUuid}'; + static const String changeUserStatus = '/invite-user/{invitedUserUuid}/disable'; + // static const String updateAutomation = '/automation/{automationId}'; - // https://syncrow-dev.azurewebsites.net/invite-user/check-email } diff --git a/macos/Podfile.lock b/macos/Podfile.lock new file mode 100644 index 00000000..0639648b --- /dev/null +++ b/macos/Podfile.lock @@ -0,0 +1,36 @@ +PODS: + - flutter_secure_storage_macos (6.1.1): + - FlutterMacOS + - FlutterMacOS (1.0.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + +EXTERNAL SOURCES: + flutter_secure_storage_macos: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos + FlutterMacOS: + :path: Flutter/ephemeral + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + +SPEC CHECKSUMS: + flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9 + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + +PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 + +COCOAPODS: 1.16.2 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index e1af4c91..eec16af6 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -21,6 +21,8 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 108157F896CD9F637B06D7C0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DAF1C60594A51D692304366 /* Pods_Runner.framework */; }; + 2D0F1F294F673EF0DB5E4CA1 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */; }; 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; @@ -60,11 +62,12 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 24D7BEF98D33245EFB9F6A1B /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* syncrow_web.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "syncrow_web.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* syncrow_web.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = syncrow_web.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -76,8 +79,15 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 5DAF1C60594A51D692304366 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + AB949539E0D0A8E2BDAB9ADF /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F244F079A053D959E1C5C362 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,6 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 2D0F1F294F673EF0DB5E4CA1 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -92,6 +103,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 108157F896CD9F637B06D7C0 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -125,6 +137,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 75DCDFECC7757C5159E8F0C5 /* Pods */, ); sourceTree = ""; }; @@ -172,9 +185,25 @@ path = Runner; sourceTree = ""; }; + 75DCDFECC7757C5159E8F0C5 /* Pods */ = { + isa = PBXGroup; + children = ( + 24D7BEF98D33245EFB9F6A1B /* Pods-Runner.debug.xcconfig */, + F244F079A053D959E1C5C362 /* Pods-Runner.release.xcconfig */, + AB949539E0D0A8E2BDAB9ADF /* Pods-Runner.profile.xcconfig */, + 96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */, + A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */, + 81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 5DAF1C60594A51D692304366 /* Pods_Runner.framework */, + E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -186,6 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + A1935203066F42991FF0ED43 /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -204,11 +234,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 8ECFD939A4D371A145DBA191 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 92D754792F50A5D35F6D5AEE /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -329,6 +361,67 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 8ECFD939A4D371A145DBA191 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 92D754792F50A5D35F6D5AEE /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + A1935203066F42991FF0ED43 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -380,6 +473,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -394,6 +488,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -408,6 +503,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16..21a3cc14 100644 --- a/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/pubspec.lock b/pubspec.lock index 86d132c3..d1c39c65 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -392,6 +392,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + number_pagination: + dependency: "direct main" + description: + name: number_pagination + sha256: "75d3a28616196e7c8df431d0fb7c48e811e462155f4cf3b5b4167b3408421327" + url: "https://pub.dev" + source: hosted + version: "1.1.6" path: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6951987c..786a39c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,7 @@ dependencies: uuid: ^4.4.2 time_picker_spinner: ^1.0.0 intl_phone_field: ^3.2.0 + number_pagination: ^1.1.6 dev_dependencies: flutter_test: From e44c3ae79647429db750084bf2d0d07969d961b1 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 19:27:42 +0400 Subject: [PATCH 035/106] pass created subpaces --- .../dialog/create_space_model_dialog.dart | 1 + .../views/add_device_type_model_widget.dart | 19 +++++++++---------- .../widgets/device_type_tile_widget.dart | 2 ++ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index a84b6409..c70e3c56 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -78,6 +78,7 @@ class CreateSpaceModelDialog extends StatelessWidget { context: context, builder: (context) => AddDeviceTypeModelWidget( products: products, + subspaces: subspaces, ), ); if (result == true) { diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index ad71116a..ab0a0cb0 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -12,13 +12,14 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? products; final ValueChanged>? onProductsSelected; final List? initialSelectedProducts; + final List? subspaces; - const AddDeviceTypeModelWidget({ - super.key, - this.products, - this.initialSelectedProducts, - this.onProductsSelected, - }); + const AddDeviceTypeModelWidget( + {super.key, + this.products, + this.initialSelectedProducts, + this.onProductsSelected, + this.subspaces}); @override Widget build(BuildContext context) { @@ -46,9 +47,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0), child: ScrollableGridViewWidget( - products: products, - crossAxisCount: crossAxisCount, - ), + products: products, crossAxisCount: crossAxisCount), ), ), ], diff --git a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart index 1190bc5c..1f39fc22 100644 --- a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart @@ -3,7 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_icon_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_name_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; From 5bd257ee5696b00297788bf88cda3b6e33142b04 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 19:56:39 +0400 Subject: [PATCH 036/106] added tag model assignment ui --- .../views/assign_tag_models_dialog.dart | 126 ++++++++++++++++++ .../space_model/models/tag_model.dart | 18 +-- .../views/add_device_type_model_widget.dart | 98 +++++++------- 3 files changed, 181 insertions(+), 61 deletions(-) create mode 100644 lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart diff --git a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart new file mode 100644 index 00000000..1c7ab2d0 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart @@ -0,0 +1,126 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class AssignTagModelsDialog extends StatefulWidget { + final List? products; + final List? subspaces; + final List? initialTags; + final ValueChanged>? onTagsAssigned; + + const AssignTagModelsDialog({ + Key? key, + required this.products, + required this.subspaces, + this.initialTags, + this.onTagsAssigned, + }) : super(key: key); + + @override + AssignTagModelsDialogState createState() => AssignTagModelsDialogState(); +} + +class AssignTagModelsDialogState extends State { + late List tags; + + @override + void initState() { + super.initState(); + + if (widget.products != null) { + print(widget.products); + tags = []; + } + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Assign Tags'), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Container( + width: MediaQuery.of(context).size.width * 0.9, + child: DataTable( + columns: const [ + DataColumn(label: Text('#')), + DataColumn(label: Text('Device')), + DataColumn(label: Text('Tag')), + DataColumn(label: Text('Location')), + ], + rows: tags.asMap().entries.map((entry) { + final index = entry.key + 1; + final tagModel = entry.value; + return DataRow(cells: [ + DataCell(Text(index.toString())), + DataCell(Text(tagModel.product?.name ?? 'Unknown')), + DataCell( + DropdownButton( + value: tagModel.tag, + onChanged: (value) { + setState(() { + tagModel.tag = value!; + }); + }, + items: List.generate(10, (index) { + final tag = 'Tag ${index + 1}'; + return DropdownMenuItem(value: tag, child: Text(tag)); + }), + ), + ), + DataCell( + DropdownButton( + value: widget.subspaces + ?.firstWhere( + (subspace) => + subspace.subspaceName == 'ssdsdf', + ) + .subspaceName ?? + 'None', + onChanged: (value) {}, + items: widget.subspaces! + .map((subspace) => DropdownMenuItem( + value: subspace.subspaceName, + child: Text(subspace.subspaceName), + )) + .toList(), + ), + ), + ]); + }).toList(), + ), + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Back'), + style: ElevatedButton.styleFrom( + backgroundColor: ColorsManager.boxColor, + foregroundColor: ColorsManager.blackColor, + ), + ), + ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + if (widget.onTagsAssigned != null) { + widget.onTagsAssigned!(tags); + } + }, + child: const Text('Save'), + style: ElevatedButton.styleFrom( + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: ColorsManager.whiteColors, + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index 356368fc..a9512107 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -1,29 +1,20 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; class TagModel { - final String uuid; - final DateTime createdAt; - final DateTime updatedAt; - final String tag; - final bool disabled; + String? uuid; + String tag; final ProductModel? product; TagModel({ - required this.uuid, - required this.createdAt, - required this.updatedAt, + this.uuid, required this.tag, - required this.disabled, this.product, }); factory TagModel.fromJson(Map json) { return TagModel( uuid: json['uuid'] ?? '', - createdAt: DateTime.parse(json['createdAt']), - updatedAt: DateTime.parse(json['updatedAt']), tag: json['tag'] ?? '', - disabled: json['disabled'] ?? false, product: json['product'] != null ? ProductModel.fromMap(json['product']) : null, @@ -33,10 +24,7 @@ class TagModel { Map toJson() { return { 'uuid': uuid, - 'createdAt': createdAt.toIso8601String(), - 'updatedAt': updatedAt.toIso8601String(), 'tag': tag, - 'disabled': disabled, 'product': product?.toMap(), }; } diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index ab0a0cb0..32828898 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; @@ -31,56 +32,61 @@ class AddDeviceTypeModelWidget extends StatelessWidget { : 3; return BlocProvider( - create: (_) => AddDeviceTypeModelBloc(initialSelectedProducts ?? []), - child: AlertDialog( - title: const Text('Add Devices'), - backgroundColor: ColorsManager.whiteColors, - content: SingleChildScrollView( - child: Container( - width: size.width * 0.9, - height: size.height * 0.65, - color: ColorsManager.textFieldGreyColor, - child: Column( - children: [ - const SizedBox(height: 16), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: ScrollableGridViewWidget( - products: products, crossAxisCount: crossAxisCount), - ), + create: (_) => AddDeviceTypeModelBloc(initialSelectedProducts ?? []), + child: Builder( + builder: (context) => AlertDialog( + title: const Text('Add Devices'), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Container( + width: size.width * 0.9, + height: size.height * 0.65, + color: ColorsManager.textFieldGreyColor, + child: Column( + children: [ + const SizedBox(height: 16), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: ScrollableGridViewWidget( + products: products, crossAxisCount: crossAxisCount), + ), + ), + ], ), - ], - ), - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ActionButton( - label: 'Cancel', - backgroundColor: ColorsManager.boxColor, - foregroundColor: ColorsManager.blackColor, - onPressed: () => Navigator.of(context).pop(), ), - ActionButton( - label: 'Continue', - backgroundColor: ColorsManager.secondaryColor, - foregroundColor: ColorsManager.whiteColors, - onPressed: () { - Navigator.of(context).pop(); - if (onProductsSelected != null) { - final selectedProducts = - context.read().state; - onProductsSelected!(selectedProducts); - } - }, + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ActionButton( + label: 'Cancel', + backgroundColor: ColorsManager.boxColor, + foregroundColor: ColorsManager.blackColor, + onPressed: () => Navigator.of(context).pop(), + ), + ActionButton( + label: 'Continue', + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: ColorsManager.whiteColors, + onPressed: () async { + print(products); + print("dfsdf"); + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AssignTagModelsDialog( + products: products, + subspaces: subspaces, + ), + ); + }, + ), + ], ), ], ), - ], - ), - ); + )); } } From 0fda5457ae9600423c2c4f6ee786f9479ab302eb Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 21:16:59 +0400 Subject: [PATCH 037/106] fixed tag model --- .../views/assign_tag_models_dialog.dart | 34 ++++++++++--------- .../model/selected_product_model.dart | 4 ++- .../all_spaces/model/space_model.dart | 1 + .../widgets/add_device_type_widget.dart | 4 +-- .../space_model/models/tag_model.dart | 9 +++-- .../tag_model/bloc/add_device_model_bloc.dart | 6 ++-- .../bloc/add_device_type_model_event.dart | 3 +- .../views/add_device_type_model_widget.dart | 28 ++++++++------- .../widgets/device_type_tile_widget.dart | 5 ++- 9 files changed, 53 insertions(+), 41 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart index 1c7ab2d0..acdb75d8 100644 --- a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -9,11 +10,13 @@ class AssignTagModelsDialog extends StatefulWidget { final List? subspaces; final List? initialTags; final ValueChanged>? onTagsAssigned; + final List addedProducts; const AssignTagModelsDialog({ Key? key, required this.products, required this.subspaces, + required this.addedProducts, this.initialTags, this.onTagsAssigned, }) : super(key: key); @@ -24,15 +27,17 @@ class AssignTagModelsDialog extends StatefulWidget { class AssignTagModelsDialogState extends State { late List tags; + late List selectedProducts; @override void initState() { super.initState(); - + if (widget.products != null) { print(widget.products); tags = []; } + selectedProducts = widget.addedProducts; } @override @@ -50,19 +55,18 @@ class AssignTagModelsDialogState extends State { DataColumn(label: Text('Tag')), DataColumn(label: Text('Location')), ], - rows: tags.asMap().entries.map((entry) { + rows: selectedProducts.asMap().entries.map((entry) { final index = entry.key + 1; - final tagModel = entry.value; + final selectedProduct = entry.value; return DataRow(cells: [ DataCell(Text(index.toString())), - DataCell(Text(tagModel.product?.name ?? 'Unknown')), + DataCell(Text(selectedProduct.productName ?? 'Unknown')), DataCell( DropdownButton( - value: tagModel.tag, + value: + 'Tag 1', // Static text that matches an item in the list onChanged: (value) { - setState(() { - tagModel.tag = value!; - }); + // Handle value change if needed }, items: List.generate(10, (index) { final tag = 'Tag ${index + 1}'; @@ -72,14 +76,12 @@ class AssignTagModelsDialogState extends State { ), DataCell( DropdownButton( - value: widget.subspaces - ?.firstWhere( - (subspace) => - subspace.subspaceName == 'ssdsdf', - ) - .subspaceName ?? - 'None', - onChanged: (value) {}, + value: widget.subspaces?.isNotEmpty == true + ? widget.subspaces!.first.subspaceName + : null, + onChanged: (value) { + // Handle value changes here + }, items: widget.subspaces! .map((subspace) => DropdownMenuItem( value: subspace.subspaceName, diff --git a/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart b/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart index 9a06698f..31d7f6f8 100644 --- a/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart @@ -1,13 +1,15 @@ class SelectedProduct { final String productId; int count; + final String productName; - SelectedProduct({required this.productId, required this.count}); + SelectedProduct({required this.productId, required this.count, required this.productName}); Map toJson() { return { 'productId': productId, 'count': count, + 'productName': productName, }; } diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index df6550f8..7406d919 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -90,6 +90,7 @@ class SpaceModel { return SelectedProduct( productId: product['product']['uuid'], count: product['productCount'], + productName: '', ); }).toList() : [], diff --git a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart index 40759b58..ed764250 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart @@ -114,7 +114,7 @@ class _AddDeviceWidgetState extends State { Widget _buildDeviceTypeTile(ProductModel product, Size size) { final selectedProduct = productCounts.firstWhere( (p) => p.productId == product.uuid, - orElse: () => SelectedProduct(productId: product.uuid, count: 0), + orElse: () => SelectedProduct(productId: product.uuid, count: 0, productName: product.catName), ); return SizedBox( @@ -143,7 +143,7 @@ class _AddDeviceWidgetState extends State { if (newCount > 0) { if (!productCounts.contains(selectedProduct)) { productCounts - .add(SelectedProduct(productId: product.uuid, count: newCount)); + .add(SelectedProduct(productId: product.uuid, count: newCount, productName: product.catName)); } else { selectedProduct.count = newCount; } diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index a9512107..820b9515 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -1,19 +1,25 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:uuid/uuid.dart'; class TagModel { String? uuid; String tag; final ProductModel? product; + String internalId; TagModel({ this.uuid, required this.tag, this.product, - }); + String? internalId, + }) : internalId = internalId ?? const Uuid().v4(); factory TagModel.fromJson(Map json) { + final String internalId = json['internalId'] ?? const Uuid().v4(); + return TagModel( uuid: json['uuid'] ?? '', + internalId: internalId, tag: json['tag'] ?? '', product: json['product'] != null ? ProductModel.fromMap(json['product']) @@ -29,4 +35,3 @@ class TagModel { }; } } - diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart index 1352e97c..1049fdce 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart @@ -13,19 +13,19 @@ class AddDeviceTypeModelBloc UpdateProductCountEvent event, Emitter> emit) { final existingProduct = state.firstWhere( (p) => p.productId == event.productId, - orElse: () => SelectedProduct(productId: event.productId, count: 0), + orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName ), ); if (event.count > 0) { if (!state.contains(existingProduct)) { emit([ ...state, - SelectedProduct(productId: event.productId, count: event.count) + SelectedProduct(productId: event.productId, count: event.count, productName: event.productName) ]); } else { final updatedList = state.map((p) { if (p.productId == event.productId) { - return SelectedProduct(productId: p.productId, count: event.count); + return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName); } return p; }).toList(); diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart index a3feaad8..c9594020 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart @@ -8,8 +8,9 @@ abstract class AddDeviceTypeModelEvent extends Equatable { class UpdateProductCountEvent extends AddDeviceTypeModelEvent { final String productId; final int count; + final String productName; - UpdateProductCountEvent({required this.productId, required this.count}); + UpdateProductCountEvent({required this.productId, required this.count, required this.productName}); @override List get props => [productId, count]; diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 32828898..7c9204ef 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; @@ -60,10 +61,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - ActionButton( + CancelButton( label: 'Cancel', - backgroundColor: ColorsManager.boxColor, - foregroundColor: ColorsManager.blackColor, onPressed: () => Navigator.of(context).pop(), ), ActionButton( @@ -71,16 +70,19 @@ class AddDeviceTypeModelWidget extends StatelessWidget { backgroundColor: ColorsManager.secondaryColor, foregroundColor: ColorsManager.whiteColors, onPressed: () async { - print(products); - print("dfsdf"); - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AssignTagModelsDialog( - products: products, - subspaces: subspaces, - ), - ); + final currentState = + context.read().state; + if (currentState.isNotEmpty) { + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AssignTagModelsDialog( + products: products, + subspaces: subspaces, + addedProducts: currentState, + ), + ); + } }, ), ], diff --git a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart index 1f39fc22..38159057 100644 --- a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart @@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_icon_widget.dart'; @@ -25,7 +24,7 @@ class DeviceTypeTileWidget extends StatelessWidget { Widget build(BuildContext context) { final selectedProduct = productCounts.firstWhere( (p) => p.productId == product.uuid, - orElse: () => SelectedProduct(productId: product.uuid, count: 0), + orElse: () => SelectedProduct(productId: product.uuid, count: 0, productName: product.catName), ); return Card( @@ -49,7 +48,7 @@ class DeviceTypeTileWidget extends StatelessWidget { onCountChanged: (newCount) { context.read().add( UpdateProductCountEvent( - productId: product.uuid, count: newCount), + productId: product.uuid, count: newCount,productName: product.catName), ); }, ), From a31eb27c929a175008cd4471e8f934f7f1147637 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 23:22:30 +0400 Subject: [PATCH 038/106] addign tag model --- .../views/assign_tag_models_dialog.dart | 186 +++++++++++++----- .../bloc/space_management_bloc.dart | 37 ++-- .../model/selected_product_model.dart | 5 +- .../all_spaces/model/space_model.dart | 11 -- .../widgets/add_device_type_widget.dart | 4 +- .../widgets/community_structure_widget.dart | 4 +- .../models/subspace_template_model.dart | 2 +- .../space_model/models/tag_model.dart | 17 +- .../tag_model/bloc/add_device_model_bloc.dart | 6 +- .../bloc/add_device_type_model_event.dart | 4 +- .../widgets/device_type_tile_widget.dart | 11 +- lib/services/space_mana_api.dart | 4 - lib/utils/color_manager.dart | 1 + 13 files changed, 190 insertions(+), 102 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart index acdb75d8..885f483b 100644 --- a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart @@ -28,16 +28,36 @@ class AssignTagModelsDialog extends StatefulWidget { class AssignTagModelsDialogState extends State { late List tags; late List selectedProducts; + late List locations; @override void initState() { super.initState(); + print(widget.addedProducts); + print(widget.subspaces); - if (widget.products != null) { - print(widget.products); - tags = []; - } + // Initialize tags from widget.initialTags or create new ones if it's empty + tags = widget.initialTags?.isNotEmpty == true + ? widget.initialTags! + : widget.addedProducts + .expand((selectedProduct) => List.generate( + selectedProduct.count, // Generate `count` number of tags + (index) => TagModel( + tag: '', // Initialize each tag with a default value + product: selectedProduct.product, + location: 'None', // Default location + ), + )) + .toList(); + + // Initialize selected products selectedProducts = widget.addedProducts; + + // Initialize locations from subspaces or empty list if null + locations = widget.subspaces != null + ? widget.subspaces!.map((subspace) => subspace.subspaceName).toList() + : []; + locations.add("None"); } @override @@ -47,51 +67,121 @@ class AssignTagModelsDialogState extends State { backgroundColor: ColorsManager.whiteColors, content: SingleChildScrollView( child: Container( - width: MediaQuery.of(context).size.width * 0.9, - child: DataTable( - columns: const [ - DataColumn(label: Text('#')), - DataColumn(label: Text('Device')), - DataColumn(label: Text('Tag')), - DataColumn(label: Text('Location')), - ], - rows: selectedProducts.asMap().entries.map((entry) { - final index = entry.key + 1; - final selectedProduct = entry.value; - return DataRow(cells: [ - DataCell(Text(index.toString())), - DataCell(Text(selectedProduct.productName ?? 'Unknown')), - DataCell( - DropdownButton( - value: - 'Tag 1', // Static text that matches an item in the list - onChanged: (value) { - // Handle value change if needed - }, - items: List.generate(10, (index) { - final tag = 'Tag ${index + 1}'; - return DropdownMenuItem(value: tag, child: Text(tag)); - }), - ), + width: MediaQuery.of(context).size.width * 0.4, + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.dataHeaderGrey, width: 1), + borderRadius: BorderRadius.circular(20), + ), + child: Theme( + data: Theme.of(context).copyWith( + dataTableTheme: DataTableThemeData( + headingRowColor: + MaterialStateProperty.all(ColorsManager.dataHeaderGrey), + headingTextStyle: const TextStyle( + color: ColorsManager.blackColor, + fontWeight: FontWeight.bold, ), - DataCell( - DropdownButton( - value: widget.subspaces?.isNotEmpty == true - ? widget.subspaces!.first.subspaceName - : null, - onChanged: (value) { - // Handle value changes here - }, - items: widget.subspaces! - .map((subspace) => DropdownMenuItem( - value: subspace.subspaceName, - child: Text(subspace.subspaceName), - )) - .toList(), - ), - ), - ]); - }).toList(), + ), + ), + child: DataTable( + border: TableBorder.all( + color: ColorsManager.dataHeaderGrey, + width: 1, + borderRadius: BorderRadius.circular(20), + ), + columns: const [ + DataColumn(label: Text('#')), + DataColumn(label: Text('Device')), + DataColumn(label: Text('Tag')), + DataColumn(label: Text('Location')), + ], + rows: tags.asMap().entries.map((entry) { + final index = entry.key + 1; + final tag = entry.value; + + return DataRow( + cells: [ + DataCell( + Center( + child: Text(index.toString()), + ), + ), + DataCell( + Center( + child: Text(tag.product?.catName ?? 'Unknown'), + ), + ), + DataCell( + Center( + child: DropdownButton( + value: tag.tag!.isNotEmpty ? tag.tag : null, + onChanged: (value) { + setState(() { + tag.tag = value ?? ''; // Update tag value + }); + }, + items: [ + const DropdownMenuItem( + value: null, + child: Text('None'), + ), + ...List.generate(10, (index) { + final tagName = 'Tag ${index + 1}'; + return DropdownMenuItem( + value: tagName, + child: Text(tagName), + ); + }), + ], + ), + ), + ), + DataCell( + Center( + child: DropdownButtonHideUnderline( + child: DropdownButton( + alignment: AlignmentDirectional.centerEnd, + value: locations.contains(tag.location) + ? tag.location + : null, // Validate value + onChanged: (value) { + setState(() { + tag.location = + value ?? 'None'; // Update location + }); + }, + dropdownColor: Colors.white, + icon: const Icon( + Icons.arrow_drop_down, + color: Colors.black, + ), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + isExpanded: true, + items: locations + .map((location) => DropdownMenuItem( + value: location, + child: Text( + location, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Colors.grey, + ), + ), + )) + .toList(), + ), + ), + ), + ), + ], + ); + }).toList(), + ), ), ), ), diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 0c002e7d..2b749a32 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -329,7 +329,6 @@ class SpaceManagementBloc Emitter emit, ) { final communities = List.from(previousState.communities); - for (var community in communities) { if (community.uuid == communityUuid) { @@ -367,26 +366,26 @@ class SpaceManagementBloc try { if (space.uuid != null && space.uuid!.isNotEmpty) { final response = await _api.updateSpace( - communityId: communityUuid, - spaceId: space.uuid!, - name: space.name, - parentId: space.parent?.uuid, - isPrivate: space.isPrivate, - position: space.position, - icon: space.icon, - direction: space.incomingConnection?.direction, - products: space.selectedProducts); + communityId: communityUuid, + spaceId: space.uuid!, + name: space.name, + parentId: space.parent?.uuid, + isPrivate: space.isPrivate, + position: space.position, + icon: space.icon, + direction: space.incomingConnection?.direction, + ); } else { // Call create if the space does not have a UUID final response = await _api.createSpace( - communityId: communityUuid, - name: space.name, - parentId: space.parent?.uuid, - isPrivate: space.isPrivate, - position: space.position, - icon: space.icon, - direction: space.incomingConnection?.direction, - products: space.selectedProducts); + communityId: communityUuid, + name: space.name, + parentId: space.parent?.uuid, + isPrivate: space.isPrivate, + position: space.position, + icon: space.icon, + direction: space.incomingConnection?.direction, + ); space.uuid = response?.uuid; } } catch (e) { @@ -426,7 +425,7 @@ class SpaceManagementBloc emit(SpaceManagementLoading()); try { List communities = await _api.fetchCommunities(); - + List updatedCommunities = await Future.wait( communities.map((community) async { List spaces = diff --git a/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart b/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart index 31d7f6f8..91314e42 100644 --- a/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart @@ -1,9 +1,12 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; + class SelectedProduct { final String productId; int count; final String productName; + final ProductModel? product; - SelectedProduct({required this.productId, required this.count, required this.productName}); + SelectedProduct({required this.productId, required this.count, required this.productName, this.product}); Map toJson() { return { diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index 7406d919..4bdf4ece 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -20,7 +20,6 @@ class SpaceModel { Offset position; bool isHovered; SpaceStatus status; - List selectedProducts; String internalId; List outgoingConnections = []; // Connections from this space @@ -41,7 +40,6 @@ class SpaceModel { this.isHovered = false, this.incomingConnection, this.status = SpaceStatus.unchanged, - this.selectedProducts = const [], }) : internalId = internalId ?? const Uuid().v4(); factory SpaceModel.fromJson(Map json, @@ -85,15 +83,6 @@ class SpaceModel { icon: json['icon'] ?? Assets.location, position: Offset(json['x'] ?? 0, json['y'] ?? 0), isHovered: false, - selectedProducts: json['spaceProducts'] != null - ? (json['spaceProducts'] as List).map((product) { - return SelectedProduct( - productId: product['product']['uuid'], - count: product['productCount'], - productName: '', - ); - }).toList() - : [], ); if (json['incomingConnections'] != null && diff --git a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart index ed764250..93a38716 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart @@ -114,7 +114,7 @@ class _AddDeviceWidgetState extends State { Widget _buildDeviceTypeTile(ProductModel product, Size size) { final selectedProduct = productCounts.firstWhere( (p) => p.productId == product.uuid, - orElse: () => SelectedProduct(productId: product.uuid, count: 0, productName: product.catName), + orElse: () => SelectedProduct(productId: product.uuid, count: 0, productName: product.catName, product: product), ); return SizedBox( @@ -143,7 +143,7 @@ class _AddDeviceWidgetState extends State { if (newCount > 0) { if (!productCounts.contains(selectedProduct)) { productCounts - .add(SelectedProduct(productId: product.uuid, count: newCount, productName: product.catName)); + .add(SelectedProduct(productId: product.uuid, count: newCount, productName: product.catName, product: product)); } else { selectedProduct.count = newCount; } diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index c11563da..a929c6fd 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -299,7 +299,7 @@ class _CommunityStructureAreaState extends State { isPrivate: false, children: [], status: SpaceStatus.newSpace, - selectedProducts: selectedProducts); + ); if (parentIndex != null && direction != null) { SpaceModel parentSpace = spaces[parentIndex]; @@ -335,14 +335,12 @@ class _CommunityStructureAreaState extends State { icon: space.icon, editSpace: space, isEdit: true, - selectedProducts: space.selectedProducts, onCreateSpace: (String name, String icon, List selectedProducts) { setState(() { // Update the space's properties space.name = name; space.icon = icon; - space.selectedProducts = selectedProducts; if (space.status != SpaceStatus.newSpace) { space.status = SpaceStatus.modified; // Mark as modified diff --git a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart index 8f9d4d4d..911494a0 100644 --- a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart @@ -2,7 +2,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model class SubspaceTemplateModel { final String? uuid; - final String subspaceName; + String subspaceName; final bool disabled; final List? tags; diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index 820b9515..1e7c594b 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -1,18 +1,21 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:uuid/uuid.dart'; class TagModel { String? uuid; - String tag; + String? tag; final ProductModel? product; String internalId; + String? location; - TagModel({ - this.uuid, - required this.tag, - this.product, - String? internalId, - }) : internalId = internalId ?? const Uuid().v4(); + TagModel( + {this.uuid, + required this.tag, + this.product, + String? internalId, + this.location}) + : internalId = internalId ?? const Uuid().v4(); factory TagModel.fromJson(Map json) { final String internalId = json['internalId'] ?? const Uuid().v4(); diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart index 1049fdce..3091d152 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart @@ -13,19 +13,19 @@ class AddDeviceTypeModelBloc UpdateProductCountEvent event, Emitter> emit) { final existingProduct = state.firstWhere( (p) => p.productId == event.productId, - orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName ), + orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName,product: event.product ), ); if (event.count > 0) { if (!state.contains(existingProduct)) { emit([ ...state, - SelectedProduct(productId: event.productId, count: event.count, productName: event.productName) + SelectedProduct(productId: event.productId, count: event.count, productName: event.productName, product: event.product) ]); } else { final updatedList = state.map((p) { if (p.productId == event.productId) { - return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName); + return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName,product: p.product); } return p; }).toList(); diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart index c9594020..1d6976f8 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; abstract class AddDeviceTypeModelEvent extends Equatable { @override @@ -9,8 +10,9 @@ class UpdateProductCountEvent extends AddDeviceTypeModelEvent { final String productId; final int count; final String productName; + final ProductModel product; - UpdateProductCountEvent({required this.productId, required this.count, required this.productName}); + UpdateProductCountEvent({required this.productId, required this.count, required this.productName, required this.product}); @override List get props => [productId, count]; diff --git a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart index 38159057..c2d38d0b 100644 --- a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart @@ -24,7 +24,11 @@ class DeviceTypeTileWidget extends StatelessWidget { Widget build(BuildContext context) { final selectedProduct = productCounts.firstWhere( (p) => p.productId == product.uuid, - orElse: () => SelectedProduct(productId: product.uuid, count: 0, productName: product.catName), + orElse: () => SelectedProduct( + productId: product.uuid, + count: 0, + productName: product.catName, + product: product), ); return Card( @@ -48,7 +52,10 @@ class DeviceTypeTileWidget extends StatelessWidget { onCountChanged: (newCount) { context.read().add( UpdateProductCountEvent( - productId: product.uuid, count: newCount,productName: product.catName), + productId: product.uuid, + count: newCount, + productName: product.catName, + product: product), ); }, ), diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index def94441..333b715e 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -167,7 +167,6 @@ class CommunitySpaceManagementApi { bool isPrivate = false, required Offset position, String? icon, - required List products, }) async { try { final body = { @@ -177,7 +176,6 @@ class CommunitySpaceManagementApi { 'y': position.dy, 'direction': direction, 'icon': icon, - 'products': products.map((product) => product.toJson()).toList(), }; if (parentId != null) { body['parentUuid'] = parentId; @@ -207,7 +205,6 @@ class CommunitySpaceManagementApi { String? direction, bool isPrivate = false, required Offset position, - required List products, }) async { try { final body = { @@ -217,7 +214,6 @@ class CommunitySpaceManagementApi { 'y': position.dy, 'direction': direction, 'icon': icon, - 'products': products.map((product) => product.toJson()).toList(), }; if (parentId != null) { body['parentUuid'] = parentId; diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 8d0aae43..eaf1e6b1 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -56,5 +56,6 @@ abstract class ColorsManager { static const Color CircleImageBackground = Color(0xFFF4F4F4); static const Color softGray = Color(0xFFD5D5D5); static const Color semiTransparentBlack = Color(0x19000000); + static const Color dataHeaderGrey = Color(0x33999999); } //background: #background: #5D5D5D; From fe8f8160ec676c91e1d50ae1fe226154c5aedcfb Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 6 Jan 2025 14:20:57 +0300 Subject: [PATCH 039/106] filter --- .../add_user_dialog/bloc/users_bloc.dart | 31 +- .../add_user_dialog/bloc/users_event.dart | 7 - .../view/popup_menu_filter.dart | 96 ++- .../users_table/bloc/user_table_bloc.dart | 95 ++- .../users_table/bloc/user_table_event.dart | 16 + .../users_table/bloc/user_table_state.dart | 9 + .../users_table/view/user_table.dart | 2 +- .../users_table/view/users_page.dart | 737 ++++++++++-------- 8 files changed, 580 insertions(+), 413 deletions(-) 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 730825cb..c06c0969 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 @@ -30,8 +30,8 @@ class UsersBloc extends Bloc { on(_onToggleNodeExpansion); on(_onToggleNodeCheck); on(_editInvitUser); - } + void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { if (formKey.currentState?.validate() ?? false) { emit(const BasicsStepValidState()); @@ -381,7 +381,7 @@ class UsersBloc extends Bloc { // Create a list of UUIDs to mark final uuidsToMark = res.spaces.map((space) => space.uuid).toList(); // Print all IDs and mark nodes in updatedCommunities - print('Printing and marking nodes in updatedCommunities:'); + debugPrint('Printing and marking nodes in updatedCommunities:'); _printAndMarkNodes(updatedCommunities, uuidsToMark); } else { print('updatedCommunities is empty!'); @@ -391,34 +391,27 @@ class UsersBloc extends Bloc { element.type == res.roleType.toString().toLowerCase().replaceAll("_", " ")) .uuid; - print('Role ID: $roleId'); + debugPrint('Role ID: $roleId'); roleSelected = roleId; add(PermissionEvent(roleUuid: roleSelected)); emit(ChangeStatusSteps()); - } else { - // emit(UsersErrorState("User not found")); - } - } else { - // emit(UsersErrorState("Invalid user ID")); - } + } else {} + } else {} } catch (e) { print("Failed to fetch user data: $e"); - // emit(UsersErrorState("Failed to fetch user data: $e")); } } - /// Recursively print all the node IDs, including nested children. - /// Recursively print all node IDs and mark nodes as `isChecked` if their UUID exists in the list. void _printAndMarkNodes(List nodes, List uuidsToMark, [int level = 0]) { for (final node in nodes) { - // Check if the current node's UUID exists in the list of UUIDs to mark. if (uuidsToMark.contains(node.uuid)) { - node.isChecked = true; // Mark the node as checked. - print( + node.isChecked = true; + debugPrint( '${' ' * level}MATCH FOUND: Node ID: ${node.uuid}, Title: ${node.title} is marked as checked.'); } else { - print('${' ' * level}Node ID: ${node.uuid}, Title: ${node.title}'); + debugPrint( + '${' ' * level}Node ID: ${node.uuid}, Title: ${node.title}'); } if (node.children.isNotEmpty) { _printAndMarkNodes(node.children, uuidsToMark, level + 1); @@ -453,8 +446,6 @@ class UsersBloc extends Bloc { emit(ChangeStatusSteps()); } -// Existing methods that remain in the BLoC: - void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { for (var child in node.children) { child.isChecked = isChecked; @@ -477,7 +468,6 @@ class UsersBloc extends Bloc { (child.children.isEmpty || _areAllChildrenChecked(child))); } -// Private helper method to find the parent of a given node. TreeNode? _findParent(List nodes, TreeNode target) { for (var node in nodes) { if (node.children.contains(target)) { @@ -490,7 +480,4 @@ class UsersBloc extends Bloc { } return null; } - - - } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart index 0f4631ff..c193bbf3 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart @@ -129,8 +129,6 @@ class UpdateNodeCheckStatus extends UsersEvent { @override List get props => [node]; } - -// Define new events class ToggleNodeHighlightEvent extends UsersEvent { final TreeNode node; @@ -161,14 +159,9 @@ class ToggleNodeCheckEvent extends UsersEvent { @override List get props => []; } - -// users_event.dart - -// 1. Extend UsersEvent class ToggleNodeCheck extends UsersEvent { final TreeNode node; - // 2. Add a constructor that takes the node to toggle ToggleNodeCheck(this.node); @override List get props => []; 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 02eac036..fbadd07e 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 @@ -1,6 +1,9 @@ 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/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/style.dart'; Future showPopUpFilterMenu({ required BuildContext context, @@ -8,20 +11,18 @@ Future showPopUpFilterMenu({ Function()? onSortZtoA, Function()? cancelButton, required Map checkboxStates, + required RelativeRect position, + // Function(String)? onTextFieldChanged, Function()? onOkPressed, List? list, }) async { - final RenderBox overlay = - Overlay.of(context).context.findRenderObject() as RenderBox; + await showMenu( context: context, - position: RelativeRect.fromLTRB( - overlay.size.width / 4, - 240, - overlay.size.width / 4, - 0, - ), + position:position, + + color: ColorsManager.whiteColors, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10)), @@ -55,28 +56,69 @@ Future showPopUpFilterMenu({ ), const PopupMenuDivider(), const PopupMenuItem( - child: Text( - "Filter by Status", - style: TextStyle(fontWeight: FontWeight.bold), - ), - ), + child: Text( + "Filter by Status", + style: TextStyle(fontWeight: FontWeight.bold), + ) + // Container( + // decoration: containerDecoration.copyWith( + // boxShadow: [], + // borderRadius: const BorderRadius.only( + // topLeft: Radius.circular(10), topRight: Radius.circular(10))), + // child: Padding( + // padding: const EdgeInsets.all(8.0), + // child: TextFormField( + // onChanged: onTextFieldChanged, + // style: const TextStyle(color: Colors.black), + // decoration: textBoxDecoration(radios: 15)!.copyWith( + // fillColor: ColorsManager.whiteColors, + // errorStyle: const TextStyle(height: 0), + // hintStyle: context.textTheme.titleSmall?.copyWith( + // color: Colors.grey, + // fontSize: 12, + // ), + // hintText: 'Search', + // suffixIcon: SizedBox( + // child: SvgPicture.asset( + // Assets.searchIconUser, + // fit: BoxFit.none, + // ), + // ), + // ), + // // "Filter by Status", + // // style: TextStyle(fontWeight: FontWeight.bold), + // ), + // ), + // ), + ), PopupMenuItem( - child: SizedBox( + child: Container( + decoration: containerDecoration.copyWith( + boxShadow: [], + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10))), + padding: const EdgeInsets.all(10), height: 200, width: 400, - child: ListView.builder( - itemCount: list?.length ?? 0, - itemBuilder: (context, index) { - final item = list![index]; - return CheckboxListTile( - title: Text(item), - value: checkboxStates[item], - onChanged: (bool? newValue) { - checkboxStates[item] = newValue ?? false; - (context as Element).markNeedsBuild(); - }, - ); - }, + child: Container( + padding: const EdgeInsets.all(10), + color: Colors.white, + child: ListView.builder( + itemCount: list?.length ?? 0, + itemBuilder: (context, index) { + final item = list![index]; + return CheckboxListTile( + dense: true, + title: Text(item), + value: checkboxStates[item], + onChanged: (bool? newValue) { + checkboxStates[item] = newValue ?? false; + (context as Element).markNeedsBuild(); + }, + ); + }, + ), ), ), ), 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 82212103..cdfef350 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 @@ -19,8 +19,9 @@ class UserTableBloc extends Bloc { on(_filterUsersByRole); on(_filterUsersByJobTitle); on(_filterUsersByCreated); - on(_filterUserActevate); + on(_filterUserStatus); on(_deleteUser); + on(_filterClear); } int itemsPerPage = 10; int currentPage = 1; @@ -31,7 +32,7 @@ class UserTableBloc extends Bloc { List roleTypes = []; List jobTitle = []; List createdBy = []; - List deActivate = []; + List status = ['active', 'invited', 'disabled']; Future _getUsers(GetUsers event, Emitter emit) async { emit(UsersLoadingState()); @@ -39,8 +40,14 @@ class UserTableBloc extends Bloc { roleTypes.clear(); jobTitle.clear(); createdBy.clear(); - deActivate.clear(); + // deActivate.clear(); users = await UserPermissionApi().fetchUsers(); + + users.sort((a, b) { + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateB.compareTo(dateA); + }); for (var user in users) { roleTypes.add(user.roleType.toString()); } @@ -50,20 +57,14 @@ class UserTableBloc extends Bloc { for (var user in users) { createdBy.add(user.invitedBy.toString()); } - for (var user in users) { - deActivate.add(user.status.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(); - - users.sort((a, b) { - final dateA = _parseDateTime(a.createdDate); - final dateB = _parseDateTime(b.createdDate); - return dateB.compareTo(dateA); - }); - initialUsers = List.from(users); + // deActivate = deActivate.toSet().toList(); _handlePageChange(ChangePage(1), emit); emit(UsersLoadedState(users: users)); } catch (e) { @@ -127,7 +128,7 @@ class UserTableBloc extends Bloc { if (currentSortOrder == "Asc") { emit(UsersLoadingState()); currentSortOrder = ""; - users = List.from(initialUsers); + users = List.from(users); emit(UsersLoadedState(users: users)); } else { emit(UsersLoadingState()); @@ -248,39 +249,83 @@ class UserTableBloc extends Bloc { emit(UsersLoadedState(users: paginatedUsers)); } + Set selectedRoles = {}; + Set selectedJobTitles = {}; + Set selectedCreatedBy = {}; + Set selectedStatuses = {}; + void _filterUsersByRole( FilterUsersByRoleEvent event, Emitter emit) { - emit(UsersLoadingState()); + selectedRoles = event.selectedRoles.toSet(); + final filteredUsers = initialUsers.where((user) { - return event.selectedRoles.contains(user.roleType); + if (selectedRoles.isEmpty) return true; + return selectedRoles.contains(user.roleType); }).toList(); + emit(UsersLoadedState(users: filteredUsers)); } void _filterUsersByJobTitle( FilterUsersByJobEvent event, Emitter emit) { - emit(UsersLoadingState()); - final filteredUsers = users.where((user) { - return event.selectedJob.contains(user.jobTitle); + selectedJobTitles = event.selectedJob.toSet(); + + final filteredUsers = initialUsers.where((user) { + if (selectedJobTitles.isEmpty) return true; + return selectedJobTitles.contains(user.jobTitle); }).toList(); + emit(UsersLoadedState(users: filteredUsers)); } void _filterUsersByCreated( FilterUsersByCreatedEvent event, Emitter emit) { - emit(UsersLoadingState()); + selectedCreatedBy = event.selectedCreatedBy.toSet(); + final filteredUsers = initialUsers.where((user) { - return event.selectedCreatedBy.contains(user.invitedBy); + if (selectedCreatedBy.isEmpty) return true; + return selectedCreatedBy.contains(user.invitedBy); }).toList(); + emit(UsersLoadedState(users: filteredUsers)); } - void _filterUserActevate( + void _filterUserStatus( FilterUsersByDeActevateEvent event, Emitter emit) { - emit(UsersLoadingState()); + selectedStatuses = event.selectedActivate.toSet(); + final filteredUsers = initialUsers.where((user) { - return event.selectedActivate.contains(user.status); + if (selectedStatuses.isEmpty) return true; + return selectedStatuses.contains(user.status); }).toList(); + emit(UsersLoadedState(users: filteredUsers)); } + + void _resetAllFilters(Emitter emit) { + selectedRoles.clear(); + selectedJobTitles.clear(); + selectedCreatedBy.clear(); + selectedStatuses.clear(); + emit(UsersLoadedState(users: initialUsers)); + } + + void _filterClear(FilterClearEvent event, Emitter emit) { + selectedRoles.clear(); + selectedJobTitles.clear(); + selectedCreatedBy.clear(); + selectedStatuses.clear(); + emit(UsersLoadedState(users: initialUsers)); + } } + // void _filterOptions(FilterOptionsEvent event, Emitter emit) { + // try { + // final query = event.query.toLowerCase(); + // final filteredOptions = event.fullOptions + // .where((option) => option.toLowerCase().contains(query)) + // .toList(); + // emit(FilterOptionsState(filteredOptions)); + // } catch (e) { + // emit(ErrorState(e.toString())); + // } + // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart index dbcd9a26..a81002ad 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart @@ -119,3 +119,19 @@ class FilterUsersByDeActevateEvent extends UserTableEvent { @override List get props => [selectedActivate]; } + +class FilterOptionsEvent extends UserTableEvent { + final String query; + final List fullOptions; + + FilterOptionsEvent({required this.query, required this.fullOptions}); + + @override + List get props => [query, fullOptions]; +} + +class FilterClearEvent extends UserTableEvent { + FilterClearEvent(); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart index 2a132947..2433f9f5 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart @@ -80,3 +80,12 @@ final class ChangeTapStatus extends UserTableState { @override List get props => [select]; } + +class FilterOptionsState extends UserTableState { + final List filteredOptions; + + FilterOptionsState(this.filteredOptions); + + @override + List get props => [filteredOptions]; +} 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 4f777ee3..813e950c 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 @@ -122,7 +122,7 @@ class _DynamicTableScreenState extends State ), if (index != 1 && index != 9 && - index != 7 && + index != 8 && index != 5) FittedBox( child: IconButton( 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 8d838ba2..3a9f2187 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 @@ -110,350 +110,425 @@ class UsersPage extends StatelessWidget { if (state is UsersLoadingState) { _blocRole.add(ChangePage(_blocRole.currentPage)); - return const Center(child: CircularProgressIndicator()); } else if (state is UsersLoadedState) { return Padding( padding: const EdgeInsets.all(20), - child: ListView( - shrinkWrap: true, - children: [ - Row( - children: [ - Container( - decoration: containerDecoration.copyWith( - borderRadius: const BorderRadius.all( - Radius.circular(20), - ), - ), - width: screenSize.width * 0.4, - child: TextFormField( - controller: searchController, - onChanged: (value) { - context.read().add(SearchUsers(value)); - }, - style: const TextStyle(color: Colors.black), - decoration: textBoxDecoration(radios: 15)!.copyWith( - fillColor: ColorsManager.whiteColors, - errorStyle: const TextStyle(height: 0), - hintStyle: context.textTheme.titleSmall?.copyWith( - color: Colors.grey, - fontSize: 12, - ), - hintText: 'Search', - suffixIcon: SizedBox( - child: SvgPicture.asset( - Assets.searchIconUser, - fit: BoxFit.none, - ), - ), - ), - ), - ), - const SizedBox(width: 20), - InkWell( - onTap: () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return const AddNewUserDialog(); - }, - ).then((v) { - if (v != null) { - _blocRole.add(const GetUsers()); - } - }); - }, - child: Container( - decoration: containerWhiteDecoration, - width: screenSize.width * 0.18, - height: 50, - child: const Center( - child: Text( - 'Add New User', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w700, - color: ColorsManager.blueColor, - ), - ), - ), - ), - ), - ], - ), - const SizedBox(height: 25), - DynamicTableScreen( - onFilter: (columnIndex) { - if (columnIndex == 0) { - showNameMenu( - context: context, - isSelected: _blocRole.currentSortOrder, - aToZTap: () { - context - .read() - .add(const SortUsersByNameAsc()); - }, - zToaTap: () { - context - .read() - .add(const SortUsersByNameDesc()); - }, - ); - } - if (columnIndex == 2) { - final Map checkboxStates = { - for (var item in _blocRole.jobTitle) - item: false, // Initialize with false - }; - - showPopUpFilterMenu( - list: _blocRole.jobTitle, - context: context, - checkboxStates: checkboxStates, - onOkPressed: () { - final selectedItems = checkboxStates.entries - .where((entry) => entry.value) - .map((entry) => entry.key) - .toList(); - Navigator.of(context).pop(); - _blocRole.add(FilterUsersByJobEvent(selectedItems)); - }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); - }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); - }, - ); - } - if (columnIndex == 3) { - final Map checkboxStates = { - for (var item in _blocRole.roleTypes) - item: false, // Initialize with false - }; - - showPopUpFilterMenu( - list: _blocRole.roleTypes, - context: context, - checkboxStates: checkboxStates, - onOkPressed: () { - final selectedItems = checkboxStates.entries - .where((entry) => entry.value) - .map((entry) => entry.key) - .toList(); - Navigator.of(context).pop(); - _blocRole.add(FilterUsersByRoleEvent(selectedItems)); - }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); - }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); - }, - ); - } - if (columnIndex == 4) { - showDateFilterMenu( - context: context, - isSelected: _blocRole.currentSortOrderDate, - aToZTap: () { - context - .read() - .add(const DateNewestToOldestEvent()); - }, - zToaTap: () { - context - .read() - .add(const DateOldestToNewestEvent()); - }, - ); - } - if (columnIndex == 6) { - final Map checkboxStates = { - for (var item in _blocRole.createdBy) - item: false, // Initialize with false - }; - - showPopUpFilterMenu( - list: _blocRole.createdBy, - context: context, - checkboxStates: checkboxStates, - onOkPressed: () { - final selectedItems = checkboxStates.entries - .where((entry) => entry.value) - .map((entry) => entry.key) - .toList(); - Navigator.of(context).pop(); - _blocRole - .add(FilterUsersByCreatedEvent(selectedItems)); - }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); - }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); - }, - ); - } - if (columnIndex == 8) { - showDeActivateFilterMenu( - context: context, - isSelected: _blocRole.currentSortOrderDate, - aToZTap: () { - context - .read() - .add(const DateNewestToOldestEvent()); - }, - zToaTap: () { - context - .read() - .add(const DateOldestToNewestEvent()); - }, - ); - } - }, - titles: const [ - "Full Name", - "Email Address", - "Job Title", - "Role", - "Creation Date", - "Creation Time", - "Created By", - "Status", - "De/Activate", - "Action" - ], - rows: state.users.map((user) { - return [ - Text('${user.firstName} ${user.lastName}'), - Text(user.email ?? ''), - Text(user.jobTitle ?? ''), - Text(user.roleType ?? ''), - Text(user.createdDate ?? ''), - Text(user.createdTime ?? ''), - Text(user.invitedBy), - changeIconStatus( - status: - user.isEnabled == false ? 'disabled' : user.status, - userId: user.uuid, - onTap: user.status != "invited" - ? () { - final newStatus = user.status == 'active' - ? 'disabled' - : user.status == 'disabled' - ? 'invited' - : 'active'; - context.read().add( - ChangeUserStatus( - userId: user.uuid, - newStatus: user.isEnabled == false - ? 'disabled' - : user.status)); - } - : null, - ), - status( - status: - user.isEnabled == false ? 'disabled' : user.status, - ), - Row( - children: [ - // actionButton( - // title: "Activity Log", - // onTap: () {}, - // ), - actionButton( - title: "Edit", - onTap: () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return EditUserDialog(userId: user.uuid); - }, - ).then((v) { - if (v != null) { - if (v != null) { - _blocRole.add(const GetUsers()); - } - } - }); - }, - ), - actionButton( - title: "Delete", - onTap: () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return DeleteUserDialog( - onTapDelete: () { - _blocRole.add( - DeleteUserEvent(user.uuid, context)); - }, - ); - }, - ).then((v) { - if (v != null) { - if (v != null) { - _blocRole.add(const GetUsers()); - } - } - }); - }, - ), - ], - ), - ]; - }).toList(), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, + child: Align( + alignment: Alignment.topCenter, + child: ListView( + shrinkWrap: true, + children: [ + Row( children: [ Container( - width: 500, - child: NumberPagination( - buttonRadius: 10, - selectedButtonColor: ColorsManager.secondaryColor, - buttonUnSelectedBorderColor: ColorsManager.grayBorder, - lastPageIcon: - const Icon(Icons.keyboard_double_arrow_right), - firstPageIcon: - const Icon(Icons.keyboard_double_arrow_left), - totalPages: - (_blocRole.users.length / _blocRole.itemsPerPage) - .ceil(), - currentPage: _blocRole.currentPage, - onPageChanged: (int pageNumber) { - _blocRole.currentPage = pageNumber; + decoration: containerDecoration.copyWith( + borderRadius: const BorderRadius.all( + Radius.circular(20), + ), + ), + width: screenSize.width * 0.4, + child: TextFormField( + controller: searchController, + onChanged: (value) { context .read() - .add(ChangePage(pageNumber)); + .add(SearchUsers(value)); }, + style: const TextStyle(color: Colors.black), + decoration: textBoxDecoration(radios: 15)!.copyWith( + fillColor: ColorsManager.whiteColors, + errorStyle: const TextStyle(height: 0), + hintStyle: context.textTheme.titleSmall?.copyWith( + color: Colors.grey, + fontSize: 12, + ), + hintText: 'Search', + suffixIcon: SizedBox( + child: SvgPicture.asset( + Assets.searchIconUser, + fit: BoxFit.none, + ), + ), + ), + ), + ), + const SizedBox(width: 20), + InkWell( + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return const AddNewUserDialog(); + }, + ).then((v) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + }); + }, + child: Container( + decoration: containerWhiteDecoration, + width: screenSize.width * 0.18, + height: 50, + child: const Center( + child: Text( + 'Add New User', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: ColorsManager.blueColor, + ), + ), + ), ), ), ], ), - ), - ], + const SizedBox(height: 25), + DynamicTableScreen( + onFilter: (columnIndex) { + _blocRole.add(FilterClearEvent()); + + if (columnIndex == 0) { + showNameMenu( + context: context, + isSelected: _blocRole.currentSortOrder, + aToZTap: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + zToaTap: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 2) { + final Map checkboxStates = { + for (var item in _blocRole.jobTitle) + item: false, // Initialize with false + }; + final RenderBox overlay = Overlay.of(context) + .context + .findRenderObject() as RenderBox; + showPopUpFilterMenu( + position: RelativeRect.fromLTRB( + overlay.size.width / 4, + 240, + overlay.size.width / 4, + 0, + ), + list: _blocRole.jobTitle, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole.add(FilterUsersByJobEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 3) { + final Map checkboxStates = { + for (var item in _blocRole.roleTypes) + item: _blocRole.selectedRoles.contains(item), + }; + final RenderBox overlay = Overlay.of(context) + .context + .findRenderObject() as RenderBox; + showPopUpFilterMenu( + position: RelativeRect.fromLTRB( + overlay.size.width / 4, + 240, + overlay.size.width / 4, + 0, + ), + list: _blocRole.roleTypes, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + context + .read() + .add(FilterUsersByRoleEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 4) { + showDateFilterMenu( + context: context, + isSelected: _blocRole.currentSortOrderDate, + aToZTap: () { + context + .read() + .add(const DateNewestToOldestEvent()); + }, + zToaTap: () { + context + .read() + .add(const DateOldestToNewestEvent()); + }, + ); + } + if (columnIndex == 6) { + final Map checkboxStates = { + for (var item in _blocRole.createdBy) + item: _blocRole.selectedCreatedBy.contains(item), + }; + final RenderBox overlay = Overlay.of(context) + .context + .findRenderObject() as RenderBox; + showPopUpFilterMenu( + position: RelativeRect.fromLTRB( + overlay.size.width / 1, + 240, + overlay.size.width / 4, + 0, + ), + list: _blocRole.createdBy, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole + .add(FilterUsersByCreatedEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + + if (columnIndex == 7) { + final Map checkboxStates = { + for (var item in _blocRole.status) + item: _blocRole.selectedCreatedBy.contains(item), + }; + final RenderBox overlay = Overlay.of(context) + .context + .findRenderObject() as RenderBox; + showPopUpFilterMenu( + position: RelativeRect.fromLTRB( + overlay.size.width / 0, + 240, + overlay.size.width / 4, + 0, + ), + list: _blocRole.status, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole + .add(FilterUsersByCreatedEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 8) { + showDeActivateFilterMenu( + context: context, + isSelected: _blocRole.currentSortOrderDate, + aToZTap: () { + context + .read() + .add(const DateNewestToOldestEvent()); + }, + zToaTap: () { + context + .read() + .add(const DateOldestToNewestEvent()); + }, + ); + } + }, + titles: const [ + "Full Name", + "Email Address", + "Job Title", + "Role", + "Creation Date", + "Creation Time", + "Created By", + "Status", + "De/Activate", + "Action" + ], + rows: state.users.map((user) { + return [ + Text('${user.firstName} ${user.lastName}'), + Text(user.email ?? ''), + Text(user.jobTitle ?? ''), + Text(user.roleType ?? ''), + Text(user.createdDate ?? ''), + Text(user.createdTime ?? ''), + Text(user.invitedBy), + status( + status: user.isEnabled == false + ? 'disabled' + : user.status, + ), + changeIconStatus( + status: user.isEnabled == false + ? 'disabled' + : user.status, + userId: user.uuid, + onTap: user.status != "invited" + ? () { + final newStatus = user.status == 'active' + ? 'disabled' + : user.status == 'disabled' + ? 'invited' + : 'active'; + context.read().add( + ChangeUserStatus( + userId: user.uuid, + newStatus: user.isEnabled == false + ? 'disabled' + : user.status)); + } + : null, + ), + Row( + children: [ + // actionButton( + // title: "Activity Log", + // onTap: () {}, + // ), + actionButton( + title: "Edit", + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return EditUserDialog(userId: user.uuid); + }, + ).then((v) { + if (v != null) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + } + }); + }, + ), + actionButton( + title: "Delete", + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return DeleteUserDialog( + onTapDelete: () { + _blocRole.add(DeleteUserEvent( + user.uuid, context)); + }, + ); + }, + ).then((v) { + if (v != null) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + } + }); + }, + ), + ], + ), + ]; + }).toList(), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + width: 500, + child: NumberPagination( + buttonRadius: 10, + selectedButtonColor: ColorsManager.secondaryColor, + buttonUnSelectedBorderColor: + ColorsManager.grayBorder, + lastPageIcon: + const Icon(Icons.keyboard_double_arrow_right), + firstPageIcon: + const Icon(Icons.keyboard_double_arrow_left), + totalPages: (_blocRole.users.length / + _blocRole.itemsPerPage) + .ceil(), + currentPage: _blocRole.currentPage, + onPageChanged: (int pageNumber) { + _blocRole.currentPage = pageNumber; + context + .read() + .add(ChangePage(pageNumber)); + }, + ), + ), + ], + ), + ), + ], + ), ), ); } else if (state is ErrorState) { From d721f6f774c31575c212e60fce7b30999a6454ea Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 6 Jan 2025 14:42:32 +0300 Subject: [PATCH 040/106] table size --- .../users_table/bloc/user_table_bloc.dart | 2 +- .../users_page/users_table/view/user_table.dart | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) 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 cdfef350..174726f6 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 @@ -23,7 +23,7 @@ class UserTableBloc extends Bloc { on(_deleteUser); on(_filterClear); } - int itemsPerPage = 10; + int itemsPerPage = 20; int currentPage = 1; List users = []; List initialUsers = []; 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 813e950c..6662a9e2 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 @@ -29,7 +29,9 @@ class _DynamicTableScreenState extends State @override void initState() { super.initState(); - columnWidths = List.filled(widget.titles.length, 150.0); + setState(() { + columnWidths = List.filled(widget.titles.length, 150.0); + }); WidgetsBinding.instance.addObserver(this); } @@ -212,6 +214,19 @@ class _DynamicTableScreenState extends State 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: [ From 8fd1259f9c731659b311bafca43dcbfd75a8ae18 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 6 Jan 2025 15:15:01 +0300 Subject: [PATCH 041/106] remove warnings --- .../bloc/roles_permission_state.dart | 6 ++- .../model/edit_user_model.dart | 4 +- .../add_user_dialog/bloc/users_bloc.dart | 19 ++++------ .../add_user_dialog/bloc/users_event.dart | 38 +++++++++---------- .../add_user_dialog/bloc/users_status.dart | 13 ++++--- .../model/permission_option_model.dart | 4 +- .../add_user_dialog/view/basics_view.dart | 11 +++--- .../view/popup_menu_filter.dart | 3 -- .../view/roles_and_permission.dart | 2 - .../view/spaces_access_view.dart | 6 +-- .../users_table/bloc/user_table_bloc.dart | 19 ++-------- .../users_table/bloc/user_table_state.dart | 10 ++--- .../users_table/view/users_page.dart | 14 +++---- lib/services/user_permission.dart | 2 +- 14 files changed, 67 insertions(+), 84 deletions(-) diff --git a/lib/pages/roles_and_permission/bloc/roles_permission_state.dart b/lib/pages/roles_and_permission/bloc/roles_permission_state.dart index 55c2a8cb..7979f7db 100644 --- a/lib/pages/roles_and_permission/bloc/roles_permission_state.dart +++ b/lib/pages/roles_and_permission/bloc/roles_permission_state.dart @@ -13,6 +13,7 @@ final class RolesLoadingState extends RolesPermissionState { @override List get props => []; } + final class UsersLoadingState extends RolesPermissionState { @override List get props => []; @@ -22,6 +23,7 @@ final class RolesLoadedState extends RolesPermissionState { @override List get props => []; } + final class UsersLoadedState extends RolesPermissionState { @override List get props => []; @@ -68,9 +70,9 @@ final class SosAutomationReportErrorState extends RolesPermissionState { } final class ChangeTapStatus extends RolesPermissionState { - bool select = true; + final bool select; - ChangeTapStatus({required this.select}); + const ChangeTapStatus({required this.select}); @override List get props => [select]; diff --git a/lib/pages/roles_and_permission/model/edit_user_model.dart b/lib/pages/roles_and_permission/model/edit_user_model.dart index 17fba1a4..4075ac12 100644 --- a/lib/pages/roles_and_permission/model/edit_user_model.dart +++ b/lib/pages/roles_and_permission/model/edit_user_model.dart @@ -149,8 +149,8 @@ class EditUserModel { final String createdTime; // e.g. "8:41:43 AM" final String status; // e.g. "invited" final String invitedBy; // e.g. "SUPER_ADMIN" - final String phoneNumber; // can be empty - final String jobTitle; // can be empty + final String? phoneNumber; // can be empty + final String? jobTitle; // can be empty final String roleType; // e.g. "ADMIN" final List spaces; 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 c06c0969..563fca93 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 @@ -29,7 +29,7 @@ class UsersBloc extends Bloc { on(getUserById); on(_onToggleNodeExpansion); on(_onToggleNodeCheck); - on(_editInvitUser); + on(_editInviteUser); } void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { @@ -88,7 +88,6 @@ class UsersBloc extends Bloc { await CommunitySpaceManagementApi().fetchCommunities(); updatedCommunities = await Future.wait( communities.map((community) async { - print(community.uuid); List spaces = await _fetchSpacesForCommunity(community.uuid); spacesNodes = _buildTreeNodes(spaces); @@ -102,7 +101,7 @@ class UsersBloc extends Bloc { ); }).toList(), ); - emit(SpacesLoadedState()); + emit(const SpacesLoadedState()); return updatedCommunities; } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); @@ -112,7 +111,7 @@ class UsersBloc extends Bloc { List _buildTreeNodes(List spaces) { return spaces.map((space) { List childNodes = - space.children != null ? _buildTreeNodes(space.children) : []; + space.children.isNotEmpty ? _buildTreeNodes(space.children) : []; return TreeNode( uuid: space.uuid!, title: space.name, @@ -212,7 +211,7 @@ class UsersBloc extends Bloc { _sendInvitUser(SendInviteUsers event, Emitter emit) async { try { emit(UsersLoadingState()); - List selectedIds = getSelectedIds(updatedCommunities) ?? []; + List selectedIds = getSelectedIds(updatedCommunities); bool res = await UserPermissionApi().sendInviteUser( email: emailController.text, firstName: firstNameController.text, @@ -249,10 +248,10 @@ class UsersBloc extends Bloc { } } - _editInvitUser(EditInviteUsers event, Emitter emit) async { + _editInviteUser(EditInviteUsers event, Emitter emit) async { try { emit(UsersLoadingState()); - List selectedIds = getSelectedIds(updatedCommunities) ?? []; + List selectedIds = getSelectedIds(updatedCommunities); bool res = await UserPermissionApi().editInviteUser( userId: event.userId, firstName: firstNameController.text, @@ -383,8 +382,6 @@ class UsersBloc extends Bloc { // Print all IDs and mark nodes in updatedCommunities debugPrint('Printing and marking nodes in updatedCommunities:'); _printAndMarkNodes(updatedCommunities, uuidsToMark); - } else { - print('updatedCommunities is empty!'); } final roleId = roles .firstWhere((element) => @@ -397,9 +394,7 @@ class UsersBloc extends Bloc { emit(ChangeStatusSteps()); } else {} } else {} - } catch (e) { - print("Failed to fetch user data: $e"); - } + } catch (_) {} } void _printAndMarkNodes(List nodes, List uuidsToMark, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart index c193bbf3..2e82168c 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart @@ -63,31 +63,31 @@ class GetBatchStatus extends UsersEvent { //isEditUser:widget.userId!=''? false:true class CheckStepStatus extends UsersEvent { final int? steps; - bool? isEditUser = false; - CheckStepStatus({this.steps, required this.isEditUser}); + final bool? isEditUser; + const CheckStepStatus({this.steps, required this.isEditUser}); @override List get props => [steps]; } class SearchAnode extends UsersEvent { - List? nodes; - String? searchTerm; - SearchAnode({this.nodes, this.searchTerm}); + final List? nodes; + final String? searchTerm; + const SearchAnode({this.nodes, this.searchTerm}); @override List get props => [nodes, searchTerm]; } class SearchPermission extends UsersEvent { - List? nodes; - String? searchTerm; - SearchPermission({this.nodes, this.searchTerm}); + final List? nodes; + final String? searchTerm; + const SearchPermission({this.nodes, this.searchTerm}); @override List get props => [nodes, searchTerm]; } -class SelecteId extends UsersEvent { - List? nodes; - SelecteId({ +class SelectedId extends UsersEvent { + final List? nodes; + const SelectedId({ this.nodes, }); @override @@ -95,7 +95,7 @@ class SelecteId extends UsersEvent { } class ValidateBasicsStep extends UsersEvent { - ValidateBasicsStep(); + const ValidateBasicsStep(); @override List get props => []; } @@ -116,7 +116,7 @@ class GetUserByIdEvent extends UsersEvent { class ToggleNodeExpansion extends UsersEvent { final TreeNode node; - ToggleNodeExpansion({required this.node}); + const ToggleNodeExpansion({required this.node}); @override List get props => [node]; @@ -125,14 +125,15 @@ class ToggleNodeExpansion extends UsersEvent { class UpdateNodeCheckStatus extends UsersEvent { final TreeNode node; - UpdateNodeCheckStatus({required this.node}); + const UpdateNodeCheckStatus({required this.node}); @override List get props => [node]; } + class ToggleNodeHighlightEvent extends UsersEvent { final TreeNode node; - ToggleNodeHighlightEvent(this.node); + const ToggleNodeHighlightEvent(this.node); @override List get props => [node]; } @@ -155,14 +156,15 @@ class ClearSelectionsEvent extends UsersEvent { class ToggleNodeCheckEvent extends UsersEvent { final TreeNode node; - ToggleNodeCheckEvent(this.node); + const ToggleNodeCheckEvent(this.node); @override List get props => []; } + class ToggleNodeCheck extends UsersEvent { final TreeNode node; - ToggleNodeCheck(this.node); + const ToggleNodeCheck(this.node); @override List get props => []; } @@ -172,5 +174,3 @@ class EditUserEvent extends UsersEvent { @override List get props => []; } - - diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart index 646dccfd..4361c37f 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart @@ -1,5 +1,4 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; sealed class UsersState extends Equatable { @@ -32,7 +31,7 @@ final class SaveState extends UsersState { } final class SpacesLoadedState extends UsersState { - SpacesLoadedState(); + const SpacesLoadedState(); @override List get props => []; } @@ -58,9 +57,9 @@ final class RolesErrorState extends UsersState { /// automation reports final class ChangeTapStatus extends UsersState { - bool select = true; + final bool select; - ChangeTapStatus({required this.select}); + const ChangeTapStatus({required this.select}); @override List get props => [select]; @@ -77,14 +76,16 @@ class BasicsStepInvalidState extends UsersState { @override List get props => []; } + final class ValidateBasics extends UsersState { @override List get props => []; } + class UsersLoadedState extends UsersState { final List updatedCommunities; - UsersLoadedState({required this.updatedCommunities}); - @override + const UsersLoadedState({required this.updatedCommunities}); + @override List get props => []; } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart index c476ebb4..4141ccdd 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart @@ -16,7 +16,9 @@ class PermissionOption { factory PermissionOption.fromJson(Map json) { return PermissionOption( id: json['id'] ?? '', - title: json['title'].toString().toLowerCase().replaceAll("_", " ") ?? '', + title: json['title'] != null + ? json['title'].toString().toLowerCase().replaceAll("_", " ") + : '', isChecked: json['isChecked'] ?? false, isHighlighted: json['isHighlighted'] ?? false, subOptions: (json['subOptions'] as List?) 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 bbca9aaa..c5025fc3 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -11,8 +11,8 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class BasicsView extends StatelessWidget { - String? userId = ''; - BasicsView({super.key, this.userId}); + final String? userId; + const BasicsView({super.key, this.userId = ''}); @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { @@ -185,11 +185,12 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( - enabled: userId!=''? false:true, + enabled: userId != '' ? false : true, onChanged: (value) { Future.delayed(const Duration(milliseconds: 200), () { - _blocRole.add(CheckStepStatus(isEditUser:userId!=''? false:true)); - _blocRole.add( ValidateBasicsStep()); + _blocRole.add(CheckStepStatus( + isEditUser: userId != '' ? false : true)); + _blocRole.add(ValidateBasicsStep()); }); }, controller: _blocRole.emailController, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart index fbadd07e..7c3c1ba5 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 @@ -1,8 +1,6 @@ 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/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; Future showPopUpFilterMenu({ @@ -12,7 +10,6 @@ Future showPopUpFilterMenu({ Function()? cancelButton, required Map checkboxStates, required RelativeRect position, - // Function(String)? onTextFieldChanged, Function()? onOkPressed, List? list, }) async { diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart index 0bc16a94..f4b91747 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart @@ -15,8 +15,6 @@ class RolesAndPermission extends StatelessWidget { const RolesAndPermission({super.key}); @override Widget build(BuildContext context) { - final size = MediaQuery.of(context).size; - return BlocBuilder(builder: (context, state) { final _blocRole = BlocProvider.of(context); return Container( diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart index dc2eefc3..f4ccfafc 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart @@ -11,12 +11,10 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class SpacesAccessView extends StatelessWidget { - String? userId = ''; - SpacesAccessView({super.key, this.userId}); + final String? userId; + const SpacesAccessView({super.key, this.userId = ''}); @override Widget build(BuildContext context) { - final size = MediaQuery.of(context).size; - return BlocBuilder(builder: (context, state) { final _blocRole = BlocProvider.of(context); return 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 174726f6..aa014bd5 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 @@ -210,7 +210,7 @@ class UserTableBloc extends Bloc { final query = event.query.toLowerCase(); final filteredUsers = initialUsers.where((user) { final fullName = "${user.firstName} ${user.lastName}".toLowerCase(); - final email = user.email?.toLowerCase() ?? ""; + final email = user.email.toLowerCase() ; return fullName.contains(query) || email.contains(query); }).toList(); emit(UsersLoadedState(users: filteredUsers)); @@ -224,7 +224,7 @@ class UserTableBloc extends Bloc { final startIndex = (pageNumber - 1) * itemsPerPage; final endIndex = startIndex + itemsPerPage; if (startIndex >= users.length) { - emit(UsersLoadedState(users: const [])); + emit(const UsersLoadedState(users: [])); return; } final paginatedUsers = users.sublist( @@ -235,11 +235,11 @@ class UserTableBloc extends Bloc { } void _handlePageChange(ChangePage event, Emitter emit) { - final itemsPerPage = 10; + const itemsPerPage = 10; final startIndex = (event.pageNumber - 1) * itemsPerPage; final endIndex = startIndex + itemsPerPage; if (startIndex >= users.length) { - emit(UsersLoadedState(users: [])); + emit(const UsersLoadedState(users: [])); return; } final paginatedUsers = users.sublist( @@ -318,14 +318,3 @@ class UserTableBloc extends Bloc { emit(UsersLoadedState(users: initialUsers)); } } - // void _filterOptions(FilterOptionsEvent event, Emitter emit) { - // try { - // final query = event.query.toLowerCase(); - // final filteredOptions = event.fullOptions - // .where((option) => option.toLowerCase().contains(query)) - // .toList(); - // emit(FilterOptionsState(filteredOptions)); - // } catch (e) { - // emit(ErrorState(e.toString())); - // } - // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart index 2433f9f5..62037315 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart @@ -26,8 +26,8 @@ final class RolesLoadedState extends UserTableState { } final class UsersLoadedState extends UserTableState { - List users = []; - UsersLoadedState({required this.users}); + final List users; + const UsersLoadedState({required this.users}); @override List get props => [users]; } @@ -73,9 +73,9 @@ final class SosAutomationReportErrorState extends UserTableState { } final class ChangeTapStatus extends UserTableState { - bool select = true; + final bool select; - ChangeTapStatus({required this.select}); + const ChangeTapStatus({required this.select}); @override List get props => [select]; @@ -84,7 +84,7 @@ final class ChangeTapStatus extends UserTableState { class FilterOptionsState extends UserTableState { final List filteredOptions; - FilterOptionsState(this.filteredOptions); + const FilterOptionsState(this.filteredOptions); @override List get props => [filteredOptions]; 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 3a9f2187..1c93d44f 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 @@ -412,7 +412,7 @@ class UsersPage extends StatelessWidget { rows: state.users.map((user) { return [ Text('${user.firstName} ${user.lastName}'), - Text(user.email ?? ''), + Text(user.email ), Text(user.jobTitle ?? ''), Text(user.roleType ?? ''), Text(user.createdDate ?? ''), @@ -430,11 +430,11 @@ class UsersPage extends StatelessWidget { userId: user.uuid, onTap: user.status != "invited" ? () { - final newStatus = user.status == 'active' - ? 'disabled' - : user.status == 'disabled' - ? 'invited' - : 'active'; + // final newStatus = user.status == 'active' + // ? 'disabled' + // : user.status == 'disabled' + // ? 'invited' + // : 'active'; context.read().add( ChangeUserStatus( userId: user.uuid, @@ -501,7 +501,7 @@ class UsersPage extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - Container( + SizedBox( width: 500, child: NumberPagination( buttonRadius: 10, diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index f62437ac..ab05523f 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -131,7 +131,7 @@ class UserPermissionApi { } } - Future fetchUserById(userUuid) async { + Future fetchUserById(userUuid) async { final response = await _httpService.get( path: ApiEndpoints.getUserById.replaceAll("{userUuid}", userUuid), showServerMessage: true, From 6ee650e9f86bd44e0b64071a4aa5228ce2de442a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 6 Jan 2025 16:38:44 +0400 Subject: [PATCH 042/106] added validation --- .../views/assign_tag_models_dialog.dart | 323 +++++++++++------- .../models/space_template_model.dart | 26 +- .../space_model/view/space_model_page.dart | 36 +- .../dialog/create_space_model_dialog.dart | 4 +- .../widgets/space_model_card_widget.dart | 15 +- .../views/add_device_type_model_widget.dart | 5 +- 6 files changed, 278 insertions(+), 131 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart index 885f483b..b04e1c35 100644 --- a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart @@ -11,15 +11,17 @@ class AssignTagModelsDialog extends StatefulWidget { final List? initialTags; final ValueChanged>? onTagsAssigned; final List addedProducts; + final List? allTags; - const AssignTagModelsDialog({ - Key? key, - required this.products, - required this.subspaces, - required this.addedProducts, - this.initialTags, - this.onTagsAssigned, - }) : super(key: key); + const AssignTagModelsDialog( + {Key? key, + required this.products, + required this.subspaces, + required this.addedProducts, + this.initialTags, + this.onTagsAssigned, + this.allTags}) + : super(key: key); @override AssignTagModelsDialogState createState() => AssignTagModelsDialogState(); @@ -29,12 +31,15 @@ class AssignTagModelsDialogState extends State { late List tags; late List selectedProducts; late List locations; + late List otherTags; + late List controllers; + Set usedTags = {}; + String? errorMessage; + bool isSaveEnabled = true; @override void initState() { super.initState(); - print(widget.addedProducts); - print(widget.subspaces); // Initialize tags from widget.initialTags or create new ones if it's empty tags = widget.initialTags?.isNotEmpty == true @@ -58,6 +63,39 @@ class AssignTagModelsDialogState extends State { ? widget.subspaces!.map((subspace) => subspace.subspaceName).toList() : []; locations.add("None"); + + otherTags = widget.allTags != null ? widget.allTags! : []; + + controllers = List.generate( + tags.length, + (index) => TextEditingController(text: tags[index].tag), + ); + + for (final tag in tags) { + if (tag.tag != null && tag.tag!.isNotEmpty) { + usedTags.add(tag.tag!); + } + } + + _validateTags(); + } + + void _validateTags() { + // Disable save if any tag is empty + final hasEmptyTag = + tags.any((tag) => tag.tag == null || tag.tag!.trim().isEmpty); + setState(() { + isSaveEnabled = !hasEmptyTag; + }); + } + + @override + void dispose() { + // Dispose of controllers + for (final controller in controllers) { + controller.dispose(); + } + super.dispose(); } @override @@ -66,123 +104,160 @@ class AssignTagModelsDialogState extends State { title: const Text('Assign Tags'), backgroundColor: ColorsManager.whiteColors, content: SingleChildScrollView( - child: Container( - width: MediaQuery.of(context).size.width * 0.4, - decoration: BoxDecoration( - border: Border.all(color: ColorsManager.dataHeaderGrey, width: 1), - borderRadius: BorderRadius.circular(20), - ), - child: Theme( - data: Theme.of(context).copyWith( - dataTableTheme: DataTableThemeData( - headingRowColor: - MaterialStateProperty.all(ColorsManager.dataHeaderGrey), - headingTextStyle: const TextStyle( - color: ColorsManager.blackColor, - fontWeight: FontWeight.bold, - ), - ), - ), - child: DataTable( - border: TableBorder.all( - color: ColorsManager.dataHeaderGrey, - width: 1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: MediaQuery.of(context).size.width * 0.4, + decoration: BoxDecoration( + border: + Border.all(color: ColorsManager.dataHeaderGrey, width: 1), borderRadius: BorderRadius.circular(20), ), - columns: const [ - DataColumn(label: Text('#')), - DataColumn(label: Text('Device')), - DataColumn(label: Text('Tag')), - DataColumn(label: Text('Location')), - ], - rows: tags.asMap().entries.map((entry) { - final index = entry.key + 1; - final tag = entry.value; + child: DataTable( + border: TableBorder.all( + color: ColorsManager.dataHeaderGrey, + width: 1, + borderRadius: BorderRadius.circular(20), + ), + columns: const [ + DataColumn(label: Text('#')), + DataColumn(label: Text('Device')), + DataColumn(label: Text('Tag')), + DataColumn(label: Text('Location')), + ], + rows: List.generate(tags.length, (index) { + final tag = tags[index]; + final controller = controllers[index]; - return DataRow( - cells: [ - DataCell( - Center( - child: Text(index.toString()), - ), - ), - DataCell( - Center( - child: Text(tag.product?.catName ?? 'Unknown'), - ), - ), - DataCell( - Center( - child: DropdownButton( - value: tag.tag!.isNotEmpty ? tag.tag : null, - onChanged: (value) { - setState(() { - tag.tag = value ?? ''; // Update tag value - }); - }, - items: [ - const DropdownMenuItem( - value: null, - child: Text('None'), + return DataRow( + cells: [ + DataCell(Center(child: Text(index.toString()))), + DataCell( + Center(child: Text(tag.product?.name ?? 'Unknown'))), + DataCell( + Row( + children: [ + Expanded( + child: TextFormField( + controller: controller, + decoration: const InputDecoration( + hintText: 'Enter Tag', + border: InputBorder.none, + hintStyle: TextStyle( + color: Colors.grey, + fontSize: 14, + ), + ), + style: const TextStyle( + color: Colors.black, + fontSize: 14, + ), + onChanged: (value) { + setState(() { + tag.tag = value.trim(); + _validateTags(); + }); + }, + ), + ), + PopupMenuButton( + icon: const Icon(Icons.arrow_drop_down, + color: Colors.black), + onSelected: (value) { + setState(() { + if (tag.tag != null && tag.tag!.isNotEmpty) { + usedTags.remove(tag.tag!); + } + controller.text = value; + tag.tag = value; + + if (tag.tag != null && tag.tag!.isNotEmpty) { + usedTags.add(tag.tag!); + } + _validateTags(); // Validate after selection + }); + }, + color: Colors.white, + itemBuilder: (context) { + return widget.allTags! + .where((tagValue) => + !usedTags.contains(tagValue)) + .map((tagValue) { + return PopupMenuItem( + value: tagValue, + child: Text( + tagValue, + style: const TextStyle( + color: Color(0xFF5D5D5D), + fontSize: 14, + ), + ), + ); + }).toList(); + }, ), - ...List.generate(10, (index) { - final tagName = 'Tag ${index + 1}'; - return DropdownMenuItem( - value: tagName, - child: Text(tagName), - ); - }), ], ), ), - ), - DataCell( - Center( - child: DropdownButtonHideUnderline( - child: DropdownButton( - alignment: AlignmentDirectional.centerEnd, - value: locations.contains(tag.location) - ? tag.location - : null, // Validate value - onChanged: (value) { - setState(() { - tag.location = - value ?? 'None'; // Update location - }); - }, - dropdownColor: Colors.white, - icon: const Icon( - Icons.arrow_drop_down, - color: Colors.black, - ), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.black, - ), - isExpanded: true, - items: locations - .map((location) => DropdownMenuItem( - value: location, - child: Text( - location, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: Colors.grey, + DataCell( + Center( + child: DropdownButtonHideUnderline( + child: DropdownButton( + alignment: AlignmentDirectional.center, + value: locations.contains(tag.location) + ? tag.location + : null, + onChanged: (value) { + setState(() { + tag.location = value ?? 'None'; + }); + }, + dropdownColor: ColorsManager.whiteColors, + icon: const Icon(Icons.arrow_drop_down, + color: Colors.black), + style: const TextStyle( + fontSize: 10, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + isExpanded: true, + items: locations + .map((location) => DropdownMenuItem( + value: location, + child: Text( + location, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Color(0xFF5D5D5D), + ), ), - ), - )) - .toList(), + )) + .toList(), + ), ), ), ), - ), - ], - ); - }).toList(), + ], + ); + }), + ), ), - ), + if (errorMessage != null) + Container( + width: MediaQuery.of(context).size.width * 0.4, + padding: const EdgeInsets.only(top: 8.0, left: 12.0), + child: Text( + errorMessage!, + style: const TextStyle( + color: Colors.red, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ), + ], ), ), actions: [ @@ -198,15 +273,19 @@ class AssignTagModelsDialogState extends State { ), ), ElevatedButton( - onPressed: () { - Navigator.of(context).pop(); - if (widget.onTagsAssigned != null) { - widget.onTagsAssigned!(tags); - } - }, + onPressed: isSaveEnabled + ? () { + Navigator.of(context).pop(); + if (widget.onTagsAssigned != null) { + widget.onTagsAssigned!(tags); + } + } + : null, // Disable the button if validation fails child: const Text('Save'), style: ElevatedButton.styleFrom( - backgroundColor: ColorsManager.secondaryColor, + backgroundColor: isSaveEnabled + ? ColorsManager.secondaryColor + : Colors.grey, // Change button color when disabled foregroundColor: ColorsManager.whiteColors, ), ), diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart index 51804178..2fa99d2b 100644 --- a/lib/pages/spaces_management/space_model/models/space_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -61,7 +61,6 @@ class SpaceTemplateModel { } } - class UpdateSubspaceTemplateModel { final String uuid; final Action action; @@ -133,3 +132,28 @@ class UpdateTagModel { }; } } + +extension SpaceTemplateExtensions on SpaceTemplateModel { + List listAllTagValues() { + final List tagValues = []; + + if (tags != null) { + tagValues.addAll( + tags!.map((tag) => tag.tag ?? '').where((tag) => tag.isNotEmpty)); + } + + if (subspaceModels != null) { + for (final subspace in subspaceModels!) { + if (subspace.tags != null) { + tagValues.addAll( + subspace.tags! + .map((tag) => tag.tag ?? '') + .where((tag) => tag.isNotEmpty), + ); + } + } + } + + return tagValues; + } +} diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 3c601e47..553f3be2 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; @@ -16,6 +14,7 @@ class SpaceModelPage extends StatelessWidget { @override Widget build(BuildContext context) { + final allTagValues = getAllTagValues(); return Scaffold( backgroundColor: ColorsManager.whiteColors, body: Padding( @@ -24,11 +23,11 @@ class SpaceModelPage extends StatelessWidget { //clipBehavior: Clip.none, shrinkWrap: false, physics: const AlwaysScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 13.0, mainAxisSpacing: 13.0, - childAspectRatio: 3.5, + childAspectRatio: calculateChildAspectRatio(context), ), itemCount: spaceModels.length + 1, itemBuilder: (context, index) { @@ -38,7 +37,7 @@ class SpaceModelPage extends StatelessWidget { showDialog( context: context, builder: (BuildContext context) { - return CreateSpaceModelDialog(products: products); + return CreateSpaceModelDialog(products: products,allTags: allTagValues,); }, ); }, @@ -91,4 +90,31 @@ class SpaceModelPage extends StatelessWidget { ), ); } + + double calculateChildAspectRatio(BuildContext context) { + double screenWidth = MediaQuery.of(context).size.width; + + // Adjust the aspect ratio based on the screen width + if (screenWidth > 1600) { + return 4.0; // For large screens + } + if (screenWidth > 1200) { + return 3.2; // For large screens + } else if (screenWidth > 800) { + return 3.0; // For medium screens + } else { + return 2.0; // For small screens + } + } + + List getAllTagValues() { + final List allTags = []; + + for (final spaceModel in spaceModels) { + if (spaceModel.tags != null) { + allTags.addAll(spaceModel.listAllTagValues()); + } + } + return allTags; + } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index c70e3c56..fce0522a 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -16,8 +16,9 @@ import '../../models/subspace_template_model.dart'; class CreateSpaceModelDialog extends StatelessWidget { final List? products; + final List? allTags; - const CreateSpaceModelDialog({Key? key, this.products}) : super(key: key); + const CreateSpaceModelDialog({Key? key, this.products, this.allTags}) : super(key: key); @override Widget build(BuildContext context) { @@ -79,6 +80,7 @@ class CreateSpaceModelDialog extends StatelessWidget { builder: (context) => AddDeviceTypeModelWidget( products: products, subspaces: subspaces, + allTags: allTags, ), ); if (result == true) { diff --git a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart index 92d5735d..208b0893 100644 --- a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart @@ -69,7 +69,8 @@ class SpaceModelCardWidget extends StatelessWidget { spacing: 3.0, runSpacing: 3.0, children: [ - for (var subspace in model.subspaceModels!.take(3)) + for (var subspace in model.subspaceModels! + .take(calculateTakeCount(context))) SubspaceChipWidget(subspace: subspace.subspaceName), ], ), @@ -126,4 +127,16 @@ class SpaceModelCardWidget extends StatelessWidget { ), ); } + + int calculateTakeCount(BuildContext context) { + double screenWidth = MediaQuery.of(context).size.width; + // Adjust the count based on the screen width + if (screenWidth > 1500) { + return 3; // For large screens + } else if (screenWidth > 1200) { + return 2; + } else { + return 1; // For smaller screens + } + } } diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 7c9204ef..3f74ebb8 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -15,13 +15,15 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final ValueChanged>? onProductsSelected; final List? initialSelectedProducts; final List? subspaces; + final List? allTags; const AddDeviceTypeModelWidget( {super.key, this.products, this.initialSelectedProducts, this.onProductsSelected, - this.subspaces}); + this.subspaces, + this.allTags}); @override Widget build(BuildContext context) { @@ -80,6 +82,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { products: products, subspaces: subspaces, addedProducts: currentState, + allTags: allTags, ), ); } From 81345f7154c9317fb9a6073cf6da32433a55090c Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 6 Jan 2025 15:41:52 +0300 Subject: [PATCH 043/106] change model url --- .../users_page/add_user_dialog/bloc/users_bloc.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 563fca93..26a1bcc7 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 @@ -7,8 +7,8 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialo 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/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/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'; From e7e0149b3a80be86f3c700cad704c94c590a262a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 7 Jan 2025 16:30:36 +0400 Subject: [PATCH 044/106] assign tag dialog --- .../views/assign_tag_models_dialog.dart | 297 ----------------- .../bloc/assign_tag_model_bloc.dart | 125 ++++++++ .../bloc/assign_tag_model_event.dart | 55 ++++ .../bloc/assign_tag_model_state.dart | 37 +++ .../views/assign_tag_models_dialog.dart | 299 ++++++++++++++++++ .../space_model/models/tag_model.dart | 12 + .../views/add_device_type_model_widget.dart | 4 +- 7 files changed, 531 insertions(+), 298 deletions(-) delete mode 100644 lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart create mode 100644 lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart create mode 100644 lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart create mode 100644 lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart create mode 100644 lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart diff --git a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart deleted file mode 100644 index b04e1c35..00000000 --- a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart +++ /dev/null @@ -1,297 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; - -class AssignTagModelsDialog extends StatefulWidget { - final List? products; - final List? subspaces; - final List? initialTags; - final ValueChanged>? onTagsAssigned; - final List addedProducts; - final List? allTags; - - const AssignTagModelsDialog( - {Key? key, - required this.products, - required this.subspaces, - required this.addedProducts, - this.initialTags, - this.onTagsAssigned, - this.allTags}) - : super(key: key); - - @override - AssignTagModelsDialogState createState() => AssignTagModelsDialogState(); -} - -class AssignTagModelsDialogState extends State { - late List tags; - late List selectedProducts; - late List locations; - late List otherTags; - late List controllers; - Set usedTags = {}; - String? errorMessage; - bool isSaveEnabled = true; - - @override - void initState() { - super.initState(); - - // Initialize tags from widget.initialTags or create new ones if it's empty - tags = widget.initialTags?.isNotEmpty == true - ? widget.initialTags! - : widget.addedProducts - .expand((selectedProduct) => List.generate( - selectedProduct.count, // Generate `count` number of tags - (index) => TagModel( - tag: '', // Initialize each tag with a default value - product: selectedProduct.product, - location: 'None', // Default location - ), - )) - .toList(); - - // Initialize selected products - selectedProducts = widget.addedProducts; - - // Initialize locations from subspaces or empty list if null - locations = widget.subspaces != null - ? widget.subspaces!.map((subspace) => subspace.subspaceName).toList() - : []; - locations.add("None"); - - otherTags = widget.allTags != null ? widget.allTags! : []; - - controllers = List.generate( - tags.length, - (index) => TextEditingController(text: tags[index].tag), - ); - - for (final tag in tags) { - if (tag.tag != null && tag.tag!.isNotEmpty) { - usedTags.add(tag.tag!); - } - } - - _validateTags(); - } - - void _validateTags() { - // Disable save if any tag is empty - final hasEmptyTag = - tags.any((tag) => tag.tag == null || tag.tag!.trim().isEmpty); - setState(() { - isSaveEnabled = !hasEmptyTag; - }); - } - - @override - void dispose() { - // Dispose of controllers - for (final controller in controllers) { - controller.dispose(); - } - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AlertDialog( - title: const Text('Assign Tags'), - backgroundColor: ColorsManager.whiteColors, - content: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: MediaQuery.of(context).size.width * 0.4, - decoration: BoxDecoration( - border: - Border.all(color: ColorsManager.dataHeaderGrey, width: 1), - borderRadius: BorderRadius.circular(20), - ), - child: DataTable( - border: TableBorder.all( - color: ColorsManager.dataHeaderGrey, - width: 1, - borderRadius: BorderRadius.circular(20), - ), - columns: const [ - DataColumn(label: Text('#')), - DataColumn(label: Text('Device')), - DataColumn(label: Text('Tag')), - DataColumn(label: Text('Location')), - ], - rows: List.generate(tags.length, (index) { - final tag = tags[index]; - final controller = controllers[index]; - - return DataRow( - cells: [ - DataCell(Center(child: Text(index.toString()))), - DataCell( - Center(child: Text(tag.product?.name ?? 'Unknown'))), - DataCell( - Row( - children: [ - Expanded( - child: TextFormField( - controller: controller, - decoration: const InputDecoration( - hintText: 'Enter Tag', - border: InputBorder.none, - hintStyle: TextStyle( - color: Colors.grey, - fontSize: 14, - ), - ), - style: const TextStyle( - color: Colors.black, - fontSize: 14, - ), - onChanged: (value) { - setState(() { - tag.tag = value.trim(); - _validateTags(); - }); - }, - ), - ), - PopupMenuButton( - icon: const Icon(Icons.arrow_drop_down, - color: Colors.black), - onSelected: (value) { - setState(() { - if (tag.tag != null && tag.tag!.isNotEmpty) { - usedTags.remove(tag.tag!); - } - controller.text = value; - tag.tag = value; - - if (tag.tag != null && tag.tag!.isNotEmpty) { - usedTags.add(tag.tag!); - } - _validateTags(); // Validate after selection - }); - }, - color: Colors.white, - itemBuilder: (context) { - return widget.allTags! - .where((tagValue) => - !usedTags.contains(tagValue)) - .map((tagValue) { - return PopupMenuItem( - value: tagValue, - child: Text( - tagValue, - style: const TextStyle( - color: Color(0xFF5D5D5D), - fontSize: 14, - ), - ), - ); - }).toList(); - }, - ), - ], - ), - ), - DataCell( - Center( - child: DropdownButtonHideUnderline( - child: DropdownButton( - alignment: AlignmentDirectional.center, - value: locations.contains(tag.location) - ? tag.location - : null, - onChanged: (value) { - setState(() { - tag.location = value ?? 'None'; - }); - }, - dropdownColor: ColorsManager.whiteColors, - icon: const Icon(Icons.arrow_drop_down, - color: Colors.black), - style: const TextStyle( - fontSize: 10, - fontWeight: FontWeight.w600, - color: Colors.black, - ), - isExpanded: true, - items: locations - .map((location) => DropdownMenuItem( - value: location, - child: Text( - location, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: Color(0xFF5D5D5D), - ), - ), - )) - .toList(), - ), - ), - ), - ), - ], - ); - }), - ), - ), - if (errorMessage != null) - Container( - width: MediaQuery.of(context).size.width * 0.4, - padding: const EdgeInsets.only(top: 8.0, left: 12.0), - child: Text( - errorMessage!, - style: const TextStyle( - color: Colors.red, - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ElevatedButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('Back'), - style: ElevatedButton.styleFrom( - backgroundColor: ColorsManager.boxColor, - foregroundColor: ColorsManager.blackColor, - ), - ), - ElevatedButton( - onPressed: isSaveEnabled - ? () { - Navigator.of(context).pop(); - if (widget.onTagsAssigned != null) { - widget.onTagsAssigned!(tags); - } - } - : null, // Disable the button if validation fails - child: const Text('Save'), - style: ElevatedButton.styleFrom( - backgroundColor: isSaveEnabled - ? ColorsManager.secondaryColor - : Colors.grey, // Change button color when disabled - foregroundColor: ColorsManager.whiteColors, - ), - ), - ], - ), - ], - ); - } -} diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart new file mode 100644 index 00000000..d51badca --- /dev/null +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -0,0 +1,125 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; + +class AssignTagModelBloc + extends Bloc { + AssignTagModelBloc() : super(AssignTagModelInitial()) { + on((event, emit) { + final tags = event.initialTags?.isNotEmpty == true + ? event.initialTags! + : event.addedProducts + .expand((selectedProduct) => List.generate( + selectedProduct.count, + (index) => TagModel( + tag: '', + product: selectedProduct.product, + location: 'None', + ), + )) + .toList(); + + emit( + AssignTagModelLoaded(tags: tags, isSaveEnabled: _validateTags(tags))); + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagModelLoaded && + currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); + tags[event.index].tag = event.tag; + emit(AssignTagModelLoaded( + tags: tags, + isSaveEnabled: _validateTags(tags), + errorMessage: _getValidationError(tags), + )); + } + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagModelLoaded && + currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); + + // Use copyWith for immutability + tags[event.index] = + tags[event.index].copyWith(location: event.location); + + emit(AssignTagModelLoaded( + tags: tags, + isSaveEnabled: _validateTags(tags), + )); + } + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagModelLoaded && + currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); + + emit(AssignTagModelLoaded( + tags: tags, + isSaveEnabled: _validateTags(tags), + errorMessage: _getValidationError(tags), + )); + } + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagModelLoaded && + currentState.tags.isNotEmpty) { + final updatedTags = List.from(currentState.tags) + ..remove(event.tagToDelete); + + emit(AssignTagModelLoaded( + tags: updatedTags, + isSaveEnabled: _validateTags(updatedTags), + )); + } else { + emit(const AssignTagModelLoaded( + tags: [], + isSaveEnabled: false, + )); + } + }); + } + + bool _validateTags(List tags) { + if (tags.isEmpty) { + return false; + } + final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); + final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); + return uniqueTags.length == tags.length && !hasEmptyTag; + } + + String? _getValidationError(List tags) { + final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); + if (hasEmptyTag) return 'Tags cannot be empty.'; + final duplicateTags = tags + .map((tag) => tag.tag?.trim() ?? '') + .fold>({}, (map, tag) { + map[tag] = (map[tag] ?? 0) + 1; + return map; + }) + .entries + .where((entry) => entry.value > 1) + .map((entry) => entry.key) + .toList(); + + if (duplicateTags.isNotEmpty) { + return 'Duplicate tags found: ${duplicateTags.join(', ')}'; + } + + return null; + } +} diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart new file mode 100644 index 00000000..75c9ddc1 --- /dev/null +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart @@ -0,0 +1,55 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; + +abstract class AssignTagModelEvent extends Equatable { + const AssignTagModelEvent(); + + @override + List get props => []; +} + +class InitializeTagModels extends AssignTagModelEvent { + final List? initialTags; + final List addedProducts; + + const InitializeTagModels({ + required this.initialTags, + required this.addedProducts, + }); + + @override + List get props => [initialTags ?? [], addedProducts]; +} + +class UpdateTagModel extends AssignTagModelEvent { + final int index; + final String tag; + + const UpdateTagModel({required this.index, required this.tag}); + + @override + List get props => [index, tag]; +} + +class UpdateLocation extends AssignTagModelEvent { + final int index; + final String location; + + const UpdateLocation({required this.index, required this.location}); + + @override + List get props => [index, location]; +} + +class ValidateTagModels extends AssignTagModelEvent {} + +class DeleteTagModel extends AssignTagModelEvent { + final TagModel tagToDelete; + final List tags; + + const DeleteTagModel({required this.tagToDelete, required this.tags}); + + @override + List get props => [tagToDelete, tags]; +} diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart new file mode 100644 index 00000000..9812a293 --- /dev/null +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart @@ -0,0 +1,37 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; + +abstract class AssignTagModelState extends Equatable { + const AssignTagModelState(); + + @override + List get props => []; +} + +class AssignTagModelInitial extends AssignTagModelState {} + +class AssignTagModelLoading extends AssignTagModelState {} + +class AssignTagModelLoaded extends AssignTagModelState { + final List tags; + final bool isSaveEnabled; + final String? errorMessage; + + const AssignTagModelLoaded({ + required this.tags, + required this.isSaveEnabled, + this.errorMessage, + }); + + @override + List get props => [tags, isSaveEnabled]; +} + +class AssignTagModelError extends AssignTagModelState { + final String errorMessage; + + const AssignTagModelError(this.errorMessage); + + @override + List get props => [errorMessage]; +} diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart new file mode 100644 index 00000000..e58d75f0 --- /dev/null +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -0,0 +1,299 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class AssignTagModelsDialog extends StatelessWidget { + final List? products; + final List? subspaces; + final List? initialTags; + final ValueChanged>? onTagsAssigned; + final List addedProducts; + final List? allTags; + + const AssignTagModelsDialog({ + Key? key, + required this.products, + required this.subspaces, + required this.addedProducts, + this.initialTags, + this.onTagsAssigned, + this.allTags, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final List locations = + (subspaces ?? []).map((subspace) => subspace.subspaceName).toList(); + return BlocProvider( + create: (_) => AssignTagModelBloc() + ..add(InitializeTagModels( + initialTags: initialTags, + addedProducts: addedProducts, + )), + child: BlocBuilder( + builder: (context, state) { + if (state is AssignTagModelLoaded) { + print( + "Rebuilding UI with updated locations: ${state.tags.map((e) => e.location)}"); + + final controllers = List.generate( + state.tags.length, + (index) => TextEditingController(text: state.tags[index].tag), + ); + + return AlertDialog( + title: const Text('Assign Tags'), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Column( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(20), + child: DataTable( + headingRowColor: WidgetStateProperty.all( + ColorsManager.dataHeaderGrey), + border: TableBorder.all( + color: ColorsManager.dataHeaderGrey, + width: 1, + borderRadius: BorderRadius.circular(20), + ), + columns: [ + DataColumn( + label: Text('#', + style: + Theme.of(context).textTheme.bodyMedium)), + DataColumn( + label: Text('Device', + style: + Theme.of(context).textTheme.bodyMedium)), + DataColumn( + label: Text('Tag', + style: + Theme.of(context).textTheme.bodyMedium)), + DataColumn( + label: Text('Location', + style: + Theme.of(context).textTheme.bodyMedium)), + ], + rows: state.tags.isEmpty + ? [ + const DataRow(cells: [ + DataCell( + Center( + child: Text( + 'No Data Available', + style: TextStyle( + fontSize: 14, + color: Colors.grey, + ), + ), + ), + ), + DataCell(SizedBox()), + DataCell(SizedBox()), + DataCell(SizedBox()), + ]) + ] + : List.generate(state.tags.length, (index) { + final tag = state.tags[index]; + final controller = controllers[index]; + + return DataRow( + cells: [ + DataCell(Text(index.toString())), + DataCell( + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + tag.product?.name ?? 'Unknown', + overflow: TextOverflow.ellipsis, + )), + IconButton( + icon: const Icon(Icons.close, + color: ColorsManager.warningRed, + size: 16), + onPressed: () { + context + .read() + .add(DeleteTagModel( + tagToDelete: tag, + tags: state.tags)); + }, + tooltip: 'Delete Tag', + ) + ], + ), + ), + DataCell( + Row( + children: [ + Expanded( + child: TextFormField( + controller: controller, + onChanged: (value) { + context + .read() + .add(UpdateTagModel( + index: index, + tag: value.trim(), + )); + }, + decoration: const InputDecoration( + hintText: 'Enter Tag', + border: InputBorder.none, + ), + style: const TextStyle( + fontSize: 14, + color: Colors.black, + ), + ), + ), + SizedBox( + width: MediaQuery.of(context) + .size + .width * + 0.15, + child: PopupMenuButton( + color: ColorsManager.whiteColors, + icon: const Icon( + Icons.arrow_drop_down, + color: Colors.black), + onSelected: (value) { + controller.text = value; + context + .read() + .add(UpdateTagModel( + index: index, + tag: value, + )); + }, + itemBuilder: (context) { + return (allTags ?? []) + .where((tagValue) => !state + .tags + .map((e) => e.tag) + .contains(tagValue)) + .map((tagValue) { + return PopupMenuItem( + textStyle: const TextStyle( + color: ColorsManager + .textPrimaryColor), + value: tagValue, + child: ConstrainedBox( + constraints: + BoxConstraints( + minWidth: MediaQuery.of( + context) + .size + .width * + 0.15, + maxWidth: MediaQuery.of( + context) + .size + .width * + 0.15, + ), + child: Text( + tagValue, + overflow: TextOverflow + .ellipsis, + ), + )); + }).toList(); + }, + ), + ), + ], + ), + ), + DataCell( + DropdownButtonHideUnderline( + child: DropdownButton( + value: tag.location ?? 'None', + dropdownColor: ColorsManager + .whiteColors, // Dropdown background + style: const TextStyle( + color: Colors + .black), // Style for selected text + items: [ + const DropdownMenuItem( + value: 'None', + child: Text( + 'None', + style: TextStyle( + color: ColorsManager + .textPrimaryColor), + ), + ), + ...locations.map((location) { + return DropdownMenuItem( + value: location, + child: Text( + location, + style: const TextStyle( + color: ColorsManager + .textPrimaryColor), + ), + ); + }).toList(), + ], + onChanged: (value) { + if (value != null) { + context + .read() + .add(UpdateLocation( + index: index, + location: value, + )); + } + }, + ), + ), + ), + ], + ); + }), + ), + ), + if (state.errorMessage != null) + Text( + state.errorMessage!, + style: const TextStyle(color: Colors.red), + ), + ], + ), + ), + actions: [ + ElevatedButton( + onPressed: state.isSaveEnabled + ? () { + if (onTagsAssigned != null) { + onTagsAssigned!(state.tags); + } + Navigator.pop(context); + } + : null, + child: const Text('Save'), + ), + ], + ); + } else if (state is AssignTagModelLoading) { + return const Center(child: CircularProgressIndicator()); + } else { + return const Center(child: Text('Something went wrong.')); + } + }, + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index 1e7c594b..1bd2dd02 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -30,6 +30,18 @@ class TagModel { ); } + TagModel copyWith({ + String? tag, + ProductModel? product, + String? location, + }) { + return TagModel( + tag: tag ?? this.tag, + product: product ?? this.product, + location: location ?? this.location, + ); + } + Map toJson() { return { 'uuid': uuid, diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 3f74ebb8..c6ee5424 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; @@ -74,6 +74,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { onPressed: () async { final currentState = context.read().state; + Navigator.of(context).pop(); + if (currentState.isNotEmpty) { await showDialog( barrierDismissible: false, From 1228e5e7374f1b2d3085bbde9871cf586633bb27 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 7 Jan 2025 17:34:38 +0400 Subject: [PATCH 045/106] space model creation --- .../bloc/assign_tag_model_bloc.dart | 2 +- .../bloc/assign_tag_model_event.dart | 4 +- .../views/assign_tag_models_dialog.dart | 64 ++++++++++++++----- .../bloc/subspace_model_bloc.dart | 4 +- .../bloc/subspace_model_event.dart | 0 .../bloc/subspace_model_state.dart | 0 .../views/create_subspace_model_dialog.dart | 6 +- .../bloc/create_space_model_bloc.dart | 10 +++ .../bloc/create_space_model_event.dart | 9 +++ .../models/space_template_model.dart | 20 +++--- .../dialog/create_space_model_dialog.dart | 24 +++++-- .../widgets/subspace_model_create_widget.dart | 2 +- .../views/add_device_type_model_widget.dart | 6 +- 13 files changed, 110 insertions(+), 41 deletions(-) rename lib/pages/spaces_management/{subspace_model => create_subspace_model}/bloc/subspace_model_bloc.dart (92%) rename lib/pages/spaces_management/{subspace_model => create_subspace_model}/bloc/subspace_model_event.dart (100%) rename lib/pages/spaces_management/{subspace_model => create_subspace_model}/bloc/subspace_model_state.dart (100%) rename lib/pages/spaces_management/{subspace_model => create_subspace_model}/views/create_subspace_model_dialog.dart (96%) diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index d51badca..42184278 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -24,7 +24,7 @@ class AssignTagModelBloc AssignTagModelLoaded(tags: tags, isSaveEnabled: _validateTags(tags))); }); - on((event, emit) { + on((event, emit) { final currentState = state; if (currentState is AssignTagModelLoaded && diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart index 75c9ddc1..697b1c2a 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart @@ -22,11 +22,11 @@ class InitializeTagModels extends AssignTagModelEvent { List get props => [initialTags ?? [], addedProducts]; } -class UpdateTagModel extends AssignTagModelEvent { +class UpdateTag extends AssignTagModelEvent { final int index; final String tag; - const UpdateTagModel({required this.index, required this.tag}); + const UpdateTag({required this.index, required this.tag}); @override List get props => [index, tag]; diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index e58d75f0..72f2b2ee 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -1,12 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class AssignTagModelsDialog extends StatelessWidget { @@ -16,6 +20,7 @@ class AssignTagModelsDialog extends StatelessWidget { final ValueChanged>? onTagsAssigned; final List addedProducts; final List? allTags; + final String spaceName; const AssignTagModelsDialog({ Key? key, @@ -25,6 +30,7 @@ class AssignTagModelsDialog extends StatelessWidget { this.initialTags, this.onTagsAssigned, this.allTags, + required this.spaceName, }) : super(key: key); @override @@ -40,9 +46,6 @@ class AssignTagModelsDialog extends StatelessWidget { child: BlocBuilder( builder: (context, state) { if (state is AssignTagModelLoaded) { - print( - "Rebuilding UI with updated locations: ${state.tags.map((e) => e.location)}"); - final controllers = List.generate( state.tags.length, (index) => TextEditingController(text: state.tags[index].tag), @@ -143,7 +146,7 @@ class AssignTagModelsDialog extends StatelessWidget { onChanged: (value) { context .read() - .add(UpdateTagModel( + .add(UpdateTag( index: index, tag: value.trim(), )); @@ -172,7 +175,7 @@ class AssignTagModelsDialog extends StatelessWidget { controller.text = value; context .read() - .add(UpdateTagModel( + .add(UpdateTag( index: index, tag: value, )); @@ -274,16 +277,47 @@ class AssignTagModelsDialog extends StatelessWidget { ), ), actions: [ - ElevatedButton( - onPressed: state.isSaveEnabled - ? () { - if (onTagsAssigned != null) { - onTagsAssigned!(state.tags); - } - Navigator.pop(context); - } - : null, - child: const Text('Save'), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + const SizedBox(width: 10), + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () => Navigator.of(context).pop(), + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + borderRadius: 10, + backgroundColor: state.isSaveEnabled + ? ColorsManager.secondaryColor + : ColorsManager.grayColor, + foregroundColor: ColorsManager.whiteColors, + onPressed: state.isSaveEnabled + ? () async { + Navigator.of(context).pop(); + + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => CreateSpaceModelDialog( + products: products, + allTags: allTags, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + subspaceModels: subspaces, + tags: state.tags), + ), + ); + } + : null, + child: const Text('Save'), + ), + ), + const SizedBox(width: 10), + ], ), ], ); diff --git a/lib/pages/spaces_management/subspace_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart similarity index 92% rename from lib/pages/spaces_management/subspace_model/bloc/subspace_model_bloc.dart rename to lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart index 7feadc4f..6c12ad04 100644 --- a/lib/pages/spaces_management/subspace_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart @@ -1,6 +1,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart'; -import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; diff --git a/lib/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart similarity index 100% rename from lib/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart rename to lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart diff --git a/lib/pages/spaces_management/subspace_model/bloc/subspace_model_state.dart b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart similarity index 100% rename from lib/pages/spaces_management/subspace_model/bloc/subspace_model_state.dart rename to lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart diff --git a/lib/pages/spaces_management/subspace_model/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart similarity index 96% rename from lib/pages/spaces_management/subspace_model/views/create_subspace_model_dialog.dart rename to lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart index 54981975..d241b41e 100644 --- a/lib/pages/spaces_management/subspace_model/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart'; -import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 25d7c731..1ce8f651 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -24,6 +24,16 @@ class CreateSpaceModelBloc emit(CreateSpaceModelLoaded(_space!)); }); + on((event, emit) { + if (_space != null) { + _space = _space!.copyWith(modelName: event.name); // Use copyWith for immutability + emit(CreateSpaceModelLoaded(_space!)); + } else { + emit(CreateSpaceModelError("Space template not initialized")); + } + }); + + on((event, emit) { if (_space != null) { final updatedSpace = _space!.copyWith( diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index 6398c7ec..ccf0e3fb 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -11,6 +11,15 @@ class UpdateSpaceTemplate extends CreateSpaceModelEvent { UpdateSpaceTemplate(this.spaceTemplate); } +class UpdateSpaceTemplateName extends CreateSpaceModelEvent { + final String name; + + UpdateSpaceTemplateName({required this.name}); + + @override + List get props => [name]; +} + class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { final List subspaces; diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart index 2fa99d2b..d493fae9 100644 --- a/lib/pages/spaces_management/space_model/models/space_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -6,7 +6,7 @@ import 'package:uuid/uuid.dart'; class SpaceTemplateModel { final String? uuid; - final String modelName; + String modelName; final List? subspaceModels; final List? tags; String internalId; @@ -35,15 +35,6 @@ class SpaceTemplateModel { ); } - Map toJson() { - return { - 'uuid': uuid, - 'modelName': modelName, - 'subspaceModels': subspaceModels?.map((e) => e.toJson()).toList(), - 'tags': tags?.map((e) => e.toJson()).toList(), - }; - } - SpaceTemplateModel copyWith({ String? uuid, String? modelName, @@ -59,6 +50,15 @@ class SpaceTemplateModel { internalId: internalId ?? this.internalId, ); } + + Map toJson() { + return { + 'uuid': uuid, + 'modelName': modelName, + 'subspaceModels': subspaceModels?.map((e) => e.toJson()).toList(), + 'tags': tags?.map((e) => e.toJson()).toList(), + }; + } } class UpdateSubspaceTemplateModel { diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index fce0522a..8b46d50a 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; @@ -17,13 +18,17 @@ import '../../models/subspace_template_model.dart'; class CreateSpaceModelDialog extends StatelessWidget { final List? products; final List? allTags; + final SpaceTemplateModel? spaceModel; - const CreateSpaceModelDialog({Key? key, this.products, this.allTags}) : super(key: key); + const CreateSpaceModelDialog( + {Key? key, this.products, this.allTags, this.spaceModel}) + : super(key: key); @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - List? subspaces = []; // Store subspaces here + List? subspaces = []; + final TextEditingController spaceNameController = TextEditingController(); return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), @@ -33,6 +38,14 @@ class CreateSpaceModelDialog extends StatelessWidget { child: BlocProvider( create: (_) { final bloc = CreateSpaceModelBloc(); + if (spaceModel != null) { + bloc.add(UpdateSpaceTemplate(spaceModel!)); + } else { + bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( + modelName: '', + subspaceModels: [], + ))); + } return bloc; }, child: BlocBuilder( @@ -51,6 +64,7 @@ class CreateSpaceModelDialog extends StatelessWidget { SizedBox( width: screenWidth * 0.25, child: TextField( + controller: spaceNameController, style: const TextStyle(color: ColorsManager.blackColor), decoration: InputDecoration( filled: true, @@ -81,12 +95,10 @@ class CreateSpaceModelDialog extends StatelessWidget { products: products, subspaces: subspaces, allTags: allTags, + spaceName: spaceNameController.text, ), ); - if (result == true) { - // Handle the result if necessary - print('Devices added successfully'); - } + if (result == true) {} }, style: TextButton.styleFrom( padding: EdgeInsets.zero, diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 4706fece..402c2ac1 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/subspace_model/views/create_subspace_model_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index c6ee5424..021e5ac9 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -16,6 +16,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? initialSelectedProducts; final List? subspaces; final List? allTags; + final String spaceName; const AddDeviceTypeModelWidget( {super.key, @@ -23,7 +24,9 @@ class AddDeviceTypeModelWidget extends StatelessWidget { this.initialSelectedProducts, this.onProductsSelected, this.subspaces, - this.allTags}); + this.allTags, + required this.spaceName + }); @override Widget build(BuildContext context) { @@ -85,6 +88,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { subspaces: subspaces, addedProducts: currentState, allTags: allTags, + spaceName: spaceName, ), ); } From 08f322165e1482f895856059b0f7211312e2bcfc Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 7 Jan 2025 19:07:42 +0400 Subject: [PATCH 046/106] edit tag model pop up --- .../views/assign_tag_models_dialog.dart | 16 ++ .../models/space_template_model.dart | 2 +- .../models/subspace_template_model.dart | 2 +- .../dialog/create_space_model_dialog.dart | 42 ++--- .../widgets/subspace_model_create_widget.dart | 14 +- .../widgets/tag_chips_display_widget.dart | 151 ++++++++++++++++++ 6 files changed, 190 insertions(+), 37 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 72f2b2ee..c2b3ba9b 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -298,7 +298,23 @@ class AssignTagModelsDialog extends StatelessWidget { onPressed: state.isSaveEnabled ? () async { Navigator.of(context).pop(); + final assignedTags = {}; + for (var tag in state.tags) { + if (tag.location == null || + subspaces == null) { + continue; + } + for (var subspace in subspaces!) { + if (tag.location == subspace.subspaceName) { + subspace.tags ??= []; + subspace.tags!.add(tag); + assignedTags.add(tag); + break; + } + } + } + state.tags.removeWhere(assignedTags.contains); await showDialog( barrierDismissible: false, context: context, diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart index d493fae9..ec2f466f 100644 --- a/lib/pages/spaces_management/space_model/models/space_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -7,7 +7,7 @@ import 'package:uuid/uuid.dart'; class SpaceTemplateModel { final String? uuid; String modelName; - final List? subspaceModels; + List? subspaceModels; final List? tags; String internalId; diff --git a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart index 911494a0..119728db 100644 --- a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart @@ -4,7 +4,7 @@ class SubspaceTemplateModel { final String? uuid; String subspaceName; final bool disabled; - final List? tags; + List? tags; SubspaceTemplateModel({ this.uuid, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 8b46d50a..93e5a3dc 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -7,8 +7,8 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -27,9 +27,10 @@ class CreateSpaceModelDialog extends StatelessWidget { @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - List? subspaces = []; - final TextEditingController spaceNameController = TextEditingController(); - + List? subspaces = spaceModel?.subspaceModels ?? []; + final TextEditingController spaceNameController = TextEditingController( + text: spaceModel?.modelName ?? '', + ); return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), backgroundColor: ColorsManager.whiteColors, @@ -38,7 +39,7 @@ class CreateSpaceModelDialog extends StatelessWidget { child: BlocProvider( create: (_) { final bloc = CreateSpaceModelBloc(); - if (spaceModel != null) { + if (spaceModel != null) { bloc.add(UpdateSpaceTemplate(spaceModel!)); } else { bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( @@ -86,28 +87,13 @@ class CreateSpaceModelDialog extends StatelessWidget { const SizedBox(height: 16), SubspaceModelCreate(context, subspaces: subspaces), const SizedBox(height: 10), - TextButton( - onPressed: () async { - final result = await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AddDeviceTypeModelWidget( - products: products, - subspaces: subspaces, - allTags: allTags, - spaceName: spaceNameController.text, - ), - ); - if (result == true) {} - }, - style: TextButton.styleFrom( - padding: EdgeInsets.zero, - ), - child: const ButtonContentWidget( - icon: Icons.add, - label: 'Add Devices', - ), - ), + TagChipDisplay(context, + screenWidth: screenWidth, + spaceModel: spaceModel, + products: products, + subspaces: subspaces, + allTags: allTags, + spaceNameController: spaceNameController), const SizedBox(height: 20), SizedBox( width: screenWidth * 0.25, diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 402c2ac1..30b84a4b 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -54,20 +54,20 @@ class SubspaceModelCreate extends StatelessWidget { ), ) : SizedBox( - width: screenWidth * 0.25, // Set the desired width + width: screenWidth * 0.25, child: Container( - padding: const EdgeInsets.all(8.0), // Add padding + padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, // Background color - borderRadius: BorderRadius.circular(15), // Rounded corners + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(15), border: Border.all( - color: ColorsManager.textFieldGreyColor, // Border color + color: ColorsManager.textFieldGreyColor, width: 3.0, // Border width ), ), child: Wrap( - spacing: 8.0, // Spacing between chips - runSpacing: 8.0, // Spacing between rows of chips + spacing: 8.0, + runSpacing: 8.0, children: [ ...subspaces.map( (subspace) => Chip( diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart new file mode 100644 index 00000000..00b2d493 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -0,0 +1,151 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class TagChipDisplay extends StatelessWidget { + final double screenWidth; + final SpaceTemplateModel? spaceModel; + final List? products; + final List? subspaces; + final List? allTags; + final TextEditingController spaceNameController; + + const TagChipDisplay( + BuildContext context, { + Key? key, + required this.screenWidth, + required this.spaceModel, + required this.products, + required this.subspaces, + required this.allTags, + required this.spaceNameController, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return (spaceModel?.subspaceModels?.isNotEmpty == true || spaceModel?.tags?.isNotEmpty == true || + spaceModel?.subspaceModels + ?.any((subspace) => subspace.tags?.isNotEmpty == true) == + true) + ? SizedBox( + width: screenWidth * 0.25, + child: Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(15), + border: Border.all( + color: ColorsManager.textFieldGreyColor, + width: 3.0, // Border width + ), + ), + child: Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: [ + // Combine tags from spaceModel and subspaces + ..._groupTags([ + ...?spaceModel?.tags, + ...?spaceModel?.subspaceModels + ?.expand((subspace) => subspace.tags ?? []) + ]).entries.map( + (entry) => Chip( + avatar: SizedBox( + width: 24, + height: 24, + child: SvgPicture.asset( + entry.key.icon ?? + 'assets/default_icon.svg', // Provide a default asset path if null + fit: BoxFit.contain, + ), + ), + label: Text( + 'x${entry.value}', // Show count + style: const TextStyle( + color: ColorsManager.spaceColor, + ), + ), + backgroundColor: + Colors.white, // Chip background color + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(16), // Rounded chip + side: const BorderSide( + color: ColorsManager.spaceColor, // Border color + ), + ), + ), + ), + GestureDetector( + onTap: () async { + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AddDeviceTypeModelWidget( + products: products, + subspaces: subspaces, + allTags: allTags, + spaceName: spaceNameController.text, + ), + ); + // Edit action + }, + child: Chip( + label: const Text( + 'Edit', + style: TextStyle( + color: ColorsManager.spaceColor), // Text color + ), + backgroundColor: + Colors.white, // Background color for "Edit" + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), // Rounded chip + side: const BorderSide( + color: ColorsManager.spaceColor), // Border color + ), + ), + ), + ], + ), + ), + ) + : TextButton( + onPressed: () async { + final result = await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AddDeviceTypeModelWidget( + products: products, + subspaces: subspaces, + allTags: allTags, + spaceName: spaceNameController.text, + ), + ); + if (result == true) {} + }, + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + ), + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Add Devices', + ), + ); + } + + Map _groupTags(List tags) { + final Map groupedTags = {}; + for (var tag in tags) { + if (tag.product != null) { + groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1; + } + } + return groupedTags; + } +} From 6b79254a89092f5ab8b3452407a6f9dc93eb4a85 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 8 Jan 2025 19:04:08 +0400 Subject: [PATCH 047/106] create space model --- .../sos/view/sos_batch_control_view.dart | 1 - .../bloc/assign_tag_model_bloc.dart | 56 +++++-- .../bloc/create_space_model_bloc.dart | 57 ++++++- .../bloc/create_space_model_event.dart | 19 ++- .../bloc/create_space_model_state.dart | 8 +- .../create_space_template_body_model.dart | 47 ++++++ .../models/space_template_model.dart | 13 +- .../space_model/models/tag_model.dart | 11 +- .../space_model/view/space_model_page.dart | 150 +++++++++--------- .../dialog/create_space_model_dialog.dart | 84 +++++++--- .../widgets/subspace_model_create_widget.dart | 15 +- .../widgets/tag_chips_display_widget.dart | 61 ++++++- .../views/add_device_type_model_widget.dart | 34 +++- .../widgets/scrollable_grid_view_widget.dart | 25 ++- lib/services/api/http_service.dart | 24 ++- lib/services/space_mana_api.dart | 1 - lib/services/space_model_mang_api.dart | 20 +++ lib/utils/constants/api_const.dart | 1 + 18 files changed, 477 insertions(+), 150 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart diff --git a/lib/pages/device_managment/sos/view/sos_batch_control_view.dart b/lib/pages/device_managment/sos/view/sos_batch_control_view.dart index bc66d69f..9082c8bd 100644 --- a/lib/pages/device_managment/sos/view/sos_batch_control_view.dart +++ b/lib/pages/device_managment/sos/view/sos_batch_control_view.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/sos/bloc/sos_device_bloc.dart'; class SOSBatchControlView extends StatelessWidget { diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index 42184278..3dd5e27d 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -7,21 +7,49 @@ class AssignTagModelBloc extends Bloc { AssignTagModelBloc() : super(AssignTagModelInitial()) { on((event, emit) { - final tags = event.initialTags?.isNotEmpty == true - ? event.initialTags! - : event.addedProducts - .expand((selectedProduct) => List.generate( - selectedProduct.count, - (index) => TagModel( - tag: '', - product: selectedProduct.product, - location: 'None', - ), - )) - .toList(); + final initialTags = event.initialTags ?? []; - emit( - AssignTagModelLoaded(tags: tags, isSaveEnabled: _validateTags(tags))); + final existingTagCounts = {}; + for (var tag in initialTags) { + if (tag.product != null) { + existingTagCounts[tag.product!.uuid] = + (existingTagCounts[tag.product!.uuid] ?? 0) + 1; + } + } + + final allTags = []; + + for (var selectedProduct in event.addedProducts) { + final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; + + if (selectedProduct.count == 0 || + selectedProduct.count <= existingCount) { + allTags.addAll(initialTags + .where((tag) => tag.product?.uuid == selectedProduct.productId)); + continue; + } + + final missingCount = selectedProduct.count - existingCount; + + allTags.addAll(initialTags + .where((tag) => tag.product?.uuid == selectedProduct.productId)); + + if (missingCount > 0) { + allTags.addAll(List.generate( + missingCount, + (index) => TagModel( + tag: '', + product: selectedProduct.product, + location: 'None', + ), + )); + } + } + + emit(AssignTagModelLoaded( + tags: allTags, + isSaveEnabled: _validateTags(allTags), + )); }); on((event, emit) { diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 1ce8f651..d93f68c5 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -1,13 +1,51 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/services/space_model_mang_api.dart'; class CreateSpaceModelBloc extends Bloc { SpaceTemplateModel? _space; - CreateSpaceModelBloc() : super(CreateSpaceModelInitial()) { + final SpaceModelManagementApi _api; + + CreateSpaceModelBloc(this._api) : super(CreateSpaceModelInitial()) { + on((event, emit) async { + try { + final spaceTemplate = event.spaceTemplate; + + + final tagBodyModels = + spaceTemplate.tags?.map((tag) => tag.toTagBodyModel()).toList() ?? + []; + + final subspaceTemplateBodyModels = + spaceTemplate.subspaceModels?.map((subspaceModel) { + final tagsubspaceBodyModels = subspaceModel.tags + ?.map((tag) => tag.toTagBodyModel()) + .toList() ?? + []; + return CreateSubspaceTemplateModel() + ..subspaceName = subspaceModel.subspaceName + ..tags = tagsubspaceBodyModels; + }).toList() ?? + []; + + final spaceModelBody = CreateSpaceTemplateBodyModel( + modelName: spaceTemplate.modelName, + tags: tagBodyModels, + subspaceModels: subspaceTemplateBodyModels); + + print(spaceModelBody); + final success = await _api.createSpaceModel(spaceModelBody); + } catch (e) { + print(e); + } + }); + on((event, emit) { emit(CreateSpaceModelLoading()); Future.delayed(const Duration(seconds: 1), () { @@ -25,24 +63,27 @@ class CreateSpaceModelBloc }); on((event, emit) { - if (_space != null) { - _space = _space!.copyWith(modelName: event.name); // Use copyWith for immutability - emit(CreateSpaceModelLoaded(_space!)); + final currentState = state; + + if (currentState is CreateSpaceModelLoaded) { + final updatedSpaceModel = + currentState.space.copyWith(modelName: event.name); + emit(CreateSpaceModelLoaded(updatedSpaceModel)); } else { emit(CreateSpaceModelError("Space template not initialized")); } }); - on((event, emit) { - if (_space != null) { - final updatedSpace = _space!.copyWith( + final currentState = state; + + if (currentState is CreateSpaceModelLoaded) { + final updatedSpace = currentState.space.copyWith( subspaceModels: [ ...(_space!.subspaceModels ?? []), ...event.subspaces, ], ); - _space = updatedSpace; emit(CreateSpaceModelLoaded(updatedSpace)); } else { emit(CreateSpaceModelError("Space template not initialized")); diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index ccf0e3fb..7944a8a8 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -1,7 +1,13 @@ +import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -abstract class CreateSpaceModelEvent {} +abstract class CreateSpaceModelEvent extends Equatable { + const CreateSpaceModelEvent(); + + @override + List get props => []; +} class LoadSpaceTemplate extends CreateSpaceModelEvent {} @@ -11,6 +17,17 @@ class UpdateSpaceTemplate extends CreateSpaceModelEvent { UpdateSpaceTemplate(this.spaceTemplate); } +class CreateSpaceTemplate extends CreateSpaceModelEvent { + final SpaceTemplateModel spaceTemplate; + + const CreateSpaceTemplate({ + required this.spaceTemplate, + }); + + @override + List get props => [spaceTemplate]; +} + class UpdateSpaceTemplateName extends CreateSpaceModelEvent { final String name; diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart index 1a9f52bb..c05e8744 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart @@ -1,6 +1,12 @@ +import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; -abstract class CreateSpaceModelState {} +abstract class CreateSpaceModelState extends Equatable { + const CreateSpaceModelState(); + + @override + List get props => []; +} class CreateSpaceModelInitial extends CreateSpaceModelState {} diff --git a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart new file mode 100644 index 00000000..896fe000 --- /dev/null +++ b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart @@ -0,0 +1,47 @@ +import 'dart:convert'; + +class TagBodyModel { + late String uuid; + late String tag; + late final String? productUuid; + + Map toJson() { + return { + 'uuid': uuid, + 'tag': tag, + 'productUuid': productUuid, + }; + } + + @override + String toString() { + return toJson().toString(); + } +} + +class CreateSubspaceTemplateModel { + late String subspaceName; + late List? tags; +} + +class CreateSpaceTemplateBodyModel { + final String modelName; + final List? tags; + final List? subspaceModels; + + CreateSpaceTemplateBodyModel({ + required this.modelName, + this.tags, + this.subspaceModels, + }); + + Map toJson() { + return { + 'modelName': modelName, + 'tags': tags, + 'subspaceModels': subspaceModels, + }; + } + + +} diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart index ec2f466f..f62bbcf6 100644 --- a/lib/pages/spaces_management/space_model/models/space_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -1,16 +1,20 @@ +import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; import 'package:uuid/uuid.dart'; -class SpaceTemplateModel { +class SpaceTemplateModel extends Equatable { final String? uuid; String modelName; List? subspaceModels; final List? tags; String internalId; + @override + List get props => [modelName, subspaceModels]; + SpaceTemplateModel({ this.uuid, String? internalId, @@ -26,9 +30,10 @@ class SpaceTemplateModel { uuid: json['uuid'] ?? '', internalId: internalId, modelName: json['modelName'] ?? '', - subspaceModels: (json['subspaceModels'] as List) - .map((item) => SubspaceTemplateModel.fromJson(item)) - .toList(), + subspaceModels: (json['subspaceModels'] as List?) + ?.map((e) => SubspaceTemplateModel.fromJson(e)) + .toList() ?? + [], tags: (json['tags'] as List) .map((item) => TagModel.fromJson(item)) .toList(), diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index 1bd2dd02..99008e76 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -1,5 +1,5 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:uuid/uuid.dart'; class TagModel { @@ -50,3 +50,12 @@ class TagModel { }; } } + +extension TagModelExtensions on TagModel { + TagBodyModel toTagBodyModel() { + return TagBodyModel() + ..uuid = uuid ?? '' + ..tag = tag ?? '' + ..productUuid = product?.uuid; + } +} \ No newline at end of file diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 553f3be2..6fecd125 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -19,73 +19,83 @@ class SpaceModelPage extends StatelessWidget { backgroundColor: ColorsManager.whiteColors, body: Padding( padding: const EdgeInsets.fromLTRB(20.0, 16.0, 16.0, 16.0), - child: GridView.builder( - //clipBehavior: Clip.none, - shrinkWrap: false, - physics: const AlwaysScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 13.0, - mainAxisSpacing: 13.0, - childAspectRatio: calculateChildAspectRatio(context), - ), - itemCount: spaceModels.length + 1, - itemBuilder: (context, index) { - if (index == spaceModels.length) { - return GestureDetector( - onTap: () { - showDialog( - context: context, - builder: (BuildContext context) { - return CreateSpaceModelDialog(products: products,allTags: allTagValues,); - }, - ); - }, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.circular(20), - boxShadow: const [ - BoxShadow( - color: ColorsManager.semiTransparentBlackColor, - blurRadius: 15, - offset: Offset(0, 4), - spreadRadius: 0, - ), - BoxShadow( - color: ColorsManager.semiTransparentBlackColor, - blurRadius: 25, - offset: Offset(0, 15), - spreadRadius: -5, - ), - ], - ), - child: Center( - child: Container( - width: 60, - height: 60, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: ColorsManager.neutralGray, - border: Border.all( - color: ColorsManager.textFieldGreyColor, - width: 2.0, - ), - ), - child: const Icon( - Icons.add, - size: 40, - color: ColorsManager.spaceColor, // Icon color - ), - ), - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0, + childAspectRatio: calculateChildAspectRatio(context), ), - ); - } + itemCount: spaceModels.length + 1, + itemBuilder: (context, index) { + if (index == spaceModels.length) { + return GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return CreateSpaceModelDialog( + products: products, + allTags: allTagValues, + ); + }, + ); + }, + child: _buildAddContainer(), + ); + } + final model = spaceModels[index]; + return SpaceModelCardWidget(model: model); + }, + ), + ), + ], + ), + ), + ); + } - final model = spaceModels[index]; - return SpaceModelCardWidget(model: model); - }, + Widget _buildAddContainer() { + return Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(20), + boxShadow: const [ + BoxShadow( + color: ColorsManager.semiTransparentBlackColor, + blurRadius: 15, + offset: Offset(0, 4), + spreadRadius: 0, + ), + BoxShadow( + color: ColorsManager.semiTransparentBlackColor, + blurRadius: 25, + offset: Offset(0, 15), + spreadRadius: -5, + ), + ], + ), + child: Center( + child: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.neutralGray, + border: Border.all( + color: ColorsManager.textFieldGreyColor, + width: 2.0, + ), + ), + child: const Icon( + Icons.add, + size: 40, + color: ColorsManager.spaceColor, + ), ), ), ); @@ -93,17 +103,15 @@ class SpaceModelPage extends StatelessWidget { double calculateChildAspectRatio(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; - - // Adjust the aspect ratio based on the screen width if (screenWidth > 1600) { - return 4.0; // For large screens + return 3; } if (screenWidth > 1200) { - return 3.2; // For large screens + return 5; } else if (screenWidth > 800) { - return 3.0; // For medium screens + return 5; } else { - return 2.0; // For small screens + return 6.0; } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 93e5a3dc..bd988782 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -7,10 +7,10 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart'; +import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import '../../models/subspace_template_model.dart'; @@ -26,31 +26,45 @@ class CreateSpaceModelDialog extends StatelessWidget { @override Widget build(BuildContext context) { + final SpaceModelManagementApi _spaceModelApi = SpaceModelManagementApi(); + final screenWidth = MediaQuery.of(context).size.width; List? subspaces = spaceModel?.subspaceModels ?? []; final TextEditingController spaceNameController = TextEditingController( text: spaceModel?.modelName ?? '', ); + return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), backgroundColor: ColorsManager.whiteColors, content: SizedBox( - width: screenWidth * 0.3, - child: BlocProvider( - create: (_) { - final bloc = CreateSpaceModelBloc(); - if (spaceModel != null) { - bloc.add(UpdateSpaceTemplate(spaceModel!)); - } else { - bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( - modelName: '', - subspaceModels: [], - ))); - } - return bloc; - }, - child: BlocBuilder( - builder: (context, state) { + width: screenWidth * 0.3, + child: BlocProvider( + create: (_) { + final bloc = CreateSpaceModelBloc(_spaceModelApi); + if (spaceModel != null) { + bloc.add(UpdateSpaceTemplate(spaceModel!)); + } else { + bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( + modelName: '', + subspaceModels: [], + ))); + } + + spaceNameController.addListener(() { + bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text)); + }); + + return bloc; + }, + child: BlocBuilder( + builder: (context, state) { + if (state is CreateSpaceModelLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is CreateSpaceModelLoaded) { + final updatedSpaceModel = state.space; + final subspaces = updatedSpaceModel.subspaceModels ?? []; + return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -66,6 +80,11 @@ class CreateSpaceModelDialog extends StatelessWidget { width: screenWidth * 0.25, child: TextField( controller: spaceNameController, + onChanged: (value) { + context + .read() + .add(UpdateSpaceTemplateName(name: value)); + }, style: const TextStyle(color: ColorsManager.blackColor), decoration: InputDecoration( filled: true, @@ -85,11 +104,12 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - SubspaceModelCreate(context, subspaces: subspaces), + SubspaceModelCreate(context, + subspaces: state.space.subspaceModels ?? []), const SizedBox(height: 10), TagChipDisplay(context, screenWidth: screenWidth, - spaceModel: spaceModel, + spaceModel: updatedSpaceModel, products: products, subspaces: subspaces, allTags: allTags, @@ -109,8 +129,14 @@ class CreateSpaceModelDialog extends StatelessWidget { Expanded( child: DefaultButton( onPressed: () { - // Return data when OK is pressed - Navigator.of(context).pop(subspaces); + final updatedSpaceTemplate = + updatedSpaceModel.copyWith( + modelName: spaceNameController.text.trim(), + ); + context.read().add( + CreateSpaceTemplate( + spaceTemplate: updatedSpaceTemplate), + ); }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, @@ -123,9 +149,19 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ], ); - }, - ), - )), + } else if (state is CreateSpaceModelError) { + return Text( + 'Error: ${state.message}', + style: const TextStyle(color: Colors.red), + ); + } + + // Default case (e.g., CreateSpaceModelInitial) + return const Center(child: Text('Initializing...')); + }, + ), + ), + ), ); } } diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 30b84a4b..35b680a0 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -10,7 +10,8 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac class SubspaceModelCreate extends StatelessWidget { final List subspaces; - const SubspaceModelCreate(BuildContext context, { + const SubspaceModelCreate( + BuildContext context, { Key? key, required this.subspaces, }) : super(key: key); @@ -26,6 +27,8 @@ class SubspaceModelCreate extends StatelessWidget { overlayColor: ColorsManager.transparentColor, ), onPressed: () async { + Navigator.of(context).pop(); + final result = await showDialog>( barrierDismissible: false, context: context, @@ -58,16 +61,16 @@ class SubspaceModelCreate extends StatelessWidget { child: Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - borderRadius: BorderRadius.circular(15), + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(15), border: Border.all( - color: ColorsManager.textFieldGreyColor, + color: ColorsManager.textFieldGreyColor, width: 3.0, // Border width ), ), child: Wrap( - spacing: 8.0, - runSpacing: 8.0, + spacing: 8.0, + runSpacing: 8.0, children: [ ...subspaces.map( (subspace) => Chip( diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 00b2d493..f60e1cf3 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; @@ -29,7 +30,8 @@ class TagChipDisplay extends StatelessWidget { @override Widget build(BuildContext context) { - return (spaceModel?.subspaceModels?.isNotEmpty == true || spaceModel?.tags?.isNotEmpty == true || + return (spaceModel?.subspaceModels?.isNotEmpty == true || + spaceModel?.tags?.isNotEmpty == true || spaceModel?.subspaceModels ?.any((subspace) => subspace.tags?.isNotEmpty == true) == true) @@ -60,8 +62,7 @@ class TagChipDisplay extends StatelessWidget { width: 24, height: 24, child: SvgPicture.asset( - entry.key.icon ?? - 'assets/default_icon.svg', // Provide a default asset path if null + entry.key.icon ?? 'assets/icons/gateway.svg', fit: BoxFit.contain, ), ), @@ -71,19 +72,19 @@ class TagChipDisplay extends StatelessWidget { color: ColorsManager.spaceColor, ), ), - backgroundColor: - Colors.white, // Chip background color + backgroundColor: Colors.white, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16), // Rounded chip + borderRadius: BorderRadius.circular(16), side: const BorderSide( - color: ColorsManager.spaceColor, // Border color + color: ColorsManager.spaceColor, ), ), ), ), GestureDetector( onTap: () async { + Navigator.of(context).pop(); + await showDialog( barrierDismissible: false, context: context, @@ -92,6 +93,10 @@ class TagChipDisplay extends StatelessWidget { subspaces: subspaces, allTags: allTags, spaceName: spaceNameController.text, + spaceTagModels: spaceModel?.tags, + initialSelectedProducts: + _createInitialSelectedProducts( + spaceModel?.tags, spaceModel?.subspaceModels), ), ); // Edit action @@ -117,6 +122,8 @@ class TagChipDisplay extends StatelessWidget { ) : TextButton( onPressed: () async { + Navigator.of(context).pop(); + final result = await showDialog( barrierDismissible: false, context: context, @@ -148,4 +155,42 @@ class TagChipDisplay extends StatelessWidget { } return groupedTags; } + + List _createInitialSelectedProducts( + List? tags, List? subspaces) { + final Map productCounts = {}; + + // Count products in spaceModel tags + if (tags != null) { + for (var tag in tags) { + if (tag.product != null) { + productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1; + } + } + } + + // Count products in subspaces + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + for (var tag in subspace.tags!) { + if (tag.product != null) { + productCounts[tag.product!] = + (productCounts[tag.product!] ?? 0) + 1; + } + } + } + } + } + + // Create SelectedProduct instances + return productCounts.entries + .map((entry) => SelectedProduct( + productId: entry.key.uuid, + count: entry.value, + productName: entry.key.name ?? 'Unnamed', + product: entry.key, + )) + .toList(); + } } diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 021e5ac9..74f11194 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assi import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart'; @@ -15,6 +16,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final ValueChanged>? onProductsSelected; final List? initialSelectedProducts; final List? subspaces; + final List? spaceTagModels; final List? allTags; final String spaceName; @@ -25,8 +27,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { this.onProductsSelected, this.subspaces, this.allTags, - required this.spaceName - }); + this.spaceTagModels, + required this.spaceName}); @override Widget build(BuildContext context) { @@ -89,6 +91,9 @@ class AddDeviceTypeModelWidget extends StatelessWidget { addedProducts: currentState, allTags: allTags, spaceName: spaceName, + initialTags: generateInitialTags( + spaceTagModels: spaceTagModels, + subspaces: subspaces), ), ); } @@ -100,4 +105,29 @@ class AddDeviceTypeModelWidget extends StatelessWidget { ), )); } + + List generateInitialTags({ + List? spaceTagModels, + List? subspaces, + }) { + final List initialTags = []; + + if (spaceTagModels != null) { + initialTags.addAll(spaceTagModels); + } + + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + initialTags.addAll( + subspace.tags!.map( + (tag) => tag.copyWith(location: subspace.subspaceName), + ), + ); + } + } + } + + return initialTags; + } } diff --git a/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart index e05c6711..2a653dde 100644 --- a/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart @@ -8,11 +8,13 @@ import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_typ class ScrollableGridViewWidget extends StatelessWidget { final List? products; final int crossAxisCount; + final List? initialProductCounts; const ScrollableGridViewWidget({ super.key, required this.products, required this.crossAxisCount, + this.initialProductCounts, }); @override @@ -36,9 +38,13 @@ class ScrollableGridViewWidget extends StatelessWidget { itemCount: products?.length ?? 0, itemBuilder: (context, index) { final product = products![index]; + final initialProductCount = _findInitialProductCount(product); + return DeviceTypeTileWidget( product: product, - productCounts: productCounts, + productCounts: initialProductCount != null + ? [...productCounts, initialProductCount] + : productCounts, ); }, ); @@ -46,4 +52,21 @@ class ScrollableGridViewWidget extends StatelessWidget { ), ); } + + SelectedProduct? _findInitialProductCount(ProductModel product) { + // Check if the product exists in initialProductCounts + if (initialProductCounts == null) return null; + final matchingProduct = initialProductCounts!.firstWhere( + (selectedProduct) => selectedProduct.productId == product.uuid, + orElse: () => SelectedProduct( + productId: '', + count: 0, + productName: '', + product: null, + ), + ); + + // Check if the product was actually found + return matchingProduct.productId.isNotEmpty ? matchingProduct : null; + } } diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart index b75f05cf..2ed3da42 100644 --- a/lib/services/api/http_service.dart +++ b/lib/services/api/http_service.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:dio/dio.dart'; import 'package:syncrow_web/services/api/http_interceptor.dart'; import 'package:syncrow_web/services/locator.dart'; @@ -42,22 +44,30 @@ class HTTPService { } } - Future post( - {required String path, - Map? queryParameters, - Options? options, - dynamic body, - bool showServerMessage = true, - required T Function(dynamic) expectedResponseModel}) async { + Future post({ + required String path, + Map? queryParameters, + Options? options, + dynamic body, + bool showServerMessage = true, + required T Function(dynamic) expectedResponseModel, + }) async { try { + final bodyString = body is Map || body is List + ? jsonEncode(body) + : body?.toString() ?? 'null'; + + print("POST Request: $path, Body: $bodyString, Query: $queryParameters"); final response = await client.post( path, data: body, queryParameters: queryParameters, options: options, ); + print("POST Response: ${response.data.toString()}"); return expectedResponseModel(response.data); } catch (error) { + print("POST Error: $error"); rethrow; } } diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 333b715e..da70d46f 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index 762f9178..82ed097f 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -34,4 +35,23 @@ class SpaceModelManagementApi { return []; } } + + Future createSpaceModel( + CreateSpaceTemplateBodyModel spaceModel) async { + try { + final response = await HTTPService().post( + path: ApiEndpoints.createSpaceModel + .replaceAll('{projectId}', TempConst.projectId), + showServerMessage: true, + body: spaceModel.toJson(), + expectedResponseModel: (json) { + return SpaceTemplateModel.fromJson(json['data']); + }, + ); + return response; + } catch (e) { + debugPrint('Error creating community: $e'); + return null; + } + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 2fdca23a..ca4527fb 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -99,4 +99,5 @@ abstract class ApiEndpoints { //space model static const String listSpaceModels = '/projects/{projectId}/space-models'; + static const String createSpaceModel = '/projects/{projectId}/space-models'; } From 9f5e9af5fa42fab99a0cd0d6ddf2b98064f636b6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 8 Jan 2025 19:56:42 +0400 Subject: [PATCH 048/106] fixed edit dialog --- .../views/assign_tag_models_dialog.dart | 21 ++++++-- .../views/create_subspace_model_dialog.dart | 49 +++++++++++++++++-- .../dialog/create_space_model_dialog.dart | 12 +++-- .../widgets/subspace_model_create_widget.dart | 49 ++++++++++--------- .../widgets/tag_chips_display_widget.dart | 6 +-- .../views/add_device_type_model_widget.dart | 31 ++++++++++-- 6 files changed, 127 insertions(+), 41 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index c2b3ba9b..182fbe55 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -21,6 +21,7 @@ class AssignTagModelsDialog extends StatelessWidget { final List addedProducts; final List? allTags; final String spaceName; + final String title; const AssignTagModelsDialog({ Key? key, @@ -31,6 +32,7 @@ class AssignTagModelsDialog extends StatelessWidget { this.onTagsAssigned, this.allTags, required this.spaceName, + required this.title }) : super(key: key); @override @@ -52,7 +54,7 @@ class AssignTagModelsDialog extends StatelessWidget { ); return AlertDialog( - title: const Text('Assign Tags'), + title: const Text(title), backgroundColor: ColorsManager.whiteColors, content: SingleChildScrollView( child: Column( @@ -284,7 +286,21 @@ class AssignTagModelsDialog extends StatelessWidget { Expanded( child: CancelButton( label: 'Cancel', - onPressed: () => Navigator.of(context).pop(), + onPressed: () async { + Navigator.of(context).pop(); + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => CreateSpaceModelDialog( + products: products, + allTags: allTags, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + subspaceModels: subspaces, + tags: initialTags), + ), + ); + }, ), ), const SizedBox(width: 10), @@ -313,7 +329,6 @@ class AssignTagModelsDialog extends StatelessWidget { } } } - state.tags.removeWhere(assignedTags.contains); await showDialog( barrierDismissible: false, diff --git a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart index d241b41e..4a290617 100644 --- a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart @@ -2,22 +2,36 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSubSpaceModelDialog extends StatelessWidget { final bool isEdit; final String dialogTitle; final List? existingSubSpaces; + final String? spaceName; + final List? spaceTagModels; + final List? allTags; + final List? products; + final SpaceTemplateModel? spaceModel; const CreateSubSpaceModelDialog({ Key? key, required this.isEdit, required this.dialogTitle, this.existingSubSpaces, + required this.allTags, + required this.spaceName, + required this.spaceTagModels, + required this.products, + required this.spaceModel, }) : super(key: key); @override @@ -153,20 +167,49 @@ class CreateSubSpaceModelDialog extends StatelessWidget { Expanded( child: CancelButton( label: 'Cancel', - onPressed: () { + onPressed: () async { Navigator.of(context).pop(); + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => + CreateSpaceModelDialog( + products: products, + allTags: allTags, + spaceModel: SpaceTemplateModel( + modelName: spaceName ?? '', + subspaceModels: existingSubSpaces, + tags: spaceTagModels, + ), + ), + ); }, ), ), const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: () { + onPressed: () async { final subSpaces = context .read() .state .subSpaces; - Navigator.of(context).pop(subSpaces); + Navigator.of(context).pop(); + + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => + CreateSpaceModelDialog( + products: products, + allTags: allTags, + spaceModel: SpaceTemplateModel( + modelName: spaceName ?? '', + subspaceModels: subSpaces, + tags: spaceTagModels, + ), + ), + ); }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index bd988782..25a8fb63 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -29,7 +29,6 @@ class CreateSpaceModelDialog extends StatelessWidget { final SpaceModelManagementApi _spaceModelApi = SpaceModelManagementApi(); final screenWidth = MediaQuery.of(context).size.width; - List? subspaces = spaceModel?.subspaceModels ?? []; final TextEditingController spaceNameController = TextEditingController( text: spaceModel?.modelName ?? '', ); @@ -104,8 +103,15 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - SubspaceModelCreate(context, - subspaces: state.space.subspaceModels ?? []), + SubspaceModelCreate( + context, + subspaces: state.space.subspaceModels ?? [], + allTags: allTags, + products: products, + spaceModel: spaceModel, + spaceTagModels: spaceModel?.tags ?? [], + spaceNameController: spaceNameController + ), const SizedBox(height: 10), TagChipDisplay(context, screenWidth: screenWidth, diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 35b680a0..31ed8659 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -1,25 +1,34 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; class SubspaceModelCreate extends StatelessWidget { final List subspaces; + final TextEditingController spaceNameController; + final List? spaceTagModels; + final List? allTags; + final List? products; + final SpaceTemplateModel? spaceModel; const SubspaceModelCreate( BuildContext context, { Key? key, required this.subspaces, + this.spaceTagModels, + required this.allTags, + required this.products, + required this.spaceModel, + required this.spaceNameController, }) : super(key: key); @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - return Container( child: subspaces.isEmpty ? TextButton( @@ -29,11 +38,16 @@ class SubspaceModelCreate extends StatelessWidget { onPressed: () async { Navigator.of(context).pop(); - final result = await showDialog>( + await showDialog>( barrierDismissible: false, context: context, builder: (BuildContext context) { return CreateSubSpaceModelDialog( + allTags: allTags, + spaceName: spaceNameController.text, + spaceModel: spaceModel, + spaceTagModels: spaceTagModels, + products: products, isEdit: true, dialogTitle: subspaces.isEmpty ? 'Create Sub-space' @@ -42,14 +56,6 @@ class SubspaceModelCreate extends StatelessWidget { ); }, ); - - if (result != null) { - subspaces.clear(); - subspaces.addAll(result); - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } }, child: const ButtonContentWidget( icon: Icons.add, @@ -90,8 +96,8 @@ class SubspaceModelCreate extends StatelessWidget { ), GestureDetector( onTap: () async { - final result = - await showDialog>( + Navigator.of(context).pop(); + await showDialog>( barrierDismissible: false, context: context, builder: (BuildContext context) { @@ -99,17 +105,14 @@ class SubspaceModelCreate extends StatelessWidget { isEdit: true, dialogTitle: 'Edit Sub-space', existingSubSpaces: subspaces, + allTags: allTags, + spaceName: spaceNameController.text, + spaceTagModels: spaceTagModels, + products: products, + spaceModel: spaceModel, ); }, ); - - if (result != null) { - subspaces.clear(); - subspaces.addAll(result); - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } }, child: Chip( label: const Text( diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index f60e1cf3..f367012d 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -30,8 +30,7 @@ class TagChipDisplay extends StatelessWidget { @override Widget build(BuildContext context) { - return (spaceModel?.subspaceModels?.isNotEmpty == true || - spaceModel?.tags?.isNotEmpty == true || + return (spaceModel?.tags?.isNotEmpty == true || spaceModel?.subspaceModels ?.any((subspace) => subspace.tags?.isNotEmpty == true) == true) @@ -160,7 +159,6 @@ class TagChipDisplay extends StatelessWidget { List? tags, List? subspaces) { final Map productCounts = {}; - // Count products in spaceModel tags if (tags != null) { for (var tag in tags) { if (tag.product != null) { @@ -169,7 +167,6 @@ class TagChipDisplay extends StatelessWidget { } } - // Count products in subspaces if (subspaces != null) { for (var subspace in subspaces) { if (subspace.tags != null) { @@ -183,7 +180,6 @@ class TagChipDisplay extends StatelessWidget { } } - // Create SelectedProduct instances return productCounts.entries .map((entry) => SelectedProduct( productId: entry.key.uuid, diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 74f11194..a8a6b1ac 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -4,8 +4,10 @@ import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart'; @@ -70,7 +72,21 @@ class AddDeviceTypeModelWidget extends StatelessWidget { children: [ CancelButton( label: 'Cancel', - onPressed: () => Navigator.of(context).pop(), + onPressed: () async { + Navigator.of(context).pop(); + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => CreateSpaceModelDialog( + products: products, + allTags: allTags, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + subspaceModels: subspaces, + tags: spaceTagModels, + )), + ); + }, ), ActionButton( label: 'Continue', @@ -82,6 +98,14 @@ class AddDeviceTypeModelWidget extends StatelessWidget { Navigator.of(context).pop(); if (currentState.isNotEmpty) { + final initialTags = generateInitialTags( + spaceTagModels: spaceTagModels, + subspaces: subspaces, + ); + + final dialogTitle = initialTags.isNotEmpty + ? 'Edit Device' + : 'Assign Tags'; await showDialog( barrierDismissible: false, context: context, @@ -91,9 +115,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { addedProducts: currentState, allTags: allTags, spaceName: spaceName, - initialTags: generateInitialTags( - spaceTagModels: spaceTagModels, - subspaces: subspaces), + initialTags: initialTags, + title: dialogTitle, ), ); } From 9b3e4a59afa538c3459aef66f94feb445d3afd27 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 8 Jan 2025 20:07:53 +0400 Subject: [PATCH 049/106] fixed api call --- .../views/assign_tag_models_dialog.dart | 2 +- .../bloc/create_space_model_bloc.dart | 3 --- .../create_space_template_body_model.dart | 11 ++++++---- .../dialog/create_space_model_dialog.dart | 20 ++++++++----------- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 182fbe55..2b2891cb 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -54,7 +54,7 @@ class AssignTagModelsDialog extends StatelessWidget { ); return AlertDialog( - title: const Text(title), + title: Text(title), backgroundColor: ColorsManager.whiteColors, content: SingleChildScrollView( child: Column( diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index d93f68c5..962d30da 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -17,7 +17,6 @@ class CreateSpaceModelBloc try { final spaceTemplate = event.spaceTemplate; - final tagBodyModels = spaceTemplate.tags?.map((tag) => tag.toTagBodyModel()).toList() ?? []; @@ -33,13 +32,11 @@ class CreateSpaceModelBloc ..tags = tagsubspaceBodyModels; }).toList() ?? []; - final spaceModelBody = CreateSpaceTemplateBodyModel( modelName: spaceTemplate.modelName, tags: tagBodyModels, subspaceModels: subspaceTemplateBodyModels); - print(spaceModelBody); final success = await _api.createSpaceModel(spaceModelBody); } catch (e) { print(e); diff --git a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart index 896fe000..e481a8b8 100644 --- a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart +++ b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - class TagBodyModel { late String uuid; late String tag; @@ -22,6 +20,13 @@ class TagBodyModel { class CreateSubspaceTemplateModel { late String subspaceName; late List? tags; + + Map toJson() { + return { + 'subspaceName': subspaceName, + 'tags': tags?.map((tag) => tag.toJson()).toList(), + }; + } } class CreateSpaceTemplateBodyModel { @@ -42,6 +47,4 @@ class CreateSpaceTemplateBodyModel { 'subspaceModels': subspaceModels, }; } - - } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 25a8fb63..b14dca52 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -13,8 +13,6 @@ import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -import '../../models/subspace_template_model.dart'; - class CreateSpaceModelDialog extends StatelessWidget { final List? products; final List? allTags; @@ -46,7 +44,7 @@ class CreateSpaceModelDialog extends StatelessWidget { } else { bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( modelName: '', - subspaceModels: [], + subspaceModels: const [], ))); } @@ -103,15 +101,13 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - SubspaceModelCreate( - context, - subspaces: state.space.subspaceModels ?? [], - allTags: allTags, - products: products, - spaceModel: spaceModel, - spaceTagModels: spaceModel?.tags ?? [], - spaceNameController: spaceNameController - ), + SubspaceModelCreate(context, + subspaces: state.space.subspaceModels ?? [], + allTags: allTags, + products: products, + spaceModel: spaceModel, + spaceTagModels: spaceModel?.tags ?? [], + spaceNameController: spaceNameController), const SizedBox(height: 10), TagChipDisplay(context, screenWidth: screenWidth, From 17e025b69fe26432ad2a3f62712688e0452e7597 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 8 Jan 2025 21:04:03 +0400 Subject: [PATCH 050/106] added empty name validation --- .../bloc/create_space_model_bloc.dart | 38 +++++++++++++------ .../bloc/create_space_model_event.dart | 6 +++ .../bloc/create_space_model_state.dart | 8 +++- .../models/space_template_model.dart | 7 ++-- .../models/subspace_template_model.dart | 7 ++-- .../dialog/create_space_model_dialog.dart | 32 ++++++++++------ lib/utils/color_manager.dart | 5 ++- 7 files changed, 70 insertions(+), 33 deletions(-) diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 962d30da..8c4f81df 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -59,18 +59,6 @@ class CreateSpaceModelBloc emit(CreateSpaceModelLoaded(_space!)); }); - on((event, emit) { - final currentState = state; - - if (currentState is CreateSpaceModelLoaded) { - final updatedSpaceModel = - currentState.space.copyWith(modelName: event.name); - emit(CreateSpaceModelLoaded(updatedSpaceModel)); - } else { - emit(CreateSpaceModelError("Space template not initialized")); - } - }); - on((event, emit) { final currentState = state; @@ -86,5 +74,31 @@ class CreateSpaceModelBloc emit(CreateSpaceModelError("Space template not initialized")); } }); + + on((event, emit) { + final currentState = state; + print('Current State: $currentState'); + if (currentState is CreateSpaceModelLoaded) { + if (event.name.trim().isEmpty) { + print("set error message"); + + emit(CreateSpaceModelLoaded( + currentState.space, + errorMessage: "Model name cannot be empty", + )); + print('State emitted: CreateSpaceModelLoaded with updated model:'); + + } else { + final updatedSpaceModel = + currentState.space.copyWith(modelName: event.name); + print( + 'State emitted: CreateSpaceModelLoaded with updated model: $updatedSpaceModel'); + + emit(CreateSpaceModelLoaded(updatedSpaceModel)); + } + } else { + emit(CreateSpaceModelError("Space template not initialized")); + } + }); } } diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index 7944a8a8..e4288837 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -42,3 +42,9 @@ class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { AddSubspacesToSpaceTemplate(this.subspaces); } + +class ValidateSpaceTemplateName extends CreateSpaceModelEvent { + final String name; + + ValidateSpaceTemplateName({required this.name}); +} \ No newline at end of file diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart index c05e8744..0fc5c48d 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart @@ -5,7 +5,7 @@ abstract class CreateSpaceModelState extends Equatable { const CreateSpaceModelState(); @override - List get props => []; + List get props => []; } class CreateSpaceModelInitial extends CreateSpaceModelState {} @@ -14,8 +14,12 @@ class CreateSpaceModelLoading extends CreateSpaceModelState {} class CreateSpaceModelLoaded extends CreateSpaceModelState { final SpaceTemplateModel space; + final String? errorMessage; - CreateSpaceModelLoaded(this.space); + CreateSpaceModelLoaded(this.space, {this.errorMessage}); + + @override + List get props => [space, errorMessage]; } class CreateSpaceModelError extends CreateSpaceModelState { diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart index f62bbcf6..dea68dbc 100644 --- a/lib/pages/spaces_management/space_model/models/space_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -34,9 +34,10 @@ class SpaceTemplateModel extends Equatable { ?.map((e) => SubspaceTemplateModel.fromJson(e)) .toList() ?? [], - tags: (json['tags'] as List) - .map((item) => TagModel.fromJson(item)) - .toList(), + tags: (json['tags'] as List?) + ?.map((item) => TagModel.fromJson(item)) + .toList() ?? + [], ); } diff --git a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart index 119728db..ac71c6b1 100644 --- a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart @@ -18,9 +18,10 @@ class SubspaceTemplateModel { uuid: json['uuid'] ?? '', subspaceName: json['subspaceName'] ?? '', disabled: json['disabled'] ?? false, - tags: (json['tags'] as List) - .map((item) => TagModel.fromJson(item)) - .toList(), + tags: (json['tags'] as List?) + ?.map((item) => TagModel.fromJson(item)) + .toList() ?? + [], ); } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index b14dca52..01e65039 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -61,6 +61,7 @@ class CreateSpaceModelDialog extends StatelessWidget { } else if (state is CreateSpaceModelLoaded) { final updatedSpaceModel = state.space; final subspaces = updatedSpaceModel.subspaceModels ?? []; + final isNameValid = spaceNameController.text.trim().isNotEmpty; return Column( mainAxisSize: MainAxisSize.min, @@ -87,6 +88,7 @@ class CreateSpaceModelDialog extends StatelessWidget { filled: true, fillColor: ColorsManager.textFieldGreyColor, hintText: 'Please enter the name', + errorText: state.errorMessage, hintStyle: const TextStyle( color: ColorsManager.lightGrayColor), border: OutlineInputBorder( @@ -130,19 +132,26 @@ class CreateSpaceModelDialog extends StatelessWidget { const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: () { - final updatedSpaceTemplate = - updatedSpaceModel.copyWith( - modelName: spaceNameController.text.trim(), - ); - context.read().add( - CreateSpaceTemplate( - spaceTemplate: updatedSpaceTemplate), - ); - }, + onPressed: state.errorMessage == null || + isNameValid + ? () { + final updatedSpaceTemplate = + updatedSpaceModel.copyWith( + modelName: + spaceNameController.text.trim(), + ); + context.read().add( + CreateSpaceTemplate( + spaceTemplate: + updatedSpaceTemplate), + ); + } + : null, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: ColorsManager.whiteColors, + foregroundColor: isNameValid + ? ColorsManager.whiteColors + : ColorsManager.whiteColorsWithOpacity, child: const Text('OK'), ), ), @@ -158,7 +167,6 @@ class CreateSpaceModelDialog extends StatelessWidget { ); } - // Default case (e.g., CreateSpaceModelInitial) return const Center(child: Text('Initializing...')); }, ), diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index eaf1e6b1..60aad8d5 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -5,8 +5,11 @@ abstract class ColorsManager { static const Color switchOffColor = Color(0x7F8D99AE); static const Color primaryColor = Color(0xFF0030CB); //023DFE static const Color secondaryTextColor = Color(0xFF848484); - static Color primaryColorWithOpacity = const Color(0xFF023DFE).withOpacity(0.6); + static Color primaryColorWithOpacity = + const Color(0xFF023DFE).withOpacity(0.6); static const Color whiteColors = Colors.white; + static Color whiteColorsWithOpacity = Colors.white.withOpacity(0.6); + static const Color secondaryColor = Color(0xFF023DFE); static const Color onSecondaryColor = Color(0xFF023DFE); static Color shadowBlackColor = Colors.black.withOpacity(0.2); From 48c064c71102aac528cf11b94f5535d1f35f4b90 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 8 Jan 2025 21:06:31 +0400 Subject: [PATCH 051/106] added create --- .../space_model/bloc/create_space_model_bloc.dart | 7 +++---- .../widgets/dialog/create_space_model_dialog.dart | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 8c4f81df..799d1e27 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -37,9 +37,9 @@ class CreateSpaceModelBloc tags: tagBodyModels, subspaceModels: subspaceTemplateBodyModels); - final success = await _api.createSpaceModel(spaceModelBody); + await _api.createSpaceModel(spaceModelBody); } catch (e) { - print(e); + emit(CreateSpaceModelError('Error creating space model')); } }); @@ -86,8 +86,7 @@ class CreateSpaceModelBloc currentState.space, errorMessage: "Model name cannot be empty", )); - print('State emitted: CreateSpaceModelLoaded with updated model:'); - + print('State emitted: CreateSpaceModelLoaded with updated model:'); } else { final updatedSpaceModel = currentState.space.copyWith(modelName: event.name); diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 01e65039..a3b9907d 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -145,6 +145,8 @@ class CreateSpaceModelDialog extends StatelessWidget { spaceTemplate: updatedSpaceTemplate), ); + + Navigator.of(context).pop(); } : null, backgroundColor: ColorsManager.secondaryColor, From 339a242e743551b188ca79948d6d47816f067d26 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 00:07:51 +0400 Subject: [PATCH 052/106] added load new space models --- .../widgets/loaded_space_widget.dart | 17 ++- .../bloc/create_space_model_bloc.dart | 20 +-- .../bloc/create_space_model_event.dart | 4 +- .../space_model/bloc/space_model_bloc.dart | 36 ++++++ .../space_model/bloc/space_model_event.dart | 18 +++ .../space_model/bloc/space_model_state.dart | 29 +++++ .../models/space_template_model.dart | 10 +- .../space_model/view/space_model_page.dart | 122 +++++++++++------- .../dialog/create_space_model_dialog.dart | 23 +++- lib/services/api/http_service.dart | 21 ++- lib/services/space_model_mang_api.dart | 22 +++- lib/utils/constants/api_const.dart | 1 + 12 files changed, 249 insertions(+), 74 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart create mode 100644 lib/pages/spaces_management/space_model/bloc/space_model_event.dart create mode 100644 lib/pages/spaces_management/space_model/bloc/space_model_state.dart diff --git a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart index 5039340c..84ed32f7 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart @@ -1,12 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/gradient_canvas_border_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart'; +import 'package:syncrow_web/services/space_model_mang_api.dart'; class LoadedSpaceView extends StatefulWidget { final List communities; @@ -47,10 +50,16 @@ class _LoadedStateViewState extends State { ), hasSpaceModels ? Expanded( - child: SpaceModelPage( - spaceModels: widget.spaceModels ?? [], - products: widget.products, - )) + child: BlocProvider( + create: (context) => SpaceModelBloc( + api: SpaceModelManagementApi(), + initialSpaceModels: widget.spaceModels ?? [], + ), + child: SpaceModelPage( + products: widget.products, + ), + ), + ) : CommunityStructureArea( selectedCommunity: widget.selectedCommunity, selectedSpace: widget.selectedSpace, diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 799d1e27..8055e217 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -15,7 +15,7 @@ class CreateSpaceModelBloc CreateSpaceModelBloc(this._api) : super(CreateSpaceModelInitial()) { on((event, emit) async { try { - final spaceTemplate = event.spaceTemplate; + late SpaceTemplateModel spaceTemplate = event.spaceTemplate; final tagBodyModels = spaceTemplate.tags?.map((tag) => tag.toTagBodyModel()).toList() ?? @@ -37,7 +37,16 @@ class CreateSpaceModelBloc tags: tagBodyModels, subspaceModels: subspaceTemplateBodyModels); - await _api.createSpaceModel(spaceModelBody); + final newSpaceTemplate = await _api.createSpaceModel(spaceModelBody); + spaceTemplate.uuid = newSpaceTemplate?.uuid ?? ''; + + if (newSpaceTemplate != null) { + emit(CreateSpaceModelLoaded(spaceTemplate)); + + if (event.onCreate != null) { + event.onCreate!(spaceTemplate); + } + } } catch (e) { emit(CreateSpaceModelError('Error creating space model')); } @@ -77,22 +86,17 @@ class CreateSpaceModelBloc on((event, emit) { final currentState = state; - print('Current State: $currentState'); if (currentState is CreateSpaceModelLoaded) { if (event.name.trim().isEmpty) { - print("set error message"); emit(CreateSpaceModelLoaded( currentState.space, errorMessage: "Model name cannot be empty", )); - print('State emitted: CreateSpaceModelLoaded with updated model:'); } else { final updatedSpaceModel = currentState.space.copyWith(modelName: event.name); - print( - 'State emitted: CreateSpaceModelLoaded with updated model: $updatedSpaceModel'); - + emit(CreateSpaceModelLoaded(updatedSpaceModel)); } } else { diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index e4288837..9342c771 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -19,9 +19,11 @@ class UpdateSpaceTemplate extends CreateSpaceModelEvent { class CreateSpaceTemplate extends CreateSpaceModelEvent { final SpaceTemplateModel spaceTemplate; + final Function(SpaceTemplateModel)? onCreate; const CreateSpaceTemplate({ required this.spaceTemplate, + this.onCreate, }); @override @@ -47,4 +49,4 @@ class ValidateSpaceTemplateName extends CreateSpaceModelEvent { final String name; ValidateSpaceTemplateName({required this.name}); -} \ No newline at end of file +} diff --git a/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart new file mode 100644 index 00000000..e383610d --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart @@ -0,0 +1,36 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/services/space_model_mang_api.dart'; + +class SpaceModelBloc extends Bloc { + final SpaceModelManagementApi api; + + SpaceModelBloc({ + required this.api, + required List initialSpaceModels, + }) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) { + on(_onCreateSpaceModel); + } + + Future _onCreateSpaceModel( + CreateSpaceModel event, Emitter emit) async { + final currentState = state; + if (currentState is SpaceModelLoaded) { + try { + final newSpaceModel = + await api.getSpaceModel(event.newSpaceModel.uuid ?? ''); + + if (newSpaceModel != null) { + final updatedSpaceModels = + List.from(currentState.spaceModels) + ..add(newSpaceModel); + emit(SpaceModelLoaded(spaceModels: updatedSpaceModels)); + } + } catch (e) { + emit(SpaceModelError(message: e.toString())); + } + } + } +} diff --git a/lib/pages/spaces_management/space_model/bloc/space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/space_model_event.dart new file mode 100644 index 00000000..78331f3c --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/space_model_event.dart @@ -0,0 +1,18 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +abstract class SpaceModelEvent extends Equatable { + @override + List get props => []; +} + +class LoadSpaceModels extends SpaceModelEvent {} + +class CreateSpaceModel extends SpaceModelEvent { + final SpaceTemplateModel newSpaceModel; + + CreateSpaceModel({required this.newSpaceModel}); + + @override + List get props => [newSpaceModel]; +} diff --git a/lib/pages/spaces_management/space_model/bloc/space_model_state.dart b/lib/pages/spaces_management/space_model/bloc/space_model_state.dart new file mode 100644 index 00000000..53adf973 --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/space_model_state.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +abstract class SpaceModelState extends Equatable { + @override + List get props => []; +} + +class SpaceModelInitial extends SpaceModelState {} + +class SpaceModelLoading extends SpaceModelState {} + +class SpaceModelLoaded extends SpaceModelState { + final List spaceModels; + + SpaceModelLoaded({required this.spaceModels}); + + @override + List get props => [spaceModels]; +} + +class SpaceModelError extends SpaceModelState { + final String message; + + SpaceModelError({required this.message}); + + @override + List get props => [message]; +} diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart index dea68dbc..4f762c9a 100644 --- a/lib/pages/spaces_management/space_model/models/space_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -6,7 +6,7 @@ import 'package:syncrow_web/utils/constants/action_enum.dart'; import 'package:uuid/uuid.dart'; class SpaceTemplateModel extends Equatable { - final String? uuid; + String? uuid; String modelName; List? subspaceModels; final List? tags; @@ -31,16 +31,18 @@ class SpaceTemplateModel extends Equatable { internalId: internalId, modelName: json['modelName'] ?? '', subspaceModels: (json['subspaceModels'] as List?) - ?.map((e) => SubspaceTemplateModel.fromJson(e)) + ?.where((e) => e is Map) // Validate type + .map((e) => + SubspaceTemplateModel.fromJson(e as Map)) .toList() ?? [], tags: (json['tags'] as List?) - ?.map((item) => TagModel.fromJson(item)) + ?.where((item) => item is Map) // Validate type + .map((item) => TagModel.fromJson(item as Map)) .toList() ?? [], ); } - SpaceTemplateModel copyWith({ String? uuid, String? modelName, diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 6fecd125..9838282c 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -1,61 +1,90 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class SpaceModelPage extends StatelessWidget { - final List spaceModels; final List? products; - const SpaceModelPage({Key? key, required this.spaceModels, this.products}) - : super(key: key); + const SpaceModelPage({Key? key, this.products}) : super(key: key); @override Widget build(BuildContext context) { - final allTagValues = getAllTagValues(); - return Scaffold( - backgroundColor: ColorsManager.whiteColors, - body: Padding( - padding: const EdgeInsets.fromLTRB(20.0, 16.0, 16.0, 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 10.0, - mainAxisSpacing: 10.0, - childAspectRatio: calculateChildAspectRatio(context), - ), - itemCount: spaceModels.length + 1, - itemBuilder: (context, index) { - if (index == spaceModels.length) { - return GestureDetector( - onTap: () { - showDialog( - context: context, - builder: (BuildContext context) { - return CreateSpaceModelDialog( - products: products, - allTags: allTagValues, - ); - }, - ); + return BlocBuilder( + builder: (context, state) { + if (state is SpaceModelLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is SpaceModelLoaded) { + final spaceModels = state.spaceModels; + final allTagValues = _getAllTagValues(spaceModels); + + return Scaffold( + backgroundColor: ColorsManager.whiteColors, + body: Padding( + padding: const EdgeInsets.fromLTRB(20.0, 16.0, 16.0, 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0, + childAspectRatio: _calculateChildAspectRatio(context), + ), + itemCount: spaceModels.length + 1, + itemBuilder: (context, index) { + if (index == spaceModels.length) { + // Add Button + return GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return CreateSpaceModelDialog( + products: products, + allTags: allTagValues, + onLoad: (newModel) { + + context.read().add( + CreateSpaceModel( + newSpaceModel: newModel), + ); + }, + ); + }, + ); + }, + child: _buildAddContainer(), + ); + } + // Render existing space model + final model = spaceModels[index]; + return SpaceModelCardWidget(model: model); }, - child: _buildAddContainer(), - ); - } - final model = spaceModels[index]; - return SpaceModelCardWidget(model: model); - }, + ), + ), + ], ), ), - ], - ), - ), + ); + } else if (state is SpaceModelError) { + return Center( + child: Text( + 'Error: ${state.message}', + style: const TextStyle(color: Colors.red), + ), + ); + } + return const Center(child: Text('Initializing...')); + }, ); } @@ -101,23 +130,22 @@ class SpaceModelPage extends StatelessWidget { ); } - double calculateChildAspectRatio(BuildContext context) { + double _calculateChildAspectRatio(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; if (screenWidth > 1600) { return 3; } if (screenWidth > 1200) { - return 5; + return 5; } else if (screenWidth > 800) { return 5; } else { - return 6.0; + return 6.0; } } - List getAllTagValues() { + List _getAllTagValues(List spaceModels) { final List allTags = []; - for (final spaceModel in spaceModels) { if (spaceModel.tags != null) { allTags.addAll(spaceModel.listAllTagValues()); diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index a3b9907d..88a23bcf 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -17,10 +17,15 @@ class CreateSpaceModelDialog extends StatelessWidget { final List? products; final List? allTags; final SpaceTemplateModel? spaceModel; + final void Function(SpaceTemplateModel newModel)? onLoad; - const CreateSpaceModelDialog( - {Key? key, this.products, this.allTags, this.spaceModel}) - : super(key: key); + const CreateSpaceModelDialog({ + Key? key, + this.products, + this.allTags, + this.spaceModel, + this.onLoad, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -142,11 +147,15 @@ class CreateSpaceModelDialog extends StatelessWidget { ); context.read().add( CreateSpaceTemplate( - spaceTemplate: - updatedSpaceTemplate), + spaceTemplate: + updatedSpaceTemplate, + onCreate: (newModel) { + onLoad!(newModel); + Navigator.of(context) + .pop(); // Close the dialog + }, + ), ); - - Navigator.of(context).pop(); } : null, backgroundColor: ColorsManager.secondaryColor, diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart index 2ed3da42..ee4584c9 100644 --- a/lib/services/api/http_service.dart +++ b/lib/services/api/http_service.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; import 'package:syncrow_web/services/api/http_interceptor.dart'; import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -34,13 +35,29 @@ class HTTPService { bool showServerMessage = true, }) async { try { + // Log the request path and query parameters + debugPrint('GET Request: $path'); + if (queryParameters != null) { + debugPrint('Query Parameters: $queryParameters'); + } + + // Perform the HTTP GET request final response = await client.get( path, queryParameters: queryParameters, ); - return expectedResponseModel(response.data); + + // Log the raw response data + debugPrint('Response Data: ${response.data}'); + + // Process the response using the expected model function + final result = expectedResponseModel(response.data); + + return result; } catch (error) { - rethrow; + // Log the error details + debugPrint('Error in GET Request: $error'); + rethrow; // Re-throw the error to propagate it further } } diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index 82ed097f..19752aea 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -50,7 +50,27 @@ class SpaceModelManagementApi { ); return response; } catch (e) { - debugPrint('Error creating community: $e'); + debugPrint('Error creating space model: $e'); + return null; + } + } + + Future getSpaceModel(String spaceModelUuid) async { + try { + final response = await HTTPService().get( + path: ApiEndpoints.getSpaceModel + .replaceAll('{projectId}', TempConst.projectId) + .replaceAll('{spaceModelUuid}', spaceModelUuid), + showServerMessage: true, + expectedResponseModel: (json) { + debugPrint('Response JSON: $json'); + + return SpaceTemplateModel.fromJson(json['data']); + }, + ); + return response; + } catch (e) { + debugPrint('Error getting space model: $e'); return null; } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index ca4527fb..a2ff63dc 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -100,4 +100,5 @@ abstract class ApiEndpoints { //space model static const String listSpaceModels = '/projects/{projectId}/space-models'; static const String createSpaceModel = '/projects/{projectId}/space-models'; + static const String getSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}'; } From d0b853b188536b95f12a6c204a2c48b0a7115108 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 12:58:42 +0400 Subject: [PATCH 053/106] revert back http --- .../space_model/view/space_model_page.dart | 17 ++++--- lib/services/api/http_service.dart | 45 ++++--------------- 2 files changed, 19 insertions(+), 43 deletions(-) diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 9838282c..ea9a9cee 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -52,7 +52,6 @@ class SpaceModelPage extends StatelessWidget { products: products, allTags: allTagValues, onLoad: (newModel) { - context.read().add( CreateSpaceModel( newSpaceModel: newModel), @@ -67,7 +66,10 @@ class SpaceModelPage extends StatelessWidget { } // Render existing space model final model = spaceModels[index]; - return SpaceModelCardWidget(model: model); + return Container( + margin: const EdgeInsets.all(8.0), + child: SpaceModelCardWidget(model: model), + ); }, ), ), @@ -90,6 +92,7 @@ class SpaceModelPage extends StatelessWidget { Widget _buildAddContainer() { return Container( + margin: const EdgeInsets.all(8.0), decoration: BoxDecoration( color: ColorsManager.whiteColors, borderRadius: BorderRadius.circular(20), @@ -111,7 +114,7 @@ class SpaceModelPage extends StatelessWidget { child: Center( child: Container( width: 60, - height: 60, + height: 0, decoration: BoxDecoration( shape: BoxShape.circle, color: ColorsManager.neutralGray, @@ -133,14 +136,14 @@ class SpaceModelPage extends StatelessWidget { double _calculateChildAspectRatio(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; if (screenWidth > 1600) { - return 3; + return 2; // Taller cards for larger screens } if (screenWidth > 1200) { - return 5; + return 3; // Adjusted height for medium screens } else if (screenWidth > 800) { - return 5; + return 3.5; // Adjusted height for smaller screens } else { - return 6.0; + return 4.0; // Default ratio for smallest screens } } diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart index ee4584c9..b75f05cf 100644 --- a/lib/services/api/http_service.dart +++ b/lib/services/api/http_service.dart @@ -1,7 +1,4 @@ -import 'dart:convert'; - import 'package:dio/dio.dart'; -import 'package:flutter/material.dart'; import 'package:syncrow_web/services/api/http_interceptor.dart'; import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -35,56 +32,32 @@ class HTTPService { bool showServerMessage = true, }) async { try { - // Log the request path and query parameters - debugPrint('GET Request: $path'); - if (queryParameters != null) { - debugPrint('Query Parameters: $queryParameters'); - } - - // Perform the HTTP GET request final response = await client.get( path, queryParameters: queryParameters, ); - - // Log the raw response data - debugPrint('Response Data: ${response.data}'); - - // Process the response using the expected model function - final result = expectedResponseModel(response.data); - - return result; + return expectedResponseModel(response.data); } catch (error) { - // Log the error details - debugPrint('Error in GET Request: $error'); - rethrow; // Re-throw the error to propagate it further + rethrow; } } - Future post({ - required String path, - Map? queryParameters, - Options? options, - dynamic body, - bool showServerMessage = true, - required T Function(dynamic) expectedResponseModel, - }) async { + Future post( + {required String path, + Map? queryParameters, + Options? options, + dynamic body, + bool showServerMessage = true, + required T Function(dynamic) expectedResponseModel}) async { try { - final bodyString = body is Map || body is List - ? jsonEncode(body) - : body?.toString() ?? 'null'; - - print("POST Request: $path, Body: $bodyString, Query: $queryParameters"); final response = await client.post( path, data: body, queryParameters: queryParameters, options: options, ); - print("POST Response: ${response.data.toString()}"); return expectedResponseModel(response.data); } catch (error) { - print("POST Error: $error"); rethrow; } } From 625b7b8304358902f22f2459a898bbdafadea2a5 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 13:03:25 +0400 Subject: [PATCH 054/106] revert back --- .env.development | 2 +- .gitignore | 3 --- lib/utils/constants/temp_const.dart | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.env.development b/.env.development index 1fd358ec..e77609dc 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,2 @@ ENV_NAME=development -BASE_URL=http://localhost:4001 \ No newline at end of file +BASE_URL=https://syncrow-dev.azurewebsites.net \ No newline at end of file diff --git a/.gitignore b/.gitignore index ae866139..29a3a501 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,3 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release -lib/utils/constants/temp_const.dart -.env.development -lib/utils/constants/temp_const.dart diff --git a/lib/utils/constants/temp_const.dart b/lib/utils/constants/temp_const.dart index bcd1a1d6..e5847b98 100644 --- a/lib/utils/constants/temp_const.dart +++ b/lib/utils/constants/temp_const.dart @@ -1,3 +1,3 @@ class TempConst { - static const projectId = '0685c781-df33-4cbf-bf65-9f4e835eb468'; + static const projectId = '0e62577c-06fa-41b9-8a92-99a21fbaf51c'; } From 097e70b906d4b431f0f76d2ae804269b8210855a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 13:32:09 +0400 Subject: [PATCH 055/106] color manager --- .../widgets/add_device_type_widget.dart | 2 +- .../community_structure_header_widget.dart | 2 +- .../widgets/dialogs/create_space_dialog.dart | 6 +-- .../widgets/dialogs/delete_dialogue.dart | 8 +-- .../dialogs/icon_selection_dialog.dart | 2 +- .../widgets/plus_button_widget.dart | 2 +- .../all_spaces/widgets/sidebar_widget.dart | 2 +- .../widgets/space_container_widget.dart | 2 +- .../all_spaces/widgets/space_widget.dart | 7 +-- .../views/assign_tag_models_dialog.dart | 8 +-- .../space_model/view/space_model_page.dart | 48 ++---------------- .../widgets/add_space_model_widget.dart | 50 +++++++++++++++++++ .../dialog/create_space_model_dialog.dart | 2 +- .../widgets/space_model_card_widget.dart | 4 +- .../widgets/subspace_chip_widget.dart | 2 +- .../widgets/subspace_model_create_widget.dart | 10 ++-- .../widgets/tag_chips_display_widget.dart | 10 ++-- 17 files changed, 88 insertions(+), 79 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/widgets/add_space_model_widget.dart diff --git a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart index 93a38716..351eacce 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart @@ -99,7 +99,7 @@ class _AddDeviceWidgetState extends State { _buildActionButton('Cancel', ColorsManager.boxColor, ColorsManager.blackColor, () { Navigator.of(context).pop(); }), - _buildActionButton('Continue', ColorsManager.secondaryColor, Colors.white, () { + _buildActionButton('Continue', ColorsManager.secondaryColor, ColorsManager.whiteColors, () { Navigator.of(context).pop(); if (widget.onProductsSelected != null) { widget.onProductsSelected!(productCounts); diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart index 63306581..612f9101 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart @@ -178,7 +178,7 @@ class _CommunityStructureHeaderState extends State { padding: 2.0, height: buttonHeight, elevation: 0, - borderColor: Colors.grey.shade300, + borderColor: ColorsManager.lightGrayColor, child: Row( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index f6a02833..222881a8 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -104,7 +104,7 @@ class CreateSpaceDialogState extends State { width: screenWidth * 0.020, height: screenWidth * 0.020, decoration: const BoxDecoration( - color: Colors.white, + color: ColorsManager.whiteColors, shape: BoxShape.circle, ), child: SvgPicture.asset( @@ -143,7 +143,7 @@ class CreateSpaceDialogState extends State { } }); }, - style: const TextStyle(color: Colors.black), + style: const TextStyle(color: ColorsManager.blackColor), decoration: InputDecoration( hintText: 'Please enter the name', hintStyle: const TextStyle( @@ -212,7 +212,7 @@ class CreateSpaceDialogState extends State { ); }, backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, + foregroundColor: ColorsManager.blackColor, borderColor: ColorsManager.neutralGray, borderRadius: 16.0, padding: 10.0, // Reduced padding for smaller size diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/delete_dialogue.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/delete_dialogue.dart index 8607f9e0..27275be1 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/delete_dialogue.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/delete_dialogue.dart @@ -40,8 +40,8 @@ void showDeleteConfirmationDialog(BuildContext context, VoidCallback onConfirm, Navigator.of(context).pop(); // Close the first dialog showProcessingPopup(context, isSpace, onConfirm); }, - style: _dialogButtonStyle(Colors.blue), - child: const Text('Continue', style: TextStyle(color: Colors.white)), + style: _dialogButtonStyle(ColorsManager.spaceColor), + child: const Text('Continue', style: TextStyle(color: ColorsManager.whiteColors)), ), ], ), @@ -83,7 +83,7 @@ void showProcessingPopup(BuildContext context, bool isSpace, VoidCallback onDele ElevatedButton( onPressed: onDelete, style: _dialogButtonStyle(ColorsManager.warningRed), - child: const Text('Delete', style: TextStyle(color: Colors.white)), + child: const Text('Delete', style: TextStyle(color: ColorsManager.whiteColors)), ), CancelButton( label: 'Cancel', @@ -108,7 +108,7 @@ Widget _buildWarningIcon() { color: ColorsManager.warningRed, shape: BoxShape.circle, ), - child: const Icon(Icons.close, color: Colors.white, size: 40), + child: const Icon(Icons.close, color: ColorsManager.whiteColors, size: 40), ); } diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart index 5251ba32..b2a01988 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart @@ -28,7 +28,7 @@ class IconSelectionDialog extends StatelessWidget { borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.2), // Shadow color + color: ColorsManager.blackColor.withOpacity(0.2), // Shadow color blurRadius: 20, // Spread of the blur offset: const Offset(0, 8), // Offset of the shadow ), diff --git a/lib/pages/spaces_management/all_spaces/widgets/plus_button_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/plus_button_widget.dart index b077ac9d..40be7284 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/plus_button_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/plus_button_widget.dart @@ -45,7 +45,7 @@ class PlusButtonWidget extends StatelessWidget { color: ColorsManager.spaceColor, shape: BoxShape.circle, ), - child: const Icon(Icons.add, color: Colors.white, size: 20), + child: const Icon(Icons.add, color: ColorsManager.whiteColors, size: 20), ), ), ); diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 2d557e25..da67e6ed 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -120,7 +120,7 @@ class _SidebarWidgetState extends State { children: [ Text('Communities', style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: Colors.black, + color: ColorsManager.blackColor, )), GestureDetector( onTap: () => _navigateToBlank(context), diff --git a/lib/pages/spaces_management/all_spaces/widgets/space_container_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/space_container_widget.dart index 78af5f10..6f52eb50 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/space_container_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/space_container_widget.dart @@ -78,7 +78,7 @@ class SpaceContainerWidget extends StatelessWidget { borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( - color: Colors.grey.withOpacity(0.5), + color: ColorsManager.lightGrayColor.withOpacity(0.5), spreadRadius: 2, blurRadius: 5, offset: const Offset(0, 3), // Shadow position diff --git a/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart index e4ce27cc..6e1f50c1 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; class SpaceWidget extends StatelessWidget { final String name; @@ -23,11 +24,11 @@ class SpaceWidget extends StatelessWidget { Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( - color: Colors.white, + color: ColorsManager.whiteColors, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( - color: Colors.grey.withOpacity(0.5), + color: ColorsManager.lightGrayColor.withOpacity(0.5), spreadRadius: 5, blurRadius: 7, offset: const Offset(0, 3), @@ -36,7 +37,7 @@ class SpaceWidget extends StatelessWidget { ), child: Row( children: [ - const Icon(Icons.location_on, color: Colors.blue), + const Icon(Icons.location_on, color: ColorsManager.spaceColor), const SizedBox(width: 8), Text(name, style: const TextStyle(fontSize: 16)), ], diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 2b2891cb..f4f2276f 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -96,7 +96,7 @@ class AssignTagModelsDialog extends StatelessWidget { 'No Data Available', style: TextStyle( fontSize: 14, - color: Colors.grey, + color: ColorsManager.lightGrayColor, ), ), ), @@ -159,7 +159,7 @@ class AssignTagModelsDialog extends StatelessWidget { ), style: const TextStyle( fontSize: 14, - color: Colors.black, + color: ColorsManager.blackColor, ), ), ), @@ -172,7 +172,7 @@ class AssignTagModelsDialog extends StatelessWidget { color: ColorsManager.whiteColors, icon: const Icon( Icons.arrow_drop_down, - color: Colors.black), + color: ColorsManager.blackColor), onSelected: (value) { controller.text = value; context @@ -273,7 +273,7 @@ class AssignTagModelsDialog extends StatelessWidget { if (state.errorMessage != null) Text( state.errorMessage!, - style: const TextStyle(color: Colors.red), + style: const TextStyle(color: ColorsManager.warningRed), ), ], ), diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index ea9a9cee..6b9818c9 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/add_space_model_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -61,7 +62,7 @@ class SpaceModelPage extends StatelessWidget { }, ); }, - child: _buildAddContainer(), + child: const AddSpaceModelWidget(), ); } // Render existing space model @@ -81,7 +82,7 @@ class SpaceModelPage extends StatelessWidget { return Center( child: Text( 'Error: ${state.message}', - style: const TextStyle(color: Colors.red), + style: const TextStyle(color: ColorsManager.warningRed), ), ); } @@ -90,49 +91,6 @@ class SpaceModelPage extends StatelessWidget { ); } - Widget _buildAddContainer() { - return Container( - margin: const EdgeInsets.all(8.0), - decoration: BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.circular(20), - boxShadow: const [ - BoxShadow( - color: ColorsManager.semiTransparentBlackColor, - blurRadius: 15, - offset: Offset(0, 4), - spreadRadius: 0, - ), - BoxShadow( - color: ColorsManager.semiTransparentBlackColor, - blurRadius: 25, - offset: Offset(0, 15), - spreadRadius: -5, - ), - ], - ), - child: Center( - child: Container( - width: 60, - height: 0, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: ColorsManager.neutralGray, - border: Border.all( - color: ColorsManager.textFieldGreyColor, - width: 2.0, - ), - ), - child: const Icon( - Icons.add, - size: 40, - color: ColorsManager.spaceColor, - ), - ), - ), - ); - } - double _calculateChildAspectRatio(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; if (screenWidth > 1600) { diff --git a/lib/pages/spaces_management/space_model/widgets/add_space_model_widget.dart b/lib/pages/spaces_management/space_model/widgets/add_space_model_widget.dart new file mode 100644 index 00000000..20876a39 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/add_space_model_widget.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class AddSpaceModelWidget extends StatelessWidget { + const AddSpaceModelWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(20), + boxShadow: const [ + BoxShadow( + color: ColorsManager.semiTransparentBlackColor, + blurRadius: 15, + offset: Offset(0, 4), + spreadRadius: 0, + ), + BoxShadow( + color: ColorsManager.semiTransparentBlackColor, + blurRadius: 25, + offset: Offset(0, 15), + spreadRadius: -5, + ), + ], + ), + child: Center( + child: Container( + width: 60, + height: 60, // Set a proper height here + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.neutralGray, + border: Border.all( + color: ColorsManager.textFieldGreyColor, + width: 2.0, + ), + ), + child: const Icon( + Icons.add, + size: 40, + color: ColorsManager.spaceColor, + ), + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 88a23bcf..51222ad8 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -174,7 +174,7 @@ class CreateSpaceModelDialog extends StatelessWidget { } else if (state is CreateSpaceModelError) { return Text( 'Error: ${state.message}', - style: const TextStyle(color: Colors.red), + style: const TextStyle(color: ColorsManager.warningRed), ); } diff --git a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart index 208b0893..4471743b 100644 --- a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart @@ -33,11 +33,11 @@ class SpaceModelCardWidget extends StatelessWidget { return Container( decoration: BoxDecoration( - color: Colors.white, + color: ColorsManager.whiteColors, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( - color: Colors.grey.withOpacity(0.5), + color: ColorsManager.lightGrayColor.withOpacity(0.5), spreadRadius: 2, blurRadius: 5, offset: const Offset(0, 3), diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart index debe3801..517448c8 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart @@ -25,7 +25,7 @@ class SubspaceChipWidget extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: const BorderSide( - color: Colors.transparent, + color: ColorsManager.transparentColor, width: 0, ), ), diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 31ed8659..22444c3d 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -85,7 +85,7 @@ class SubspaceModelCreate extends StatelessWidget { style: const TextStyle( color: ColorsManager.spaceColor), // Text color ), - backgroundColor: Colors.white, // Chip background color + backgroundColor: ColorsManager.whiteColors, // Chip background color shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), // Rounded chip @@ -118,15 +118,15 @@ class SubspaceModelCreate extends StatelessWidget { label: const Text( 'Edit', style: TextStyle( - color: ColorsManager.spaceColor), // Text color + color: ColorsManager.spaceColor), ), backgroundColor: - Colors.white, // Background color for "Edit" + ColorsManager.whiteColors, shape: RoundedRectangleBorder( borderRadius: - BorderRadius.circular(16), // Rounded chip + BorderRadius.circular(16), side: const BorderSide( - color: ColorsManager.spaceColor), // Border color + color: ColorsManager.spaceColor), ), ), ), diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index f367012d..9cb356ef 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -71,7 +71,7 @@ class TagChipDisplay extends StatelessWidget { color: ColorsManager.spaceColor, ), ), - backgroundColor: Colors.white, + backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: const BorderSide( @@ -104,14 +104,14 @@ class TagChipDisplay extends StatelessWidget { label: const Text( 'Edit', style: TextStyle( - color: ColorsManager.spaceColor), // Text color + color: ColorsManager.spaceColor), ), backgroundColor: - Colors.white, // Background color for "Edit" + ColorsManager.whiteColors, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), // Rounded chip + borderRadius: BorderRadius.circular(16), side: const BorderSide( - color: ColorsManager.spaceColor), // Border color + color: ColorsManager.spaceColor), ), ), ), From 67516817ec8048ee324189e8988a5600cd541c4a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 16:18:19 +0400 Subject: [PATCH 056/106] add asset validation --- .../widgets/dialogs/create_space_dialog.dart | 10 +++++--- lib/services/space_mana_api.dart | 25 +++++++++++-------- lib/utils/asset_validator.dart | 15 +++++++++++ 3 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 lib/utils/asset_validator.dart diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 222881a8..054606cb 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/hoverable_button.dart'; +import 'package:syncrow_web/utils/asset_validator.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart'; @@ -60,7 +61,7 @@ class CreateSpaceDialogState extends State { @override @override - Widget build(BuildContext context) { + Future build(BuildContext context) async { final screenWidth = MediaQuery.of(context).size.width; return AlertDialog( @@ -91,7 +92,9 @@ class CreateSpaceDialogState extends State { ), ), SvgPicture.asset( - selectedIcon, + await AssetValidator.isValidAsset(selectedIcon) + ? selectedIcon + : Assets.location, width: screenWidth * 0.04, height: screenWidth * 0.04, ), @@ -143,7 +146,8 @@ class CreateSpaceDialogState extends State { } }); }, - style: const TextStyle(color: ColorsManager.blackColor), + style: + const TextStyle(color: ColorsManager.blackColor), decoration: InputDecoration( hintText: 'Please enter the name', hintStyle: const TextStyle( diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index be789844..b637cbc7 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -19,23 +19,26 @@ class CommunitySpaceManagementApi { .replaceAll('{projectId}', TempConst.projectId), queryParameters: {'page': page}, expectedResponseModel: (json) { - List jsonData = json['data']; - hasNext = json['hasNext'] ?? false; - int currentPage = json['page'] ?? 1; - List communityList = jsonData.map((jsonItem) { - return CommunityModel.fromJson(jsonItem); - }).toList(); - - allCommunities.addAll(communityList); - page = currentPage + 1; - return communityList; + try { + List jsonData = json['data'] ?? []; + hasNext = json['hasNext'] ?? false; + int currentPage = json['page'] ?? 1; + List communityList = jsonData.map((jsonItem) { + return CommunityModel.fromJson(jsonItem); + }).toList(); + allCommunities.addAll(communityList); + page = currentPage + 1; + return communityList; + } catch (_) { + hasNext = false; + return []; + } }, ); } return allCommunities; } catch (e) { - debugPrint('Error fetching communities: $e'); return []; } } diff --git a/lib/utils/asset_validator.dart b/lib/utils/asset_validator.dart new file mode 100644 index 00000000..add6a3f4 --- /dev/null +++ b/lib/utils/asset_validator.dart @@ -0,0 +1,15 @@ +import 'package:flutter/services.dart'; + +class AssetValidator { + static Future isValidAsset(String? assetPath) async { + if (assetPath == null || assetPath.isEmpty) { + return false; + } + try { + await rootBundle.load(assetPath); + return true; + } catch (_) { + return false; + } + } +} From 1ab8c8341d0653614fbeb400a50e1b90d9de3900 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 16:31:20 +0400 Subject: [PATCH 057/106] updated widget --- .../widgets/dialogs/create_space_dialog.dart | 112 +++-------------- .../selected_products_button_widget.dart | 118 ++++++++++++++++++ 2 files changed, 134 insertions(+), 96 deletions(-) create mode 100644 lib/pages/spaces_management/all_spaces/widgets/selected_products_button_widget.dart diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 054606cb..1ad8f52c 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -8,7 +8,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/hoverable_button.dart'; -import 'package:syncrow_web/utils/asset_validator.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/selected_products_button_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart'; @@ -60,8 +60,7 @@ class CreateSpaceDialogState extends State { } @override - @override - Future build(BuildContext context) async { + Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; return AlertDialog( @@ -92,9 +91,7 @@ class CreateSpaceDialogState extends State { ), ), SvgPicture.asset( - await AssetValidator.isValidAsset(selectedIcon) - ? selectedIcon - : Assets.location, + selectedIcon, width: screenWidth * 0.04, height: screenWidth * 0.04, ), @@ -107,7 +104,7 @@ class CreateSpaceDialogState extends State { width: screenWidth * 0.020, height: screenWidth * 0.020, decoration: const BoxDecoration( - color: ColorsManager.whiteColors, + color: Colors.white, shape: BoxShape.circle, ), child: SvgPicture.asset( @@ -146,8 +143,7 @@ class CreateSpaceDialogState extends State { } }); }, - style: - const TextStyle(color: ColorsManager.blackColor), + style: const TextStyle(color: Colors.black), decoration: InputDecoration( hintText: 'Please enter the name', hintStyle: const TextStyle( @@ -199,7 +195,16 @@ class CreateSpaceDialogState extends State { ), const SizedBox(height: 16), if (selectedProducts.isNotEmpty) - _buildSelectedProductsButtons(widget.products ?? []) + SelectedProductsButtons( + products: widget.products ?? [], + selectedProducts: selectedProducts, + onProductsUpdated: (updatedProducts) { + setState(() { + selectedProducts = updatedProducts; + }); + }, + context: context, + ) else DefaultButton( onPressed: () { @@ -216,7 +221,7 @@ class CreateSpaceDialogState extends State { ); }, backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: ColorsManager.blackColor, + foregroundColor: Colors.black, borderColor: ColorsManager.neutralGray, borderRadius: 16.0, padding: 10.0, // Reduced padding for smaller size @@ -317,74 +322,6 @@ class CreateSpaceDialogState extends State { ); } - Widget _buildSelectedProductsButtons(List products) { - final screenWidth = MediaQuery.of(context).size.width; - - return Container( - width: screenWidth * 0.6, - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: ColorsManager.neutralGray, - width: 2, - ), - ), - child: Wrap( - spacing: 8, - runSpacing: 8, - children: [ - for (var i = 0; i < selectedProducts.length; i++) ...[ - HoverableButton( - iconPath: - _mapIconToProduct(selectedProducts[i].productId, products), - text: 'x${selectedProducts[i].count}', - onTap: () { - setState(() { - selectedProducts.remove(selectedProducts[i]); - }); - // Handle button tap - }, - ), - if (i < selectedProducts.length - 1) - const SizedBox( - width: 2), // Add space except after the last button - ], - const SizedBox(width: 2), - GestureDetector( - onTap: () { - showDialog( - context: context, - builder: (context) => AddDeviceWidget( - products: widget.products, - initialSelectedProducts: selectedProducts, - onProductsSelected: (selectedProductsMap) { - setState(() { - selectedProducts = selectedProductsMap; - }); - }, - ), - ); - }, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.circular(16), - ), - child: const Icon( - Icons.add, - color: ColorsManager.spaceColor, - size: 24, - ), - ), - ), - ], - ), - ); - } - bool _isNameConflict(String value) { return (widget.parentSpace?.children.any((child) => child.name == value) ?? false) || @@ -393,21 +330,4 @@ class CreateSpaceDialogState extends State { (widget.editSpace?.children.any((child) => child.name == value) ?? false); } - - String _mapIconToProduct(String uuid, List products) { - // Find the product with the matching UUID - final product = products.firstWhere( - (product) => product.uuid == uuid, - orElse: () => ProductModel( - uuid: '', - catName: '', - prodId: '', - prodType: '', - name: '', - icon: Assets.presenceSensor, - ), - ); - - return product.icon ?? Assets.presenceSensor; - } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/selected_products_button_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/selected_products_button_widget.dart new file mode 100644 index 00000000..7076a580 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/widgets/selected_products_button_widget.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/hoverable_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class SelectedProductsButtons extends StatelessWidget { + final List products; + final List selectedProducts; + final Function(List) onProductsUpdated; + final BuildContext context; + + const SelectedProductsButtons({ + Key? key, + required this.products, + required this.selectedProducts, + required this.onProductsUpdated, + required this.context, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return Container( + width: screenWidth * 0.6, + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: ColorsManager.neutralGray, + width: 2, + ), + ), + child: Wrap( + spacing: 8, + runSpacing: 8, + children: [ + ..._buildSelectedProductButtons(), + _buildAddButton(), + ], + ), + ); + } + + List _buildSelectedProductButtons() { + return [ + for (var i = 0; i < selectedProducts.length; i++) ...[ + HoverableButton( + iconPath: _mapIconToProduct(selectedProducts[i].productId, products), + text: 'x${selectedProducts[i].count}', + onTap: () { + _removeProduct(i); + }, + ), + if (i < selectedProducts.length - 1) + const SizedBox(width: 2), // Add space except after the last button + ], + ]; + } + + Widget _buildAddButton() { + return GestureDetector( + onTap: _showAddDeviceDialog, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(16), + ), + child: const Icon( + Icons.add, + color: ColorsManager.spaceColor, + size: 24, + ), + ), + ); + } + + void _showAddDeviceDialog() { + showDialog( + context: context, + builder: (context) => AddDeviceWidget( + products: products, + initialSelectedProducts: selectedProducts, + onProductsSelected: (selectedProductsMap) { + onProductsUpdated(selectedProductsMap); + }, + ), + ); + } + + void _removeProduct(int index) { + final updatedProducts = [...selectedProducts]; + updatedProducts.removeAt(index); + onProductsUpdated(updatedProducts); + } + + String _mapIconToProduct(String uuid, List products) { + // Find the product with the matching UUID + final product = products.firstWhere( + (product) => product.uuid == uuid, + orElse: () => ProductModel( + uuid: '', + catName: '', + prodId: '', + prodType: '', + name: '', + icon: Assets.presenceSensor, + ), + ); + + return product.icon ?? Assets.presenceSensor; + } +} From a7e75548133ff007a8e79f1cc7270a1fe71c78c6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 16:39:46 +0400 Subject: [PATCH 058/106] added buttons --- .../widgets/dialogs/create_space_dialog.dart | 71 +++++++++++++++++++ lib/utils/constants/assets.dart | 1 + 2 files changed, 72 insertions(+) diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 1ad8f52c..25c3d5b3 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -194,6 +194,77 @@ class CreateSpaceDialogState extends State { ), ), const SizedBox(height: 16), + DefaultButton( + onPressed: () {}, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.addIcon, + width: screenWidth * + 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Link a space model', + overflow: TextOverflow + .ellipsis, // Prevent overflow + style: Theme.of(context) + .textTheme + .bodyMedium, + ), + ), + ], + ), + )), + const SizedBox(height: 30), + DefaultButton( + onPressed: () {}, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.addIcon, + width: screenWidth * + 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Create sub space', + overflow: TextOverflow + .ellipsis, // Prevent overflow + style: Theme.of(context) + .textTheme + .bodyMedium, + ), + ), + ], + ), + )), if (selectedProducts.isNotEmpty) SelectedProductsButtons( products: widget.products ?? [], diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 1f4074c4..a9deb3c7 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -397,5 +397,6 @@ class Assets { 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'; + static const String link = 'assets/icons/link.svg'; } //user_management.svg From e70df16de35b781436104cfe6f82bc81e33b1d32 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 16:39:54 +0400 Subject: [PATCH 059/106] added asset --- assets/icons/link.svg | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 assets/icons/link.svg diff --git a/assets/icons/link.svg b/assets/icons/link.svg new file mode 100644 index 00000000..9cd75905 --- /dev/null +++ b/assets/icons/link.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + From 8aa493a15ec65befad17948b6c6180bf3d42b8ee Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sat, 11 Jan 2025 15:42:35 +0400 Subject: [PATCH 060/106] added space model link to space dialog --- .../bloc/space_management_bloc.dart | 20 +- .../bloc/space_management_state.dart | 8 +- .../all_spaces/model/space_model.dart | 4 +- .../view/spaces_management_page.dart | 8 +- .../widgets/community_structure_widget.dart | 7 + .../widgets/dialogs/create_space_dialog.dart | 538 ++++++++++-------- .../widgets/loaded_space_widget.dart | 35 +- .../bloc/link_space_model_bloc.dart | 12 + .../bloc/link_space_model_event.dart | 7 + .../bloc/link_space_model_state.dart | 9 + .../view/link_space_model_dialog.dart | 131 +++++ .../space_model/view/space_model_page.dart | 8 +- 12 files changed, 502 insertions(+), 285 deletions(-) create mode 100644 lib/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart create mode 100644 lib/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart create mode 100644 lib/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart create mode 100644 lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 2b749a32..f8ed1b86 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -172,8 +172,12 @@ class SpaceManagementBloc }).toList(), ); + List spaceModels = + await _spaceModelApi.listSpaceModels(page: 1); emit(SpaceManagementLoaded( - communities: updatedCommunities, products: _cachedProducts ?? [])); + communities: updatedCommunities, + products: _cachedProducts ?? [], + spaceModels: spaceModels)); } catch (e) { emit(SpaceManagementError('Error loading communities and spaces: $e')); } @@ -276,12 +280,16 @@ class SpaceManagementBloc final communities = List.from( (previousState as dynamic).communities, ); + + final spaceModels = List.from( + (previousState as dynamic).spaceModels, + ); emit(SpaceManagementLoaded( - communities: communities, - products: _cachedProducts ?? [], - selectedCommunity: selectedCommunity, - selectedSpace: selectedSpace, - )); + communities: communities, + products: _cachedProducts ?? [], + selectedCommunity: selectedCommunity, + selectedSpace: selectedSpace, + spaceModels: spaceModels ?? [])); } } catch (e) { emit(SpaceManagementError('Error updating state: $e')); diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart index 3ceafba9..635c244d 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart @@ -20,12 +20,15 @@ class SpaceManagementLoaded extends SpaceManagementState { final List products; CommunityModel? selectedCommunity; SpaceModel? selectedSpace; + List? spaceModels; SpaceManagementLoaded( {required this.communities, required this.products, this.selectedCommunity, - this.selectedSpace}); + this.selectedSpace, + this.spaceModels + }); } class SpaceModelManagenetLoaded extends SpaceManagementState { @@ -35,10 +38,13 @@ class SpaceModelManagenetLoaded extends SpaceManagementState { class BlankState extends SpaceManagementState { final List communities; final List products; + List? spaceModels; + BlankState({ required this.communities, required this.products, + this.spaceModels }); } diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index 4bdf4ece..ba5d83d6 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -1,7 +1,7 @@ import 'dart:ui'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:uuid/uuid.dart'; @@ -21,6 +21,7 @@ class SpaceModel { bool isHovered; SpaceStatus status; String internalId; + SpaceTemplateModel? spaceModel; List outgoingConnections = []; // Connections from this space Connection? incomingConnection; // Connections to this space @@ -40,6 +41,7 @@ class SpaceModel { this.isHovered = false, this.incomingConnection, this.status = SpaceStatus.unchanged, + this.spaceModel, }) : internalId = internalId ?? const Uuid().v4(); factory SpaceModel.fromJson(Map json, diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index 77f878b7..24b1b5cb 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -51,19 +51,25 @@ class SpaceManagementPageState extends State { selectedCommunity: null, selectedSpace: null, products: state.products, + shouldNavigateToSpaceModelPage: false, ); } else if (state is SpaceManagementLoaded) { + print("sdksndsnadf ${state.spaceModels}"); return LoadedSpaceView( communities: state.communities, selectedCommunity: state.selectedCommunity, selectedSpace: state.selectedSpace, products: state.products, + spaceModels: state.spaceModels, + shouldNavigateToSpaceModelPage: false, ); } else if (state is SpaceModelLoaded) { return LoadedSpaceView( communities: state.communities, products: state.products, - spaceModels: state.spaceModels); + spaceModels: state.spaceModels, + shouldNavigateToSpaceModelPage: true, + ); } else if (state is SpaceManagementError) { return Center(child: Text('Error: ${state.errorMessage}')); } diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index a929c6fd..9225f5a9 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -17,6 +17,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/c import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/curved_line_painter.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_card_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CommunityStructureArea extends StatefulWidget { @@ -26,6 +27,8 @@ class CommunityStructureArea extends StatefulWidget { final ValueChanged? onSpaceSelected; final List communities; final List spaces; + final List? spaceModels; + CommunityStructureArea({ this.selectedCommunity, @@ -34,6 +37,7 @@ class CommunityStructureArea extends StatefulWidget { this.products, required this.spaces, this.onSpaceSelected, + this.spaceModels, }); @override @@ -49,9 +53,11 @@ class _CommunityStructureAreaState extends State { bool isEditingName = false; late TransformationController _transformationController; + @override void initState() { super.initState(); + print("sxdsf ${widget.spaceModels}"); spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; @@ -284,6 +290,7 @@ class _CommunityStructureAreaState extends State { builder: (BuildContext context) { return CreateSpaceDialog( products: widget.products, + spaceModels: widget.spaceModels, parentSpace: parentIndex != null ? spaces[parentIndex] : null, onCreateSpace: (String name, String icon, List selectedProducts) { diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 25c3d5b3..f3d11d91 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -7,8 +7,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_pr import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/hoverable_button.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/selected_products_button_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart'; @@ -23,6 +23,7 @@ class CreateSpaceDialog extends StatefulWidget { final List selectedProducts; final SpaceModel? parentSpace; final SpaceModel? editSpace; + final List? spaceModels; const CreateSpaceDialog( {super.key, @@ -33,7 +34,8 @@ class CreateSpaceDialog extends StatefulWidget { this.icon, this.isEdit = false, this.editSpace, - this.selectedProducts = const []}); + this.selectedProducts = const [], + this.spaceModels}); @override CreateSpaceDialogState createState() => CreateSpaceDialogState(); @@ -59,275 +61,289 @@ class CreateSpaceDialogState extends State { enteredName.isNotEmpty || nameController.text.isNotEmpty; } + @override @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - return AlertDialog( title: widget.isEdit ? const Text('Edit Space') : const Text('Create New Space'), backgroundColor: ColorsManager.whiteColors, content: SizedBox( - width: screenWidth * 0.5, // Limit dialog width + width: screenWidth * 0.5, child: SingleChildScrollView( - // Scrollable content to prevent overflow - child: Column( - mainAxisSize: MainAxisSize.min, + child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Stack( - alignment: Alignment.center, - children: [ - Container( - width: screenWidth * 0.1, // Adjusted width - height: screenWidth * 0.1, // Adjusted height - decoration: const BoxDecoration( - color: ColorsManager.boxColor, - shape: BoxShape.circle, - ), - ), - SvgPicture.asset( - selectedIcon, - width: screenWidth * 0.04, - height: screenWidth * 0.04, - ), - Positioned( - top: 6, - right: 6, - child: InkWell( - onTap: _showIconSelectionDialog, - child: Container( - width: screenWidth * 0.020, - height: screenWidth * 0.020, - decoration: const BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - ), - child: SvgPicture.asset( - Assets.iconEdit, - width: screenWidth * 0.06, - height: screenWidth * 0.06, - ), - ), - ), - ), - ], - ), - const SizedBox(width: 16), - Expanded( - // Ensure the text field expands responsively - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + Expanded( + flex: 1, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + // crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Stack( + alignment: Alignment.center, children: [ - TextField( - controller: nameController, - onChanged: (value) { - enteredName = value.trim(); - setState(() { - isNameFieldExist = false; - isOkButtonEnabled = false; - isNameFieldInvalid = value.isEmpty; - - if (!isNameFieldInvalid) { - if (_isNameConflict(value)) { - isNameFieldExist = true; - isOkButtonEnabled = false; - } else { - isNameFieldExist = false; - isOkButtonEnabled = true; - } - } - }); - }, - style: const TextStyle(color: Colors.black), - decoration: InputDecoration( - hintText: 'Please enter the name', - hintStyle: const TextStyle( - fontSize: 14, - color: ColorsManager.lightGrayColor, - fontWeight: FontWeight.w400, - ), - filled: true, - fillColor: ColorsManager.boxColor, - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide( - color: isNameFieldInvalid || isNameFieldExist - ? ColorsManager.red - : ColorsManager.boxColor, - width: 1.5, + Container( + width: screenWidth * 0.1, + height: screenWidth * 0.1, + decoration: const BoxDecoration( + color: ColorsManager.boxColor, + shape: BoxShape.circle, + ), + ), + SvgPicture.asset( + selectedIcon, + width: screenWidth * 0.04, + height: screenWidth * 0.04, + ), + Positioned( + top: 20, + right: 20, + child: InkWell( + onTap: _showIconSelectionDialog, + child: Container( + width: 24, + height: 24, + decoration: const BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide( - color: ColorsManager.boxColor, - width: 1.5, + child: SvgPicture.asset( + Assets.iconEdit, + width: 16, + height: 16, ), ), ), ), - if (isNameFieldInvalid) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - '*Space name should not be empty.', - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: ColorsManager.red), - ), - ), - if (isNameFieldExist) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - '*Name already exist', - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: ColorsManager.red), - ), - ), - const SizedBox(height: 16), - DefaultButton( - onPressed: () {}, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.addIcon, - width: screenWidth * - 0.015, // Adjust icon size - height: screenWidth * 0.015, - ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Link a space model', - overflow: TextOverflow - .ellipsis, // Prevent overflow - style: Theme.of(context) - .textTheme - .bodyMedium, - ), - ), - ], - ), - )), - const SizedBox(height: 30), - DefaultButton( - onPressed: () {}, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.addIcon, - width: screenWidth * - 0.015, // Adjust icon size - height: screenWidth * 0.015, - ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Create sub space', - overflow: TextOverflow - .ellipsis, // Prevent overflow - style: Theme.of(context) - .textTheme - .bodyMedium, - ), - ), - ], - ), - )), - if (selectedProducts.isNotEmpty) - SelectedProductsButtons( - products: widget.products ?? [], - selectedProducts: selectedProducts, - onProductsUpdated: (updatedProducts) { - setState(() { - selectedProducts = updatedProducts; - }); - }, - context: context, - ) - else - DefaultButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => AddDeviceWidget( - products: widget.products, - onProductsSelected: (selectedProductsMap) { - setState(() { - selectedProducts = selectedProductsMap; - }); - }, - ), - ); - }, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.addIcon, - width: screenWidth * - 0.015, // Adjust icon size - height: screenWidth * 0.015, - ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Add devices / Assign a space model', - overflow: TextOverflow - .ellipsis, // Prevent overflow - style: Theme.of(context) - .textTheme - .bodyMedium, - ), - ), - ], - ), - )), ], ), - ), - ], + ], + ), + ), + const SizedBox(width: 20), + Expanded( + flex: 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + controller: nameController, + onChanged: (value) { + enteredName = value.trim(); + setState(() { + isNameFieldExist = false; + isOkButtonEnabled = false; + isNameFieldInvalid = value.isEmpty; + + if (!isNameFieldInvalid) { + if (_isNameConflict(value)) { + isNameFieldExist = true; + isOkButtonEnabled = false; + } else { + isNameFieldExist = false; + isOkButtonEnabled = true; + } + } + }); + }, + style: const TextStyle(color: Colors.black), + decoration: InputDecoration( + hintText: 'Please enter the name', + hintStyle: const TextStyle( + fontSize: 14, + color: ColorsManager.lightGrayColor, + fontWeight: FontWeight.w400, + ), + filled: true, + fillColor: ColorsManager.boxColor, + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: isNameFieldInvalid || isNameFieldExist + ? ColorsManager.red + : ColorsManager.boxColor, + width: 1.5, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide( + color: ColorsManager.boxColor, + width: 1.5, + ), + ), + ), + ), + if (isNameFieldInvalid) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + '*Space name should not be empty.', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.red), + ), + ), + if (isNameFieldExist) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + '*Name already exist', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.red), + ), + ), + const SizedBox(height: 10), + DefaultButton( + onPressed: () { + _showLinkSpaceModelDialog(context); + }, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.link, + width: screenWidth * 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Link a space model', + overflow: + TextOverflow.ellipsis, // Prevent overflow + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + ], + ), + ), + ), + const SizedBox(height: 25), + const Row( + children: [ + Expanded( + child: Divider( + color: ColorsManager.neutralGray, + thickness: 1.0, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + 'OR', + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ), + Expanded( + child: Divider( + color: ColorsManager.neutralGray, + thickness: 1.0, + ), + ), + ], + ), + const SizedBox(height: 25), + DefaultButton( + onPressed: () {}, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.addIcon, + width: screenWidth * 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Create sub space', + overflow: + TextOverflow.ellipsis, // Prevent overflow + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + ], + ), + ), + ), + const SizedBox(height: 10), + DefaultButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => AddDeviceWidget( + products: widget.products, + onProductsSelected: (selectedProductsMap) { + setState(() { + selectedProducts = selectedProductsMap; + }); + }, + ), + ); + }, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.addIcon, + width: screenWidth * 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Add devices', + overflow: + TextOverflow.ellipsis, // Prevent overflow + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + ], + ), + ), + ), + ], + ), ), ], ), @@ -401,4 +417,22 @@ class CreateSpaceDialogState extends State { (widget.editSpace?.children.any((child) => child.name == value) ?? false); } + + void _showLinkSpaceModelDialog(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return LinkSpaceModelDialog( + spaceModels: widget.spaceModels ?? [], + onSave: (selectedModel) { + if (selectedModel != null) { + print('Selected Model: ${selectedModel.modelName}'); + } else { + print('No model selected'); + } + }, + ); + }, + ); + } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart index 84ed32f7..cdba0a5a 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart @@ -11,12 +11,13 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_tem import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; -class LoadedSpaceView extends StatefulWidget { +class LoadedSpaceView extends StatelessWidget { final List communities; final CommunityModel? selectedCommunity; final SpaceModel? selectedSpace; final List? products; final List? spaceModels; + final bool shouldNavigateToSpaceModelPage; const LoadedSpaceView({ super.key, @@ -25,17 +26,11 @@ class LoadedSpaceView extends StatefulWidget { this.selectedSpace, this.products, this.spaceModels, + required this.shouldNavigateToSpaceModelPage }); - @override - _LoadedStateViewState createState() => _LoadedStateViewState(); -} - -class _LoadedStateViewState extends State { @override Widget build(BuildContext context) { - final bool hasSpaceModels = - widget.spaceModels != null && widget.spaceModels!.isNotEmpty; return Stack( clipBehavior: Clip.none, @@ -43,29 +38,29 @@ class _LoadedStateViewState extends State { Row( children: [ SidebarWidget( - communities: widget.communities, - selectedSpaceUuid: widget.selectedSpace?.uuid ?? - widget.selectedCommunity?.uuid ?? - '', + communities: communities, + selectedSpaceUuid: + selectedSpace?.uuid ?? selectedCommunity?.uuid ?? '', ), - hasSpaceModels + shouldNavigateToSpaceModelPage ? Expanded( child: BlocProvider( create: (context) => SpaceModelBloc( api: SpaceModelManagementApi(), - initialSpaceModels: widget.spaceModels ?? [], + initialSpaceModels: spaceModels ?? [], ), child: SpaceModelPage( - products: widget.products, + products: products, ), ), ) : CommunityStructureArea( - selectedCommunity: widget.selectedCommunity, - selectedSpace: widget.selectedSpace, - spaces: widget.selectedCommunity?.spaces ?? [], - products: widget.products, - communities: widget.communities, + selectedCommunity: selectedCommunity, + selectedSpace: selectedSpace, + spaces: selectedCommunity?.spaces ?? [], + products: products, + communities: communities, + spaceModels: spaceModels, ), ], ), diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart new file mode 100644 index 00000000..aa9a446d --- /dev/null +++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart @@ -0,0 +1,12 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart'; + + +class SpaceModelBloc extends Bloc { + SpaceModelBloc() : super(SpaceModelInitial()) { + on((event, emit) { + emit(SpaceModelSelectedState(event.selectedIndex)); + }); + } +} \ No newline at end of file diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart new file mode 100644 index 00000000..8bff0202 --- /dev/null +++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart @@ -0,0 +1,7 @@ +abstract class SpaceModelEvent {} + +class SpaceModelSelectedEvent extends SpaceModelEvent { + final int selectedIndex; + + SpaceModelSelectedEvent(this.selectedIndex); +} diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart new file mode 100644 index 00000000..cc745e4d --- /dev/null +++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart @@ -0,0 +1,9 @@ +abstract class SpaceModelState {} + +class SpaceModelInitial extends SpaceModelState {} + +class SpaceModelSelectedState extends SpaceModelState { + final int selectedIndex; + + SpaceModelSelectedState(this.selectedIndex); +} diff --git a/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart b/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart new file mode 100644 index 00000000..44cbbc67 --- /dev/null +++ b/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class LinkSpaceModelDialog extends StatelessWidget { + final Function(SpaceTemplateModel?)? onSave; + final int? initialSelectedIndex; + final List spaceModels; + + const LinkSpaceModelDialog({ + Key? key, + this.onSave, + this.initialSelectedIndex, + required this.spaceModels, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => SpaceModelBloc() + ..add( + SpaceModelSelectedEvent(initialSelectedIndex ?? -1), + ), + child: Builder( + builder: (context) { + final bloc = context.read(); + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: const Text('Link a space model'), + content: spaceModels.isNotEmpty + ? Container( + color: ColorsManager.textFieldGreyColor, + width: MediaQuery.of(context).size.width * 0.7, + height: MediaQuery.of(context).size.height * 0.6, + child: BlocBuilder( + builder: (context, state) { + int selectedIndex = -1; + if (state is SpaceModelSelectedState) { + selectedIndex = state.selectedIndex; + } + return GridView.builder( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0, + childAspectRatio: 3, + ), + itemCount: spaceModels.length, + itemBuilder: (BuildContext context, int index) { + final model = spaceModels[index]; + final isSelected = selectedIndex == index; + return GestureDetector( + onTap: () { + bloc.add(SpaceModelSelectedEvent(index)); + }, + child: Container( + margin: const EdgeInsets.all(10.0), + decoration: BoxDecoration( + border: Border.all( + color: isSelected + ? ColorsManager.spaceColor + : Colors.transparent, + width: 2.0, + ), + borderRadius: BorderRadius.circular(8.0), + ), + child: SpaceModelCardWidget(model: model), + ), + ); + }, + ); + }, + ), + ) + : const Text('No space models available.'), + actions: [ + Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CancelButton( + onPressed: () { + Navigator.of(context).pop(); + }, + label: 'Cancel', + ), + const SizedBox(width: 10), + BlocBuilder( + builder: (context, state) { + final isEnabled = state is SpaceModelSelectedState && + state.selectedIndex >= 0; + return SizedBox( + width: 140, + child: DefaultButton( + height: 40, + borderRadius: 10, + onPressed: isEnabled + ? () { + if (onSave != null) { + final selectedModel = + state is SpaceModelSelectedState + ? spaceModels[state.selectedIndex] + : null; + onSave!(selectedModel); + } + Navigator.of(context).pop(); + } + : null, + child: const Text('Save'), + ), + ); + }, + ), + ], + ), + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 6b9818c9..a76543ae 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -94,14 +94,14 @@ class SpaceModelPage extends StatelessWidget { double _calculateChildAspectRatio(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; if (screenWidth > 1600) { - return 2; // Taller cards for larger screens + return 2; } if (screenWidth > 1200) { - return 3; // Adjusted height for medium screens + return 3; } else if (screenWidth > 800) { - return 3.5; // Adjusted height for smaller screens + return 3.5; } else { - return 4.0; // Default ratio for smallest screens + return 4.0; } } From bfbc32d51b24e4d61e72e6d92f20745248dc594a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sat, 11 Jan 2025 18:37:34 +0400 Subject: [PATCH 061/106] moved select space a bit down --- .../all_spaces/widgets/dialogs/create_space_dialog.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index f3d11d91..0b86f6e2 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -82,6 +82,7 @@ class CreateSpaceDialogState extends State { mainAxisAlignment: MainAxisAlignment.center, // crossAxisAlignment: CrossAxisAlignment.center, children: [ + const SizedBox(height: 50), Stack( alignment: Alignment.center, children: [ From 15640ff0df4a7882e0393d5c9f5d9e405a1cd423 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 01:15:44 +0400 Subject: [PATCH 062/106] added space model select --- .../widgets/community_structure_widget.dart | 30 +++-- .../widgets/dialogs/create_space_dialog.dart | 122 +++++++++++++----- .../widgets/subspace_model_create_widget.dart | 1 + 3 files changed, 107 insertions(+), 46 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 9225f5a9..f48e2470 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -29,7 +29,6 @@ class CommunityStructureArea extends StatefulWidget { final List spaces; final List? spaceModels; - CommunityStructureArea({ this.selectedCommunity, this.selectedSpace, @@ -53,7 +52,6 @@ class _CommunityStructureAreaState extends State { bool isEditingName = false; late TransformationController _transformationController; - @override void initState() { super.initState(); @@ -292,21 +290,24 @@ class _CommunityStructureAreaState extends State { products: widget.products, spaceModels: widget.spaceModels, parentSpace: parentIndex != null ? spaces[parentIndex] : null, - onCreateSpace: (String name, String icon, - List selectedProducts) { + onCreateSpace: (String name, + String icon, + List selectedProducts, + SpaceTemplateModel? spaceModel) { setState(() { // Set the first space in the center or use passed position Offset centerPosition = position ?? _getCenterPosition(screenSize); SpaceModel newSpace = SpaceModel( - name: name, - icon: icon, - position: centerPosition, - isPrivate: false, - children: [], - status: SpaceStatus.newSpace, - ); + name: name, + icon: icon, + position: centerPosition, + isPrivate: false, + children: [], + status: SpaceStatus.newSpace, + spaceModel: spaceModel, + ); if (parentIndex != null && direction != null) { SpaceModel parentSpace = spaces[parentIndex]; @@ -342,12 +343,15 @@ class _CommunityStructureAreaState extends State { icon: space.icon, editSpace: space, isEdit: true, - onCreateSpace: (String name, String icon, - List selectedProducts) { + onCreateSpace: (String name, + String icon, + List selectedProducts, + SpaceTemplateModel? spaceModel) { setState(() { // Update the space's properties space.name = name; space.icon = icon; + space.spaceModel = spaceModel; if (space.status != SpaceStatus.newSpace) { space.status = SpaceStatus.modified; // Mark as modified diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 0b86f6e2..e46b0d9e 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -14,8 +14,8 @@ import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart'; class CreateSpaceDialog extends StatefulWidget { - final Function(String, String, List selectedProducts) - onCreateSpace; + final Function(String, String, List selectedProducts, + SpaceTemplateModel? spaceModel) onCreateSpace; final List? products; final String? name; final String? icon; @@ -43,6 +43,7 @@ class CreateSpaceDialog extends StatefulWidget { class CreateSpaceDialogState extends State { String selectedIcon = Assets.location; + SpaceTemplateModel? selectedSpaceModel; String enteredName = ''; List selectedProducts = []; late TextEditingController nameController; @@ -201,41 +202,93 @@ class CreateSpaceDialogState extends State { ), ), const SizedBox(height: 10), - DefaultButton( - onPressed: () { - _showLinkSpaceModelDialog(context); - }, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, + if (selectedSpaceModel == null) + DefaultButton( + onPressed: () { + _showLinkSpaceModelDialog(context); + }, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.link, + width: + screenWidth * 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Link a space model', + overflow: + TextOverflow.ellipsis, // Prevent overflow + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + ], + ), + ), + ), + if (selectedSpaceModel != null) + Container( + width: screenWidth * 0.35, + padding: const EdgeInsets.symmetric( + vertical: 10.0, horizontal: 16.0), + decoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(10), + ), + child: Wrap( + spacing: 8.0, + runSpacing: 8.0, children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.link, - width: screenWidth * 0.015, // Adjust icon size - height: screenWidth * 0.015, + Chip( + label: Text( + selectedSpaceModel?.modelName ?? '', + style: const TextStyle( + color: ColorsManager.spaceColor), ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Link a space model', - overflow: - TextOverflow.ellipsis, // Prevent overflow - style: Theme.of(context).textTheme.bodyMedium, + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide( + color: ColorsManager.transparentColor, + width: 0, + ), ), - ), + deleteIcon: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager.lightGrayColor, + width: 1.5, + ), + ), + child: const Icon( + Icons.close, + size: 16, + color: ColorsManager.lightGrayColor, + ), + ), + onDeleted: () => setState(() { + this.selectedSpaceModel = null; + })), + ], ), ), - ), const SizedBox(height: 25), const Row( children: [ @@ -374,8 +427,8 @@ class CreateSpaceDialogState extends State { ? enteredName : (widget.name ?? ''); if (newName.isNotEmpty) { - widget.onCreateSpace( - newName, selectedIcon, selectedProducts); + widget.onCreateSpace(newName, selectedIcon, + selectedProducts, selectedSpaceModel); Navigator.of(context).pop(); } } @@ -427,6 +480,9 @@ class CreateSpaceDialogState extends State { spaceModels: widget.spaceModels ?? [], onSave: (selectedModel) { if (selectedModel != null) { + setState(() { + this.selectedSpaceModel = selectedModel; + }); print('Selected Model: ${selectedModel.modelName}'); } else { print('No model selected'); diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 22444c3d..832c9ea3 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -134,6 +134,7 @@ class SubspaceModelCreate extends StatelessWidget { ), ), ), + ); } } From cfc1b544b7e0582884bd7a9d196f04a69a76ce75 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 09:10:33 +0400 Subject: [PATCH 063/106] added subspaces --- .../all_spaces/model/space_model.dart | 43 +++ .../all_spaces/model/subspace_model.dart | 110 ++++++ .../all_spaces/model/tag.dart | 61 ++++ .../widgets/dialogs/create_space_dialog.dart | 341 ++++++++++++------ .../create_subspace/bloc/subspace_bloc.dart | 57 +++ .../create_subspace/bloc/subspace_event.dart | 18 + .../create_subspace/bloc/subspace_state.dart | 26 ++ .../views/create_subspace_model_dialog.dart | 196 ++++++++++ .../view/link_space_model_dialog.dart | 5 +- 9 files changed, 735 insertions(+), 122 deletions(-) create mode 100644 lib/pages/spaces_management/all_spaces/model/subspace_model.dart create mode 100644 lib/pages/spaces_management/all_spaces/model/tag.dart create mode 100644 lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart create mode 100644 lib/pages/spaces_management/create_subspace/bloc/subspace_event.dart create mode 100644 lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart create mode 100644 lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index ba5d83d6..64a98803 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -1,6 +1,8 @@ import 'dart:ui'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:uuid/uuid.dart'; @@ -22,6 +24,8 @@ class SpaceModel { SpaceStatus status; String internalId; SpaceTemplateModel? spaceModel; + final List? tags; + List? subspaces; List outgoingConnections = []; // Connections from this space Connection? incomingConnection; // Connections to this space @@ -42,6 +46,8 @@ class SpaceModel { this.incomingConnection, this.status = SpaceStatus.unchanged, this.spaceModel, + this.tags, + this.subspaces, }) : internalId = internalId ?? const Uuid().v4(); factory SpaceModel.fromJson(Map json, @@ -64,6 +70,11 @@ class SpaceModel { name: json['spaceName'], isPrivate: json['isPrivate'] ?? false, invitationCode: json['invitationCode'], + subspaces: (json['subspaces'] as List?) + ?.where((e) => e is Map) // Validate type + .map((e) => SubspaceModel.fromJson(e as Map)) + .toList() ?? + [], parent: parentInternalId != null ? SpaceModel( internalId: parentInternalId, @@ -85,6 +96,11 @@ class SpaceModel { icon: json['icon'] ?? Assets.location, position: Offset(json['x'] ?? 0, json['y'] ?? 0), isHovered: false, + tags: (json['tags'] as List?) + ?.where((item) => item is Map) // Validate type + .map((item) => Tag.fromJson(item as Map)) + .toList() ?? + [], ); if (json['incomingConnections'] != null && @@ -110,6 +126,7 @@ class SpaceModel { 'isPrivate': isPrivate, 'invitationCode': invitationCode, 'parent': parent?.uuid, + 'subspaces': subspaces?.map((e) => e.toJson()).toList(), 'community': community?.toMap(), 'children': children.map((child) => child.toMap()).toList(), 'icon': icon, @@ -117,6 +134,7 @@ class SpaceModel { 'isHovered': isHovered, 'outgoingConnections': outgoingConnections.map((c) => c.toMap()).toList(), 'incomingConnection': incomingConnection?.toMap(), + 'tags': tags?.map((e) => e.toJson()).toList(), }; } @@ -124,3 +142,28 @@ class SpaceModel { outgoingConnections.add(connection); } } + +extension SpaceExtensions on SpaceModel { + List listAllTagValues() { + final List tagValues = []; + + if (tags != null) { + tagValues.addAll( + tags!.map((tag) => tag.tag ?? '').where((tag) => tag.isNotEmpty)); + } + + if (subspaces != null) { + for (final subspace in subspaces!) { + if (subspace.tags != null) { + tagValues.addAll( + subspace.tags! + .map((tag) => tag.tag ?? '') + .where((tag) => tag.isNotEmpty), + ); + } + } + } + + return tagValues; + } +} diff --git a/lib/pages/spaces_management/all_spaces/model/subspace_model.dart b/lib/pages/spaces_management/all_spaces/model/subspace_model.dart new file mode 100644 index 00000000..2c86523f --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/model/subspace_model.dart @@ -0,0 +1,110 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/utils/constants/action_enum.dart'; + +import 'tag.dart'; + +class SubspaceModel { + final String? uuid; + String subspaceName; + final bool disabled; + List? tags; + + SubspaceModel({ + this.uuid, + required this.subspaceName, + required this.disabled, + this.tags, + }); + + factory SubspaceModel.fromJson(Map json) { + return SubspaceModel( + uuid: json['uuid'] ?? '', + subspaceName: json['subspaceName'] ?? '', + disabled: json['disabled'] ?? false, + tags: (json['tags'] as List?) + ?.map((item) => Tag.fromJson(item)) + .toList() ?? + [], + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'subspaceName': subspaceName, + 'disabled': disabled, + 'tags': tags?.map((e) => e.toJson()).toList() ?? [], + }; + } +} + +class UpdateSubspaceModel { + final String uuid; + final Action action; + final String? subspaceName; + final List? tags; + UpdateSubspaceModel({ + required this.action, + required this.uuid, + this.subspaceName, + this.tags, + }); + + factory UpdateSubspaceModel.fromJson(Map json) { + return UpdateSubspaceModel( + action: ActionExtension.fromValue(json['action']), + uuid: json['uuid'] ?? '', + subspaceName: json['subspaceName'] ?? '', + tags: (json['tags'] as List) + .map((item) => UpdateTag.fromJson(item)) + .toList(), + ); + } + + Map toJson() { + return { + 'action': action.value, + 'uuid': uuid, + 'subspaceName': subspaceName, + 'tags': tags?.map((e) => e.toJson()).toList() ?? [], + }; + } +} + +class UpdateTag { + final Action action; + final String? uuid; + final String tag; + final bool disabled; + final ProductModel? product; + + UpdateTag({ + required this.action, + this.uuid, + required this.tag, + required this.disabled, + this.product, + }); + + factory UpdateTag.fromJson(Map json) { + return UpdateTag( + action: ActionExtension.fromValue(json['action']), + uuid: json['uuid'] ?? '', + tag: json['tag'] ?? '', + disabled: json['disabled'] ?? false, + product: json['product'] != null + ? ProductModel.fromMap(json['product']) + : null, + ); + } + + Map toJson() { + return { + 'action': action.value, + 'uuid': uuid, + 'tag': tag, + 'disabled': disabled, + 'product': product?.toMap(), + }; + } +} \ No newline at end of file diff --git a/lib/pages/spaces_management/all_spaces/model/tag.dart b/lib/pages/spaces_management/all_spaces/model/tag.dart new file mode 100644 index 00000000..b70865c7 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/model/tag.dart @@ -0,0 +1,61 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; +import 'package:uuid/uuid.dart'; + +class Tag { + String? uuid; + String? tag; + final ProductModel? product; + String internalId; + String? location; + + Tag( + {this.uuid, + required this.tag, + this.product, + String? internalId, + this.location}) + : internalId = internalId ?? const Uuid().v4(); + + factory Tag.fromJson(Map json) { + final String internalId = json['internalId'] ?? const Uuid().v4(); + + return Tag( + uuid: json['uuid'] ?? '', + internalId: internalId, + tag: json['tag'] ?? '', + product: json['product'] != null + ? ProductModel.fromMap(json['product']) + : null, + ); + } + + Tag copyWith({ + String? tag, + ProductModel? product, + String? location, + }) { + return Tag( + tag: tag ?? this.tag, + product: product ?? this.product, + location: location ?? this.location, + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'tag': tag, + 'product': product?.toMap(), + }; + } +} + +extension TagModelExtensions on Tag { + TagBodyModel toTagBodyModel() { + return TagBodyModel() + ..uuid = uuid ?? '' + ..tag = tag ?? '' + ..productUuid = product?.uuid; + } +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index e46b0d9e..ea6363de 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -5,8 +5,11 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -24,6 +27,7 @@ class CreateSpaceDialog extends StatefulWidget { final SpaceModel? parentSpace; final SpaceModel? editSpace; final List? spaceModels; + final List? subspaces; const CreateSpaceDialog( {super.key, @@ -35,7 +39,8 @@ class CreateSpaceDialog extends StatefulWidget { this.isEdit = false, this.editSpace, this.selectedProducts = const [], - this.spaceModels}); + this.spaceModels, + this.subspaces}); @override CreateSpaceDialogState createState() => CreateSpaceDialogState(); @@ -50,6 +55,7 @@ class CreateSpaceDialogState extends State { bool isOkButtonEnabled = false; bool isNameFieldInvalid = false; bool isNameFieldExist = false; + List? subspaces; @override void initState() { @@ -202,93 +208,93 @@ class CreateSpaceDialogState extends State { ), ), const SizedBox(height: 10), - if (selectedSpaceModel == null) - DefaultButton( - onPressed: () { - _showLinkSpaceModelDialog(context); - }, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.link, - width: - screenWidth * 0.015, // Adjust icon size - height: screenWidth * 0.015, - ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Link a space model', - overflow: - TextOverflow.ellipsis, // Prevent overflow - style: Theme.of(context).textTheme.bodyMedium, - ), - ), - ], - ), - ), - ), - if (selectedSpaceModel != null) - Container( - width: screenWidth * 0.35, - padding: const EdgeInsets.symmetric( - vertical: 10.0, horizontal: 16.0), - decoration: BoxDecoration( - color: ColorsManager.boxColor, - borderRadius: BorderRadius.circular(10), - ), - child: Wrap( - spacing: 8.0, - runSpacing: 8.0, - children: [ - Chip( - label: Text( - selectedSpaceModel?.modelName ?? '', - style: const TextStyle( - color: ColorsManager.spaceColor), - ), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: const BorderSide( - color: ColorsManager.transparentColor, - width: 0, - ), - ), - deleteIcon: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: ColorsManager.lightGrayColor, - width: 1.5, + selectedSpaceModel == null + ? DefaultButton( + onPressed: () { + _showLinkSpaceModelDialog(context); + }, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.link, + width: screenWidth * + 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), ), - ), - child: const Icon( - Icons.close, - size: 16, - color: ColorsManager.lightGrayColor, - ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Link a space model', + overflow: TextOverflow + .ellipsis, // Prevent overflow + style: Theme.of(context) + .textTheme + .bodyMedium, + ), + ), + ], ), - onDeleted: () => setState(() { - this.selectedSpaceModel = null; - })), - - ], - ), - ), + ), + ) + : Container( + width: screenWidth * 0.35, + padding: const EdgeInsets.symmetric( + vertical: 10.0, horizontal: 16.0), + decoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(10), + ), + child: Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: [ + Chip( + label: Text( + selectedSpaceModel?.modelName ?? '', + style: const TextStyle( + color: ColorsManager.spaceColor), + ), + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide( + color: ColorsManager.transparentColor, + width: 0, + ), + ), + deleteIcon: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager.lightGrayColor, + width: 1.5, + ), + ), + child: const Icon( + Icons.close, + size: 16, + color: ColorsManager.lightGrayColor, + ), + ), + onDeleted: () => setState(() { + this.selectedSpaceModel = null; + })), + ], + ), + ), const SizedBox(height: 25), const Row( children: [ @@ -317,39 +323,111 @@ class CreateSpaceDialogState extends State { ], ), const SizedBox(height: 25), - DefaultButton( - onPressed: () {}, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.addIcon, - width: screenWidth * 0.015, // Adjust icon size - height: screenWidth * 0.015, + subspaces == null + ? DefaultButton( + onPressed: () { + _showSubSpaceModelDialog(context, enteredName, [], + false, widget.products, subspaces); + }, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.addIcon, + width: screenWidth * + 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Create sub space', + overflow: TextOverflow + .ellipsis, // Prevent overflow + style: Theme.of(context) + .textTheme + .bodyMedium, + ), + ), + ], ), ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Create sub space', - overflow: - TextOverflow.ellipsis, // Prevent overflow - style: Theme.of(context).textTheme.bodyMedium, + ) + : SizedBox( + width: screenWidth * 0.35, + child: Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(15), + border: Border.all( + color: ColorsManager.textFieldGreyColor, + width: 3.0, // Border width + ), + ), + child: Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: [ + if (subspaces != null) + ...subspaces!.map( + (subspace) => Chip( + label: Text( + subspace.subspaceName, + style: const TextStyle( + color: ColorsManager + .spaceColor), // Text color + ), + backgroundColor: ColorsManager + .whiteColors, // Chip background color + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 16), // Rounded chip + side: const BorderSide( + color: ColorsManager + .spaceColor), // Border color + ), + ), + ), + GestureDetector( + onTap: () async { + _showSubSpaceModelDialog( + context, + enteredName, + [], + false, + widget.products, + subspaces); + }, + child: Chip( + label: const Text( + 'Edit', + style: TextStyle( + color: ColorsManager.spaceColor), + ), + backgroundColor: + ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: const BorderSide( + color: ColorsManager.spaceColor), + ), + ), + ), + ], ), ), - ], - ), - ), - ), + ), const SizedBox(height: 10), DefaultButton( onPressed: () { @@ -481,15 +559,40 @@ class CreateSpaceDialogState extends State { onSave: (selectedModel) { if (selectedModel != null) { setState(() { - this.selectedSpaceModel = selectedModel; + selectedSpaceModel = selectedModel; }); - print('Selected Model: ${selectedModel.modelName}'); - } else { - print('No model selected'); } }, ); }, ); } + + void _showSubSpaceModelDialog( + BuildContext context, + String name, + final List? spaceTags, + bool isEdit, + List? products, + final List? existingSubSpaces) { + showDialog( + context: context, + builder: (BuildContext context) { + return CreateSubSpaceDialog( + spaceName: name, + dialogTitle: 'Create Sub-space', + spaceTags: spaceTags, + isEdit: isEdit, + products: products, + existingSubSpaces: existingSubSpaces, + onSave: (slectedSubspaces) { + if (slectedSubspaces != null) { + setState(() { + subspaces = slectedSubspaces; + }); + } + }); + }, + ); + } } diff --git a/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart b/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart new file mode 100644 index 00000000..6a072e4a --- /dev/null +++ b/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart @@ -0,0 +1,57 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/utils/constants/action_enum.dart'; + +import 'subspace_event.dart'; +import 'subspace_state.dart'; + +class SubSpaceBloc extends Bloc { + SubSpaceBloc() : super(SubSpaceState([], [], '')) { + on((event, emit) { + final existingNames = + state.subSpaces.map((e) => e.subspaceName).toSet(); + + if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) { + emit(SubSpaceState( + state.subSpaces, + state.updatedSubSpaceModels, + 'Subspace name already exists.', + )); + } else { + final updatedSubSpaces = List.from(state.subSpaces) + ..add(event.subSpace); + + emit(SubSpaceState( + updatedSubSpaces, + state.updatedSubSpaceModels, + '', + )); + } + }); + + // Handle RemoveSubSpace Event + on((event, emit) { + final updatedSubSpaces = List.from(state.subSpaces) + ..remove(event.subSpace); + + final updatedSubspaceModels = List.from( + state.updatedSubSpaceModels, + ); + + if (event.subSpace.uuid?.isNotEmpty ?? false) { + updatedSubspaceModels.add(UpdateSubspaceModel( + action: Action.delete, + uuid: event.subSpace.uuid!, + )); + } + + emit(SubSpaceState( + updatedSubSpaces, + updatedSubspaceModels, + '', // Clear error message + )); + }); + + // Handle UpdateSubSpace Event + } +} diff --git a/lib/pages/spaces_management/create_subspace/bloc/subspace_event.dart b/lib/pages/spaces_management/create_subspace/bloc/subspace_event.dart new file mode 100644 index 00000000..cdb62ea1 --- /dev/null +++ b/lib/pages/spaces_management/create_subspace/bloc/subspace_event.dart @@ -0,0 +1,18 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; + +abstract class SubSpaceEvent {} + +class AddSubSpace extends SubSpaceEvent { + final SubspaceModel subSpace; + AddSubSpace(this.subSpace); +} + +class RemoveSubSpace extends SubSpaceEvent { + final SubspaceModel subSpace; + RemoveSubSpace(this.subSpace); +} + +class UpdateSubSpace extends SubSpaceEvent { + final SubspaceModel updatedSubSpace; + UpdateSubSpace(this.updatedSubSpace); +} diff --git a/lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart b/lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart new file mode 100644 index 00000000..d1374ea1 --- /dev/null +++ b/lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart @@ -0,0 +1,26 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; + +class SubSpaceState { + final List subSpaces; + final List updatedSubSpaceModels; + final String errorMessage; + + SubSpaceState( + this.subSpaces, + this.updatedSubSpaceModels, + this.errorMessage, + ); + + + SubSpaceState copyWith({ + List? subSpaces, + List? updatedSubSpaceModels, + String? errorMessage, + }) { + return SubSpaceState( + subSpaces ?? this.subSpaces, + updatedSubSpaceModels ?? this.updatedSubSpaceModels, + errorMessage ?? this.errorMessage, + ); + } +} diff --git a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart new file mode 100644 index 00000000..6fd0b936 --- /dev/null +++ b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart @@ -0,0 +1,196 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_event.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_state.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CreateSubSpaceDialog extends StatelessWidget { + final bool isEdit; + final String dialogTitle; + final List? existingSubSpaces; + final String? spaceName; + final List? spaceTags; + final List? products; + final Function(List?)? onSave; + + const CreateSubSpaceDialog( + {Key? key, + required this.isEdit, + required this.dialogTitle, + this.existingSubSpaces, + required this.spaceName, + required this.spaceTags, + required this.products, + required this.onSave}) + : super(key: key); + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + final textController = TextEditingController(); + + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: BlocProvider( + create: (_) { + final bloc = SubSpaceBloc(); + if (existingSubSpaces != null) { + for (var subSpace in existingSubSpaces!) { + bloc.add(AddSubSpace(subSpace)); + } + } + return bloc; + }, + child: BlocBuilder( + builder: (context, state) { + return Container( + color: ColorsManager.whiteColors, + child: SizedBox( + width: screenWidth * 0.35, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + dialogTitle, + style: Theme.of(context) + .textTheme + .headlineLarge + ?.copyWith(color: ColorsManager.blackColor), + ), + const SizedBox(height: 16), + Container( + width: screenWidth * 0.35, + padding: const EdgeInsets.symmetric( + vertical: 10.0, horizontal: 16.0), + decoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(10), + ), + child: Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: [ + ...state.subSpaces.map( + (subSpace) => Chip( + label: Text( + subSpace.subspaceName, + style: const TextStyle( + color: ColorsManager.spaceColor), + ), + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide( + color: ColorsManager.transparentColor, + width: 0, + ), + ), + deleteIcon: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager.lightGrayColor, + width: 1.5, + ), + ), + child: const Icon( + Icons.close, + size: 16, + color: ColorsManager.lightGrayColor, + ), + ), + onDeleted: () => context + .read() + .add(RemoveSubSpace(subSpace)), + ), + ), + SizedBox( + width: 200, + child: TextField( + controller: textController, + decoration: InputDecoration( + border: InputBorder.none, + hintText: state.subSpaces.isEmpty + ? 'Please enter the name' + : null, + hintStyle: const TextStyle( + color: ColorsManager.lightGrayColor), + ), + onSubmitted: (value) { + if (value.trim().isNotEmpty) { + context.read().add( + AddSubSpace(SubspaceModel( + subspaceName: value.trim(), + disabled: false))); + textController.clear(); + } + }, + style: const TextStyle( + color: ColorsManager.blackColor), + ), + ), + if (state.errorMessage.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + state.errorMessage, + style: const TextStyle( + color: ColorsManager.warningRed, + fontSize: 12, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () async {}, + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + onPressed: () async { + final subSpaces = context + .read() + .state + .subSpaces; + onSave!(subSpaces); + Navigator.of(context).pop(); + }, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ], + ), + ], + ), + ), + )); + }, + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart b/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart index 44cbbc67..2ab969df 100644 --- a/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart +++ b/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart @@ -106,9 +106,8 @@ class LinkSpaceModelDialog extends StatelessWidget { ? () { if (onSave != null) { final selectedModel = - state is SpaceModelSelectedState - ? spaceModels[state.selectedIndex] - : null; + spaceModels[state.selectedIndex]; + onSave!(selectedModel); } Navigator.of(context).pop(); From 6591ef16648d1d57f34edb7abb5bcc8df4905786 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 09:25:29 +0400 Subject: [PATCH 064/106] load space models --- .../bloc/space_management_bloc.dart | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index f8ed1b86..554fdafe 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -52,10 +52,14 @@ class SpaceManagementBloc break; } } + + var prevSpaceModels = await fetchSpaceModels(previousState); + emit(SpaceManagementLoaded( communities: updatedCommunities, products: previousState.products, selectedCommunity: previousState.selectedCommunity, + spaceModels: prevSpaceModels, )); } } else { @@ -66,6 +70,23 @@ class SpaceManagementBloc } } + Future> fetchSpaceModels( + SpaceManagementState previousState) async { + List prevSpaceModels = []; + + if (previousState is SpaceManagementLoaded || previousState is BlankState) { + prevSpaceModels = List.from( + (previousState as dynamic).spaceModels ?? [], + ); + } + + if (prevSpaceModels.isEmpty) { + prevSpaceModels = await _spaceModelApi.listSpaceModels(page: 1); + } + + return prevSpaceModels; + } + void _onloadProducts() async { if (_cachedProducts == null) { final products = await _productApi.fetchProducts(); @@ -89,19 +110,24 @@ class SpaceManagementBloc return await _api.getSpaceHierarchy(communityUuid); } - void _onNewCommunity( + Future _onNewCommunity( NewCommunityEvent event, Emitter emit, - ) { + ) async { try { + final previousState = state; + if (event.communities.isEmpty) { emit(const SpaceManagementError('No communities provided.')); return; } + var prevSpaceModels = await fetchSpaceModels(previousState); + emit(BlankState( communities: event.communities, products: _cachedProducts ?? [], + spaceModels: prevSpaceModels, )); } catch (error) { emit(SpaceManagementError('Error loading communities: $error')); @@ -112,6 +138,7 @@ class SpaceManagementBloc BlankStateEvent event, Emitter emit) async { try { final previousState = state; + var prevSpaceModels = await fetchSpaceModels(previousState); if (previousState is SpaceManagementLoaded || previousState is BlankState) { @@ -119,6 +146,7 @@ class SpaceManagementBloc emit(BlankState( communities: List.from(prevCommunities), products: _cachedProducts ?? [], + spaceModels: prevSpaceModels, )); return; } @@ -139,6 +167,7 @@ class SpaceManagementBloc })); emit(BlankState( + spaceModels: prevSpaceModels, communities: updatedCommunities, products: _cachedProducts ?? [], )); @@ -217,6 +246,7 @@ class SpaceManagementBloc try { CommunityModel? newCommunity = await _api.createCommunity(event.name, event.description); + var prevSpaceModels = await fetchSpaceModels(previousState); if (newCommunity != null) { if (previousState is SpaceManagementLoaded || @@ -226,6 +256,7 @@ class SpaceManagementBloc ); final updatedCommunities = prevCommunities..add(newCommunity); emit(SpaceManagementLoaded( + spaceModels: prevSpaceModels, communities: updatedCommunities, products: _cachedProducts ?? [], selectedCommunity: newCommunity, From 3c5e0a777814018cfa1dae1bdea99885952c815d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 10:04:44 +0400 Subject: [PATCH 065/106] device type select --- .../bloc/add_device_model_bloc.dart | 38 +++++ .../bloc/add_device_type_model_event.dart | 19 +++ .../views/add_device_type_model_widget.dart | 130 +++++++++++++++ .../widgets/device_type_tile_widget.dart | 67 ++++++++ .../widgets/scrollable_grid_view_widget.dart | 70 ++++++++ .../widgets/dialogs/create_space_dialog.dart | 154 ++++++++++++------ .../widgets/tag_chips_display_widget.dart | 1 + .../views/add_device_type_model_widget.dart | 2 - 8 files changed, 433 insertions(+), 48 deletions(-) create mode 100644 lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart create mode 100644 lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart create mode 100644 lib/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart create mode 100644 lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart create mode 100644 lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart diff --git a/lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart new file mode 100644 index 00000000..10f3327e --- /dev/null +++ b/lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart @@ -0,0 +1,38 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart'; + +class AddDeviceTypeBloc + extends Bloc> { + AddDeviceTypeBloc(List initialProducts) + : super(initialProducts) { + on(_onUpdateProductCount); + } + + void _onUpdateProductCount( + UpdateProductCountEvent event, Emitter> emit) { + final existingProduct = state.firstWhere( + (p) => p.productId == event.productId, + orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName,product: event.product ), + ); + + if (event.count > 0) { + if (!state.contains(existingProduct)) { + emit([ + ...state, + SelectedProduct(productId: event.productId, count: event.count, productName: event.productName, product: event.product) + ]); + } else { + final updatedList = state.map((p) { + if (p.productId == event.productId) { + return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName,product: p.product); + } + return p; + }).toList(); + emit(updatedList); + } + } else { + emit(state.where((p) => p.productId != event.productId).toList()); + } + } +} diff --git a/lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart b/lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart new file mode 100644 index 00000000..addb6d67 --- /dev/null +++ b/lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart @@ -0,0 +1,19 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; + +abstract class AddDeviceTypeEvent extends Equatable { + @override + List get props => []; +} + +class UpdateProductCountEvent extends AddDeviceTypeEvent { + final String productId; + final int count; + final String productName; + final ProductModel product; + + UpdateProductCountEvent({required this.productId, required this.count, required this.productName, required this.product}); + + @override + List get props => [productId, count]; +} diff --git a/lib/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart new file mode 100644 index 00000000..38f5650b --- /dev/null +++ b/lib/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +import '../bloc/add_device_model_bloc.dart'; + +class AddDeviceTypeWidget extends StatelessWidget { + final List? products; + final ValueChanged>? onProductsSelected; + final List? initialSelectedProducts; + final List? subspaces; + final List? spaceTags; + final List? allTags; + final String spaceName; + + const AddDeviceTypeWidget( + {super.key, + this.products, + this.initialSelectedProducts, + this.onProductsSelected, + this.subspaces, + this.allTags, + this.spaceTags, + required this.spaceName}); + + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + final crossAxisCount = size.width > 1200 + ? 8 + : size.width > 800 + ? 5 + : 3; + + return BlocProvider( + create: (_) => AddDeviceTypeBloc(initialSelectedProducts ?? []), + child: Builder( + builder: (context) => AlertDialog( + title: const Text('Add Devices'), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Container( + width: size.width * 0.9, + height: size.height * 0.65, + color: ColorsManager.textFieldGreyColor, + child: Column( + children: [ + const SizedBox(height: 16), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: ScrollableGridViewWidget( + products: products, crossAxisCount: crossAxisCount), + ), + ), + ], + ), + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CancelButton( + label: 'Cancel', + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + ActionButton( + label: 'Continue', + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: ColorsManager.whiteColors, + onPressed: () async { + final currentState = + context.read().state; + Navigator.of(context).pop(); + + if (currentState.isNotEmpty) { + final initialTags = generateInitialTags( + spaceTags: spaceTags, + subspaces: subspaces, + ); + + final dialogTitle = initialTags.isNotEmpty + ? 'Edit Device' + : 'Assign Tags'; + } + }, + ), + ], + ), + ], + ), + )); + } + + List generateInitialTags({ + List? spaceTags, + List? subspaces, + }) { + final List initialTags = []; + + if (spaceTags != null) { + initialTags.addAll(spaceTags); + } + + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + initialTags.addAll( + subspace.tags!.map( + (tag) => tag.copyWith(location: subspace.subspaceName), + ), + ); + } + } + } + + return initialTags; + } +} diff --git a/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart new file mode 100644 index 00000000..2feea7d9 --- /dev/null +++ b/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_icon_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_name_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class DeviceTypeTileWidget extends StatelessWidget { + final ProductModel product; + final List productCounts; + + const DeviceTypeTileWidget({ + super.key, + required this.product, + required this.productCounts, + }); + + @override + Widget build(BuildContext context) { + final selectedProduct = productCounts.firstWhere( + (p) => p.productId == product.uuid, + orElse: () => SelectedProduct( + productId: product.uuid, + count: 0, + productName: product.catName, + product: product), + ); + + return Card( + elevation: 2, + color: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + DeviceIconWidget(icon: product.icon ?? Assets.doorSensor), + const SizedBox(height: 4), + DeviceNameWidget(name: product.name), + const SizedBox(height: 4), + CounterWidget( + initialCount: selectedProduct.count, + onCountChanged: (newCount) { + context.read().add( + UpdateProductCountEvent( + productId: product.uuid, + count: newCount, + productName: product.catName, + product: product), + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart b/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart new file mode 100644 index 00000000..aeee6f1b --- /dev/null +++ b/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; + +class ScrollableGridViewWidget extends StatelessWidget { + final List? products; + final int crossAxisCount; + final List? initialProductCounts; + + const ScrollableGridViewWidget({ + super.key, + required this.products, + required this.crossAxisCount, + this.initialProductCounts, + }); + + @override + Widget build(BuildContext context) { + final scrollController = ScrollController(); + + return Scrollbar( + controller: scrollController, + thumbVisibility: true, + child: BlocBuilder>( + builder: (context, productCounts) { + return GridView.builder( + controller: scrollController, + shrinkWrap: true, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + mainAxisSpacing: 6, + crossAxisSpacing: 4, + childAspectRatio: .8, + ), + itemCount: products?.length ?? 0, + itemBuilder: (context, index) { + final product = products![index]; + final initialProductCount = _findInitialProductCount(product); + + return DeviceTypeTileWidget( + product: product, + productCounts: initialProductCount != null + ? [...productCounts, initialProductCount] + : productCounts, + ); + }, + ); + }, + ), + ); + } + + SelectedProduct? _findInitialProductCount(ProductModel product) { + if (initialProductCounts == null) return null; + final matchingProduct = initialProductCounts!.firstWhere( + (selectedProduct) => selectedProduct.productId == product.uuid, + orElse: () => SelectedProduct( + productId: '', + count: 0, + productName: '', + product: null, + ), + ); + + return matchingProduct.productId.isNotEmpty ? matchingProduct : null; + } +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index ea6363de..e7e103e3 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; @@ -28,6 +29,7 @@ class CreateSpaceDialog extends StatefulWidget { final SpaceModel? editSpace; final List? spaceModels; final List? subspaces; + final List? tags; const CreateSpaceDialog( {super.key, @@ -40,7 +42,8 @@ class CreateSpaceDialog extends StatefulWidget { this.editSpace, this.selectedProducts = const [], this.spaceModels, - this.subspaces}); + this.subspaces, + this.tags}); @override CreateSpaceDialogState createState() => CreateSpaceDialogState(); @@ -56,6 +59,7 @@ class CreateSpaceDialogState extends State { bool isNameFieldInvalid = false; bool isNameFieldExist = false; List? subspaces; + List? tags; @override void initState() { @@ -326,7 +330,7 @@ class CreateSpaceDialogState extends State { subspaces == null ? DefaultButton( onPressed: () { - _showSubSpaceModelDialog(context, enteredName, [], + _showSubSpaceDialog(context, enteredName, [], false, widget.products, subspaces); }, backgroundColor: ColorsManager.textFieldGreyColor, @@ -401,7 +405,7 @@ class CreateSpaceDialogState extends State { ), GestureDetector( onTap: () async { - _showSubSpaceModelDialog( + _showSubSpaceDialog( context, enteredName, [], @@ -429,51 +433,50 @@ class CreateSpaceDialogState extends State { ), ), const SizedBox(height: 10), - DefaultButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => AddDeviceWidget( - products: widget.products, - onProductsSelected: (selectedProductsMap) { - setState(() { - selectedProducts = selectedProductsMap; - }); + (tags?.isNotEmpty == true || + subspaces?.any((subspace) => + subspace.tags?.isNotEmpty == true) == + true) + ? Container() + : DefaultButton( + onPressed: () { + _showTagCreateDialog(context, enteredName, tags, + subspaces, widget.products); }, - ), - ); - }, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.addIcon, - width: screenWidth * 0.015, // Adjust icon size - height: screenWidth * 0.015, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.addIcon, + width: screenWidth * + 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Add devices', + overflow: TextOverflow + .ellipsis, // Prevent overflow + style: Theme.of(context) + .textTheme + .bodyMedium, + ), + ), + ], ), ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Add devices', - overflow: - TextOverflow.ellipsis, // Prevent overflow - style: Theme.of(context).textTheme.bodyMedium, - ), - ), - ], - ), - ), - ), + ) ], ), ), @@ -560,6 +563,7 @@ class CreateSpaceDialogState extends State { if (selectedModel != null) { setState(() { selectedSpaceModel = selectedModel; + subspaces = null; }); } }, @@ -568,7 +572,7 @@ class CreateSpaceDialogState extends State { ); } - void _showSubSpaceModelDialog( + void _showSubSpaceDialog( BuildContext context, String name, final List? spaceTags, @@ -589,10 +593,68 @@ class CreateSpaceDialogState extends State { if (slectedSubspaces != null) { setState(() { subspaces = slectedSubspaces; + selectedSpaceModel = null; }); } }); }, ); } + + void _showTagCreateDialog( + BuildContext context, + String name, + final List? spaceTags, + List? subspaces, + List? products) { + showDialog( + context: context, + builder: (BuildContext context) { + return AddDeviceTypeWidget( + spaceName: name, + products: products, + subspaces: subspaces, + spaceTags: spaceTags, + allTags: [], + initialSelectedProducts: + createInitialSelectedProducts(tags, subspaces), + ); + }, + ); + } + + List createInitialSelectedProducts( + List? tags, List? subspaces) { + final Map productCounts = {}; + + if (tags != null) { + for (var tag in tags) { + if (tag.product != null) { + productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1; + } + } + } + + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + for (var tag in subspace.tags!) { + if (tag.product != null) { + productCounts[tag.product!] = + (productCounts[tag.product!] ?? 0) + 1; + } + } + } + } + } + + return productCounts.entries + .map((entry) => SelectedProduct( + productId: entry.key.uuid, + count: entry.value, + productName: entry.key.name ?? 'Unnamed', + product: entry.key, + )) + .toList(); + } } diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 9cb356ef..799eb71b 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -189,4 +189,5 @@ class TagChipDisplay extends StatelessWidget { )) .toList(); } + } diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index a8a6b1ac..f7f4c3f7 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -15,7 +15,6 @@ import 'package:syncrow_web/utils/color_manager.dart'; class AddDeviceTypeModelWidget extends StatelessWidget { final List? products; - final ValueChanged>? onProductsSelected; final List? initialSelectedProducts; final List? subspaces; final List? spaceTagModels; @@ -26,7 +25,6 @@ class AddDeviceTypeModelWidget extends StatelessWidget { {super.key, this.products, this.initialSelectedProducts, - this.onProductsSelected, this.subspaces, this.allTags, this.spaceTagModels, From 1be52adcc8feea402b562a6888c1e62c3949d83d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 10:43:47 +0400 Subject: [PATCH 066/106] added assign tag --- ...idget.dart => add_device_type_widget.dart} | 21 +- .../widgets/dialogs/create_space_dialog.dart | 106 +++++- .../assign_tag/bloc/assign_tag_bloc.dart | 153 ++++++++ .../assign_tag/bloc/assign_tag_event.dart | 55 +++ .../assign_tag/bloc/assign_tag_state.dart | 38 ++ .../assign_tag/views/assign_tag_dialog.dart | 340 ++++++++++++++++++ 6 files changed, 707 insertions(+), 6 deletions(-) rename lib/pages/spaces_management/add_device_type/views/{add_device_type_model_widget.dart => add_device_type_widget.dart} (82%) create mode 100644 lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart create mode 100644 lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart create mode 100644 lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart create mode 100644 lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart diff --git a/lib/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart similarity index 82% rename from lib/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart rename to lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart index 38f5650b..677b9fb9 100644 --- a/lib/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -import '../bloc/add_device_model_bloc.dart'; class AddDeviceTypeWidget extends StatelessWidget { final List? products; @@ -20,6 +20,8 @@ class AddDeviceTypeWidget extends StatelessWidget { final List? spaceTags; final List? allTags; final String spaceName; + final Function(List,List?)? onSave; + const AddDeviceTypeWidget( {super.key, @@ -29,6 +31,7 @@ class AddDeviceTypeWidget extends StatelessWidget { this.subspaces, this.allTags, this.spaceTags, + this.onSave, required this.spaceName}); @override @@ -93,6 +96,20 @@ class AddDeviceTypeWidget extends StatelessWidget { final dialogTitle = initialTags.isNotEmpty ? 'Edit Device' : 'Assign Tags'; + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AssignTagDialog( + products: products, + subspaces: subspaces, + addedProducts: currentState, + allTags: allTags, + spaceName: spaceName, + initialTags: initialTags, + title: dialogTitle, + onSave: onSave, + ), + ); } }, ), diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index e7e103e3..ed4a22b9 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -2,13 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart'; @@ -72,7 +71,6 @@ class CreateSpaceDialogState extends State { enteredName.isNotEmpty || nameController.text.isNotEmpty; } - @override @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; @@ -437,7 +435,80 @@ class CreateSpaceDialogState extends State { subspaces?.any((subspace) => subspace.tags?.isNotEmpty == true) == true) - ? Container() + ? SizedBox( + width: screenWidth * 0.25, + child: Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(15), + border: Border.all( + color: ColorsManager.textFieldGreyColor, + width: 3.0, // Border width + ), + ), + child: Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: [ + // Combine tags from spaceModel and subspaces + ..._groupTags([ + ...?tags, + ...?subspaces?.expand( + (subspace) => subspace.tags ?? []) + ]).entries.map( + (entry) => Chip( + avatar: SizedBox( + width: 24, + height: 24, + child: SvgPicture.asset( + entry.key.icon ?? + 'assets/icons/gateway.svg', + fit: BoxFit.contain, + ), + ), + label: Text( + 'x${entry.value}', // Show count + style: const TextStyle( + color: ColorsManager.spaceColor, + ), + ), + backgroundColor: + ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(16), + side: const BorderSide( + color: ColorsManager.spaceColor, + ), + ), + ), + ), + GestureDetector( + onTap: () async { + _showTagCreateDialog(context, enteredName, + tags, subspaces, widget.products); + // Edit action + }, + child: Chip( + label: const Text( + 'Edit', + style: TextStyle( + color: ColorsManager.spaceColor), + ), + backgroundColor: + ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: const BorderSide( + color: ColorsManager.spaceColor), + ), + ), + ), + ], + ), + ), + ) : DefaultButton( onPressed: () { _showTagCreateDialog(context, enteredName, tags, @@ -618,6 +689,23 @@ class CreateSpaceDialogState extends State { allTags: [], initialSelectedProducts: createInitialSelectedProducts(tags, subspaces), + onSave: (selectedSpaceTags, selectedSubspaces) { + setState(() { + if (spaceTags != null) selectedSpaceTags = spaceTags; + if (selectedSubspaces != null) { + if (subspaces != null) { + for (final subspace in subspaces) { + for (final selectedSubspace in selectedSubspaces) { + if (subspace.subspaceName == + selectedSubspace.subspaceName) { + subspace.tags = selectedSubspace.tags; + } + } + } + } + } + }); + }, ); }, ); @@ -657,4 +745,14 @@ class CreateSpaceDialogState extends State { )) .toList(); } + + Map _groupTags(List tags) { + final Map groupedTags = {}; + for (var tag in tags) { + if (tag.product != null) { + groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1; + } + } + return groupedTags; + } } diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart new file mode 100644 index 00000000..6adcc6a7 --- /dev/null +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart @@ -0,0 +1,153 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; + +class AssignTagBloc + extends Bloc { + AssignTagBloc() : super(AssignTagInitial()) { + on((event, emit) { + final initialTags = event.initialTags ?? []; + + final existingTagCounts = {}; + for (var tag in initialTags) { + if (tag.product != null) { + existingTagCounts[tag.product!.uuid] = + (existingTagCounts[tag.product!.uuid] ?? 0) + 1; + } + } + + final allTags = []; + + for (var selectedProduct in event.addedProducts) { + final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; + + if (selectedProduct.count == 0 || + selectedProduct.count <= existingCount) { + allTags.addAll(initialTags + .where((tag) => tag.product?.uuid == selectedProduct.productId)); + continue; + } + + final missingCount = selectedProduct.count - existingCount; + + allTags.addAll(initialTags + .where((tag) => tag.product?.uuid == selectedProduct.productId)); + + if (missingCount > 0) { + allTags.addAll(List.generate( + missingCount, + (index) => Tag( + tag: '', + product: selectedProduct.product, + location: 'None', + ), + )); + } + } + + emit(AssignTagLoaded( + tags: allTags, + isSaveEnabled: _validateTags(allTags), + )); + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagLoaded && + currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); + tags[event.index].tag = event.tag; + emit(AssignTagLoaded( + tags: tags, + isSaveEnabled: _validateTags(tags), + errorMessage: _getValidationError(tags), + )); + } + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagLoaded && + currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); + + // Use copyWith for immutability + tags[event.index] = + tags[event.index].copyWith(location: event.location); + + emit(AssignTagLoaded( + tags: tags, + isSaveEnabled: _validateTags(tags), + )); + } + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagLoaded && + currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); + + emit(AssignTagLoaded( + tags: tags, + isSaveEnabled: _validateTags(tags), + errorMessage: _getValidationError(tags), + )); + } + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagLoaded && + currentState.tags.isNotEmpty) { + final updatedTags = List.from(currentState.tags) + ..remove(event.tagToDelete); + + emit(AssignTagLoaded( + tags: updatedTags, + isSaveEnabled: _validateTags(updatedTags), + )); + } else { + emit(const AssignTagLoaded( + tags: [], + isSaveEnabled: false, + )); + } + }); + } + + bool _validateTags(List tags) { + if (tags.isEmpty) { + return false; + } + final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); + final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); + return uniqueTags.length == tags.length && !hasEmptyTag; + } + + String? _getValidationError(List tags) { + final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); + if (hasEmptyTag) return 'Tags cannot be empty.'; + final duplicateTags = tags + .map((tag) => tag.tag?.trim() ?? '') + .fold>({}, (map, tag) { + map[tag] = (map[tag] ?? 0) + 1; + return map; + }) + .entries + .where((entry) => entry.value > 1) + .map((entry) => entry.key) + .toList(); + + if (duplicateTags.isNotEmpty) { + return 'Duplicate tags found: ${duplicateTags.join(', ')}'; + } + + return null; + } +} diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart new file mode 100644 index 00000000..9116b094 --- /dev/null +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart @@ -0,0 +1,55 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; + +abstract class AssignTagEvent extends Equatable { + const AssignTagEvent(); + + @override + List get props => []; +} + +class InitializeTags extends AssignTagEvent { + final List? initialTags; + final List addedProducts; + + const InitializeTags({ + required this.initialTags, + required this.addedProducts, + }); + + @override + List get props => [initialTags ?? [], addedProducts]; +} + +class UpdateTagEvent extends AssignTagEvent { + final int index; + final String tag; + + const UpdateTagEvent({required this.index, required this.tag}); + + @override + List get props => [index, tag]; +} + +class UpdateLocation extends AssignTagEvent { + final int index; + final String location; + + const UpdateLocation({required this.index, required this.location}); + + @override + List get props => [index, location]; +} + +class ValidateTags extends AssignTagEvent {} + +class DeleteTag extends AssignTagEvent { + final Tag tagToDelete; + final List tags; + + const DeleteTag({required this.tagToDelete, required this.tags}); + + @override + List get props => [tagToDelete, tags]; +} diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart new file mode 100644 index 00000000..19cf4435 --- /dev/null +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart @@ -0,0 +1,38 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; + +abstract class AssignTagState extends Equatable { + const AssignTagState(); + + @override + List get props => []; +} + +class AssignTagInitial extends AssignTagState {} + +class AssignTagLoading extends AssignTagState {} + +class AssignTagLoaded extends AssignTagState { + final List tags; + final bool isSaveEnabled; + final String? errorMessage; + + const AssignTagLoaded({ + required this.tags, + required this.isSaveEnabled, + this.errorMessage, + }); + + @override + List get props => [tags, isSaveEnabled]; +} + +class AssignTagError extends AssignTagState { + final String errorMessage; + + const AssignTagError(this.errorMessage); + + @override + List get props => [errorMessage]; +} diff --git a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart new file mode 100644 index 00000000..31f9bec1 --- /dev/null +++ b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart @@ -0,0 +1,340 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class AssignTagDialog extends StatelessWidget { + final List? products; + final List? subspaces; + final List? initialTags; + final ValueChanged>? onTagsAssigned; + final List addedProducts; + final List? allTags; + final String spaceName; + final String title; + final Function(List, List?)? onSave; + + const AssignTagDialog( + {Key? key, + required this.products, + required this.subspaces, + required this.addedProducts, + this.initialTags, + this.onTagsAssigned, + this.allTags, + required this.spaceName, + required this.title, + this.onSave}) + : super(key: key); + + @override + Widget build(BuildContext context) { + final List locations = + (subspaces ?? []).map((subspace) => subspace.subspaceName).toList(); + return BlocProvider( + create: (_) => AssignTagBloc() + ..add(InitializeTags( + initialTags: initialTags, + addedProducts: addedProducts, + )), + child: BlocBuilder( + builder: (context, state) { + if (state is AssignTagLoaded) { + final controllers = List.generate( + state.tags.length, + (index) => TextEditingController(text: state.tags[index].tag), + ); + + return AlertDialog( + title: Text(title), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Column( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(20), + child: DataTable( + headingRowColor: WidgetStateProperty.all( + ColorsManager.dataHeaderGrey), + border: TableBorder.all( + color: ColorsManager.dataHeaderGrey, + width: 1, + borderRadius: BorderRadius.circular(20), + ), + columns: [ + DataColumn( + label: Text('#', + style: + Theme.of(context).textTheme.bodyMedium)), + DataColumn( + label: Text('Device', + style: + Theme.of(context).textTheme.bodyMedium)), + DataColumn( + label: Text('Tag', + style: + Theme.of(context).textTheme.bodyMedium)), + DataColumn( + label: Text('Location', + style: + Theme.of(context).textTheme.bodyMedium)), + ], + rows: state.tags.isEmpty + ? [ + const DataRow(cells: [ + DataCell( + Center( + child: Text( + 'No Data Available', + style: TextStyle( + fontSize: 14, + color: ColorsManager.lightGrayColor, + ), + ), + ), + ), + DataCell(SizedBox()), + DataCell(SizedBox()), + DataCell(SizedBox()), + ]) + ] + : List.generate(state.tags.length, (index) { + final tag = state.tags[index]; + final controller = controllers[index]; + + return DataRow( + cells: [ + DataCell(Text(index.toString())), + DataCell( + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + tag.product?.name ?? 'Unknown', + overflow: TextOverflow.ellipsis, + )), + IconButton( + icon: const Icon(Icons.close, + color: ColorsManager.warningRed, + size: 16), + onPressed: () { + context.read().add( + DeleteTag( + tagToDelete: tag, + tags: state.tags)); + }, + tooltip: 'Delete Tag', + ) + ], + ), + ), + DataCell( + Row( + children: [ + Expanded( + child: TextFormField( + controller: controller, + onChanged: (value) { + context + .read() + .add(UpdateTagEvent( + index: index, + tag: value.trim(), + )); + }, + decoration: const InputDecoration( + hintText: 'Enter Tag', + border: InputBorder.none, + ), + style: const TextStyle( + fontSize: 14, + color: ColorsManager.blackColor, + ), + ), + ), + SizedBox( + width: MediaQuery.of(context) + .size + .width * + 0.15, + child: PopupMenuButton( + color: ColorsManager.whiteColors, + icon: const Icon( + Icons.arrow_drop_down, + color: + ColorsManager.blackColor), + onSelected: (value) { + controller.text = value; + context + .read() + .add(UpdateTagEvent( + index: index, + tag: value, + )); + }, + itemBuilder: (context) { + return (allTags ?? []) + .where((tagValue) => !state + .tags + .map((e) => e.tag) + .contains(tagValue)) + .map((tagValue) { + return PopupMenuItem( + textStyle: const TextStyle( + color: ColorsManager + .textPrimaryColor), + value: tagValue, + child: ConstrainedBox( + constraints: + BoxConstraints( + minWidth: MediaQuery.of( + context) + .size + .width * + 0.15, + maxWidth: MediaQuery.of( + context) + .size + .width * + 0.15, + ), + child: Text( + tagValue, + overflow: TextOverflow + .ellipsis, + ), + )); + }).toList(); + }, + ), + ), + ], + ), + ), + DataCell( + DropdownButtonHideUnderline( + child: DropdownButton( + value: tag.location ?? 'None', + dropdownColor: ColorsManager + .whiteColors, // Dropdown background + style: const TextStyle( + color: Colors + .black), // Style for selected text + items: [ + const DropdownMenuItem( + value: 'None', + child: Text( + 'None', + style: TextStyle( + color: ColorsManager + .textPrimaryColor), + ), + ), + ...locations.map((location) { + return DropdownMenuItem( + value: location, + child: Text( + location, + style: const TextStyle( + color: ColorsManager + .textPrimaryColor), + ), + ); + }).toList(), + ], + onChanged: (value) { + if (value != null) { + context + .read() + .add(UpdateLocation( + index: index, + location: value, + )); + } + }, + ), + ), + ), + ], + ); + }), + ), + ), + if (state.errorMessage != null) + Text( + state.errorMessage!, + style: const TextStyle(color: ColorsManager.warningRed), + ), + ], + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + const SizedBox(width: 10), + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + borderRadius: 10, + backgroundColor: state.isSaveEnabled + ? ColorsManager.secondaryColor + : ColorsManager.grayColor, + foregroundColor: ColorsManager.whiteColors, + onPressed: state.isSaveEnabled + ? () async { + Navigator.of(context).pop(); + final assignedTags = {}; + for (var tag in state.tags) { + if (tag.location == null || + subspaces == null) { + continue; + } + for (var subspace in subspaces!) { + if (tag.location == subspace.subspaceName) { + subspace.tags ??= []; + subspace.tags!.add(tag); + assignedTags.add(tag); + break; + } + } + } + onSave!(state.tags,subspaces); + } + : null, + child: const Text('Save'), + ), + ), + const SizedBox(width: 10), + ], + ), + ], + ); + } else if (state is AssignTagLoading) { + return const Center(child: CircularProgressIndicator()); + } else { + return const Center(child: Text('Something went wrong.')); + } + }, + ), + ); + } +} From a588351482cf5f7e8b0c7fdc057ecac478477aae Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 11:37:10 +0400 Subject: [PATCH 067/106] fixed space creation api --- .../views/add_device_type_widget.dart | 4 +- .../bloc/space_management_bloc.dart | 40 +++++++++++++++---- .../model/create_subspace_model.dart | 13 ++++++ .../all_spaces/model/space_model.dart | 2 +- .../all_spaces/model/tag.dart | 7 ++++ .../view/spaces_management_page.dart | 1 - .../widgets/community_structure_widget.dart | 30 +++++++++----- .../widgets/dialogs/create_space_dialog.dart | 24 +++++------ .../space_model/models/tag_body_model.dart | 16 ++++++++ lib/services/space_mana_api.dart | 31 +++++++++----- 10 files changed, 123 insertions(+), 45 deletions(-) create mode 100644 lib/pages/spaces_management/all_spaces/model/create_subspace_model.dart create mode 100644 lib/pages/spaces_management/space_model/models/tag_body_model.dart diff --git a/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart index 677b9fb9..9b9d6886 100644 --- a/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart +++ b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart @@ -107,7 +107,9 @@ class AddDeviceTypeWidget extends StatelessWidget { spaceName: spaceName, initialTags: initialTags, title: dialogTitle, - onSave: onSave, + onSave: (tags,subspaces){ + onSave!(tags,subspaces); + }, ), ); } diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 554fdafe..6458bdf6 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -1,10 +1,14 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; @@ -343,7 +347,7 @@ class SpaceManagementBloc emit(SpaceCreationSuccess(spaces: updatedSpaces)); if (previousState is SpaceManagementLoaded) { - _updateLoadedState( + await _updateLoadedState( previousState, allSpaces, event.communityUuid, @@ -361,23 +365,25 @@ class SpaceManagementBloc } } - void _updateLoadedState( + Future _updateLoadedState( SpaceManagementLoaded previousState, List allSpaces, String communityUuid, Emitter emit, - ) { + ) async { + var prevSpaceModels = await fetchSpaceModels(previousState); + final communities = List.from(previousState.communities); for (var community in communities) { if (community.uuid == communityUuid) { community.spaces = allSpaces; emit(SpaceManagementLoaded( - communities: communities, - products: _cachedProducts ?? [], - selectedCommunity: community, - selectedSpace: null, - )); + communities: communities, + products: _cachedProducts ?? [], + selectedCommunity: community, + selectedSpace: null, + spaceModels: prevSpaceModels)); return; } } @@ -416,6 +422,21 @@ class SpaceManagementBloc ); } else { // Call create if the space does not have a UUID + final List tagBodyModels = space.tags != null + ? space.tags!.map((tag) => tag.toCreateTagBodyModel()).toList() + : []; + + final createSubspaceBodyModels = space.subspaces?.map((subspace) { + final tagBodyModels = subspace.tags + ?.map((tag) => tag.toCreateTagBodyModel()) + .toList() ?? + []; + return CreateSubspaceModel() + ..subspaceName = subspace.subspaceName + ..tags = tagBodyModels; + }).toList() ?? + []; + final response = await _api.createSpace( communityId: communityUuid, name: space.name, @@ -424,6 +445,9 @@ class SpaceManagementBloc position: space.position, icon: space.icon, direction: space.incomingConnection?.direction, + spaceModelUuid: space.spaceModel?.uuid, + tags: tagBodyModels, + subspaces: createSubspaceBodyModels, ); space.uuid = response?.uuid; } diff --git a/lib/pages/spaces_management/all_spaces/model/create_subspace_model.dart b/lib/pages/spaces_management/all_spaces/model/create_subspace_model.dart new file mode 100644 index 00000000..ce480169 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/model/create_subspace_model.dart @@ -0,0 +1,13 @@ +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; + +class CreateSubspaceModel { + late String subspaceName; + late List? tags; + + Map toJson() { + return { + 'subspaceName': subspaceName, + 'tags': tags?.map((tag) => tag.toJson()).toList(), + }; + } +} diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index 64a98803..c9393cce 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -24,7 +24,7 @@ class SpaceModel { SpaceStatus status; String internalId; SpaceTemplateModel? spaceModel; - final List? tags; + List? tags; List? subspaces; List outgoingConnections = []; // Connections from this space diff --git a/lib/pages/spaces_management/all_spaces/model/tag.dart b/lib/pages/spaces_management/all_spaces/model/tag.dart index b70865c7..98494f7f 100644 --- a/lib/pages/spaces_management/all_spaces/model/tag.dart +++ b/lib/pages/spaces_management/all_spaces/model/tag.dart @@ -1,5 +1,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:uuid/uuid.dart'; class Tag { @@ -58,4 +59,10 @@ extension TagModelExtensions on Tag { ..tag = tag ?? '' ..productUuid = product?.uuid; } + + CreateTagBodyModel toCreateTagBodyModel() { + return CreateTagBodyModel() + ..tag = tag ?? '' + ..productUuid = product?.uuid; + } } diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index 24b1b5cb..e601cca4 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -54,7 +54,6 @@ class SpaceManagementPageState extends State { shouldNavigateToSpaceModelPage: false, ); } else if (state is SpaceManagementLoaded) { - print("sdksndsnadf ${state.spaceModels}"); return LoadedSpaceView( communities: state.communities, selectedCommunity: state.selectedCommunity, diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index f48e2470..b7743a01 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -11,6 +11,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_pr import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/blank_community_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart'; @@ -55,7 +57,6 @@ class _CommunityStructureAreaState extends State { @override void initState() { super.initState(); - print("sxdsf ${widget.spaceModels}"); spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; @@ -293,21 +294,24 @@ class _CommunityStructureAreaState extends State { onCreateSpace: (String name, String icon, List selectedProducts, - SpaceTemplateModel? spaceModel) { + SpaceTemplateModel? spaceModel, + List? subspaces, + List? tags) { setState(() { // Set the first space in the center or use passed position Offset centerPosition = position ?? _getCenterPosition(screenSize); SpaceModel newSpace = SpaceModel( - name: name, - icon: icon, - position: centerPosition, - isPrivate: false, - children: [], - status: SpaceStatus.newSpace, - spaceModel: spaceModel, - ); + name: name, + icon: icon, + position: centerPosition, + isPrivate: false, + children: [], + status: SpaceStatus.newSpace, + spaceModel: spaceModel, + subspaces: subspaces, + tags: tags); if (parentIndex != null && direction != null) { SpaceModel parentSpace = spaces[parentIndex]; @@ -346,12 +350,16 @@ class _CommunityStructureAreaState extends State { onCreateSpace: (String name, String icon, List selectedProducts, - SpaceTemplateModel? spaceModel) { + SpaceTemplateModel? spaceModel, + List? subspaces, + List? tags) { setState(() { // Update the space's properties space.name = name; space.icon = icon; space.spaceModel = spaceModel; + space.subspaces= subspaces; + space.tags = tags; if (space.status != SpaceStatus.newSpace) { space.status = SpaceStatus.modified; // Mark as modified diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index ed4a22b9..6f3e9fcb 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -18,7 +18,7 @@ import 'package:syncrow_web/utils/constants/space_icon_const.dart'; class CreateSpaceDialog extends StatefulWidget { final Function(String, String, List selectedProducts, - SpaceTemplateModel? spaceModel) onCreateSpace; + SpaceTemplateModel? spaceModel, List? subspaces, List? tags) onCreateSpace; final List? products; final String? name; final String? icon; @@ -487,7 +487,7 @@ class CreateSpaceDialogState extends State { GestureDetector( onTap: () async { _showTagCreateDialog(context, enteredName, - tags, subspaces, widget.products); + widget.products); // Edit action }, child: Chip( @@ -511,8 +511,8 @@ class CreateSpaceDialogState extends State { ) : DefaultButton( onPressed: () { - _showTagCreateDialog(context, enteredName, tags, - subspaces, widget.products); + _showTagCreateDialog( + context, enteredName, widget.products); }, backgroundColor: ColorsManager.textFieldGreyColor, foregroundColor: Colors.black, @@ -580,7 +580,7 @@ class CreateSpaceDialogState extends State { : (widget.name ?? ''); if (newName.isNotEmpty) { widget.onCreateSpace(newName, selectedIcon, - selectedProducts, selectedSpaceModel); + selectedProducts, selectedSpaceModel,subspaces,tags); Navigator.of(context).pop(); } } @@ -673,11 +673,7 @@ class CreateSpaceDialogState extends State { } void _showTagCreateDialog( - BuildContext context, - String name, - final List? spaceTags, - List? subspaces, - List? products) { + BuildContext context, String name, List? products) { showDialog( context: context, builder: (BuildContext context) { @@ -685,16 +681,18 @@ class CreateSpaceDialogState extends State { spaceName: name, products: products, subspaces: subspaces, - spaceTags: spaceTags, + spaceTags: tags, allTags: [], initialSelectedProducts: createInitialSelectedProducts(tags, subspaces), onSave: (selectedSpaceTags, selectedSubspaces) { setState(() { - if (spaceTags != null) selectedSpaceTags = spaceTags; + tags = selectedSpaceTags; + selectedSpaceModel = null; + if (selectedSubspaces != null) { if (subspaces != null) { - for (final subspace in subspaces) { + for (final subspace in subspaces!) { for (final selectedSubspace in selectedSubspaces) { if (subspace.subspaceName == selectedSubspace.subspaceName) { diff --git a/lib/pages/spaces_management/space_model/models/tag_body_model.dart b/lib/pages/spaces_management/space_model/models/tag_body_model.dart new file mode 100644 index 00000000..d66e2884 --- /dev/null +++ b/lib/pages/spaces_management/space_model/models/tag_body_model.dart @@ -0,0 +1,16 @@ +class CreateTagBodyModel { + late String tag; + late final String? productUuid; + + Map toJson() { + return { + 'tag': tag, + 'productUuid': productUuid, + }; + } + + @override + String toString() { + return toJson().toString(); + } +} diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index b637cbc7..2a2d42ad 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; import 'package:syncrow_web/utils/constants/temp_const.dart'; @@ -161,15 +164,17 @@ class CommunitySpaceManagementApi { } } - Future createSpace({ - required String communityId, - required String name, - String? parentId, - String? direction, - bool isPrivate = false, - required Offset position, - String? icon, - }) async { + Future createSpace( + {required String communityId, + required String name, + String? parentId, + String? direction, + bool isPrivate = false, + required Offset position, + String? spaceModelUuid, + String? icon, + List? tags, + List? subspaces}) async { try { final body = { 'spaceName': name, @@ -182,6 +187,13 @@ class CommunitySpaceManagementApi { if (parentId != null) { body['parentUuid'] = parentId; } + if (tags != null) { + body['tags'] = tags; + } + + if (spaceModelUuid != null) body['spaceModelUuid'] = spaceModelUuid; + + if (subspaces != null) body['subspaces'] = subspaces; final response = await HTTPService().post( path: ApiEndpoints.createSpace .replaceAll('{communityId}', communityId) @@ -263,7 +275,6 @@ class CommunitySpaceManagementApi { .replaceAll('{communityId}', communityId) .replaceAll('{projectId}', TempConst.projectId), expectedResponseModel: (json) { - print(json); final spaceModels = (json['data'] as List) .map((spaceJson) => SpaceModel.fromJson(spaceJson)) .toList(); From a381fd317dde92024a9b1c88d39a5ae288d120c4 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 12:10:54 +0400 Subject: [PATCH 068/106] added space delete --- .../all_spaces/model/space_model.dart | 2 +- .../community_structure_header_widget.dart | 27 ++++++++++++------- .../widgets/community_structure_widget.dart | 16 +++++++---- lib/services/api/http_service.dart | 13 ++++++++- lib/utils/constants/temp_const.dart | 2 +- 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index c9393cce..c8da9d9e 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -7,7 +7,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_tem import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:uuid/uuid.dart'; -enum SpaceStatus { newSpace, modified, unchanged, deleted } +enum SpaceStatus { newSpace, modified, unchanged, deleted, parentDeleted } class SpaceModel { String? uuid; diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart index 612f9101..02d3819a 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -17,6 +18,7 @@ class CommunityStructureHeader extends StatefulWidget { final ValueChanged onNameSubmitted; final List communities; final CommunityModel? community; + final SpaceModel? selectedSpace; const CommunityStructureHeader( {super.key, @@ -29,7 +31,8 @@ class CommunityStructureHeader extends StatefulWidget { required this.onEditName, required this.onNameSubmitted, this.community, - required this.communities}); + required this.communities, + this.selectedSpace}); @override State createState() => @@ -137,10 +140,8 @@ class _CommunityStructureHeaderState extends State { ], ), ), - if (widget.isSave) ...[ - const SizedBox(width: 8), - _buildActionButtons(theme), - ], + const SizedBox(width: 8), + _buildActionButtons(theme), ], ), ], @@ -152,11 +153,19 @@ class _CommunityStructureHeaderState extends State { alignment: WrapAlignment.end, spacing: 10, children: [ + if (widget.isSave) + _buildButton( + label: "Save", + icon: const Icon(Icons.save, + size: 18, color: ColorsManager.spaceColor), + onPressed: widget.onSave, + theme: theme), + if(widget.selectedSpace!= null) _buildButton( - label: "Save", - icon: const Icon(Icons.save, - size: 18, color: ColorsManager.spaceColor), - onPressed: widget.onSave, + label: "Delete", + icon: const Icon(Icons.delete, + size: 18, color: ColorsManager.warningRed), + onPressed: widget.onDelete, theme: theme), ], ); diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index b7743a01..05a80780 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -131,6 +131,7 @@ class _CommunityStructureAreaState extends State { isEditingName: isEditingName, nameController: _nameController, onSave: _saveSpaces, + selectedSpace: widget.selectedSpace, onDelete: _onDelete, onEditName: () { setState(() { @@ -176,7 +177,8 @@ class _CommunityStructureAreaState extends State { painter: CurvedLinePainter([connection])), ), for (var entry in spaces.asMap().entries) - if (entry.value.status != SpaceStatus.deleted) + if (entry.value.status != SpaceStatus.deleted || + entry.value.status != SpaceStatus.parentDeleted) Positioned( left: entry.value.position.dx, top: entry.value.position.dy, @@ -358,7 +360,7 @@ class _CommunityStructureAreaState extends State { space.name = name; space.icon = icon; space.spaceModel = spaceModel; - space.subspaces= subspaces; + space.subspaces = subspaces; space.tags = tags; if (space.status != SpaceStatus.newSpace) { @@ -382,7 +384,8 @@ class _CommunityStructureAreaState extends State { List result = []; void flatten(SpaceModel space) { - if (space.status == SpaceStatus.deleted) return; + if (space.status == SpaceStatus.deleted || + space.status == SpaceStatus.parentDeleted) return; result.add(space); @@ -475,7 +478,8 @@ class _CommunityStructureAreaState extends State { void _markChildrenAsDeleted(SpaceModel parent) { for (var child in parent.children) { - child.status = SpaceStatus.deleted; + child.status = SpaceStatus.parentDeleted; + _markChildrenAsDeleted(child); } } @@ -483,7 +487,9 @@ class _CommunityStructureAreaState extends State { void _removeConnectionsForDeletedSpaces() { connections.removeWhere((connection) { return connection.startSpace.status == SpaceStatus.deleted || - connection.endSpace.status == SpaceStatus.deleted; + connection.endSpace.status == SpaceStatus.deleted || + connection.startSpace.status == SpaceStatus.parentDeleted || + connection.endSpace.status == SpaceStatus.parentDeleted; }); } diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart index b75f05cf..324f1938 100644 --- a/lib/services/api/http_service.dart +++ b/lib/services/api/http_service.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:dio/dio.dart'; import 'package:syncrow_web/services/api/http_interceptor.dart'; import 'package:syncrow_web/services/locator.dart'; @@ -121,13 +123,22 @@ class HTTPService { required T Function(dynamic) expectedResponseModel, bool showServerMessage = true, }) async { + log('DELETE Request Initiated', name: 'API DELETE'); + log('Path: $path', name: 'API DELETE'); + if (queryParameters != null) { + log('Query Parameters: $queryParameters', name: 'API DELETE'); + } + try { final response = await client.delete( path, queryParameters: queryParameters, ); + log('Response: ${response.data}', name: 'API DELETE'); return expectedResponseModel(response.data); - } catch (error) { + } catch (error, stackTrace) { + log('Error during DELETE Request: $error', + name: 'API DELETE', error: error, stackTrace: stackTrace); rethrow; } } diff --git a/lib/utils/constants/temp_const.dart b/lib/utils/constants/temp_const.dart index e5847b98..bcd1a1d6 100644 --- a/lib/utils/constants/temp_const.dart +++ b/lib/utils/constants/temp_const.dart @@ -1,3 +1,3 @@ class TempConst { - static const projectId = '0e62577c-06fa-41b9-8a92-99a21fbaf51c'; + static const projectId = '0685c781-df33-4cbf-bf65-9f4e835eb468'; } From 2abe7a6feb6e17d39ec9976337730d84a8df29c6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 12:11:32 +0400 Subject: [PATCH 069/106] revert --- lib/services/api/http_service.dart | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart index 324f1938..b75f05cf 100644 --- a/lib/services/api/http_service.dart +++ b/lib/services/api/http_service.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:dio/dio.dart'; import 'package:syncrow_web/services/api/http_interceptor.dart'; import 'package:syncrow_web/services/locator.dart'; @@ -123,22 +121,13 @@ class HTTPService { required T Function(dynamic) expectedResponseModel, bool showServerMessage = true, }) async { - log('DELETE Request Initiated', name: 'API DELETE'); - log('Path: $path', name: 'API DELETE'); - if (queryParameters != null) { - log('Query Parameters: $queryParameters', name: 'API DELETE'); - } - try { final response = await client.delete( path, queryParameters: queryParameters, ); - log('Response: ${response.data}', name: 'API DELETE'); return expectedResponseModel(response.data); - } catch (error, stackTrace) { - log('Error during DELETE Request: $error', - name: 'API DELETE', error: error, stackTrace: stackTrace); + } catch (error) { rethrow; } } From 408c40aa60ef1d9e599c85e940fc7690f98a558b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 12:12:14 +0400 Subject: [PATCH 070/106] revert --- .env.development | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.development b/.env.development index e77609dc..1fd358ec 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,2 @@ ENV_NAME=development -BASE_URL=https://syncrow-dev.azurewebsites.net \ No newline at end of file +BASE_URL=http://localhost:4001 \ No newline at end of file From 15023e5882cafec4e2380f6ec1f2712bd5ef747d Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 12 Jan 2025 15:32:03 +0300 Subject: [PATCH 071/106] fixes filter and table view and add user dialog --- .../model/roles_user_model.dart | 2 +- .../add_user_dialog/view/add_user_dialog.dart | 6 +- .../view/delete_user_dialog.dart | 55 ++- .../view/popup_menu_filter.dart | 255 +++++----- .../add_user_dialog/view/role_dropdown.dart | 6 +- .../users_table/bloc/user_table_bloc.dart | 70 ++- .../users_table/bloc/user_table_event.dart | 33 +- .../users_table/view/user_table.dart | 436 +++++++----------- .../users_table/view/users_page.dart | 108 ++--- lib/services/user_permission.dart | 13 +- 10 files changed, 496 insertions(+), 488 deletions(-) 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 6298dbe6..44e3f493 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,7 @@ class RolesUserModel { invitedBy: json['invitedBy'].toString().toLowerCase().replaceAll("_", " "), phoneNumber: json['phoneNumber'], - jobTitle: json['jobTitle'].toString(), + jobTitle: json['jobTitle'] ?? "-", createdDate: json['createdDate'], createdTime: json['createdTime'], ); diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart index 48df801a..f29aec8e 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -114,7 +114,7 @@ class _AddNewUserDialogState extends State { currentStep++; if (currentStep == 2) { _blocRole.add( - CheckStepStatus(isEditUser: false)); + const CheckStepStatus(isEditUser: false)); } else if (currentStep == 3) { _blocRole .add(const CheckSpacesStepStatus()); @@ -151,7 +151,7 @@ class _AddNewUserDialogState extends State { Widget _getFormContent() { switch (currentStep) { case 1: - return BasicsView( + return const BasicsView( userId: '', ); case 2: @@ -172,7 +172,7 @@ class _AddNewUserDialogState extends State { bloc.add(const CheckSpacesStepStatus()); currentStep = step; Future.delayed(const Duration(milliseconds: 500), () { - bloc.add(ValidateBasicsStep()); + bloc.add(const ValidateBasicsStep()); }); }); diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart index 837ee62c..10e8c273 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart @@ -11,7 +11,14 @@ class DeleteUserDialog extends StatefulWidget { } class _DeleteUserDialogState extends State { - int currentStep = 1; + bool isLoading = false; + bool _isDisposed = false; + + @override + void dispose() { + _isDisposed = true; + super.dispose(); + } @override Widget build(BuildContext context) { @@ -56,7 +63,7 @@ class _DeleteUserDialogState extends State { Expanded( child: InkWell( onTap: () { - Navigator.of(context).pop(true); + Navigator.of(context).pop(false); // Return false if canceled }, child: Container( padding: const EdgeInsets.all(10), @@ -76,7 +83,26 @@ class _DeleteUserDialogState extends State { )), Expanded( child: InkWell( - onTap: widget.onTapDelete, + onTap: isLoading + ? null + : () async { + setState(() { + isLoading = true; + }); + + try { + if (widget.onTapDelete != null) { + await widget.onTapDelete!(); + } + } finally { + if (!_isDisposed) { + setState(() { + isLoading = false; + }); + } + } + Navigator.of(context).pop(true); + }, child: Container( padding: const EdgeInsets.all(10), decoration: const BoxDecoration( @@ -91,13 +117,22 @@ class _DeleteUserDialogState extends State { ), ), ), - child: const Center( - child: Text( - 'Delete', - style: TextStyle( - color: ColorsManager.red, - ), - ))), + child: Center( + child: isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: ColorsManager.red, + strokeWidth: 2.0, + ), + ) + : const Text( + 'Delete', + style: TextStyle( + color: ColorsManager.red, + ), + ))), )), ], ) 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 7c3c1ba5..c3a245c1 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 @@ -5,141 +5,156 @@ import 'package:syncrow_web/utils/style.dart'; Future showPopUpFilterMenu({ required BuildContext context, - Function()? onSortAtoZ, - Function()? onSortZtoA, + required Function(String value)? onSortAtoZ, // Accept a parameter + required Function(String value)? onSortZtoA, // Accept a parameter Function()? cancelButton, required Map checkboxStates, required RelativeRect position, Function()? onOkPressed, List? list, + String? isSelected, }) async { - - await showMenu( context: context, - position:position, - - + position: position, color: ColorsManager.whiteColors, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10)), ), items: [ PopupMenuItem( - onTap: onSortAtoZ, - child: ListTile( - leading: Image.asset( - Assets.AtoZIcon, - width: 25, - ), - title: const Text( - "Sort A to Z", - style: TextStyle(color: Colors.blueGrey), - ), - ), - ), - PopupMenuItem( - onTap: onSortZtoA, - child: ListTile( - leading: Image.asset( - Assets.ZtoAIcon, - width: 25, - ), - title: const Text( - "Sort Z to A", - style: TextStyle(color: Colors.blueGrey), - ), - ), - ), - const PopupMenuDivider(), - const PopupMenuItem( - child: Text( - "Filter by Status", - style: TextStyle(fontWeight: FontWeight.bold), - ) - // Container( - // decoration: containerDecoration.copyWith( - // boxShadow: [], - // borderRadius: const BorderRadius.only( - // topLeft: Radius.circular(10), topRight: Radius.circular(10))), - // child: Padding( - // padding: const EdgeInsets.all(8.0), - // child: TextFormField( - // onChanged: onTextFieldChanged, - // style: const TextStyle(color: Colors.black), - // decoration: textBoxDecoration(radios: 15)!.copyWith( - // fillColor: ColorsManager.whiteColors, - // errorStyle: const TextStyle(height: 0), - // hintStyle: context.textTheme.titleSmall?.copyWith( - // color: Colors.grey, - // fontSize: 12, - // ), - // hintText: 'Search', - // suffixIcon: SizedBox( - // child: SvgPicture.asset( - // Assets.searchIconUser, - // fit: BoxFit.none, - // ), - // ), - // ), - // // "Filter by Status", - // // style: TextStyle(fontWeight: FontWeight.bold), - // ), - // ), - // ), - ), - PopupMenuItem( - child: Container( - decoration: containerDecoration.copyWith( - boxShadow: [], - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(10), - bottomRight: Radius.circular(10))), - padding: const EdgeInsets.all(10), - height: 200, - width: 400, - child: Container( - padding: const EdgeInsets.all(10), - color: Colors.white, - child: ListView.builder( - itemCount: list?.length ?? 0, - itemBuilder: (context, index) { - final item = list![index]; - return CheckboxListTile( - dense: true, - title: Text(item), - value: checkboxStates[item], - onChanged: (bool? newValue) { - checkboxStates[item] = newValue ?? false; - (context as Element).markNeedsBuild(); - }, - ); - }, - ), - ), - ), - ), - PopupMenuItem( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - Navigator.of(context).pop(); // Close the menu - }, - child: const Text("Cancel"), - ), - GestureDetector( - onTap: onOkPressed, - child: const Text( - "OK", - style: TextStyle( - color: ColorsManager.spaceColor, + enabled: false, + child: StatefulBuilder( + builder: (context, setState) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + child: ListTile( + onTap: () { + setState(() { + if (isSelected == 'Asc') { + isSelected = null; + onSortAtoZ?.call(''); + } else { + onSortAtoZ?.call('Asc'); + isSelected = 'Asc'; + } + }); + }, + leading: Image.asset( + Assets.AtoZIcon, + width: 25, + ), + title: Text( + "Sort A to Z", + style: TextStyle( + color: isSelected == "Asc" + ? ColorsManager.blackColor + : ColorsManager.grayColor), + ), + ), ), - ), - ), - ], + ListTile( + onTap: () { + setState(() { + if (isSelected == 'Desc') { + isSelected = null; + onSortZtoA?.call(''); + } else { + onSortZtoA?.call('Desc'); + isSelected = 'Desc'; + } + }); + }, + leading: Image.asset( + Assets.ZtoAIcon, + width: 25, + ), + title: Text( + "Sort Z to A", + style: TextStyle( + color: isSelected == "Desc" + ? ColorsManager.blackColor + : ColorsManager.grayColor), + ), + ), + const Divider(), + const Text( + "Filter by Status", + style: TextStyle(fontWeight: FontWeight.bold), + ), + Container( + decoration: containerDecoration.copyWith( + boxShadow: [], + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10), + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10))), + padding: const EdgeInsets.all(10), + height: 200, + width: 400, + child: Container( + padding: const EdgeInsets.all(10), + color: Colors.white, + child: ListView.builder( + itemCount: list?.length ?? 0, + itemBuilder: (context, index) { + final item = list![index]; + return Row( + children: [ + Checkbox( + value: checkboxStates[item], + onChanged: (bool? newValue) { + checkboxStates[item] = newValue ?? false; + (context as Element).markNeedsBuild(); + }, + ), + Text( + item, + style: TextStyle(color: ColorsManager.grayColor), + ), + ], + ); + }, + ), + ), + ), + const SizedBox( + height: 10, + ), + const Divider(), + const SizedBox( + height: 10, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); // Close the menu + }, + child: const Text("Cancel"), + ), + GestureDetector( + onTap: onOkPressed, + child: const Text( + "OK", + style: TextStyle( + color: ColorsManager.spaceColor, + ), + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + ], + ); + }, ), ), ], diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart index 1bc9331e..c8126dbd 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart @@ -52,14 +52,16 @@ class _RoleDropdownState extends State { SizedBox( child: DropdownButtonFormField( dropdownColor: ColorsManager.whiteColors, - alignment: Alignment.center, + // alignment: Alignment., focusColor: Colors.white, autofocus: true, value: selectedRole.isNotEmpty ? selectedRole : null, items: widget.bloc!.roles.map((role) { return DropdownMenuItem( value: role.uuid, - child: Text(role.type), + child: Text( + ' ${role.type[0].toUpperCase()}${role.type.substring(1)}', + ), ); }).toList(), onChanged: (value) { 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 aa014bd5..b1131ca4 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 @@ -133,7 +133,10 @@ class UserTableBloc extends Bloc { } else { emit(UsersLoadingState()); currentSortOrder = "Asc"; - users.sort((a, b) => a.firstName!.compareTo(b.firstName!)); + users.sort((a, b) => a.firstName + .toString() + .toLowerCase() + .compareTo(b.firstName.toString().toLowerCase())); emit(UsersLoadedState(users: users)); } } @@ -164,6 +167,7 @@ class UserTableBloc extends Bloc { emit(UsersLoadedState(users: users)); } else { emit(UsersLoadingState()); + currentSortOrder = "NewestToOldest"; users.sort((a, b) { final dateA = _parseDateTime(a.createdDate); final dateB = _parseDateTime(b.createdDate); @@ -188,6 +192,7 @@ class UserTableBloc extends Bloc { final dateB = _parseDateTime(b.createdDate); return dateA.compareTo(dateB); }); + currentSortOrder = "OldestToNewest"; emit(UsersLoadedState(users: users)); } } @@ -210,7 +215,7 @@ class UserTableBloc extends Bloc { final query = event.query.toLowerCase(); final filteredUsers = initialUsers.where((user) { final fullName = "${user.firstName} ${user.lastName}".toLowerCase(); - final email = user.email.toLowerCase() ; + final email = user.email.toLowerCase(); return fullName.contains(query) || email.contains(query); }).toList(); emit(UsersLoadedState(users: filteredUsers)); @@ -256,49 +261,96 @@ class UserTableBloc extends Bloc { void _filterUsersByRole( FilterUsersByRoleEvent event, Emitter emit) { - selectedRoles = event.selectedRoles.toSet(); + selectedRoles = event.selectedRoles!.toSet(); final filteredUsers = initialUsers.where((user) { if (selectedRoles.isEmpty) return true; return selectedRoles.contains(user.roleType); }).toList(); + if (event.sortOrder == "Asc") { + currentSortOrder = "Asc"; + filteredUsers.sort((a, b) => a.firstName + .toString() + .toLowerCase() + .compareTo(b.firstName.toString().toLowerCase())); + } else if (event.sortOrder == "Desc") { + currentSortOrder = "Desc"; + filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); + } else { + currentSortOrder = ""; + } + emit(UsersLoadedState(users: filteredUsers)); } void _filterUsersByJobTitle( FilterUsersByJobEvent event, Emitter emit) { - selectedJobTitles = event.selectedJob.toSet(); - + selectedJobTitles = event.selectedJob!.toSet(); + emit(UsersLoadingState()); final filteredUsers = initialUsers.where((user) { if (selectedJobTitles.isEmpty) return true; return selectedJobTitles.contains(user.jobTitle); }).toList(); - + if (event.sortOrder == "Asc") { + currentSortOrder = "Asc"; + filteredUsers.sort((a, b) => a.firstName + .toString() + .toLowerCase() + .compareTo(b.firstName.toString().toLowerCase())); + } else if (event.sortOrder == "Desc") { + currentSortOrder = "Desc"; + filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); + } else { + currentSortOrder = ""; + } emit(UsersLoadedState(users: filteredUsers)); } void _filterUsersByCreated( FilterUsersByCreatedEvent event, Emitter emit) { - selectedCreatedBy = event.selectedCreatedBy.toSet(); + selectedCreatedBy = event.selectedCreatedBy!.toSet(); final filteredUsers = initialUsers.where((user) { if (selectedCreatedBy.isEmpty) return true; return selectedCreatedBy.contains(user.invitedBy); }).toList(); + if (event.sortOrder == "Asc") { + currentSortOrder = "Asc"; + filteredUsers.sort((a, b) => a.firstName + .toString() + .toLowerCase() + .compareTo(b.firstName.toString().toLowerCase())); + } else if (event.sortOrder == "Desc") { + currentSortOrder = "Desc"; + filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); + } else { + currentSortOrder = ""; + } emit(UsersLoadedState(users: filteredUsers)); } void _filterUserStatus( FilterUsersByDeActevateEvent event, Emitter emit) { - selectedStatuses = event.selectedActivate.toSet(); + selectedStatuses = event.selectedActivate!.toSet(); final filteredUsers = initialUsers.where((user) { if (selectedStatuses.isEmpty) return true; return selectedStatuses.contains(user.status); }).toList(); - + if (event.sortOrder == "Asc") { + currentSortOrder = "Asc"; + filteredUsers.sort((a, b) => a.firstName + .toString() + .toLowerCase() + .compareTo(b.firstName.toString().toLowerCase())); + } else if (event.sortOrder == "Desc") { + currentSortOrder = "Desc"; + filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); + } else { + currentSortOrder = ""; + } emit(UsersLoadedState(users: filteredUsers)); } diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart index a81002ad..1d9567cf 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart @@ -89,35 +89,36 @@ class DeleteUserEvent extends UserTableEvent { } class FilterUsersByRoleEvent extends UserTableEvent { - final List selectedRoles; + final List? selectedRoles; + final String? sortOrder; - FilterUsersByRoleEvent(this.selectedRoles); - @override - List get props => [selectedRoles]; + const FilterUsersByRoleEvent({this.selectedRoles, this.sortOrder}); + List get props => [selectedRoles, sortOrder]; } class FilterUsersByJobEvent extends UserTableEvent { - final List selectedJob; + final List? selectedJob; + final String? sortOrder; - FilterUsersByJobEvent(this.selectedJob); - @override - List get props => [selectedJob]; + const FilterUsersByJobEvent({this.selectedJob, this.sortOrder}); + List get props => [selectedJob, sortOrder]; } class FilterUsersByCreatedEvent extends UserTableEvent { - final List selectedCreatedBy; + final List? selectedCreatedBy; - FilterUsersByCreatedEvent(this.selectedCreatedBy); - @override - List get props => [selectedCreatedBy]; + final String? sortOrder; + + const FilterUsersByCreatedEvent({this.selectedCreatedBy, this.sortOrder}); + List get props => [selectedCreatedBy, sortOrder]; } class FilterUsersByDeActevateEvent extends UserTableEvent { - final List selectedActivate; + final List? selectedActivate; + final String? sortOrder; - FilterUsersByDeActevateEvent(this.selectedActivate); - @override - List get props => [selectedActivate]; + const FilterUsersByDeActevateEvent({this.selectedActivate, this.sortOrder}); + List get props => [selectedActivate, sortOrder]; } class FilterOptionsEvent extends UserTableEvent { 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 6662a9e2..ca641cbe 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 @@ -19,19 +19,13 @@ class DynamicTableScreen extends StatefulWidget { class _DynamicTableScreenState extends State with WidgetsBindingObserver { late List columnWidths; + late double totalWidth; - // @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(); - setState(() { - columnWidths = List.filled(widget.titles.length, 150.0); - }); + columnWidths = List.filled(widget.titles.length, 150.0); + totalWidth = columnWidths.reduce((a, b) => a + b); WidgetsBinding.instance.addObserver(this); } @@ -44,7 +38,6 @@ class _DynamicTableScreenState extends State @override void didChangeMetrics() { super.didChangeMetrics(); - // Screen size might have changed final newScreenWidth = MediaQuery.of(context).size.width; setState(() { columnWidths = List.generate(widget.titles.length, (index) { @@ -64,8 +57,6 @@ class _DynamicTableScreenState extends State @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.generate(widget.titles.length, (index) { if (index == 1) { @@ -77,280 +68,193 @@ class _DynamicTableScreenState extends State }); setState(() {}); } - return Container( - child: 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( - children: [ - // Header Row with Resizable Columns - Container( - width: MediaQuery.of(context).size.width, - 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, - ), + 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( + 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); - } - }, + ), + if (index != 1 && + index != 9 && + index != 8 && + index != 5) + FittedBox( + child: IconButton( + icon: SvgPicture.asset( + Assets.filterTableIcon, + fit: BoxFit.none, ), - ) - ], - ), - ), - ), - 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 - widget.rows.isEmpty - ? Container( - child: SizedBox( - height: MediaQuery.of(context).size.height / 2, - child: Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - children: [ - SvgPicture.asset(Assets.emptyTable), - const SizedBox( - height: 15, + onPressed: () { + if (widget.onFilter != null) { + widget.onFilter!(index); + } + }, ), - const Text( - 'No Users', - style: TextStyle( - color: ColorsManager.lightGrayColor, - fontSize: 16, - fontWeight: FontWeight.w700), - ) - ], - ), + ) ], ), ), ), - ) - : Center( - child: Container( - // height: MediaQuery.of(context).size.height * 0.59, - width: MediaQuery.of(context).size.width, - decoration: containerDecoration.copyWith( - 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; - } + 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, + ), + ), + ), + ), + ], + ); + }), + ), + ), + 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; - }); - 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) { + } 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: const Divider( - color: ColorsManager.boxDivider, - thickness: 1, - height: 1, + 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, + ), + ); + }), + ), + ], + ); + }, ), ), - ], - ), + ), + ], ), ), ), ); } } - - - - // 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(), - // ], - // ), - // ), - // ), - // ); - // } \ No newline at end of file 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 1c93d44f..dae47196 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 @@ -107,7 +107,6 @@ class UsersPage extends StatelessWidget { builder: (context, state) { 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()); @@ -189,8 +188,6 @@ class UsersPage extends StatelessWidget { const SizedBox(height: 25), DynamicTableScreen( onFilter: (columnIndex) { - _blocRole.add(FilterClearEvent()); - if (columnIndex == 0) { showNameMenu( context: context, @@ -210,11 +207,12 @@ class UsersPage extends StatelessWidget { if (columnIndex == 2) { final Map checkboxStates = { for (var item in _blocRole.jobTitle) - item: false, // Initialize with false + item: _blocRole.selectedJobTitles.contains(item), }; final RenderBox overlay = Overlay.of(context) .context .findRenderObject() as RenderBox; + showPopUpFilterMenu( position: RelativeRect.fromLTRB( overlay.size.width / 4, @@ -225,26 +223,28 @@ class UsersPage extends StatelessWidget { list: _blocRole.jobTitle, context: context, checkboxStates: checkboxStates, + isSelected: _blocRole.currentSortOrder, onOkPressed: () { + _blocRole.add(FilterClearEvent()); final selectedItems = checkboxStates.entries .where((entry) => entry.value) .map((entry) => entry.key) .toList(); Navigator.of(context).pop(); - _blocRole.add(FilterUsersByJobEvent(selectedItems)); + _blocRole.add(FilterUsersByJobEvent( + selectedJob: selectedItems, + sortOrder: _blocRole.currentSortOrder, + )); }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); + onSortAtoZ: (v) { + _blocRole.currentSortOrder = v; }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); + onSortZtoA: (v) { + _blocRole.currentSortOrder = v; }, ); } + if (columnIndex == 3) { final Map checkboxStates = { for (var item in _blocRole.roleTypes) @@ -263,32 +263,31 @@ class UsersPage extends StatelessWidget { list: _blocRole.roleTypes, context: context, checkboxStates: checkboxStates, + isSelected: _blocRole.currentSortOrder, onOkPressed: () { + _blocRole.add(FilterClearEvent()); final selectedItems = checkboxStates.entries .where((entry) => entry.value) .map((entry) => entry.key) .toList(); Navigator.of(context).pop(); - context - .read() - .add(FilterUsersByRoleEvent(selectedItems)); + context.read().add( + FilterUsersByRoleEvent( + selectedRoles: selectedItems, + sortOrder: _blocRole.currentSortOrder)); }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); + onSortAtoZ: (v) { + _blocRole.currentSortOrder = v; }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); + onSortZtoA: (v) { + _blocRole.currentSortOrder = v; }, ); } if (columnIndex == 4) { showDateFilterMenu( context: context, - isSelected: _blocRole.currentSortOrderDate, + isSelected: _blocRole.currentSortOrder, aToZTap: () { context .read() @@ -319,32 +318,30 @@ class UsersPage extends StatelessWidget { list: _blocRole.createdBy, context: context, checkboxStates: checkboxStates, + isSelected: _blocRole.currentSortOrder, onOkPressed: () { + _blocRole.add(FilterClearEvent()); final selectedItems = checkboxStates.entries .where((entry) => entry.value) .map((entry) => entry.key) .toList(); Navigator.of(context).pop(); - _blocRole - .add(FilterUsersByCreatedEvent(selectedItems)); + _blocRole.add(FilterUsersByCreatedEvent( + selectedCreatedBy: selectedItems, + sortOrder: _blocRole.currentSortOrder)); }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); + onSortAtoZ: (v) { + _blocRole.currentSortOrder = v; }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); + onSortZtoA: (v) { + _blocRole.currentSortOrder = v; }, ); } - if (columnIndex == 7) { final Map checkboxStates = { for (var item in _blocRole.status) - item: _blocRole.selectedCreatedBy.contains(item), + item: _blocRole.selectedStatuses.contains(item), }; final RenderBox overlay = Overlay.of(context) .context @@ -359,24 +356,24 @@ class UsersPage extends StatelessWidget { list: _blocRole.status, context: context, checkboxStates: checkboxStates, + isSelected: _blocRole.currentSortOrder, onOkPressed: () { + _blocRole.add(FilterClearEvent()); + final selectedItems = checkboxStates.entries .where((entry) => entry.value) .map((entry) => entry.key) .toList(); Navigator.of(context).pop(); - _blocRole - .add(FilterUsersByCreatedEvent(selectedItems)); + _blocRole.add(FilterUsersByDeActevateEvent( + selectedActivate: selectedItems, + sortOrder: _blocRole.currentSortOrder)); }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); + onSortAtoZ: (v) { + _blocRole.currentSortOrder = v; }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); + onSortZtoA: (v) { + _blocRole.currentSortOrder = v; }, ); } @@ -412,8 +409,8 @@ class UsersPage extends StatelessWidget { rows: state.users.map((user) { return [ Text('${user.firstName} ${user.lastName}'), - Text(user.email ), - Text(user.jobTitle ?? ''), + Text(user.email), + Text(user.jobTitle ?? '-'), Text(user.roleType ?? ''), Text(user.createdDate ?? ''), Text(user.createdTime ?? ''), @@ -476,11 +473,17 @@ class UsersPage extends StatelessWidget { barrierDismissible: false, builder: (BuildContext context) { return DeleteUserDialog( - onTapDelete: () { + onTapDelete: () async { + try { _blocRole.add(DeleteUserEvent( user.uuid, context)); - }, - ); + await Future.delayed( + const Duration(seconds: 2)); + return true; + } catch (e) { + return false; + } + }); }, ).then((v) { if (v != null) { @@ -504,6 +507,7 @@ class UsersPage extends StatelessWidget { SizedBox( width: 500, child: NumberPagination( + visiblePagesCount: 4, buttonRadius: 10, selectedButtonColor: ColorsManager.secondaryColor, buttonUnSelectedBorderColor: diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index ab05523f..527010d0 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -71,8 +71,8 @@ class UserPermissionApi { "firstName": firstName, "lastName": lastName, "email": email, - "jobTitle": jobTitle != '' ? jobTitle : " ", - "phoneNumber": phoneNumber != '' ? phoneNumber : " ", + "jobTitle": jobTitle != '' ? jobTitle : null, + "phoneNumber": phoneNumber != '' ? phoneNumber : null, "roleUuid": roleUuid, "projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c", "spaceUuids": spaceUuids, @@ -119,13 +119,8 @@ class UserPermissionApi { ); return response ?? 'Unknown error occurred'; } on DioException catch (e) { - if (e.response != null) { - final errorMessage = e.response?.data['error']; - return errorMessage is String - ? errorMessage - : 'Error occurred while checking email'; - } - return 'Error occurred while checking email'; + final errorMessage = e.response?.data['error']; + return errorMessage; } catch (e) { return e.toString(); } From eb7eeebf1860b0b3fcdcc751542fa983b526409e Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 13 Jan 2025 11:07:18 +0300 Subject: [PATCH 072/106] fixes add user view and table and user status --- .../add_user_dialog/view/add_user_dialog.dart | 3 +- .../add_user_dialog/view/basics_view.dart | 46 ++++++++++--------- .../view/permission_management.dart | 6 +-- .../view/popup_menu_filter.dart | 4 +- .../users_table/bloc/user_table_bloc.dart | 2 +- .../users_table/view/user_table.dart | 8 ++-- 6 files changed, 37 insertions(+), 32 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart index f29aec8e..700e8f46 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -237,10 +237,11 @@ class _AddNewUserDialogState extends State { onTap: () { setState(() { currentStep = step; - bloc.add(CheckStepStatus(isEditUser: false)); + bloc.add(const CheckStepStatus(isEditUser: false)); if (step3 == 3) { bloc.add(const CheckRoleStepStatus()); } + }); }, child: Column( diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index c5025fc3..7261ba50 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 @@ -47,7 +47,9 @@ class BasicsView extends StatelessWidget { ), Row( children: [ - Expanded( + SizedBox( + width: MediaQuery.of(context).size.width * 0.18, + height: MediaQuery.of(context).size.width * 0.08, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -76,12 +78,12 @@ class BasicsView extends StatelessWidget { child: TextFormField( style: const TextStyle(color: ColorsManager.blackColor), - onChanged: (value) { - Future.delayed(const Duration(milliseconds: 200), - () { - _blocRole.add(ValidateBasicsStep()); - }); - }, + // onChanged: (value) { + // Future.delayed(const Duration(milliseconds: 200), + // () { + // _blocRole.add(const ValidateBasicsStep()); + // }); + // }, controller: _blocRole.firstNameController, decoration: inputTextFormDeco( hintText: "Enter first name", @@ -103,7 +105,9 @@ class BasicsView extends StatelessWidget { ), ), const SizedBox(width: 10), - Expanded( + SizedBox( + width: MediaQuery.of(context).size.width * 0.18, + height: MediaQuery.of(context).size.width * 0.08, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -128,12 +132,12 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( - onChanged: (value) { - Future.delayed(const Duration(milliseconds: 200), - () { - _blocRole.add(ValidateBasicsStep()); - }); - }, + // onChanged: (value) { + // Future.delayed(const Duration(milliseconds: 200), + // () { + // _blocRole.add(ValidateBasicsStep()); + // }); + // }, controller: _blocRole.lastNameController, style: const TextStyle(color: Colors.black), decoration: @@ -186,13 +190,13 @@ class BasicsView extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: TextFormField( enabled: userId != '' ? false : true, - onChanged: (value) { - Future.delayed(const Duration(milliseconds: 200), () { - _blocRole.add(CheckStepStatus( - isEditUser: userId != '' ? false : true)); - _blocRole.add(ValidateBasicsStep()); - }); - }, + // onChanged: (value) { + // Future.delayed(const Duration(milliseconds: 200), () { + // _blocRole.add(CheckStepStatus( + // isEditUser: userId != '' ? false : true)); + // _blocRole.add(ValidateBasicsStep()); + // }); + // }, controller: _blocRole.emailController, style: const TextStyle(color: ColorsManager.blackColor), decoration: inputTextFormDeco(hintText: "name@example.com") diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart index 266d431e..c5c38e76 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart @@ -128,7 +128,7 @@ class _PermissionManagementState extends State { ), const SizedBox(width: 8), Text( - option.title, + ' ${option.title[0].toUpperCase()}${option.title.substring(1)}', style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w700, fontSize: 12, @@ -184,7 +184,7 @@ class _PermissionManagementState extends State { ), const SizedBox(width: 8), Text( - subOption.title, + ' ${subOption.title[0].toUpperCase()}${subOption.title.substring(1)}', style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w700, fontSize: 12, @@ -246,7 +246,7 @@ class _PermissionManagementState extends State { ), const SizedBox(width: 8), Text( - child.title, + ' ${child.title[0].toUpperCase()}${child.title.substring(1)}', style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w400, fontSize: 12, 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 c3a245c1..80228657 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 @@ -5,8 +5,8 @@ import 'package:syncrow_web/utils/style.dart'; Future showPopUpFilterMenu({ required BuildContext context, - required Function(String value)? onSortAtoZ, // Accept a parameter - required Function(String value)? onSortZtoA, // Accept a parameter + required Function(String value)? onSortAtoZ, + required Function(String value)? onSortZtoA, Function()? cancelButton, required Map checkboxStates, required RelativeRect position, 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 b1131ca4..c50667be 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 @@ -93,7 +93,7 @@ class UserTableBloc extends Bloc { try { emit(UsersLoadingState()); bool res = await UserPermissionApi().changeUserStatusById( - event.userId, event.newStatus == "disabled" ? true : false); + event.userId, event.newStatus == "disabled" ? false : true); if (res == true) { add(const GetUsers()); // users = users.map((user) { 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 ca641cbe..92229643 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 @@ -46,7 +46,7 @@ class _DynamicTableScreenState extends State 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 + 0.1; // 25% of screen width for the tenth column } return newScreenWidth * 0.09; // Default to 10% of screen width for other columns @@ -57,14 +57,14 @@ class _DynamicTableScreenState extends State @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - if (columnWidths.every((width) => width == 120.0)) { + 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.2; + return screenWidth * 0.1; } - return screenWidth * 0.11; + return screenWidth * 0.09; }); setState(() {}); } From acbb6ca7c09ae14c60dc634ad4d86b76fbd7df5e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 13 Jan 2025 12:12:22 +0400 Subject: [PATCH 073/106] removed try catch --- .../bloc/space_management_bloc.dart | 41 ++++---- lib/services/space_model_mang_api.dart | 97 ++++++++----------- 2 files changed, 64 insertions(+), 74 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 6458bdf6..20f642fd 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -76,19 +76,24 @@ class SpaceManagementBloc Future> fetchSpaceModels( SpaceManagementState previousState) async { - List prevSpaceModels = []; + try { + List prevSpaceModels = []; - if (previousState is SpaceManagementLoaded || previousState is BlankState) { - prevSpaceModels = List.from( - (previousState as dynamic).spaceModels ?? [], - ); + if (previousState is SpaceManagementLoaded || + previousState is BlankState) { + prevSpaceModels = List.from( + (previousState as dynamic).spaceModels ?? [], + ); + } + + if (prevSpaceModels.isEmpty) { + prevSpaceModels = await _spaceModelApi.listSpaceModels(page: 1); + } + + return prevSpaceModels; + } catch (e) { + return []; } - - if (prevSpaceModels.isEmpty) { - prevSpaceModels = await _spaceModelApi.listSpaceModels(page: 1); - } - - return prevSpaceModels; } void _onloadProducts() async { @@ -184,6 +189,7 @@ class SpaceManagementBloc LoadCommunityAndSpacesEvent event, Emitter emit, ) async { + var prevState = state; emit(SpaceManagementLoading()); try { _onloadProducts(); @@ -205,12 +211,11 @@ class SpaceManagementBloc }).toList(), ); - List spaceModels = - await _spaceModelApi.listSpaceModels(page: 1); + final prevSpaceModels = await fetchSpaceModels(prevState); emit(SpaceManagementLoaded( communities: updatedCommunities, products: _cachedProducts ?? [], - spaceModels: spaceModels)); + spaceModels: prevSpaceModels)); } catch (e) { emit(SpaceManagementError('Error loading communities and spaces: $e')); } @@ -487,6 +492,8 @@ class SpaceManagementBloc SpaceModelLoadEvent event, Emitter emit) async { emit(SpaceManagementLoading()); try { + var prevState = state; + List communities = await _api.fetchCommunities(); List updatedCommunities = await Future.wait( @@ -505,12 +512,12 @@ class SpaceManagementBloc }).toList(), ); - List spaceModels = - await _spaceModelApi.listSpaceModels(page: 1); + var prevSpaceModels = await fetchSpaceModels(prevState); + emit(SpaceModelLoaded( communities: updatedCommunities, products: _cachedProducts ?? [], - spaceModels: spaceModels)); + spaceModels: prevSpaceModels)); } catch (e) { emit(SpaceManagementError('Error loading communities and spaces: $e')); } diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index 19752aea..b13d62ad 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -7,71 +7,54 @@ import 'package:syncrow_web/utils/constants/temp_const.dart'; class SpaceModelManagementApi { Future> listSpaceModels({int page = 1}) async { - try { - List spaceModels = []; - bool hasNext = true; - while (hasNext) { - await HTTPService().get( - path: ApiEndpoints.listSpaceModels - .replaceAll('{projectId}', TempConst.projectId), - queryParameters: {'page': page}, - expectedResponseModel: (json) { - List jsonData = json['data']; - hasNext = json['hasNext'] ?? false; - int currentPage = json['page'] ?? 1; - List spaceModelList = jsonData.map((jsonItem) { - return SpaceTemplateModel.fromJson(jsonItem); - }).toList(); + List spaceModels = []; + bool hasNext = true; + while (hasNext) { + await HTTPService().get( + path: ApiEndpoints.listSpaceModels + .replaceAll('{projectId}', TempConst.projectId), + queryParameters: {'page': page}, + expectedResponseModel: (json) { + List jsonData = json['data']; + hasNext = json['hasNext'] ?? false; + int currentPage = json['page'] ?? 1; + List spaceModelList = jsonData.map((jsonItem) { + return SpaceTemplateModel.fromJson(jsonItem); + }).toList(); - spaceModels.addAll(spaceModelList); - page = currentPage + 1; - return spaceModelList; - }, - ); - } - return spaceModels; - } catch (e) { - debugPrint('Error fetching space models: $e'); - return []; + spaceModels.addAll(spaceModelList); + page = currentPage + 1; + return spaceModelList; + }, + ); } + return spaceModels; } Future createSpaceModel( CreateSpaceTemplateBodyModel spaceModel) async { - try { - final response = await HTTPService().post( - path: ApiEndpoints.createSpaceModel - .replaceAll('{projectId}', TempConst.projectId), - showServerMessage: true, - body: spaceModel.toJson(), - expectedResponseModel: (json) { - return SpaceTemplateModel.fromJson(json['data']); - }, - ); - return response; - } catch (e) { - debugPrint('Error creating space model: $e'); - return null; - } + final response = await HTTPService().post( + path: ApiEndpoints.createSpaceModel + .replaceAll('{projectId}', TempConst.projectId), + showServerMessage: true, + body: spaceModel.toJson(), + expectedResponseModel: (json) { + return SpaceTemplateModel.fromJson(json['data']); + }, + ); + return response; } Future getSpaceModel(String spaceModelUuid) async { - try { - final response = await HTTPService().get( - path: ApiEndpoints.getSpaceModel - .replaceAll('{projectId}', TempConst.projectId) - .replaceAll('{spaceModelUuid}', spaceModelUuid), - showServerMessage: true, - expectedResponseModel: (json) { - debugPrint('Response JSON: $json'); - - return SpaceTemplateModel.fromJson(json['data']); - }, - ); - return response; - } catch (e) { - debugPrint('Error getting space model: $e'); - return null; - } + final response = await HTTPService().get( + path: ApiEndpoints.getSpaceModel + .replaceAll('{projectId}', TempConst.projectId) + .replaceAll('{spaceModelUuid}', spaceModelUuid), + showServerMessage: true, + expectedResponseModel: (json) { + return SpaceTemplateModel.fromJson(json['data']); + }, + ); + return response; } } From 210fbf7497aab1b45bb3143cb493ea60a397f184 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 13 Jan 2025 12:16:49 +0400 Subject: [PATCH 074/106] initialize with const --- .../assign_tag_models/bloc/assign_tag_model_event.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart index 697b1c2a..38642d96 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart @@ -10,16 +10,16 @@ abstract class AssignTagModelEvent extends Equatable { } class InitializeTagModels extends AssignTagModelEvent { - final List? initialTags; + final List initialTags; final List addedProducts; const InitializeTagModels({ - required this.initialTags, + this.initialTags = const [], required this.addedProducts, }); @override - List get props => [initialTags ?? [], addedProducts]; + List get props => [initialTags, addedProducts]; } class UpdateTag extends AssignTagModelEvent { From a4e7f30411f245b4265a36c17437f1f47096e440 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 13 Jan 2025 14:14:09 +0400 Subject: [PATCH 075/106] moved pagination to bloc --- .../bloc/space_management_bloc.dart | 16 +++++++-- .../views/assign_tag_models_dialog.dart | 4 +-- lib/services/space_model_mang_api.dart | 35 +++++++------------ 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 20f642fd..26e444ca 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -6,7 +6,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:syncrow_web/services/product_api.dart'; @@ -77,6 +76,7 @@ class SpaceManagementBloc Future> fetchSpaceModels( SpaceManagementState previousState) async { try { + List allSpaces = []; List prevSpaceModels = []; if (previousState is SpaceManagementLoaded || @@ -87,10 +87,22 @@ class SpaceManagementBloc } if (prevSpaceModels.isEmpty) { + bool hasNext = true; + int page = 1; + + while (hasNext) { + final spaces = await _spaceModelApi.listSpaceModels(page: page); + if (spaces.isNotEmpty) { + allSpaces.addAll(spaces); + page++; + } else { + hasNext = false; + } + } prevSpaceModels = await _spaceModelApi.listSpaceModels(page: 1); } - return prevSpaceModels; + return allSpaces; } catch (e) { return []; } diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index f4f2276f..1b162832 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -16,7 +16,7 @@ import 'package:syncrow_web/utils/color_manager.dart'; class AssignTagModelsDialog extends StatelessWidget { final List? products; final List? subspaces; - final List? initialTags; + final List initialTags; final ValueChanged>? onTagsAssigned; final List addedProducts; final List? allTags; @@ -28,7 +28,7 @@ class AssignTagModelsDialog extends StatelessWidget { required this.products, required this.subspaces, required this.addedProducts, - this.initialTags, + required this.initialTags, this.onTagsAssigned, this.allTags, required this.spaceName, diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index b13d62ad..ee241189 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -1,4 +1,3 @@ -import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; @@ -7,28 +6,18 @@ import 'package:syncrow_web/utils/constants/temp_const.dart'; class SpaceModelManagementApi { Future> listSpaceModels({int page = 1}) async { - List spaceModels = []; - bool hasNext = true; - while (hasNext) { - await HTTPService().get( - path: ApiEndpoints.listSpaceModels - .replaceAll('{projectId}', TempConst.projectId), - queryParameters: {'page': page}, - expectedResponseModel: (json) { - List jsonData = json['data']; - hasNext = json['hasNext'] ?? false; - int currentPage = json['page'] ?? 1; - List spaceModelList = jsonData.map((jsonItem) { - return SpaceTemplateModel.fromJson(jsonItem); - }).toList(); - - spaceModels.addAll(spaceModelList); - page = currentPage + 1; - return spaceModelList; - }, - ); - } - return spaceModels; + final response = await HTTPService().get( + path: ApiEndpoints.listSpaceModels + .replaceAll('{projectId}', TempConst.projectId), + queryParameters: {'page': page}, + expectedResponseModel: (json) { + List jsonData = json['data']; + return jsonData.map((jsonItem) { + return SpaceTemplateModel.fromJson(jsonItem); + }).toList(); + }, + ); + return response; } Future createSpaceModel( From 20a9f19480314d00a51b2536e7da7f79f3cc4834 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 13 Jan 2025 14:36:17 +0300 Subject: [PATCH 076/106] check if title is not empty and remove nullable --- .../add_user_dialog/view/add_user_dialog.dart | 2 +- .../users_page/add_user_dialog/view/basics_view.dart | 1 - .../add_user_dialog/view/permission_management.dart | 6 +++--- .../add_user_dialog/view/popup_menu_filter.dart | 12 ++++++------ 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart index 700e8f46..ec35b3fd 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -155,7 +155,7 @@ class _AddNewUserDialogState extends State { userId: '', ); case 2: - return SpacesAccessView(); + return const SpacesAccessView(); case 3: return const RolesAndPermission(); default: 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 7261ba50..53d9a333 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 @@ -4,7 +4,6 @@ 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/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'; diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart index c5c38e76..aee84ed4 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart @@ -128,7 +128,7 @@ class _PermissionManagementState extends State { ), const SizedBox(width: 8), Text( - ' ${option.title[0].toUpperCase()}${option.title.substring(1)}', + ' ${option.title.isNotEmpty ? option.title[0].toUpperCase() : ''}${option.title.substring(1)}', style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w700, fontSize: 12, @@ -184,7 +184,7 @@ class _PermissionManagementState extends State { ), const SizedBox(width: 8), Text( - ' ${subOption.title[0].toUpperCase()}${subOption.title.substring(1)}', + ' ${subOption.title.isNotEmpty ? subOption.title[0].toUpperCase() : ''}${subOption.title.substring(1)}', style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w700, fontSize: 12, @@ -246,7 +246,7 @@ class _PermissionManagementState extends State { ), const SizedBox(width: 8), Text( - ' ${child.title[0].toUpperCase()}${child.title.substring(1)}', + ' ${child.title.isNotEmpty ? child.title[0].toUpperCase() : ''}${child.title.substring(1)}', style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w400, fontSize: 12, 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 80228657..120a1a3a 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 @@ -5,8 +5,8 @@ import 'package:syncrow_web/utils/style.dart'; Future showPopUpFilterMenu({ required BuildContext context, - required Function(String value)? onSortAtoZ, - required Function(String value)? onSortZtoA, + required Function(String value) onSortAtoZ, + required Function(String value) onSortZtoA, Function()? cancelButton, required Map checkboxStates, required RelativeRect position, @@ -35,9 +35,9 @@ Future showPopUpFilterMenu({ setState(() { if (isSelected == 'Asc') { isSelected = null; - onSortAtoZ?.call(''); + onSortAtoZ.call(''); } else { - onSortAtoZ?.call('Asc'); + onSortAtoZ.call('Asc'); isSelected = 'Asc'; } }); @@ -60,9 +60,9 @@ Future showPopUpFilterMenu({ setState(() { if (isSelected == 'Desc') { isSelected = null; - onSortZtoA?.call(''); + onSortZtoA.call(''); } else { - onSortZtoA?.call('Desc'); + onSortZtoA.call('Desc'); isSelected = 'Desc'; } }); From db7eaa53afadeb986fd2c0793b524892a9ceda14 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 13 Jan 2025 15:40:38 +0300 Subject: [PATCH 077/106] check type isNotEmpty --- .../users_page/add_user_dialog/view/role_dropdown.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart index c8126dbd..3a5ac65c 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart @@ -60,7 +60,7 @@ class _RoleDropdownState extends State { return DropdownMenuItem( value: role.uuid, child: Text( - ' ${role.type[0].toUpperCase()}${role.type.substring(1)}', + ' ${role.type.isNotEmpty ? role.type[0].toUpperCase() : ''}${role.type.substring(1)}', ), ); }).toList(), From 12df07e681ad3aa6a480f058c9b58c856c370912 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 14 Jan 2025 12:14:48 +0400 Subject: [PATCH 078/106] changed chip to list --- .../view/link_space_model_dialog.dart | 2 +- .../space_model/view/space_model_page.dart | 2 +- .../widgets/dynamic_product_widget.dart | 66 +++++++++ .../widgets/dynamic_room_widget.dart | 58 ++++++++ .../widgets/ellipsis_item_widget.dart | 25 ++++ .../widgets/flexible_item_widget.dart | 44 ++++++ .../space_model/widgets/room_name_widget.dart | 27 ++++ .../widgets/space_model_card_widget.dart | 127 +++++++----------- .../widgets/subspace_chip_widget.dart | 30 +++-- 9 files changed, 288 insertions(+), 93 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/ellipsis_item_widget.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/flexible_item_widget.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/room_name_widget.dart diff --git a/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart b/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart index 2ab969df..69023857 100644 --- a/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart +++ b/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart @@ -72,7 +72,7 @@ class LinkSpaceModelDialog extends StatelessWidget { ), borderRadius: BorderRadius.circular(8.0), ), - child: SpaceModelCardWidget(model: model), + child: SpaceModelCardWidget(model: model,), ), ); }, diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index a76543ae..eab43e08 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -69,7 +69,7 @@ class SpaceModelPage extends StatelessWidget { final model = spaceModels[index]; return Container( margin: const EdgeInsets.all(8.0), - child: SpaceModelCardWidget(model: model), + child: SpaceModelCardWidget(model:model), ); }, ), diff --git a/lib/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart b/lib/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart new file mode 100644 index 00000000..4f42e3bf --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/ellipsis_item_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/flexible_item_widget.dart'; + +import 'package:syncrow_web/utils/color_manager.dart'; + +class DynamicProductWidget extends StatelessWidget { + final Map productTagCount; + final double maxWidth; + final double maxHeight; + + const DynamicProductWidget({ + Key? key, + required this.productTagCount, + required this.maxWidth, + required this.maxHeight, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + const double itemSpacing = 8.0; + const double lineSpacing = 8.0; + const double textPadding = 16.0; + const double itemHeight = 40.0; + + List productWidgets = []; + double currentLineWidth = 0.0; + double currentHeight = itemHeight; + + for (final product in productTagCount.entries) { + final String prodType = product.key; + final int count = product.value; + + final TextPainter textPainter = TextPainter( + text: TextSpan( + text: 'x$count', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + ), + textDirection: TextDirection.ltr, + )..layout(); + + final double itemWidth = textPainter.width + textPadding + 20; + + if (currentLineWidth + itemWidth + itemSpacing > maxWidth) { + currentHeight += itemHeight + lineSpacing; + if (currentHeight > maxHeight) { + productWidgets.add(const EllipsisItemWidget()); + break; + } + currentLineWidth = 0.0; + } + + productWidgets.add(FlexibleItemWidget(prodType: prodType, count: count)); + currentLineWidth += itemWidth + itemSpacing; + } + + return Wrap( + spacing: itemSpacing, + runSpacing: lineSpacing, + children: productWidgets, + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart b/lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart new file mode 100644 index 00000000..e24c7704 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/room_name_widget.dart'; + +class DynamicRoomWidget extends StatelessWidget { + final List? subspaceModels; + final double maxWidth; + final double maxHeight; + + const DynamicRoomWidget({ + Key? key, + required this.subspaceModels, + required this.maxWidth, + required this.maxHeight, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + const double itemSpacing = 8.0; + const double lineSpacing = 8.0; + const double textPadding = 16.0; + const double itemHeight = 30.0; + + List roomWidgets = []; + double currentLineWidth = 0.0; + double currentHeight = itemHeight; + + for (final subspace in subspaceModels!) { + final TextPainter textPainter = TextPainter( + text: TextSpan( + text: subspace.subspaceName, + style: const TextStyle(fontSize: 16), + ), + textDirection: TextDirection.ltr, + )..layout(); + + final double itemWidth = textPainter.width + textPadding; + + if (currentLineWidth + itemWidth + itemSpacing > maxWidth) { + currentHeight += itemHeight + lineSpacing; + if (currentHeight > maxHeight) { + roomWidgets.add(const RoomNameWidget(name: "...")); + break; + } + currentLineWidth = 0.0; + } + + roomWidgets.add(RoomNameWidget(name: subspace.subspaceName)); + currentLineWidth += itemWidth + itemSpacing; + } + + return Wrap( + spacing: itemSpacing, + runSpacing: lineSpacing, + children: roomWidgets, + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/ellipsis_item_widget.dart b/lib/pages/spaces_management/space_model/widgets/ellipsis_item_widget.dart new file mode 100644 index 00000000..7ede09a7 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/ellipsis_item_widget.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class EllipsisItemWidget extends StatelessWidget { + const EllipsisItemWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: ColorsManager.transparentColor), + ), + child: Text( + "...", + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/flexible_item_widget.dart b/lib/pages/spaces_management/space_model/widgets/flexible_item_widget.dart new file mode 100644 index 00000000..c28a82b8 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/flexible_item_widget.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class FlexibleItemWidget extends StatelessWidget { + final String prodType; + final int count; + + const FlexibleItemWidget({ + Key? key, + required this.prodType, + required this.count, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: ColorsManager.transparentColor), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + prodType, + width: 15, + height: 16, + ), + const SizedBox(width: 4), + Text( + 'x$count', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + ), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/room_name_widget.dart b/lib/pages/spaces_management/space_model/widgets/room_name_widget.dart new file mode 100644 index 00000000..d59f8c1e --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/room_name_widget.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class RoomNameWidget extends StatelessWidget { + final String name; + + const RoomNameWidget({Key? key, required this.name}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: ColorsManager.transparentColor), + ), + child: Text( + name, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart index 4471743b..ac8b49d0 100644 --- a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/room_name_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class SpaceModelCardWidget extends StatelessWidget { @@ -32,111 +33,81 @@ class SpaceModelCardWidget extends StatelessWidget { } return Container( + padding: const EdgeInsets.all(16.0), decoration: BoxDecoration( - color: ColorsManager.whiteColors, + color: Colors.white, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( - color: ColorsManager.lightGrayColor.withOpacity(0.5), + color: Colors.grey.withOpacity(0.5), spreadRadius: 2, blurRadius: 5, offset: const Offset(0, 3), ), ], ), - padding: const EdgeInsets.fromLTRB(16.0, 14.0, 8.0, 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Flexible( - child: Text( - model.modelName, - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: ColorsManager.blackColor, - fontWeight: FontWeight.bold, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + Text( + model.modelName, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), const SizedBox(height: 10), Expanded( child: Row( - crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Left Container Expanded( - child: Wrap( - spacing: 3.0, - runSpacing: 3.0, - children: [ - for (var subspace in model.subspaceModels! - .take(calculateTakeCount(context))) - SubspaceChipWidget(subspace: subspace.subspaceName), - ], + flex: 1, // Distribute space proportionally + child: Container( + padding: const EdgeInsets.all(8.0), + child: LayoutBuilder( + builder: (context, constraints) { + return Align( + alignment: Alignment.topLeft, + child: DynamicRoomWidget( + subspaceModels: model.subspaceModels, + maxWidth: constraints.maxWidth, + maxHeight: constraints.maxHeight, + ), + ); + }, + ), ), ), - if (productTagCount.isNotEmpty) - Container( - width: 1, - height: double.infinity, - color: ColorsManager.softGray, - margin: const EdgeInsets.symmetric(horizontal: 4.0), - ), - const SizedBox(width: 7), + Container( + width: 1.0, // Thickness of the line + color: ColorsManager.softGray, // Subtle grey color + margin: const EdgeInsets.symmetric( + vertical: 6.0), // Top and bottom spacing + ), // Right Container Expanded( - child: Wrap( - spacing: 4.0, - runSpacing: 4.0, - children: productTagCount.entries.map((entry) { - final prodType = entry.key; - final count = entry.value; - - return Chip( - label: Row( - mainAxisSize: MainAxisSize.min, - children: [ - SvgPicture.asset( - prodType, - width: 15, - height: 16, - ), - const SizedBox(width: 4), - Text( - 'x$count', // Product count - style: const TextStyle(fontSize: 12), - ), - ], - ), - backgroundColor: ColorsManager.textFieldGreyColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: const BorderSide( - color: ColorsManager.transparentColor, - width: 0, - ), - ), - ); - }).toList(), + flex: 1, // Distribute space proportionally + child: Container( + padding: const EdgeInsets.all(8.0), + child: LayoutBuilder( + builder: (context, constraints) { + return Align( + alignment: Alignment.topLeft, + child: DynamicProductWidget( + productTagCount: productTagCount, + maxWidth: constraints.maxWidth, + maxHeight: constraints.maxHeight)); + }, + ), ), ), ], ), ), - const SizedBox(height: 5), ], ), ); } - - int calculateTakeCount(BuildContext context) { - double screenWidth = MediaQuery.of(context).size.width; - // Adjust the count based on the screen width - if (screenWidth > 1500) { - return 3; // For large screens - } else if (screenWidth > 1200) { - return 2; - } else { - return 1; // For smaller screens - } - } } diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart index 517448c8..8f987c51 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + class SubspaceChipWidget extends StatelessWidget { final String subspace; @@ -11,24 +14,25 @@ class SubspaceChipWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Chip( - label: Text( - subspace, - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - backgroundColor: ColorsManager.textFieldGreyColor, - labelStyle: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: ColorsManager.spaceColor), - shape: RoundedRectangleBorder( + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, borderRadius: BorderRadius.circular(8), - side: const BorderSide( + border: Border.all( color: ColorsManager.transparentColor, width: 0, ), ), + child: Text( + subspace, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + ), ); } } From a220483310a6f64c6fd49488ecacbc87642b3be7 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 14 Jan 2025 12:22:51 +0400 Subject: [PATCH 079/106] revert back --- lib/utils/constants/temp_const.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/constants/temp_const.dart b/lib/utils/constants/temp_const.dart index bcd1a1d6..e5847b98 100644 --- a/lib/utils/constants/temp_const.dart +++ b/lib/utils/constants/temp_const.dart @@ -1,3 +1,3 @@ class TempConst { - static const projectId = '0685c781-df33-4cbf-bf65-9f4e835eb468'; + static const projectId = '0e62577c-06fa-41b9-8a92-99a21fbaf51c'; } From cf2690123e3e6757bd04c08188110ae4538ef3e3 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 14 Jan 2025 12:22:59 +0400 Subject: [PATCH 080/106] revert back --- .env.development | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.development b/.env.development index 1fd358ec..e77609dc 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,2 @@ ENV_NAME=development -BASE_URL=http://localhost:4001 \ No newline at end of file +BASE_URL=https://syncrow-dev.azurewebsites.net \ No newline at end of file From 0bb24604bc1803df0fb50549ffff6ff2b01b78ba Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 15 Jan 2025 09:39:26 +0400 Subject: [PATCH 081/106] fixed subspace create --- .../views/create_subspace_model_dialog.dart | 26 +++++++----- .../bloc/create_space_model_event.dart | 1 + .../dialog/create_space_model_dialog.dart | 3 +- .../widgets/subspace_model_create_widget.dart | 40 +++++++++---------- 4 files changed, 38 insertions(+), 32 deletions(-) diff --git a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart index 4a290617..da149d47 100644 --- a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart @@ -21,18 +21,20 @@ class CreateSubSpaceModelDialog extends StatelessWidget { final List? allTags; final List? products; final SpaceTemplateModel? spaceModel; + final void Function(SpaceTemplateModel newModel)? onLoad; - const CreateSubSpaceModelDialog({ - Key? key, - required this.isEdit, - required this.dialogTitle, - this.existingSubSpaces, - required this.allTags, - required this.spaceName, - required this.spaceTagModels, - required this.products, - required this.spaceModel, - }) : super(key: key); + const CreateSubSpaceModelDialog( + {Key? key, + required this.isEdit, + required this.dialogTitle, + this.existingSubSpaces, + required this.allTags, + required this.spaceName, + required this.spaceTagModels, + required this.products, + required this.spaceModel, + this.onLoad}) + : super(key: key); @override Widget build(BuildContext context) { @@ -176,6 +178,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget { CreateSpaceModelDialog( products: products, allTags: allTags, + onLoad: onLoad, spaceModel: SpaceTemplateModel( modelName: spaceName ?? '', subspaceModels: existingSubSpaces, @@ -203,6 +206,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget { CreateSpaceModelDialog( products: products, allTags: allTags, + onLoad: onLoad, spaceModel: SpaceTemplateModel( modelName: spaceName ?? '', subspaceModels: subSpaces, diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index 9342c771..1d7f6012 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -21,6 +21,7 @@ class CreateSpaceTemplate extends CreateSpaceModelEvent { final SpaceTemplateModel spaceTemplate; final Function(SpaceTemplateModel)? onCreate; + const CreateSpaceTemplate({ required this.spaceTemplate, this.onCreate, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 51222ad8..64d5ec2f 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -110,11 +110,12 @@ class CreateSpaceModelDialog extends StatelessWidget { const SizedBox(height: 16), SubspaceModelCreate(context, subspaces: state.space.subspaceModels ?? [], + onLoad:onLoad, allTags: allTags, products: products, spaceModel: spaceModel, spaceTagModels: spaceModel?.tags ?? [], - spaceNameController: spaceNameController), + spaceNameController: spaceNameController,), const SizedBox(height: 10), TagChipDisplay(context, screenWidth: screenWidth, diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 832c9ea3..f62f15fa 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -14,17 +14,18 @@ class SubspaceModelCreate extends StatelessWidget { final List? allTags; final List? products; final SpaceTemplateModel? spaceModel; + final void Function(SpaceTemplateModel newModel)? onLoad; - const SubspaceModelCreate( - BuildContext context, { - Key? key, - required this.subspaces, - this.spaceTagModels, - required this.allTags, - required this.products, - required this.spaceModel, - required this.spaceNameController, - }) : super(key: key); + const SubspaceModelCreate(BuildContext context, + {Key? key, + required this.subspaces, + this.spaceTagModels, + required this.allTags, + required this.products, + required this.spaceModel, + required this.spaceNameController, + this.onLoad}) + : super(key: key); @override Widget build(BuildContext context) { @@ -53,6 +54,7 @@ class SubspaceModelCreate extends StatelessWidget { ? 'Create Sub-space' : 'Edit Sub-space', existingSubSpaces: subspaces, + onLoad: onLoad, ); }, ); @@ -85,7 +87,8 @@ class SubspaceModelCreate extends StatelessWidget { style: const TextStyle( color: ColorsManager.spaceColor), // Text color ), - backgroundColor: ColorsManager.whiteColors, // Chip background color + backgroundColor: + ColorsManager.whiteColors, // Chip background color shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), // Rounded chip @@ -110,6 +113,7 @@ class SubspaceModelCreate extends StatelessWidget { spaceTagModels: spaceTagModels, products: products, spaceModel: spaceModel, + onLoad: onLoad, ); }, ); @@ -117,16 +121,13 @@ class SubspaceModelCreate extends StatelessWidget { child: Chip( label: const Text( 'Edit', - style: TextStyle( - color: ColorsManager.spaceColor), + style: TextStyle(color: ColorsManager.spaceColor), ), - backgroundColor: - ColorsManager.whiteColors, + backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16), - side: const BorderSide( - color: ColorsManager.spaceColor), + borderRadius: BorderRadius.circular(16), + side: + const BorderSide(color: ColorsManager.spaceColor), ), ), ), @@ -134,7 +135,6 @@ class SubspaceModelCreate extends StatelessWidget { ), ), ), - ); } } From 5975adb5e2458b42df4ff0e7769c4faa9610fba8 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 15 Jan 2025 10:22:42 +0400 Subject: [PATCH 082/106] cleaned subspace model create --- .../views/create_subspace_model_dialog.dart | 50 +----------- .../dialog/create_space_model_dialog.dart | 17 ++-- .../widgets/subspace_model_create_widget.dart | 78 ++++++------------- 3 files changed, 34 insertions(+), 111 deletions(-) diff --git a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart index da149d47..29ed3846 100644 --- a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart @@ -2,38 +2,24 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSubSpaceModelDialog extends StatelessWidget { final bool isEdit; final String dialogTitle; final List? existingSubSpaces; - final String? spaceName; - final List? spaceTagModels; - final List? allTags; - final List? products; - final SpaceTemplateModel? spaceModel; - final void Function(SpaceTemplateModel newModel)? onLoad; + final void Function(List newSubspaces)? onUpdate; const CreateSubSpaceModelDialog( {Key? key, required this.isEdit, required this.dialogTitle, this.existingSubSpaces, - required this.allTags, - required this.spaceName, - required this.spaceTagModels, - required this.products, - required this.spaceModel, - this.onLoad}) + this.onUpdate}) : super(key: key); @override @@ -171,21 +157,6 @@ class CreateSubSpaceModelDialog extends StatelessWidget { label: 'Cancel', onPressed: () async { Navigator.of(context).pop(); - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => - CreateSpaceModelDialog( - products: products, - allTags: allTags, - onLoad: onLoad, - spaceModel: SpaceTemplateModel( - modelName: spaceName ?? '', - subspaceModels: existingSubSpaces, - tags: spaceTagModels, - ), - ), - ); }, ), ), @@ -198,22 +169,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget { .state .subSpaces; Navigator.of(context).pop(); - - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => - CreateSpaceModelDialog( - products: products, - allTags: allTags, - onLoad: onLoad, - spaceModel: SpaceTemplateModel( - modelName: spaceName ?? '', - subspaceModels: subSpaces, - tags: spaceTagModels, - ), - ), - ); + onUpdate!(subSpaces); }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 64d5ec2f..98c82f2a 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -108,14 +108,15 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - SubspaceModelCreate(context, - subspaces: state.space.subspaceModels ?? [], - onLoad:onLoad, - allTags: allTags, - products: products, - spaceModel: spaceModel, - spaceTagModels: spaceModel?.tags ?? [], - spaceNameController: spaceNameController,), + SubspaceModelCreate( + context, + subspaces: state.space.subspaceModels ?? [], + onSpaceModelUpdate: (updatedSubspaces) { + context + .read() + .add(AddSubspacesToSpaceTemplate(updatedSubspaces)); + }, + ), const SizedBox(height: 10), TagChipDisplay(context, screenWidth: screenWidth, diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index f62f15fa..7781bb5e 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -1,30 +1,16 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class SubspaceModelCreate extends StatelessWidget { final List subspaces; - final TextEditingController spaceNameController; - final List? spaceTagModels; - final List? allTags; - final List? products; - final SpaceTemplateModel? spaceModel; - final void Function(SpaceTemplateModel newModel)? onLoad; + final void Function(List newSubspaces)? + onSpaceModelUpdate; const SubspaceModelCreate(BuildContext context, - {Key? key, - required this.subspaces, - this.spaceTagModels, - required this.allTags, - required this.products, - required this.spaceModel, - required this.spaceNameController, - this.onLoad}) + {Key? key, required this.subspaces, this.onSpaceModelUpdate}) : super(key: key); @override @@ -37,27 +23,7 @@ class SubspaceModelCreate extends StatelessWidget { overlayColor: ColorsManager.transparentColor, ), onPressed: () async { - Navigator.of(context).pop(); - - await showDialog>( - barrierDismissible: false, - context: context, - builder: (BuildContext context) { - return CreateSubSpaceModelDialog( - allTags: allTags, - spaceName: spaceNameController.text, - spaceModel: spaceModel, - spaceTagModels: spaceTagModels, - products: products, - isEdit: true, - dialogTitle: subspaces.isEmpty - ? 'Create Sub-space' - : 'Edit Sub-space', - existingSubSpaces: subspaces, - onLoad: onLoad, - ); - }, - ); + await _openDialog(context, 'Create Sub-space'); }, child: const ButtonContentWidget( icon: Icons.add, @@ -99,24 +65,7 @@ class SubspaceModelCreate extends StatelessWidget { ), GestureDetector( onTap: () async { - Navigator.of(context).pop(); - await showDialog>( - barrierDismissible: false, - context: context, - builder: (BuildContext context) { - return CreateSubSpaceModelDialog( - isEdit: true, - dialogTitle: 'Edit Sub-space', - existingSubSpaces: subspaces, - allTags: allTags, - spaceName: spaceNameController.text, - spaceTagModels: spaceTagModels, - products: products, - spaceModel: spaceModel, - onLoad: onLoad, - ); - }, - ); + await _openDialog(context, 'Edit Sub-space'); }, child: Chip( label: const Text( @@ -137,4 +86,21 @@ class SubspaceModelCreate extends StatelessWidget { ), ); } + + Future _openDialog(BuildContext context, String dialogTitle) async { + await showDialog( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: dialogTitle, + existingSubSpaces: subspaces, + onUpdate: (subspaceModels) { + onSpaceModelUpdate!(subspaceModels); + }, + ); + }, + ); + } } From a7256c8d5d8ce8e0a68659284f1e3acf936270a5 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 15 Jan 2025 11:29:19 +0400 Subject: [PATCH 083/106] updated duplication ui --- .../bloc/subspace_model_bloc.dart | 36 ++++- .../bloc/subspace_model_state.dart | 4 + .../views/create_subspace_model_dialog.dart | 137 +++++++++++------- 3 files changed, 116 insertions(+), 61 deletions(-) diff --git a/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart index 6c12ad04..1e8d0ddc 100644 --- a/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart @@ -6,19 +6,23 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_ import 'package:syncrow_web/utils/constants/action_enum.dart'; class SubSpaceModelBloc extends Bloc { - SubSpaceModelBloc() : super(SubSpaceModelState([], [], '')) { + SubSpaceModelBloc() : super(SubSpaceModelState([], [], '', {})) { // Handle AddSubSpaceModel Event on((event, emit) { - // Check for duplicate names (case-insensitive) final existingNames = state.subSpaces.map((e) => e.subspaceName.toLowerCase()).toSet(); if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) { - // Emit state with an error message if duplicate name exists + final updatedDuplicates = Set.from(state.duplicates) + ..add(event.subSpace.subspaceName.toLowerCase()); + final updatedSubSpaces = + List.from(state.subSpaces) + ..add(event.subSpace); emit(SubSpaceModelState( - state.subSpaces, + updatedSubSpaces, state.updatedSubSpaceModels, - 'Subspace name already exists.', + '*Duplicated sub-space name', + updatedDuplicates, )); } else { // Add subspace if no duplicate exists @@ -29,7 +33,9 @@ class SubSpaceModelBloc extends Bloc { emit(SubSpaceModelState( updatedSubSpaces, state.updatedSubSpaceModels, - '', // Clear error message + '', + state.duplicates, +// Clear error message )); } }); @@ -42,6 +48,16 @@ class SubSpaceModelBloc extends Bloc { final updatedSubspaceModels = List.from( state.updatedSubSpaceModels, ); + final nameOccurrences = {}; + for (final subSpace in updatedSubSpaces) { + final lowerName = subSpace.subspaceName.toLowerCase(); + nameOccurrences[lowerName] = (nameOccurrences[lowerName] ?? 0) + 1; + } + + final updatedDuplicates = nameOccurrences.entries + .where((entry) => entry.value > 1) + .map((entry) => entry.key) + .toSet(); if (event.subSpace.uuid?.isNotEmpty ?? false) { updatedSubspaceModels.add(UpdateSubspaceTemplateModel( @@ -53,7 +69,9 @@ class SubSpaceModelBloc extends Bloc { emit(SubSpaceModelState( updatedSubSpaces, updatedSubspaceModels, - '', // Clear error message + '', + updatedDuplicates, +// Clear error message )); }); @@ -78,7 +96,9 @@ class SubSpaceModelBloc extends Bloc { emit(SubSpaceModelState( updatedSubSpaces, updatedSubspaceModels, - '', // Clear error message + '', + state.duplicates, +// Clear error message )); }); } diff --git a/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart index ab60a813..207d9601 100644 --- a/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart +++ b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart @@ -5,22 +5,26 @@ class SubSpaceModelState { final List subSpaces; final List updatedSubSpaceModels; final String errorMessage; + final Set duplicates; SubSpaceModelState( this.subSpaces, this.updatedSubSpaceModels, this.errorMessage, + this.duplicates, ); SubSpaceModelState copyWith({ List? subSpaces, List? updatedSubSpaceModels, String? errorMessage, + Set? duplicates, }) { return SubSpaceModelState( subSpaces ?? this.subSpaces, updatedSubSpaceModels ?? this.updatedSubSpaceModels, errorMessage ?? this.errorMessage, + duplicates ?? this.duplicates, ); } } diff --git a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart index 29ed3846..82aa3684 100644 --- a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart @@ -46,7 +46,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget { return Container( color: ColorsManager.whiteColors, child: SizedBox( - width: screenWidth * 0.35, + width: screenWidth * 0.3, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( @@ -73,41 +73,64 @@ class CreateSubSpaceModelDialog extends StatelessWidget { spacing: 8.0, runSpacing: 8.0, children: [ - ...state.subSpaces.map( - (subSpace) => Chip( - label: Text( - subSpace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor), - ), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: const BorderSide( - color: ColorsManager.transparentColor, - width: 0, - ), - ), - deleteIcon: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: ColorsManager.lightGrayColor, - width: 1.5, + ...state.subSpaces.asMap().entries.map( + (entry) { + final index = entry.key; + final subSpace = entry.value; + + final lowerName = + subSpace.subspaceName.toLowerCase(); + + final duplicateIndices = state.subSpaces + .asMap() + .entries + .where((e) => + e.value.subspaceName.toLowerCase() == + lowerName) + .map((e) => e.key) + .toList(); + final isDuplicate = + duplicateIndices.length > 1 && + duplicateIndices.indexOf(index) != 0; + + return Chip( + label: Text( + subSpace.subspaceName, + style: const TextStyle( + color: ColorsManager.spaceColor, ), ), - child: const Icon( - Icons.close, - size: 16, - color: ColorsManager.lightGrayColor, + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: BorderSide( + color: isDuplicate + ? ColorsManager.red + : ColorsManager.transparentColor, + width: 0, + ), ), - ), - onDeleted: () => context - .read() - .add(RemoveSubSpaceModel(subSpace)), - ), + deleteIcon: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager.lightGrayColor, + width: 1.5, + ), + ), + child: const Icon( + Icons.close, + size: 16, + color: ColorsManager.lightGrayColor, + ), + ), + onDeleted: () => context + .read() + .add(RemoveSubSpaceModel(subSpace)), + ); + }, ), SizedBox( width: 200, @@ -135,20 +158,20 @@ class CreateSubSpaceModelDialog extends StatelessWidget { color: ColorsManager.blackColor), ), ), - if (state.errorMessage.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - state.errorMessage, - style: const TextStyle( - color: ColorsManager.warningRed, - fontSize: 12, - ), - ), - ), ], ), ), + if (state.errorMessage.isNotEmpty) + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Text( + state.errorMessage, + style: const TextStyle( + color: ColorsManager.red, + fontSize: 12, + ), + ), + ), const SizedBox(height: 16), Row( children: [ @@ -163,17 +186,25 @@ class CreateSubSpaceModelDialog extends StatelessWidget { const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: () async { - final subSpaces = context - .read() - .state - .subSpaces; - Navigator.of(context).pop(); - onUpdate!(subSpaces); - }, + onPressed: (state.subSpaces.isEmpty || + state.errorMessage.isNotEmpty) + ? null + : () async { + final subSpaces = context + .read() + .state + .subSpaces; + Navigator.of(context).pop(); + if (onUpdate != null) { + onUpdate!(subSpaces); + } + }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: ColorsManager.whiteColors, + foregroundColor: state.subSpaces.isEmpty || + state.errorMessage.isNotEmpty + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, child: const Text('OK'), ), ), From c12c73f20a3467f4580605f982a8e982dc0e6029 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 16 Jan 2025 00:10:45 +0400 Subject: [PATCH 084/106] add close button color --- .../views/assign_tag_models_dialog.dart | 71 ++++++++++++------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 1b162832..7a287fb7 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -23,17 +23,17 @@ class AssignTagModelsDialog extends StatelessWidget { final String spaceName; final String title; - const AssignTagModelsDialog({ - Key? key, - required this.products, - required this.subspaces, - required this.addedProducts, - required this.initialTags, - this.onTagsAssigned, - this.allTags, - required this.spaceName, - required this.title - }) : super(key: key); + const AssignTagModelsDialog( + {Key? key, + required this.products, + required this.subspaces, + required this.addedProducts, + required this.initialTags, + this.onTagsAssigned, + this.allTags, + required this.spaceName, + required this.title}) + : super(key: key); @override Widget build(BuildContext context) { @@ -123,19 +123,38 @@ class AssignTagModelsDialog extends StatelessWidget { tag.product?.name ?? 'Unknown', overflow: TextOverflow.ellipsis, )), - IconButton( - icon: const Icon(Icons.close, - color: ColorsManager.warningRed, - size: 16), - onPressed: () { - context - .read() - .add(DeleteTagModel( - tagToDelete: tag, - tags: state.tags)); - }, - tooltip: 'Delete Tag', - ) + const SizedBox(width: 10), + Container( + width: 20.0, + height: 20.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager + .lightGrayColor, + width: 1.0, + ), + ), + child: IconButton( + icon: const Icon( + Icons.close, + color: ColorsManager + .lightGreyColor, + size: 16, + ), + onPressed: () { + context + .read() + .add(DeleteTagModel( + tagToDelete: tag, + tags: state.tags)); + }, + tooltip: 'Delete Tag', + padding: EdgeInsets.zero, + constraints: + const BoxConstraints(), + ), + ), ], ), ), @@ -154,7 +173,6 @@ class AssignTagModelsDialog extends StatelessWidget { )); }, decoration: const InputDecoration( - hintText: 'Enter Tag', border: InputBorder.none, ), style: const TextStyle( @@ -172,7 +190,8 @@ class AssignTagModelsDialog extends StatelessWidget { color: ColorsManager.whiteColors, icon: const Icon( Icons.arrow_drop_down, - color: ColorsManager.blackColor), + color: + ColorsManager.blackColor), onSelected: (value) { controller.text = value; context From 60028cdf786639acac55f5402b8abae16c4a53f1 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 16 Jan 2025 02:03:08 +0400 Subject: [PATCH 085/106] only appears the separator if there is both space model and product --- .../space_model/widgets/space_model_card_widget.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart index ac8b49d0..323d7ac0 100644 --- a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart @@ -81,12 +81,12 @@ class SpaceModelCardWidget extends StatelessWidget { ), ), ), + if(productTagCount.isNotEmpty) Container( - width: 1.0, // Thickness of the line - color: ColorsManager.softGray, // Subtle grey color - margin: const EdgeInsets.symmetric( - vertical: 6.0), // Top and bottom spacing - ), // Right Container + width: 1.0, + color: ColorsManager.softGray, + margin: const EdgeInsets.symmetric(vertical: 6.0), + ), Expanded( flex: 1, // Distribute space proportionally child: Container( From 8a95f9355666b97d9bc51fd2f139591f8fa8bf86 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 16 Jan 2025 02:14:04 +0400 Subject: [PATCH 086/106] fixed list view --- lib/common/dialog_dropdown.dart | 138 +++++++++++++++ lib/common/dialog_textfield_dropdown.dart | 160 ++++++++++++++++++ .../bloc/assign_tag_model_bloc.dart | 3 +- .../views/assign_tag_models_dialog.dart | 158 +++++------------ 4 files changed, 344 insertions(+), 115 deletions(-) create mode 100644 lib/common/dialog_dropdown.dart create mode 100644 lib/common/dialog_textfield_dropdown.dart diff --git a/lib/common/dialog_dropdown.dart b/lib/common/dialog_dropdown.dart new file mode 100644 index 00000000..7274b3c0 --- /dev/null +++ b/lib/common/dialog_dropdown.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DialogDropdown extends StatefulWidget { + final List items; + final ValueChanged onSelected; + final String? selectedValue; + + const DialogDropdown({ + Key? key, + required this.items, + required this.onSelected, + this.selectedValue, + }) : super(key: key); + + @override + _DialogDropdownState createState() => _DialogDropdownState(); +} + +class _DialogDropdownState extends State { + bool _isOpen = false; + late OverlayEntry _overlayEntry; + + @override + void initState() { + super.initState(); + } + + void _toggleDropdown() { + if (_isOpen) { + _closeDropdown(); + } else { + _openDropdown(); + } + } + + void _openDropdown() { + _overlayEntry = _createOverlayEntry(); + Overlay.of(context).insert(_overlayEntry); + _isOpen = true; + } + + void _closeDropdown() { + _overlayEntry.remove(); + _isOpen = false; + } + + OverlayEntry _createOverlayEntry() { + final renderBox = context.findRenderObject() as RenderBox; + final size = renderBox.size; + final offset = renderBox.localToGlobal(Offset.zero); + + return OverlayEntry( + builder: (context) { + return GestureDetector( + onTap: () { + _closeDropdown(); + }, + behavior: HitTestBehavior.translucent, + child: Stack( + children: [ + Positioned( + left: offset.dx, + top: offset.dy + size.height, + width: size.width, + child: Material( + elevation: 4.0, + child: Container( + color: ColorsManager.whiteColors, + constraints: const BoxConstraints( + maxHeight: 200.0, // Set max height for dropdown + ), + child: ListView.builder( + shrinkWrap: true, + itemCount: widget.items.length, + itemBuilder: (context, index) { + final item = widget.items[index]; + return Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: ColorsManager.lightGrayBorderColor, + width: 1.0, + ), + ), + ), + child: ListTile( + title: Text( + item, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: ColorsManager.textPrimaryColor, + ), + ), + onTap: () { + widget.onSelected(item); + _closeDropdown(); + }, + ), + ); + }, + ), + ), + ), + ), + ], + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: _toggleDropdown, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.transparentColor), + borderRadius: BorderRadius.circular(8.0), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.selectedValue ?? 'Select an item', + style: Theme.of(context).textTheme.bodyMedium, + ), + const Icon(Icons.arrow_drop_down), + ], + ), + ), + ); + } +} diff --git a/lib/common/dialog_textfield_dropdown.dart b/lib/common/dialog_textfield_dropdown.dart new file mode 100644 index 00000000..807f3417 --- /dev/null +++ b/lib/common/dialog_textfield_dropdown.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DialogTextfieldDropdown extends StatefulWidget { + final List items; + final ValueChanged onSelected; + final String? initialValue; + + const DialogTextfieldDropdown({ + Key? key, + required this.items, + required this.onSelected, + this.initialValue, + }) : super(key: key); + + @override + _DialogTextfieldDropdownState createState() => + _DialogTextfieldDropdownState(); +} + +class _DialogTextfieldDropdownState extends State { + bool _isOpen = false; + late OverlayEntry _overlayEntry; + final TextEditingController _controller = TextEditingController(); + late List _filteredItems; // Filtered items list + + @override + void initState() { + super.initState(); + _controller.text = widget.initialValue ?? 'Select Tag'; + _filteredItems = List.from(widget.items); // Initialize filtered items + } + + void _toggleDropdown() { + if (_isOpen) { + _closeDropdown(); + } else { + _openDropdown(); + } + } + + void _openDropdown() { + _overlayEntry = _createOverlayEntry(); + Overlay.of(context).insert(_overlayEntry); + _isOpen = true; + } + + void _closeDropdown() { + _overlayEntry.remove(); + _isOpen = false; + } + + OverlayEntry _createOverlayEntry() { + final renderBox = context.findRenderObject() as RenderBox; + final size = renderBox.size; + final offset = renderBox.localToGlobal(Offset.zero); + + return OverlayEntry( + builder: (context) { + return GestureDetector( + onTap: () { + _closeDropdown(); + }, + behavior: HitTestBehavior.translucent, + child: Stack( + children: [ + Positioned( + left: offset.dx, + top: offset.dy + size.height, + width: size.width, + child: Material( + elevation: 4.0, + child: Container( + color: ColorsManager.whiteColors, + constraints: const BoxConstraints( + maxHeight: 200.0, + ), + child: ListView.builder( + shrinkWrap: true, + itemCount: _filteredItems.length, + itemBuilder: (context, index) { + final item = _filteredItems[index]; + return Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: ColorsManager.lightGrayBorderColor, + width: 1.0, + ), + ), + ), + child: ListTile( + title: Text(item, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: ColorsManager.textPrimaryColor)), + onTap: () { + _controller.text = item; + widget.onSelected(item); + setState(() { + _filteredItems + .remove(item); // Remove selected item + }); + _closeDropdown(); + }, + ), + ); + }, + ), + ), + ), + ), + ], + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: _toggleDropdown, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.transparentColor), + borderRadius: BorderRadius.circular(8.0), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: TextFormField( + controller: _controller, + onChanged: (value) { + setState(() { + _filteredItems = widget.items + .where((item) => + item.toLowerCase().contains(value.toLowerCase())) + .toList(); // Filter items dynamically + }); + widget.onSelected(value); + }, + style: Theme.of(context).textTheme.bodyMedium, + decoration: const InputDecoration( + hintText: 'Enter or Select tag', + border: InputBorder.none, + ), + ), + ), + const Icon(Icons.arrow_drop_down), + ], + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index 3dd5e27d..ce4a38c2 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -127,7 +127,8 @@ class AssignTagModelBloc } final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); - return uniqueTags.length == tags.length && !hasEmptyTag; + final isValid = uniqueTags.length == tags.length && !hasEmptyTag; + return isValid; } String? _getValidationError(List tags) { diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 7a287fb7..3712f170 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/common/dialog_dropdown.dart'; +import 'package:syncrow_web/common/dialog_textfield_dropdown.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; @@ -79,6 +81,8 @@ class AssignTagModelsDialog extends StatelessWidget { style: Theme.of(context).textTheme.bodyMedium)), DataColumn( + numeric: false, + headingRowAlignment: MainAxisAlignment.start, label: Text('Tag', style: Theme.of(context).textTheme.bodyMedium)), @@ -109,6 +113,8 @@ class AssignTagModelsDialog extends StatelessWidget { : List.generate(state.tags.length, (index) { final tag = state.tags[index]; final controller = controllers[index]; + final availableTags = getAvailableTags( + allTags ?? [], state.tags, tag); return DataRow( cells: [ @@ -159,130 +165,43 @@ class AssignTagModelsDialog extends StatelessWidget { ), ), DataCell( - Row( - children: [ - Expanded( - child: TextFormField( - controller: controller, - onChanged: (value) { - context - .read() - .add(UpdateTag( - index: index, - tag: value.trim(), - )); - }, - decoration: const InputDecoration( - border: InputBorder.none, - ), - style: const TextStyle( - fontSize: 14, - color: ColorsManager.blackColor, - ), - ), + Container( + alignment: Alignment + .centerLeft, // Align cell content to the left + child: SizedBox( + width: double + .infinity, // Ensure full width for dropdown + child: DialogTextfieldDropdown( + items: availableTags ?? [], + onSelected: (value) { + controller.text = value; + context + .read() + .add(UpdateTag( + index: index, + tag: value, + )); + }, ), - SizedBox( - width: MediaQuery.of(context) - .size - .width * - 0.15, - child: PopupMenuButton( - color: ColorsManager.whiteColors, - icon: const Icon( - Icons.arrow_drop_down, - color: - ColorsManager.blackColor), - onSelected: (value) { - controller.text = value; - context - .read() - .add(UpdateTag( - index: index, - tag: value, - )); - }, - itemBuilder: (context) { - return (allTags ?? []) - .where((tagValue) => !state - .tags - .map((e) => e.tag) - .contains(tagValue)) - .map((tagValue) { - return PopupMenuItem( - textStyle: const TextStyle( - color: ColorsManager - .textPrimaryColor), - value: tagValue, - child: ConstrainedBox( - constraints: - BoxConstraints( - minWidth: MediaQuery.of( - context) - .size - .width * - 0.15, - maxWidth: MediaQuery.of( - context) - .size - .width * - 0.15, - ), - child: Text( - tagValue, - overflow: TextOverflow - .ellipsis, - ), - )); - }).toList(); - }, - ), - ), - ], + ), ), ), DataCell( - DropdownButtonHideUnderline( - child: DropdownButton( - value: tag.location ?? 'None', - dropdownColor: ColorsManager - .whiteColors, // Dropdown background - style: const TextStyle( - color: Colors - .black), // Style for selected text - items: [ - const DropdownMenuItem( - value: 'None', - child: Text( - 'None', - style: TextStyle( - color: ColorsManager - .textPrimaryColor), - ), - ), - ...locations.map((location) { - return DropdownMenuItem( - value: location, - child: Text( - location, - style: const TextStyle( - color: ColorsManager - .textPrimaryColor), - ), - ); - }).toList(), - ], - onChanged: (value) { - if (value != null) { + SizedBox( + width: double.infinity, + child: DialogDropdown( + items: locations, + selectedValue: + tag.location ?? 'None', + onSelected: (value) { context .read() .add(UpdateLocation( index: index, location: value, )); - } - }, - ), - ), + }, + )), ), ], ); @@ -380,4 +299,15 @@ class AssignTagModelsDialog extends StatelessWidget { ), ); } + + List getAvailableTags( + List allTags, List currentTags, TagModel currentTag) { + print("happening"); + return allTags + .where((tagValue) => !currentTags + .where((e) => e != currentTag) // Exclude the current row + .map((e) => e.tag) + .contains(tagValue)) + .toList(); + } } From 9706c2655c9a6306723e30be874a268559872ee2 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 16 Jan 2025 02:15:24 +0400 Subject: [PATCH 087/106] added color --- .../assign_tag_models/views/assign_tag_models_dialog.dart | 1 - lib/utils/color_manager.dart | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 3712f170..b84c32e5 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -302,7 +302,6 @@ class AssignTagModelsDialog extends StatelessWidget { List getAvailableTags( List allTags, List currentTags, TagModel currentTag) { - print("happening"); return allTags .where((tagValue) => !currentTags .where((e) => e != currentTag) // Exclude the current row diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index d1d111dd..301365ed 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -68,6 +68,7 @@ abstract class ColorsManager { static const Color disabledRedText = Color(0xFF890002); static const Color invitedOrange = Color(0xFFFFE193); static const Color invitedOrangeText = Color(0xFFFFBF00); + static const Color lightGrayBorderColor = Color(0xB2D5D5D5); //background: #F8F8F8; } From bae5ae17a731089ae60d168fb47f1e895af798e8 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 16 Jan 2025 10:17:57 +0400 Subject: [PATCH 088/106] updated the condition --- .../widgets/space_model_card_widget.dart | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart index 323d7ac0..df0fba4f 100644 --- a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/room_name_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class SpaceModelCardWidget extends StatelessWidget { @@ -81,12 +80,12 @@ class SpaceModelCardWidget extends StatelessWidget { ), ), ), - if(productTagCount.isNotEmpty) - Container( - width: 1.0, - color: ColorsManager.softGray, - margin: const EdgeInsets.symmetric(vertical: 6.0), - ), + if (productTagCount.isNotEmpty && model.subspaceModels != null) + Container( + width: 1.0, + color: ColorsManager.softGray, + margin: const EdgeInsets.symmetric(vertical: 6.0), + ), Expanded( flex: 1, // Distribute space proportionally child: Container( From 145086b9de4b7254a370deb9104aecd5dd628fa9 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 17 Jan 2025 12:40:17 +0400 Subject: [PATCH 089/106] updated grid view --- .../tag_model/bloc/add_device_model_bloc.dart | 85 +++++--- .../bloc/add_device_model_state.dart | 36 ++++ .../bloc/add_device_type_model_event.dart | 19 ++ .../views/add_device_type_model_widget.dart | 196 ++++++++++-------- .../widgets/scrollable_grid_view_widget.dart | 9 +- 5 files changed, 231 insertions(+), 114 deletions(-) create mode 100644 lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart index 3091d152..9c617a12 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart @@ -1,38 +1,75 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart'; class AddDeviceTypeModelBloc - extends Bloc> { - AddDeviceTypeModelBloc(List initialProducts) - : super(initialProducts) { + extends Bloc { + AddDeviceTypeModelBloc() : super(AddDeviceModelInitial()) { + on(_onInitializeTagModels); on(_onUpdateProductCount); } - void _onUpdateProductCount( - UpdateProductCountEvent event, Emitter> emit) { - final existingProduct = state.firstWhere( - (p) => p.productId == event.productId, - orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName,product: event.product ), - ); + void _onInitializeTagModels( + InitializeDeviceTypeModel event, Emitter emit) { + emit(AddDeviceModelLoaded( + selectedProducts: event.addedProducts, + initialTag: event.initialTags, + )); + } - if (event.count > 0) { - if (!state.contains(existingProduct)) { - emit([ - ...state, - SelectedProduct(productId: event.productId, count: event.count, productName: event.productName, product: event.product) - ]); + void _onUpdateProductCount( + UpdateProductCountEvent event, Emitter emit) { + final currentState = state; + + if (currentState is AddDeviceModelLoaded) { + final existingProduct = currentState.selectedProducts.firstWhere( + (p) => p.productId == event.productId, + orElse: () => SelectedProduct( + productId: event.productId, + count: 0, + productName: event.productName, + product: event.product, + ), + ); + + List updatedProducts; + + if (event.count > 0) { + if (!currentState.selectedProducts.contains(existingProduct)) { + updatedProducts = [ + ...currentState.selectedProducts, + SelectedProduct( + productId: event.productId, + count: event.count, + productName: event.productName, + product: event.product, + ), + ]; + } else { + updatedProducts = currentState.selectedProducts.map((p) { + if (p.productId == event.productId) { + return SelectedProduct( + productId: p.productId, + count: event.count, + productName: p.productName, + product: p.product, + ); + } + return p; + }).toList(); + } } else { - final updatedList = state.map((p) { - if (p.productId == event.productId) { - return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName,product: p.product); - } - return p; - }).toList(); - emit(updatedList); + // Remove the product if the count is 0 + updatedProducts = currentState.selectedProducts + .where((p) => p.productId != event.productId) + .toList(); } - } else { - emit(state.where((p) => p.productId != event.productId).toList()); + + // Emit the updated state + emit(AddDeviceModelLoaded( + selectedProducts: updatedProducts, + initialTag: currentState.initialTag)); } } } diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart new file mode 100644 index 00000000..f45471cd --- /dev/null +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart @@ -0,0 +1,36 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; + +abstract class AddDeviceModelState extends Equatable { + const AddDeviceModelState(); + + @override + List get props => []; +} + +class AddDeviceModelInitial extends AddDeviceModelState {} + +class AddDeviceModelLoading extends AddDeviceModelState {} + +class AddDeviceModelLoaded extends AddDeviceModelState { + final List selectedProducts; + final List initialTag; + + const AddDeviceModelLoaded({ + required this.selectedProducts, + required this.initialTag, + }); + + @override + List get props => [selectedProducts, initialTag]; +} + +class AddDeviceModelError extends AddDeviceModelState { + final String errorMessage; + + const AddDeviceModelError(this.errorMessage); + + @override + List get props => [errorMessage]; +} diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart index 1d6976f8..9b3a8b1e 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart @@ -1,11 +1,16 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; abstract class AddDeviceTypeModelEvent extends Equatable { + const AddDeviceTypeModelEvent(); + @override List get props => []; } + class UpdateProductCountEvent extends AddDeviceTypeModelEvent { final String productId; final int count; @@ -17,3 +22,17 @@ class UpdateProductCountEvent extends AddDeviceTypeModelEvent { @override List get props => [productId, count]; } + + +class InitializeDeviceTypeModel extends AddDeviceTypeModelEvent { + final List initialTags; + final List addedProducts; + + const InitializeDeviceTypeModel({ + this.initialTags = const [], + required this.addedProducts, + }); + + @override + List get props => [initialTags, addedProducts]; +} diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index f7f4c3f7..ea0a94a6 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -9,6 +9,8 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -21,14 +23,15 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? allTags; final String spaceName; - const AddDeviceTypeModelWidget( - {super.key, - this.products, - this.initialSelectedProducts, - this.subspaces, - this.allTags, - this.spaceTagModels, - required this.spaceName}); + const AddDeviceTypeModelWidget({ + super.key, + this.products, + this.initialSelectedProducts, + this.subspaces, + this.allTags, + this.spaceTagModels, + required this.spaceName, + }); @override Widget build(BuildContext context) { @@ -40,91 +43,108 @@ class AddDeviceTypeModelWidget extends StatelessWidget { : 3; return BlocProvider( - create: (_) => AddDeviceTypeModelBloc(initialSelectedProducts ?? []), - child: Builder( - builder: (context) => AlertDialog( - title: const Text('Add Devices'), - backgroundColor: ColorsManager.whiteColors, - content: SingleChildScrollView( - child: Container( - width: size.width * 0.9, - height: size.height * 0.65, - color: ColorsManager.textFieldGreyColor, - child: Column( - children: [ - const SizedBox(height: 16), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: ScrollableGridViewWidget( - products: products, crossAxisCount: crossAxisCount), - ), + create: (_) => AddDeviceTypeModelBloc() + ..add(InitializeDeviceTypeModel( + initialTags: spaceTagModels ?? [], + addedProducts: initialSelectedProducts ?? [], + )), + child: Builder( + builder: (context) => AlertDialog( + title: const Text('Add Devices'), + backgroundColor: ColorsManager.whiteColors, + content: BlocBuilder( + builder: (context, state) { + if (state is AddDeviceModelLoading) { + return const Center(child: CircularProgressIndicator()); + } + if (state is AddDeviceModelLoaded) { + return SingleChildScrollView( + child: Container( + width: size.width * 0.9, + height: size.height * 0.65, + color: ColorsManager.textFieldGreyColor, + child: Column( + children: [ + const SizedBox(height: 16), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: ScrollableGridViewWidget( + products: products, + crossAxisCount: crossAxisCount, + initialProductCounts: state.selectedProducts, + ), + ), + ), + ], ), - ], + ), + ); + } + return const SizedBox(); + }, + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CancelButton( + label: 'Cancel', + onPressed: () async { + Navigator.of(context).pop(); + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => CreateSpaceModelDialog( + products: products, + allTags: allTags, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + subspaceModels: subspaces, + tags: spaceTagModels, + ), + ), + ); + }, ), - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CancelButton( - label: 'Cancel', - onPressed: () async { - Navigator.of(context).pop(); - await showDialog( + ActionButton( + label: 'Continue', + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: ColorsManager.whiteColors, + onPressed: () async { + final state = context.read().state; + if (state is AddDeviceModelLoaded && + state.selectedProducts.isNotEmpty) { + final initialTags = generateInitialTags( + spaceTagModels: spaceTagModels, + subspaces: subspaces, + ); + + final dialogTitle = initialTags.isNotEmpty + ? 'Edit Device' + : 'Assign Tags'; + await showDialog( barrierDismissible: false, context: context, - builder: (context) => CreateSpaceModelDialog( - products: products, - allTags: allTags, - spaceModel: SpaceTemplateModel( - modelName: spaceName, - subspaceModels: subspaces, - tags: spaceTagModels, - )), - ); - }, - ), - ActionButton( - label: 'Continue', - backgroundColor: ColorsManager.secondaryColor, - foregroundColor: ColorsManager.whiteColors, - onPressed: () async { - final currentState = - context.read().state; - Navigator.of(context).pop(); - - if (currentState.isNotEmpty) { - final initialTags = generateInitialTags( - spaceTagModels: spaceTagModels, + builder: (context) => AssignTagModelsDialog( + products: products, subspaces: subspaces, - ); - - final dialogTitle = initialTags.isNotEmpty - ? 'Edit Device' - : 'Assign Tags'; - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AssignTagModelsDialog( - products: products, - subspaces: subspaces, - addedProducts: currentState, - allTags: allTags, - spaceName: spaceName, - initialTags: initialTags, - title: dialogTitle, - ), - ); - } - }, - ), - ], - ), - ], - ), - )); + addedProducts: state.selectedProducts, + allTags: allTags, + spaceName: spaceName, + initialTags: state.initialTag, + title: dialogTitle, + ), + ); + } + }, + ), + ], + ), + ], + ), + ), + ); } List generateInitialTags({ diff --git a/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart index 2a653dde..3e32ccd8 100644 --- a/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart'; class ScrollableGridViewWidget extends StatelessWidget { @@ -24,8 +25,12 @@ class ScrollableGridViewWidget extends StatelessWidget { return Scrollbar( controller: scrollController, thumbVisibility: true, - child: BlocBuilder>( - builder: (context, productCounts) { + child: BlocBuilder( + builder: (context, state) { + final productCounts = state is AddDeviceModelLoaded + ? state.selectedProducts + : []; + return GridView.builder( controller: scrollController, shrinkWrap: true, From 7109421358fae255e0d7477c2510f027302c5c04 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 17 Jan 2025 23:51:56 +0400 Subject: [PATCH 090/106] fixed first time flow --- .../widgets/tag_chips_display_widget.dart | 4 +-- .../views/add_device_type_model_widget.dart | 25 ++++++++----------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 799eb71b..21283ca4 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -88,6 +88,7 @@ class TagChipDisplay extends StatelessWidget { barrierDismissible: false, context: context, builder: (context) => AddDeviceTypeModelWidget( + isCreate: false, products: products, subspaces: subspaces, allTags: allTags, @@ -121,8 +122,6 @@ class TagChipDisplay extends StatelessWidget { ) : TextButton( onPressed: () async { - Navigator.of(context).pop(); - final result = await showDialog( barrierDismissible: false, context: context, @@ -131,6 +130,7 @@ class TagChipDisplay extends StatelessWidget { subspaces: subspaces, allTags: allTags, spaceName: spaceNameController.text, + isCreate: true, ), ); if (result == true) {} diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index ea0a94a6..dbc4d6ea 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -22,6 +22,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? spaceTagModels; final List? allTags; final String spaceName; + final bool isCreate; const AddDeviceTypeModelWidget({ super.key, @@ -31,6 +32,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { this.allTags, this.spaceTagModels, required this.spaceName, + required this.isCreate, }); @override @@ -68,7 +70,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { const SizedBox(height: 16), Expanded( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), + padding: + const EdgeInsets.symmetric(horizontal: 20.0), child: ScrollableGridViewWidget( products: products, crossAxisCount: crossAxisCount, @@ -91,20 +94,9 @@ class AddDeviceTypeModelWidget extends StatelessWidget { CancelButton( label: 'Cancel', onPressed: () async { - Navigator.of(context).pop(); - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => CreateSpaceModelDialog( - products: products, - allTags: allTags, - spaceModel: SpaceTemplateModel( - modelName: spaceName, - subspaceModels: subspaces, - tags: spaceTagModels, - ), - ), - ); + if (isCreate) { + Navigator.of(context).pop(); + } }, ), ActionButton( @@ -119,6 +111,9 @@ class AddDeviceTypeModelWidget extends StatelessWidget { spaceTagModels: spaceTagModels, subspaces: subspaces, ); + if (isCreate) { + Navigator.of(context).pop(); + } final dialogTitle = initialTags.isNotEmpty ? 'Edit Device' From ec5b7d4395ccc4e0570e674a43ba682e78b49d4d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 17 Jan 2025 23:53:43 +0400 Subject: [PATCH 091/106] changed button name --- .../views/assign_tag_models_dialog.dart | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index b84c32e5..d4015a96 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -223,21 +223,9 @@ class AssignTagModelsDialog extends StatelessWidget { const SizedBox(width: 10), Expanded( child: CancelButton( - label: 'Cancel', + label: 'Add New Device', onPressed: () async { Navigator.of(context).pop(); - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => CreateSpaceModelDialog( - products: products, - allTags: allTags, - spaceModel: SpaceTemplateModel( - modelName: spaceName, - subspaceModels: subspaces, - tags: initialTags), - ), - ); }, ), ), From 440263e2f94b40c64ca6c21d8d236d3501676e7e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sat, 18 Jan 2025 00:32:48 +0400 Subject: [PATCH 092/106] added initial flow --- .../views/assign_tag_models_dialog.dart | 35 +++--- .../bloc/create_space_model_bloc.dart | 18 ++++ .../bloc/create_space_model_event.dart | 8 ++ .../models/space_template_model.dart | 2 +- .../dialog/create_space_model_dialog.dart | 29 +++-- .../widgets/tag_chips_display_widget.dart | 42 ++++---- .../views/add_device_type_model_widget.dart | 101 +++++++++++------- 7 files changed, 152 insertions(+), 83 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index d4015a96..e6242c5e 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -9,10 +9,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_pr import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class AssignTagModelsDialog extends StatelessWidget { @@ -24,6 +22,8 @@ class AssignTagModelsDialog extends StatelessWidget { final List? allTags; final String spaceName; final String title; + final void Function( + List? tags, List? subspaces)? onUpdate; const AssignTagModelsDialog( {Key? key, @@ -34,7 +34,8 @@ class AssignTagModelsDialog extends StatelessWidget { this.onTagsAssigned, this.allTags, required this.spaceName, - required this.title}) + required this.title, + this.onUpdate}) : super(key: key); @override @@ -226,6 +227,21 @@ class AssignTagModelsDialog extends StatelessWidget { label: 'Add New Device', onPressed: () async { Navigator.of(context).pop(); + final assignedTags = {}; + for (var tag in state.tags) { + if (tag.location == null || subspaces == null) { + continue; + } + for (var subspace in subspaces!) { + if (tag.location == subspace.subspaceName) { + subspace.tags ??= []; + subspace.tags!.add(tag); + assignedTags.add(tag); + break; + } + } + } + state.tags.removeWhere(assignedTags.contains); }, ), ), @@ -256,18 +272,7 @@ class AssignTagModelsDialog extends StatelessWidget { } } state.tags.removeWhere(assignedTags.contains); - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => CreateSpaceModelDialog( - products: products, - allTags: allTags, - spaceModel: SpaceTemplateModel( - modelName: spaceName, - subspaceModels: subspaces, - tags: state.tags), - ), - ); + onUpdate!(state.tags,subspaces); } : null, child: const Text('Save'), diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 8055e217..ec17482f 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -84,6 +84,24 @@ class CreateSpaceModelBloc } }); + + + on((event, emit) { + final currentState = state; + + if (currentState is CreateSpaceModelLoaded) { + final updatedTags = currentState.space.copyWith( + tags: [ + ...(_space!.tags ?? []), + ...event.tags, + ], + ); + emit(CreateSpaceModelLoaded(updatedTags)); + } else { + emit(CreateSpaceModelError("Space template not initialized")); + } + }); + on((event, emit) { final currentState = state; if (currentState is CreateSpaceModelLoaded) { diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index 1d7f6012..2bcb12b6 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -1,6 +1,7 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; abstract class CreateSpaceModelEvent extends Equatable { const CreateSpaceModelEvent(); @@ -46,6 +47,13 @@ class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { AddSubspacesToSpaceTemplate(this.subspaces); } +class AddTagsToSpaceTemplate extends CreateSpaceModelEvent { + final List tags; + + AddTagsToSpaceTemplate(this.tags); +} + + class ValidateSpaceTemplateName extends CreateSpaceModelEvent { final String name; diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart index 4f762c9a..84f568a5 100644 --- a/lib/pages/spaces_management/space_model/models/space_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -13,7 +13,7 @@ class SpaceTemplateModel extends Equatable { String internalId; @override - List get props => [modelName, subspaceModels]; + List get props => [modelName, subspaceModels, tags]; SpaceTemplateModel({ this.uuid, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 98c82f2a..1e2fc517 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -118,13 +118,28 @@ class CreateSpaceModelDialog extends StatelessWidget { }, ), const SizedBox(height: 10), - TagChipDisplay(context, - screenWidth: screenWidth, - spaceModel: updatedSpaceModel, - products: products, - subspaces: subspaces, - allTags: allTags, - spaceNameController: spaceNameController), + TagChipDisplay( + context, + screenWidth: screenWidth, + spaceModel: updatedSpaceModel, + products: products, + subspaces: subspaces, + allTags: allTags, + spaceNameController: spaceNameController, + onLoad: (tags, subspaces) { + if(subspaces!=null){ + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + if(tags!=null){ + context + .read() + .add(AddTagsToSpaceTemplate(tags)); + + } + }, + ), const SizedBox(height: 20), SizedBox( width: screenWidth * 0.25, diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 21283ca4..d83db22a 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -16,17 +16,19 @@ class TagChipDisplay extends StatelessWidget { final List? subspaces; final List? allTags; final TextEditingController spaceNameController; + final void Function( + List? tags, List? subspaces)? onLoad; - const TagChipDisplay( - BuildContext context, { - Key? key, - required this.screenWidth, - required this.spaceModel, - required this.products, - required this.subspaces, - required this.allTags, - required this.spaceNameController, - }) : super(key: key); + const TagChipDisplay(BuildContext context, + {Key? key, + required this.screenWidth, + required this.spaceModel, + required this.products, + required this.subspaces, + required this.allTags, + required this.spaceNameController, + this.onLoad}) + : super(key: key); @override Widget build(BuildContext context) { @@ -104,15 +106,12 @@ class TagChipDisplay extends StatelessWidget { child: Chip( label: const Text( 'Edit', - style: TextStyle( - color: ColorsManager.spaceColor), + style: TextStyle(color: ColorsManager.spaceColor), ), - backgroundColor: - ColorsManager.whiteColors, + backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: const BorderSide( - color: ColorsManager.spaceColor), + borderRadius: BorderRadius.circular(16), + side: const BorderSide(color: ColorsManager.spaceColor), ), ), ), @@ -122,7 +121,7 @@ class TagChipDisplay extends StatelessWidget { ) : TextButton( onPressed: () async { - final result = await showDialog( + await showDialog( barrierDismissible: false, context: context, builder: (context) => AddDeviceTypeModelWidget( @@ -131,9 +130,13 @@ class TagChipDisplay extends StatelessWidget { allTags: allTags, spaceName: spaceNameController.text, isCreate: true, + onLoad: (tags, subspaces) { + if (onLoad != null) { + onLoad!(tags, subspaces); + } + }, ), ); - if (result == true) {} }, style: TextButton.styleFrom( padding: EdgeInsets.zero, @@ -189,5 +192,4 @@ class TagChipDisplay extends StatelessWidget { )) .toList(); } - } diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index dbc4d6ea..8ed903b1 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -1,17 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart'; -import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -23,6 +21,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? allTags; final String spaceName; final bool isCreate; + final void Function( + List? tags, List? subspaces)? onLoad; const AddDeviceTypeModelWidget({ super.key, @@ -33,6 +33,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { this.spaceTagModels, required this.spaceName, required this.isCreate, + this.onLoad, }); @override @@ -94,46 +95,66 @@ class AddDeviceTypeModelWidget extends StatelessWidget { CancelButton( label: 'Cancel', onPressed: () async { - if (isCreate) { - Navigator.of(context).pop(); - } - }, - ), - ActionButton( - label: 'Continue', - backgroundColor: ColorsManager.secondaryColor, - foregroundColor: ColorsManager.whiteColors, - onPressed: () async { - final state = context.read().state; - if (state is AddDeviceModelLoaded && - state.selectedProducts.isNotEmpty) { - final initialTags = generateInitialTags( - spaceTagModels: spaceTagModels, - subspaces: subspaces, - ); - if (isCreate) { - Navigator.of(context).pop(); - } - - final dialogTitle = initialTags.isNotEmpty - ? 'Edit Device' - : 'Assign Tags'; - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AssignTagModelsDialog( - products: products, - subspaces: subspaces, - addedProducts: state.selectedProducts, - allTags: allTags, - spaceName: spaceName, - initialTags: state.initialTag, - title: dialogTitle, - ), - ); + if (isCreate) { + Navigator.of(context).pop(); } }, ), + SizedBox( + width: 140, + child: + BlocBuilder( + builder: (context, state) { + final isDisabled = state is AddDeviceModelLoaded && + state.selectedProducts.isEmpty; + + return DefaultButton( + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: isDisabled + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, + borderRadius: 10, + onPressed: isDisabled + ? null // Disable the button + : () async { + if (state is AddDeviceModelLoaded && + state.selectedProducts.isNotEmpty) { + final initialTags = generateInitialTags( + spaceTagModels: spaceTagModels, + subspaces: subspaces, + ); + if (isCreate) { + Navigator.of(context).pop(); + } + + final dialogTitle = initialTags.isNotEmpty + ? 'Edit Device' + : 'Assign Tags'; + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AssignTagModelsDialog( + products: products, + subspaces: subspaces, + addedProducts: state.selectedProducts, + allTags: allTags, + spaceName: spaceName, + initialTags: state.initialTag, + title: dialogTitle, + onUpdate: (tags, subspaces) { + if (onLoad != null) { + onLoad!(tags, subspaces); + } + }, + ), + ); + } + }, + child: const Text('Next'), + ); + }, + ), + ), ], ), ], From fe680d15f20a893748db580fd81dd35752b73213 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 20 Jan 2025 10:28:46 +0400 Subject: [PATCH 093/106] edit and create --- .../space_model/view/space_model_page.dart | 30 ++++++++++++++----- .../dialog/create_space_model_dialog.dart | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index eab43e08..d0b5a300 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -67,10 +67,24 @@ class SpaceModelPage extends StatelessWidget { } // Render existing space model final model = spaceModels[index]; - return Container( - margin: const EdgeInsets.all(8.0), - child: SpaceModelCardWidget(model:model), - ); + return GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return CreateSpaceModelDialog( + products: products, + allTags: allTagValues, + spaceModel: model, + onLoad: (newModel) {}, + ); + }, + ); + }, + child: Container( + margin: const EdgeInsets.all(8.0), + child: SpaceModelCardWidget(model: model), + )); }, ), ), @@ -94,14 +108,14 @@ class SpaceModelPage extends StatelessWidget { double _calculateChildAspectRatio(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; if (screenWidth > 1600) { - return 2; + return 2; } if (screenWidth > 1200) { - return 3; + return 3; } else if (screenWidth > 800) { - return 3.5; + return 3.5; } else { - return 4.0; + return 4.0; } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 1e2fc517..e5dea2b9 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -72,7 +72,7 @@ class CreateSpaceModelDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - 'Create New Space Model', + spaceModel?.uuid == null ? 'Create New Space Model': 'Edit Space Model', style: Theme.of(context) .textTheme .headlineLarge From 2f6bd31aa2c65b7769fd24b033310ae69f35e185 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 20 Jan 2025 10:52:23 +0400 Subject: [PATCH 094/106] helper class --- .../spaces_management/helper/tag_helper.dart | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 lib/pages/spaces_management/helper/tag_helper.dart diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart new file mode 100644 index 00000000..ed4525ae --- /dev/null +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -0,0 +1,76 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; + +class TagHelper { + static List generateInitialTags({ + List? spaceTagModels, + List? subspaces, + }) { + final List initialTags = []; + + if (spaceTagModels != null) { + initialTags.addAll(spaceTagModels); + } + + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + initialTags.addAll( + subspace.tags!.map( + (tag) => tag.copyWith(location: subspace.subspaceName), + ), + ); + } + } + } + + return initialTags; + } + + static Map groupTags(List tags) { + final Map groupedTags = {}; + for (var tag in tags) { + if (tag.product != null) { + groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1; + } + } + return groupedTags; + } + + static List createInitialSelectedProducts( + List? tags, List? subspaces) { + final Map productCounts = {}; + + if (tags != null) { + for (var tag in tags) { + if (tag.product != null) { + productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1; + } + } + } + + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + for (var tag in subspace.tags!) { + if (tag.product != null) { + productCounts[tag.product!] = + (productCounts[tag.product!] ?? 0) + 1; + } + } + } + } + } + + return productCounts.entries + .map((entry) => SelectedProduct( + productId: entry.key.uuid, + count: entry.value, + productName: entry.key.name ?? 'Unnamed', + product: entry.key, + )) + .toList(); + } +} From eb53671e3a00a56a7d2df69058176dbbd3c4cc97 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 20 Jan 2025 21:47:22 +0400 Subject: [PATCH 095/106] edit flow --- .../assign_tag/bloc/assign_tag_bloc.dart | 17 +- .../assign_tag/views/assign_tag_dialog.dart | 6 +- .../bloc/assign_tag_model_bloc.dart | 3 +- .../views/assign_tag_models_dialog.dart | 147 ++++++++++++++---- .../spaces_management/helper/tag_helper.dart | 16 +- .../bloc/create_space_model_bloc.dart | 102 +++++++++--- .../models/subspace_template_model.dart | 24 ++- .../space_model/models/tag_model.dart | 13 +- .../dialog/create_space_model_dialog.dart | 42 ++--- .../widgets/tag_chips_display_widget.dart | 98 ++++-------- .../views/add_device_type_model_widget.dart | 19 ++- 11 files changed, 315 insertions(+), 172 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart index 6adcc6a7..4a85348f 100644 --- a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart @@ -3,8 +3,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; -class AssignTagBloc - extends Bloc { +class AssignTagBloc extends Bloc { AssignTagBloc() : super(AssignTagInitial()) { on((event, emit) { final initialTags = event.initialTags ?? []; @@ -40,7 +39,7 @@ class AssignTagBloc (index) => Tag( tag: '', product: selectedProduct.product, - location: 'None', + location: 'Main Space', ), )); } @@ -55,8 +54,7 @@ class AssignTagBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagLoaded && - currentState.tags.isNotEmpty) { + if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); tags[event.index].tag = event.tag; emit(AssignTagLoaded( @@ -70,8 +68,7 @@ class AssignTagBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagLoaded && - currentState.tags.isNotEmpty) { + if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); // Use copyWith for immutability @@ -88,8 +85,7 @@ class AssignTagBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagLoaded && - currentState.tags.isNotEmpty) { + if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); emit(AssignTagLoaded( @@ -103,8 +99,7 @@ class AssignTagBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagLoaded && - currentState.tags.isNotEmpty) { + if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { final updatedTags = List.from(currentState.tags) ..remove(event.tagToDelete); diff --git a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart index 31f9bec1..959c83df 100644 --- a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart +++ b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart @@ -224,7 +224,7 @@ class AssignTagDialog extends StatelessWidget { DataCell( DropdownButtonHideUnderline( child: DropdownButton( - value: tag.location ?? 'None', + value: tag.location ?? 'Main', dropdownColor: ColorsManager .whiteColors, // Dropdown background style: const TextStyle( @@ -232,9 +232,9 @@ class AssignTagDialog extends StatelessWidget { .black), // Style for selected text items: [ const DropdownMenuItem( - value: 'None', + value: 'Main Space', child: Text( - 'None', + 'Main Space', style: TextStyle( color: ColorsManager .textPrimaryColor), diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index ce4a38c2..c8c4cea4 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -40,7 +40,7 @@ class AssignTagModelBloc (index) => TagModel( tag: '', product: selectedProduct.product, - location: 'None', + location: 'Main Space', ), )); } @@ -123,6 +123,7 @@ class AssignTagModelBloc bool _validateTags(List tags) { if (tags.isEmpty) { + print("tags empty"); return false; } final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index e6242c5e..82a721f0 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assig import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class AssignTagModelsDialog extends StatelessWidget { @@ -40,8 +41,11 @@ class AssignTagModelsDialog extends StatelessWidget { @override Widget build(BuildContext context) { - final List locations = - (subspaces ?? []).map((subspace) => subspace.subspaceName).toList(); + final List locations = (subspaces ?? []) + .map((subspace) => subspace.subspaceName) + .toList() + ..add('Main Space'); + return BlocProvider( create: (_) => AssignTagModelBloc() ..add(InitializeTagModels( @@ -173,7 +177,8 @@ class AssignTagModelsDialog extends StatelessWidget { width: double .infinity, // Ensure full width for dropdown child: DialogTextfieldDropdown( - items: availableTags ?? [], + items: availableTags, + initialValue: tag.tag, onSelected: (value) { controller.text = value; context @@ -223,26 +228,55 @@ class AssignTagModelsDialog extends StatelessWidget { children: [ const SizedBox(width: 10), Expanded( - child: CancelButton( - label: 'Add New Device', - onPressed: () async { - Navigator.of(context).pop(); - final assignedTags = {}; - for (var tag in state.tags) { - if (tag.location == null || subspaces == null) { - continue; - } - for (var subspace in subspaces!) { - if (tag.location == subspace.subspaceName) { - subspace.tags ??= []; - subspace.tags!.add(tag); - assignedTags.add(tag); - break; + child: Builder( + builder: (buttonContext) => CancelButton( + label: 'Add New Device', + onPressed: () async { + Navigator.of(context).pop(); + + for (var tag in state.tags) { + if (tag.location == null || subspaces == null) { + continue; } + + final previousTagSubspace = + checkTagExistInSubspace(tag, subspaces ?? []); + + if (tag.location == 'Main Space') { + removeTagFromSubspace(tag, previousTagSubspace); + } else if (tag.location != + previousTagSubspace?.subspaceName) { + removeTagFromSubspace(tag, previousTagSubspace); + moveToNewSubspace(tag, subspaces ?? []); + state.tags.removeWhere( + (t) => t.internalId == tag.internalId); + } else { + updateTagInSubspace(tag, previousTagSubspace); + state.tags.removeWhere( + (t) => t.internalId == tag.internalId); + } + + await showDialog( + barrierDismissible: false, + context: + Navigator.of(context, rootNavigator: true) + .context, + builder: (context) => AddDeviceTypeModelWidget( + products: products, + subspaces: subspaces, + isCreate: false, + initialSelectedProducts: addedProducts, + allTags: allTags, + spaceName: spaceName, + spaceTagModels: state.tags, + onUpdate: (tags, subspaces) { + onUpdate?.call(state.tags, subspaces); + }, + ), + ); } - } - state.tags.removeWhere(assignedTags.contains); - }, + }, + ), ), ), const SizedBox(width: 10), @@ -256,23 +290,38 @@ class AssignTagModelsDialog extends StatelessWidget { onPressed: state.isSaveEnabled ? () async { Navigator.of(context).pop(); - final assignedTags = {}; + for (var tag in state.tags) { if (tag.location == null || subspaces == null) { continue; } - for (var subspace in subspaces!) { - if (tag.location == subspace.subspaceName) { - subspace.tags ??= []; - subspace.tags!.add(tag); - assignedTags.add(tag); - break; - } + + final previousTagSubspace = + checkTagExistInSubspace( + tag, subspaces ?? []); + + if (tag.location == 'Main Space') { + removeTagFromSubspace( + tag, previousTagSubspace); + } else if (tag.location != + previousTagSubspace?.subspaceName) { + removeTagFromSubspace( + tag, previousTagSubspace); + moveToNewSubspace(tag, subspaces ?? []); + state.tags.removeWhere( + (t) => t.internalId == tag.internalId); + } else { + updateTagInSubspace( + tag, previousTagSubspace); + state.tags.removeWhere( + (t) => t.internalId == tag.internalId); } } - state.tags.removeWhere(assignedTags.contains); - onUpdate!(state.tags,subspaces); + print("tryinh yo save"); + + onUpdate?.call(state.tags, subspaces); + } : null, child: const Text('Save'), @@ -302,4 +351,40 @@ class AssignTagModelsDialog extends StatelessWidget { .contains(tagValue)) .toList(); } + + void removeTagFromSubspace(TagModel tag, SubspaceTemplateModel? subspace) { + subspace?.tags?.removeWhere((t) => t.internalId == tag.internalId); + } + + SubspaceTemplateModel? checkTagExistInSubspace( + TagModel tag, List? subspaces) { + if (subspaces == null) return null; + for (var subspace in subspaces) { + if (subspace.tags == null) return null; + for (var t in subspace.tags!) { + if (tag.internalId == t.internalId) return subspace; + } + } + return null; + } + + void moveToNewSubspace(TagModel tag, List subspaces) { + final targetSubspace = subspaces + .firstWhere((subspace) => subspace.subspaceName == tag.location); + + targetSubspace.tags ??= []; + if (targetSubspace.tags?.any((t) => t.internalId == tag.internalId) != + true) { + targetSubspace.tags?.add(tag); + } + } + + void updateTagInSubspace(TagModel tag, SubspaceTemplateModel? subspace) { + final currentTag = subspace?.tags?.firstWhere( + (t) => t.internalId == tag.internalId, + ); + if (currentTag != null) { + currentTag.tag = tag.tag; + } + } } diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index ed4525ae..bfff02a9 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -17,15 +17,19 @@ class TagHelper { if (subspaces != null) { for (var subspace in subspaces) { if (subspace.tags != null) { - initialTags.addAll( - subspace.tags!.map( - (tag) => tag.copyWith(location: subspace.subspaceName), - ), - ); + for (var existingTag in subspace.tags!) { + initialTags.addAll( + subspace.tags!.map( + (tag) => tag.copyWith( + location: subspace.subspaceName, + internalId: existingTag.internalId, + tag: existingTag.tag), + ), + ); + } } } } - return initialTags; } diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index ec17482f..6f2f2018 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -3,6 +3,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; @@ -67,36 +68,102 @@ class CreateSpaceModelBloc _space = event.spaceTemplate; emit(CreateSpaceModelLoaded(_space!)); }); - on((event, emit) { final currentState = state; if (currentState is CreateSpaceModelLoaded) { - final updatedSpace = currentState.space.copyWith( - subspaceModels: [ - ...(_space!.subspaceModels ?? []), - ...event.subspaces, - ], - ); + final eventSubspaceIds = + event.subspaces.map((e) => e.internalId).toSet(); + + // Update or retain subspaces + final updatedSubspaces = currentState.space.subspaceModels + ?.where((subspace) => + eventSubspaceIds.contains(subspace.internalId)) + .map((subspace) { + final matchingEventSubspace = event.subspaces.firstWhere( + (e) => e.internalId == subspace.internalId, + orElse: () => subspace, + ); + + // Update the subspace's tags + final eventTagIds = matchingEventSubspace.tags + ?.map((e) => e.internalId) + .toSet() ?? + {}; + + final updatedTags = [ + ...?subspace.tags?.map((tag) { + final matchingTag = + matchingEventSubspace.tags?.firstWhere( + (e) => e.internalId == tag.internalId, + orElse: () => tag, + ); + final isUpdated = matchingTag != tag; + return isUpdated + ? tag.copyWith(tag: matchingTag?.tag) + : tag; + }) ?? + [], + ...?matchingEventSubspace.tags?.where( + (e) => + subspace.tags + ?.every((t) => t.internalId != e.internalId) ?? + true, + ) ?? + [], + ]; + return subspace.copyWith( + subspaceName: matchingEventSubspace.subspaceName, + tags: updatedTags, + ); + }).toList() ?? + []; + + // Add new subspaces + event.subspaces + .where((e) => + updatedSubspaces.every((s) => s.internalId != e.internalId)) + .forEach((newSubspace) { + updatedSubspaces.add(newSubspace); + }); + + final updatedSpace = + currentState.space.copyWith(subspaceModels: updatedSubspaces); + emit(CreateSpaceModelLoaded(updatedSpace)); } else { emit(CreateSpaceModelError("Space template not initialized")); } }); - - on((event, emit) { final currentState = state; if (currentState is CreateSpaceModelLoaded) { - final updatedTags = currentState.space.copyWith( - tags: [ - ...(_space!.tags ?? []), - ...event.tags, - ], - ); - emit(CreateSpaceModelLoaded(updatedTags)); + final eventTagIds = event.tags.map((e) => e.internalId).toSet(); + + final updatedTags = currentState.space.tags + ?.where((tag) => eventTagIds.contains(tag.internalId)) + .map((tag) { + final matchingEventTag = event.tags.firstWhere( + (e) => e.internalId == tag.internalId, + orElse: () => tag, + ); + return matchingEventTag != tag + ? tag.copyWith(tag: matchingEventTag.tag) + : tag; + }).toList() ?? + []; + + event.tags + .where( + (e) => updatedTags.every((t) => t.internalId != e.internalId)) + .forEach((e) { + updatedTags.add(e); + }); + + emit(CreateSpaceModelLoaded( + currentState.space.copyWith(tags: updatedTags))); } else { emit(CreateSpaceModelError("Space template not initialized")); } @@ -106,7 +173,6 @@ class CreateSpaceModelBloc final currentState = state; if (currentState is CreateSpaceModelLoaded) { if (event.name.trim().isEmpty) { - emit(CreateSpaceModelLoaded( currentState.space, errorMessage: "Model name cannot be empty", @@ -114,7 +180,7 @@ class CreateSpaceModelBloc } else { final updatedSpaceModel = currentState.space.copyWith(modelName: event.name); - + emit(CreateSpaceModelLoaded(updatedSpaceModel)); } } else { diff --git a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart index ac71c6b1..6c73741b 100644 --- a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart @@ -1,22 +1,28 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:uuid/uuid.dart'; class SubspaceTemplateModel { final String? uuid; String subspaceName; final bool disabled; List? tags; + String internalId; SubspaceTemplateModel({ this.uuid, required this.subspaceName, required this.disabled, this.tags, - }); + String? internalId, + }) : internalId = internalId ?? const Uuid().v4(); factory SubspaceTemplateModel.fromJson(Map json) { + final String internalId = json['internalId'] ?? const Uuid().v4(); + return SubspaceTemplateModel( uuid: json['uuid'] ?? '', subspaceName: json['subspaceName'] ?? '', + internalId: internalId, disabled: json['disabled'] ?? false, tags: (json['tags'] as List?) ?.map((item) => TagModel.fromJson(item)) @@ -33,4 +39,20 @@ class SubspaceTemplateModel { 'tags': tags?.map((e) => e.toJson()).toList() ?? [], }; } + + SubspaceTemplateModel copyWith({ + String? uuid, + String? subspaceName, + bool? disabled, + List? tags, + String? internalId, + }) { + return SubspaceTemplateModel( + uuid: uuid ?? this.uuid, + subspaceName: subspaceName ?? this.subspaceName, + disabled: disabled ?? this.disabled, + tags: tags ?? this.tags, + internalId: internalId ?? this.internalId, + ); + } } diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index 99008e76..48f89167 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -30,15 +30,16 @@ class TagModel { ); } - TagModel copyWith({ - String? tag, - ProductModel? product, - String? location, - }) { + TagModel copyWith( + {String? tag, + ProductModel? product, + String? location, + String? internalId}) { return TagModel( tag: tag ?? this.tag, product: product ?? this.product, location: location ?? this.location, + internalId: internalId ?? this.internalId, ); } @@ -58,4 +59,4 @@ extension TagModelExtensions on TagModel { ..tag = tag ?? '' ..productUuid = product?.uuid; } -} \ No newline at end of file +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index e5dea2b9..e671410c 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -44,15 +44,15 @@ class CreateSpaceModelDialog extends StatelessWidget { child: BlocProvider( create: (_) { final bloc = CreateSpaceModelBloc(_spaceModelApi); - if (spaceModel != null) { - bloc.add(UpdateSpaceTemplate(spaceModel!)); - } else { - bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( - modelName: '', - subspaceModels: const [], - ))); - } - + if (spaceModel != null) { + bloc.add(UpdateSpaceTemplate(spaceModel!)); + } else { + bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( + modelName: '', + subspaceModels: const [], + ))); + } + spaceNameController.addListener(() { bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text)); }); @@ -72,7 +72,9 @@ class CreateSpaceModelDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - spaceModel?.uuid == null ? 'Create New Space Model': 'Edit Space Model', + spaceModel?.uuid == null + ? 'Create New Space Model' + : 'Edit Space Model', style: Theme.of(context) .textTheme .headlineLarge @@ -127,16 +129,18 @@ class CreateSpaceModelDialog extends StatelessWidget { allTags: allTags, spaceNameController: spaceNameController, onLoad: (tags, subspaces) { - if(subspaces!=null){ - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } - if(tags!=null){ + if (context.read().state + is CreateSpaceModelLoaded) { + if (subspaces != null) { context - .read() - .add(AddTagsToSpaceTemplate(tags)); - + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + if (tags != null) { + context + .read() + .add(AddTagsToSpaceTemplate(tags)); + } } }, ), diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index d83db22a..1f7e0d40 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; @@ -53,7 +54,7 @@ class TagChipDisplay extends StatelessWidget { runSpacing: 8.0, children: [ // Combine tags from spaceModel and subspaces - ..._groupTags([ + ...TagHelper.groupTags([ ...?spaceModel?.tags, ...?spaceModel?.subspaceModels ?.expand((subspace) => subspace.tags ?? []) @@ -84,24 +85,32 @@ class TagChipDisplay extends StatelessWidget { ), GestureDetector( onTap: () async { - Navigator.of(context).pop(); + // Use the Navigator's context for showDialog + final navigatorContext = + Navigator.of(context).overlay?.context; - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AddDeviceTypeModelWidget( - isCreate: false, - products: products, - subspaces: subspaces, - allTags: allTags, - spaceName: spaceNameController.text, - spaceTagModels: spaceModel?.tags, - initialSelectedProducts: - _createInitialSelectedProducts( - spaceModel?.tags, spaceModel?.subspaceModels), - ), - ); - // Edit action + if (navigatorContext != null) { + await showDialog( + barrierDismissible: false, + context: navigatorContext, + builder: (context) => AssignTagModelsDialog( + products: products, + subspaces: subspaces, + allTags: allTags, + initialTags: TagHelper.generateInitialTags( + subspaces: subspaces, + spaceTagModels: spaceModel?.tags ?? []), + title: 'Edit Device', + addedProducts: + TagHelper.createInitialSelectedProducts( + spaceModel?.tags ?? [], subspaces), + spaceName: spaceModel?.modelName ?? '', + onUpdate: (tags, subspaces){ + print("here"); + onLoad?.call(tags, subspaces);} + ), + ); + } }, child: Chip( label: const Text( @@ -130,11 +139,7 @@ class TagChipDisplay extends StatelessWidget { allTags: allTags, spaceName: spaceNameController.text, isCreate: true, - onLoad: (tags, subspaces) { - if (onLoad != null) { - onLoad!(tags, subspaces); - } - }, + onLoad: (tags, subspaces) => onLoad?.call(tags, subspaces), ), ); }, @@ -147,49 +152,4 @@ class TagChipDisplay extends StatelessWidget { ), ); } - - Map _groupTags(List tags) { - final Map groupedTags = {}; - for (var tag in tags) { - if (tag.product != null) { - groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1; - } - } - return groupedTags; - } - - List _createInitialSelectedProducts( - List? tags, List? subspaces) { - final Map productCounts = {}; - - if (tags != null) { - for (var tag in tags) { - if (tag.product != null) { - productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1; - } - } - } - - if (subspaces != null) { - for (var subspace in subspaces) { - if (subspace.tags != null) { - for (var tag in subspace.tags!) { - if (tag.product != null) { - productCounts[tag.product!] = - (productCounts[tag.product!] ?? 0) + 1; - } - } - } - } - } - - return productCounts.entries - .map((entry) => SelectedProduct( - productId: entry.key.uuid, - count: entry.value, - productName: entry.key.name ?? 'Unnamed', - product: entry.key, - )) - .toList(); - } } diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 8ed903b1..e324fa06 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -23,6 +23,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final bool isCreate; final void Function( List? tags, List? subspaces)? onLoad; + final void Function( + List? tags, List? subspaces)? onUpdate; const AddDeviceTypeModelWidget({ super.key, @@ -34,10 +36,18 @@ class AddDeviceTypeModelWidget extends StatelessWidget { required this.spaceName, required this.isCreate, this.onLoad, + this.onUpdate, }); @override Widget build(BuildContext context) { + + if (spaceTagModels != null) { + for (var tag in spaceTagModels!) { + print(tag.tag); + } + } + final size = MediaQuery.of(context).size; final crossAxisCount = size.width > 1200 ? 8 @@ -123,15 +133,12 @@ class AddDeviceTypeModelWidget extends StatelessWidget { spaceTagModels: spaceTagModels, subspaces: subspaces, ); - if (isCreate) { - Navigator.of(context).pop(); - } final dialogTitle = initialTags.isNotEmpty ? 'Edit Device' : 'Assign Tags'; + Navigator.of(context).pop(); await showDialog( - barrierDismissible: false, context: context, builder: (context) => AssignTagModelsDialog( products: products, @@ -142,9 +149,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { initialTags: state.initialTag, title: dialogTitle, onUpdate: (tags, subspaces) { - if (onLoad != null) { - onLoad!(tags, subspaces); - } + onLoad?.call(tags, subspaces); }, ), ); From 0e912207e533746a7cf57863b687e727e99a0cde Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 21 Jan 2025 11:41:53 +0400 Subject: [PATCH 096/106] flow --- .../bloc/assign_tag_model_bloc.dart | 1 - .../views/assign_tag_models_dialog.dart | 17 ++++----- .../widgets/tag_chips_display_widget.dart | 36 ++++++++++--------- .../views/add_device_type_model_widget.dart | 9 ++--- 4 files changed, 29 insertions(+), 34 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index c8c4cea4..b5251868 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -123,7 +123,6 @@ class AssignTagModelBloc bool _validateTags(List tags) { if (tags.isEmpty) { - print("tags empty"); return false; } final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 82a721f0..a6b6b7f0 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -232,8 +232,6 @@ class AssignTagModelsDialog extends StatelessWidget { builder: (buttonContext) => CancelButton( label: 'Add New Device', onPressed: () async { - Navigator.of(context).pop(); - for (var tag in state.tags) { if (tag.location == null || subspaces == null) { continue; @@ -255,13 +253,13 @@ class AssignTagModelsDialog extends StatelessWidget { state.tags.removeWhere( (t) => t.internalId == tag.internalId); } - + } + if (context.mounted) { await showDialog( barrierDismissible: false, - context: - Navigator.of(context, rootNavigator: true) - .context, - builder: (context) => AddDeviceTypeModelWidget( + context: context, + builder: (dialogContext) => + AddDeviceTypeModelWidget( products: products, subspaces: subspaces, isCreate: false, @@ -271,6 +269,7 @@ class AssignTagModelsDialog extends StatelessWidget { spaceTagModels: state.tags, onUpdate: (tags, subspaces) { onUpdate?.call(state.tags, subspaces); + Navigator.of(context).pop(); }, ), ); @@ -318,10 +317,8 @@ class AssignTagModelsDialog extends StatelessWidget { (t) => t.internalId == tag.internalId); } } - print("tryinh yo save"); - + onUpdate?.call(state.tags, subspaces); - } : null, child: const Text('Save'), diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 1f7e0d40..18efd501 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -94,21 +94,20 @@ class TagChipDisplay extends StatelessWidget { barrierDismissible: false, context: navigatorContext, builder: (context) => AssignTagModelsDialog( - products: products, - subspaces: subspaces, - allTags: allTags, - initialTags: TagHelper.generateInitialTags( - subspaces: subspaces, - spaceTagModels: spaceModel?.tags ?? []), - title: 'Edit Device', - addedProducts: - TagHelper.createInitialSelectedProducts( - spaceModel?.tags ?? [], subspaces), - spaceName: spaceModel?.modelName ?? '', - onUpdate: (tags, subspaces){ - print("here"); - onLoad?.call(tags, subspaces);} - ), + products: products, + subspaces: subspaces, + allTags: allTags, + initialTags: TagHelper.generateInitialTags( + subspaces: subspaces, + spaceTagModels: spaceModel?.tags ?? []), + title: 'Edit Device', + addedProducts: + TagHelper.createInitialSelectedProducts( + spaceModel?.tags ?? [], subspaces), + spaceName: spaceModel?.modelName ?? '', + onUpdate: (tags, subspaces) { + onLoad?.call(tags, subspaces); + }), ); } }, @@ -139,7 +138,12 @@ class TagChipDisplay extends StatelessWidget { allTags: allTags, spaceName: spaceNameController.text, isCreate: true, - onLoad: (tags, subspaces) => onLoad?.call(tags, subspaces), + onUpdate: (tags, subspaces) { + onLoad?.call(tags, subspaces); + }, + onLoad: (tags, subspaces) { + onLoad?.call(tags, subspaces); + }, ), ); }, diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index e324fa06..7944eb4f 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -41,12 +41,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { @override Widget build(BuildContext context) { - - if (spaceTagModels != null) { - for (var tag in spaceTagModels!) { - print(tag.tag); - } - } + final size = MediaQuery.of(context).size; final crossAxisCount = size.width > 1200 @@ -149,7 +144,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { initialTags: state.initialTag, title: dialogTitle, onUpdate: (tags, subspaces) { - onLoad?.call(tags, subspaces); + onUpdate?.call(tags, subspaces); }, ), ); From 81e9e58627cd4fa5166417feacf64fb12f9287d8 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 21 Jan 2025 15:21:25 +0400 Subject: [PATCH 097/106] fixed duplicate tag issue --- .../bloc/assign_tag_model_bloc.dart | 17 +- .../bloc/assign_tag_model_state.dart | 8 +- .../views/assign_tag_models_dialog.dart | 512 +++++++++--------- .../bloc/create_space_model_bloc.dart | 3 +- .../dialog/create_space_model_dialog.dart | 4 + 5 files changed, 283 insertions(+), 261 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index b5251868..92d7c0e1 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -47,14 +47,13 @@ class AssignTagModelBloc } emit(AssignTagModelLoaded( - tags: allTags, - isSaveEnabled: _validateTags(allTags), - )); + tags: allTags, + isSaveEnabled: _validateTags(allTags), + errorMessage: '')); }); on((event, emit) { final currentState = state; - if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); @@ -122,9 +121,7 @@ class AssignTagModelBloc } bool _validateTags(List tags) { - if (tags.isEmpty) { - return false; - } + final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); final isValid = uniqueTags.length == tags.length && !hasEmptyTag; @@ -133,7 +130,11 @@ class AssignTagModelBloc String? _getValidationError(List tags) { final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); - if (hasEmptyTag) return 'Tags cannot be empty.'; + if (hasEmptyTag) { + return 'Tags cannot be empty.'; + } + + // Check for duplicate tags final duplicateTags = tags .map((tag) => tag.tag?.trim() ?? '') .fold>({}, (map, tag) { diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart index 9812a293..a51a9e8f 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart @@ -5,7 +5,7 @@ abstract class AssignTagModelState extends Equatable { const AssignTagModelState(); @override - List get props => []; + List get props => []; } class AssignTagModelInitial extends AssignTagModelState {} @@ -15,7 +15,7 @@ class AssignTagModelLoading extends AssignTagModelState {} class AssignTagModelLoaded extends AssignTagModelState { final List tags; final bool isSaveEnabled; - final String? errorMessage; + final String? errorMessage; const AssignTagModelLoaded({ required this.tags, @@ -24,7 +24,7 @@ class AssignTagModelLoaded extends AssignTagModelState { }); @override - List get props => [tags, isSaveEnabled]; + List get props => [tags, isSaveEnabled, errorMessage]; } class AssignTagModelError extends AssignTagModelState { @@ -33,5 +33,5 @@ class AssignTagModelError extends AssignTagModelState { const AssignTagModelError(this.errorMessage); @override - List get props => [errorMessage]; + List get props => [errorMessage]; } diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index a6b6b7f0..3a2499f9 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -47,249 +47,202 @@ class AssignTagModelsDialog extends StatelessWidget { ..add('Main Space'); return BlocProvider( - create: (_) => AssignTagModelBloc() - ..add(InitializeTagModels( - initialTags: initialTags, - addedProducts: addedProducts, - )), - child: BlocBuilder( - builder: (context, state) { - if (state is AssignTagModelLoaded) { - final controllers = List.generate( - state.tags.length, - (index) => TextEditingController(text: state.tags[index].tag), - ); + create: (_) => AssignTagModelBloc() + ..add(InitializeTagModels( + initialTags: initialTags, + addedProducts: addedProducts, + )), + child: BlocListener( + listener: (context, state) {}, + child: BlocBuilder( + builder: (context, state) { + if (state is AssignTagModelLoaded) { + final controllers = List.generate( + state.tags.length, + (index) => TextEditingController(text: state.tags[index].tag), + ); - return AlertDialog( - title: Text(title), - backgroundColor: ColorsManager.whiteColors, - content: SingleChildScrollView( - child: Column( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(20), - child: DataTable( - headingRowColor: WidgetStateProperty.all( - ColorsManager.dataHeaderGrey), - border: TableBorder.all( - color: ColorsManager.dataHeaderGrey, - width: 1, + return AlertDialog( + title: Text(title), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Column( + children: [ + ClipRRect( borderRadius: BorderRadius.circular(20), - ), - columns: [ - DataColumn( - label: Text('#', - style: - Theme.of(context).textTheme.bodyMedium)), - DataColumn( - label: Text('Device', - style: - Theme.of(context).textTheme.bodyMedium)), - DataColumn( - numeric: false, - headingRowAlignment: MainAxisAlignment.start, - label: Text('Tag', - style: - Theme.of(context).textTheme.bodyMedium)), - DataColumn( - label: Text('Location', - style: - Theme.of(context).textTheme.bodyMedium)), - ], - rows: state.tags.isEmpty - ? [ - const DataRow(cells: [ - DataCell( - Center( - child: Text( - 'No Data Available', - style: TextStyle( - fontSize: 14, - color: ColorsManager.lightGrayColor, + child: DataTable( + headingRowColor: WidgetStateProperty.all( + ColorsManager.dataHeaderGrey), + border: TableBorder.all( + color: ColorsManager.dataHeaderGrey, + width: 1, + borderRadius: BorderRadius.circular(20), + ), + columns: [ + DataColumn( + label: Text('#', + style: Theme.of(context) + .textTheme + .bodyMedium)), + DataColumn( + label: Text('Device', + style: Theme.of(context) + .textTheme + .bodyMedium)), + DataColumn( + numeric: false, + headingRowAlignment: MainAxisAlignment.start, + label: Text('Tag', + style: Theme.of(context) + .textTheme + .bodyMedium)), + DataColumn( + label: Text('Location', + style: Theme.of(context) + .textTheme + .bodyMedium)), + ], + rows: state.tags.isEmpty + ? [ + const DataRow(cells: [ + DataCell( + Center( + child: Text( + 'No Data Available', + style: TextStyle( + fontSize: 14, + color: + ColorsManager.lightGrayColor, + ), + ), ), ), - ), - ), - DataCell(SizedBox()), - DataCell(SizedBox()), - DataCell(SizedBox()), - ]) - ] - : List.generate(state.tags.length, (index) { - final tag = state.tags[index]; - final controller = controllers[index]; - final availableTags = getAvailableTags( - allTags ?? [], state.tags, tag); + DataCell(SizedBox()), + DataCell(SizedBox()), + DataCell(SizedBox()), + ]) + ] + : List.generate(state.tags.length, (index) { + final tag = state.tags[index]; + final controller = controllers[index]; + final availableTags = getAvailableTags( + allTags ?? [], state.tags, tag); - return DataRow( - cells: [ - DataCell(Text(index.toString())), - DataCell( - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - tag.product?.name ?? 'Unknown', - overflow: TextOverflow.ellipsis, - )), - const SizedBox(width: 10), + return DataRow( + cells: [ + DataCell(Text(index.toString())), + DataCell( + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + tag.product?.name ?? 'Unknown', + overflow: TextOverflow.ellipsis, + )), + const SizedBox(width: 10), + Container( + width: 20.0, + height: 20.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager + .lightGrayColor, + width: 1.0, + ), + ), + child: IconButton( + icon: const Icon( + Icons.close, + color: ColorsManager + .lightGreyColor, + size: 16, + ), + onPressed: () { + context + .read< + AssignTagModelBloc>() + .add(DeleteTagModel( + tagToDelete: tag, + tags: state.tags)); + }, + tooltip: 'Delete Tag', + padding: EdgeInsets.zero, + constraints: + const BoxConstraints(), + ), + ), + ], + ), + ), + DataCell( Container( - width: 20.0, - height: 20.0, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: ColorsManager - .lightGrayColor, - width: 1.0, + alignment: Alignment + .centerLeft, // Align cell content to the left + child: SizedBox( + width: double + .infinity, // Ensure full width for dropdown + child: DialogTextfieldDropdown( + items: availableTags, + initialValue: tag.tag, + onSelected: (value) { + controller.text = value; + context + .read< + AssignTagModelBloc>() + .add(UpdateTag( + index: index, + tag: value, + )); + }, ), ), - child: IconButton( - icon: const Icon( - Icons.close, - color: ColorsManager - .lightGreyColor, - size: 16, - ), - onPressed: () { - context - .read() - .add(DeleteTagModel( - tagToDelete: tag, - tags: state.tags)); - }, - tooltip: 'Delete Tag', - padding: EdgeInsets.zero, - constraints: - const BoxConstraints(), - ), - ), - ], - ), - ), - DataCell( - Container( - alignment: Alignment - .centerLeft, // Align cell content to the left - child: SizedBox( - width: double - .infinity, // Ensure full width for dropdown - child: DialogTextfieldDropdown( - items: availableTags, - initialValue: tag.tag, - onSelected: (value) { - controller.text = value; - context - .read() - .add(UpdateTag( - index: index, - tag: value, - )); - }, ), ), - ), - ), - DataCell( - SizedBox( - width: double.infinity, - child: DialogDropdown( - items: locations, - selectedValue: - tag.location ?? 'None', - onSelected: (value) { - context - .read() - .add(UpdateLocation( - index: index, - location: value, - )); - }, - )), - ), - ], - ); - }), - ), - ), - if (state.errorMessage != null) - Text( - state.errorMessage!, - style: const TextStyle(color: ColorsManager.warningRed), - ), - ], - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - const SizedBox(width: 10), - Expanded( - child: Builder( - builder: (buttonContext) => CancelButton( - label: 'Add New Device', - onPressed: () async { - for (var tag in state.tags) { - if (tag.location == null || subspaces == null) { - continue; - } - - final previousTagSubspace = - checkTagExistInSubspace(tag, subspaces ?? []); - - if (tag.location == 'Main Space') { - removeTagFromSubspace(tag, previousTagSubspace); - } else if (tag.location != - previousTagSubspace?.subspaceName) { - removeTagFromSubspace(tag, previousTagSubspace); - moveToNewSubspace(tag, subspaces ?? []); - state.tags.removeWhere( - (t) => t.internalId == tag.internalId); - } else { - updateTagInSubspace(tag, previousTagSubspace); - state.tags.removeWhere( - (t) => t.internalId == tag.internalId); - } - } - if (context.mounted) { - await showDialog( - barrierDismissible: false, - context: context, - builder: (dialogContext) => - AddDeviceTypeModelWidget( - products: products, - subspaces: subspaces, - isCreate: false, - initialSelectedProducts: addedProducts, - allTags: allTags, - spaceName: spaceName, - spaceTagModels: state.tags, - onUpdate: (tags, subspaces) { - onUpdate?.call(state.tags, subspaces); - Navigator.of(context).pop(); - }, - ), - ); - } - }, + DataCell( + SizedBox( + width: double.infinity, + child: DialogDropdown( + items: locations, + selectedValue: + tag.location ?? 'None', + onSelected: (value) { + context + .read< + AssignTagModelBloc>() + .add(UpdateLocation( + index: index, + location: value, + )); + }, + )), + ), + ], + ); + }), + ), ), - ), + if (state.errorMessage != null) + Text( + state.errorMessage!, + style: const TextStyle( + color: ColorsManager.warningRed), + ), + ], ), - const SizedBox(width: 10), - Expanded( - child: DefaultButton( - borderRadius: 10, - backgroundColor: state.isSaveEnabled - ? ColorsManager.secondaryColor - : ColorsManager.grayColor, - foregroundColor: ColorsManager.whiteColors, - onPressed: state.isSaveEnabled - ? () async { - Navigator.of(context).pop(); - + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + const SizedBox(width: 10), + Expanded( + child: Builder( + builder: (buttonContext) => CancelButton( + label: 'Add New Device', + onPressed: () async { for (var tag in state.tags) { if (tag.location == null || subspaces == null) { @@ -317,26 +270,89 @@ class AssignTagModelsDialog extends StatelessWidget { (t) => t.internalId == tag.internalId); } } + if (context.mounted) { + await showDialog( + barrierDismissible: false, + context: context, + builder: (dialogContext) => + AddDeviceTypeModelWidget( + products: products, + subspaces: subspaces, + isCreate: false, + initialSelectedProducts: addedProducts, + allTags: allTags, + spaceName: spaceName, + spaceTagModels: state.tags, + onUpdate: (tags, subspaces) { + onUpdate?.call(state.tags, subspaces); + Navigator.of(context).pop(); + }, + ), + ); + } + }, + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + borderRadius: 10, + backgroundColor: state.isSaveEnabled + ? ColorsManager.secondaryColor + : ColorsManager.grayColor, + foregroundColor: ColorsManager.whiteColors, + onPressed: state.isSaveEnabled + ? () async { + Navigator.of(context).pop(); - onUpdate?.call(state.tags, subspaces); - } - : null, - child: const Text('Save'), - ), + for (var tag in state.tags) { + if (tag.location == null || + subspaces == null) { + continue; + } + + final previousTagSubspace = + checkTagExistInSubspace( + tag, subspaces ?? []); + + if (tag.location == 'Main Space') { + removeTagFromSubspace( + tag, previousTagSubspace); + } else if (tag.location != + previousTagSubspace?.subspaceName) { + removeTagFromSubspace( + tag, previousTagSubspace); + moveToNewSubspace(tag, subspaces ?? []); + state.tags.removeWhere((t) => + t.internalId == tag.internalId); + } else { + updateTagInSubspace( + tag, previousTagSubspace); + state.tags.removeWhere((t) => + t.internalId == tag.internalId); + } + } + + onUpdate?.call(state.tags, subspaces); + } + : null, + child: const Text('Save'), + ), + ), + const SizedBox(width: 10), + ], ), - const SizedBox(width: 10), ], - ), - ], - ); - } else if (state is AssignTagModelLoading) { - return const Center(child: CircularProgressIndicator()); - } else { - return const Center(child: Text('Something went wrong.')); - } - }, - ), - ); + ); + } else if (state is AssignTagModelLoading) { + return const Center(child: CircularProgressIndicator()); + } else { + return const Center(child: Text('Something went wrong.')); + } + }, + ), + )); } List getAvailableTags( diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 6f2f2018..33defb52 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -3,7 +3,6 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; @@ -53,6 +52,8 @@ class CreateSpaceModelBloc } }); + + on((event, emit) { emit(CreateSpaceModelLoading()); Future.delayed(const Duration(seconds: 1), () { diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index e671410c..a3f34e31 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -166,6 +166,10 @@ class CreateSpaceModelDialog extends StatelessWidget { modelName: spaceNameController.text.trim(), ); + if(updatedSpaceTemplate.uuid != null){ + + } + context.read().add( CreateSpaceTemplate( spaceTemplate: From e47f3d6d59d2982fa495ba2a6283ba7323a6359a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 21 Jan 2025 20:26:30 +0400 Subject: [PATCH 098/106] fixed space model creation --- .../views/assign_tag_models_dialog.dart | 37 ++++-- .../bloc/create_space_model_bloc.dart | 11 +- .../bloc/create_space_model_event.dart | 5 +- .../space_model/view/space_model_page.dart | 20 ++-- .../dialog/create_space_model_dialog.dart | 111 +++++++++--------- .../widgets/tag_chips_display_widget.dart | 49 ++++---- .../views/add_device_type_model_widget.dart | 37 +++--- 7 files changed, 144 insertions(+), 126 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 3a2499f9..006a5f7c 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -9,8 +9,10 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_pr import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -23,8 +25,8 @@ class AssignTagModelsDialog extends StatelessWidget { final List? allTags; final String spaceName; final String title; - final void Function( - List? tags, List? subspaces)? onUpdate; + final BuildContext? pageContext; + final List? otherSpaceModels; const AssignTagModelsDialog( {Key? key, @@ -36,7 +38,8 @@ class AssignTagModelsDialog extends StatelessWidget { this.allTags, required this.spaceName, required this.title, - this.onUpdate}) + this.pageContext, + this.otherSpaceModels}) : super(key: key); @override @@ -271,6 +274,8 @@ class AssignTagModelsDialog extends StatelessWidget { } } if (context.mounted) { + Navigator.of(context).pop(); + await showDialog( barrierDismissible: false, context: context, @@ -282,11 +287,9 @@ class AssignTagModelsDialog extends StatelessWidget { initialSelectedProducts: addedProducts, allTags: allTags, spaceName: spaceName, + otherSpaceModels: otherSpaceModels, spaceTagModels: state.tags, - onUpdate: (tags, subspaces) { - onUpdate?.call(state.tags, subspaces); - Navigator.of(context).pop(); - }, + pageContext: pageContext, ), ); } @@ -304,8 +307,6 @@ class AssignTagModelsDialog extends StatelessWidget { foregroundColor: ColorsManager.whiteColors, onPressed: state.isSaveEnabled ? () async { - Navigator.of(context).pop(); - for (var tag in state.tags) { if (tag.location == null || subspaces == null) { @@ -333,8 +334,24 @@ class AssignTagModelsDialog extends StatelessWidget { t.internalId == tag.internalId); } } + Navigator.of(context) + .popUntil((route) => route.isFirst); - onUpdate?.call(state.tags, subspaces); + await showDialog( + context: context, + builder: (BuildContext dialogContext) { + return CreateSpaceModelDialog( + products: products, + allTags: allTags, + pageContext: pageContext, + otherSpaceModels: otherSpaceModels, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + tags: state.tags, + subspaceModels: subspaces), + ); + }, + ); } : null, child: const Text('Save'), diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 33defb52..417ba1fd 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; @@ -52,8 +54,6 @@ class CreateSpaceModelBloc } }); - - on((event, emit) { emit(CreateSpaceModelLoading()); Future.delayed(const Duration(seconds: 1), () { @@ -173,7 +173,12 @@ class CreateSpaceModelBloc on((event, emit) { final currentState = state; if (currentState is CreateSpaceModelLoaded) { - if (event.name.trim().isEmpty) { + if (event.allModels.contains(event.name) == true) { + emit(CreateSpaceModelLoaded( + currentState.space, + errorMessage: "Duplicate Model name", + )); + } else if (event.name.trim().isEmpty) { emit(CreateSpaceModelLoaded( currentState.space, errorMessage: "Model name cannot be empty", diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index 2bcb12b6..a78ae78f 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -34,11 +34,12 @@ class CreateSpaceTemplate extends CreateSpaceModelEvent { class UpdateSpaceTemplateName extends CreateSpaceModelEvent { final String name; + final List allModels; - UpdateSpaceTemplateName({required this.name}); + UpdateSpaceTemplateName({required this.name, required this.allModels}); @override - List get props => [name]; + List get props => [name,allModels]; } class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index d0b5a300..47bea955 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/add_space_model_widget.dart'; @@ -24,6 +23,7 @@ class SpaceModelPage extends StatelessWidget { } else if (state is SpaceModelLoaded) { final spaceModels = state.spaceModels; final allTagValues = _getAllTagValues(spaceModels); + final allSpaceModelNames = _getAllSpaceModelName(spaceModels); return Scaffold( backgroundColor: ColorsManager.whiteColors, @@ -52,12 +52,8 @@ class SpaceModelPage extends StatelessWidget { return CreateSpaceModelDialog( products: products, allTags: allTagValues, - onLoad: (newModel) { - context.read().add( - CreateSpaceModel( - newSpaceModel: newModel), - ); - }, + pageContext: context, + otherSpaceModels: allSpaceModelNames, ); }, ); @@ -76,7 +72,7 @@ class SpaceModelPage extends StatelessWidget { products: products, allTags: allTagValues, spaceModel: model, - onLoad: (newModel) {}, + otherSpaceModels: allSpaceModelNames, ); }, ); @@ -128,4 +124,12 @@ class SpaceModelPage extends StatelessWidget { } return allTags; } + + List _getAllSpaceModelName(List spaceModels) { + final List names = []; + for (final spaceModel in spaceModels) { + names.add(spaceModel.modelName); + } + return names; + } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index a3f34e31..4c4b8a7c 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -8,6 +8,8 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; @@ -17,15 +19,17 @@ class CreateSpaceModelDialog extends StatelessWidget { final List? products; final List? allTags; final SpaceTemplateModel? spaceModel; - final void Function(SpaceTemplateModel newModel)? onLoad; + final BuildContext? pageContext; + final List? otherSpaceModels; - const CreateSpaceModelDialog({ - Key? key, - this.products, - this.allTags, - this.spaceModel, - this.onLoad, - }) : super(key: key); + const CreateSpaceModelDialog( + {Key? key, + this.products, + this.allTags, + this.spaceModel, + this.pageContext, + this.otherSpaceModels}) + : super(key: key); @override Widget build(BuildContext context) { @@ -44,17 +48,17 @@ class CreateSpaceModelDialog extends StatelessWidget { child: BlocProvider( create: (_) { final bloc = CreateSpaceModelBloc(_spaceModelApi); - if (spaceModel != null) { - bloc.add(UpdateSpaceTemplate(spaceModel!)); - } else { - bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( - modelName: '', - subspaceModels: const [], - ))); - } - + if (spaceModel != null) { + bloc.add(UpdateSpaceTemplate(spaceModel!)); + } else { + bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( + modelName: '', + subspaceModels: const [], + ))); + } + spaceNameController.addListener(() { - bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text)); + bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text,allModels: otherSpaceModels ??[])); }); return bloc; @@ -86,9 +90,10 @@ class CreateSpaceModelDialog extends StatelessWidget { child: TextField( controller: spaceNameController, onChanged: (value) { - context - .read() - .add(UpdateSpaceTemplateName(name: value)); + context.read().add( + UpdateSpaceTemplateName( + name: value, + allModels: otherSpaceModels ?? [])); }, style: const TextStyle(color: ColorsManager.blackColor), decoration: InputDecoration( @@ -128,21 +133,8 @@ class CreateSpaceModelDialog extends StatelessWidget { subspaces: subspaces, allTags: allTags, spaceNameController: spaceNameController, - onLoad: (tags, subspaces) { - if (context.read().state - is CreateSpaceModelLoaded) { - if (subspaces != null) { - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } - if (tags != null) { - context - .read() - .add(AddTagsToSpaceTemplate(tags)); - } - } - }, + pageContext: pageContext, + otherSpaceModels: otherSpaceModels, ), const SizedBox(height: 20), SizedBox( @@ -161,26 +153,35 @@ class CreateSpaceModelDialog extends StatelessWidget { onPressed: state.errorMessage == null || isNameValid ? () { - final updatedSpaceTemplate = - updatedSpaceModel.copyWith( - modelName: - spaceNameController.text.trim(), - ); - if(updatedSpaceTemplate.uuid != null){ - + if (updatedSpaceModel.uuid == null) { + final updatedSpaceTemplate = + updatedSpaceModel.copyWith( + modelName: + spaceNameController.text.trim(), + ); + if (updatedSpaceTemplate.uuid != + null) {} + + context + .read() + .add( + CreateSpaceTemplate( + spaceTemplate: + updatedSpaceTemplate, + onCreate: (newModel) { + if (pageContext != null) { + pageContext! + .read() + .add(CreateSpaceModel( + newSpaceModel: + newModel)); + } + Navigator.of(context) + .pop(); // Close the dialog + }, + ), + ); } - - context.read().add( - CreateSpaceTemplate( - spaceTemplate: - updatedSpaceTemplate, - onCreate: (newModel) { - onLoad!(newModel); - Navigator.of(context) - .pop(); // Close the dialog - }, - ), - ); } : null, backgroundColor: ColorsManager.secondaryColor, diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 18efd501..0c46076f 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -5,7 +5,6 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assi import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -17,8 +16,8 @@ class TagChipDisplay extends StatelessWidget { final List? subspaces; final List? allTags; final TextEditingController spaceNameController; - final void Function( - List? tags, List? subspaces)? onLoad; + final BuildContext? pageContext; + final List? otherSpaceModels; const TagChipDisplay(BuildContext context, {Key? key, @@ -28,7 +27,8 @@ class TagChipDisplay extends StatelessWidget { required this.subspaces, required this.allTags, required this.spaceNameController, - this.onLoad}) + this.pageContext, + this.otherSpaceModels}) : super(key: key); @override @@ -91,24 +91,22 @@ class TagChipDisplay extends StatelessWidget { if (navigatorContext != null) { await showDialog( - barrierDismissible: false, - context: navigatorContext, - builder: (context) => AssignTagModelsDialog( - products: products, - subspaces: subspaces, - allTags: allTags, - initialTags: TagHelper.generateInitialTags( + barrierDismissible: false, + context: navigatorContext, + builder: (context) => AssignTagModelsDialog( + products: products, subspaces: subspaces, - spaceTagModels: spaceModel?.tags ?? []), - title: 'Edit Device', - addedProducts: - TagHelper.createInitialSelectedProducts( - spaceModel?.tags ?? [], subspaces), - spaceName: spaceModel?.modelName ?? '', - onUpdate: (tags, subspaces) { - onLoad?.call(tags, subspaces); - }), - ); + pageContext: pageContext, + allTags: allTags, + initialTags: TagHelper.generateInitialTags( + subspaces: subspaces, + spaceTagModels: spaceModel?.tags ?? []), + title: 'Edit Device', + addedProducts: + TagHelper.createInitialSelectedProducts( + spaceModel?.tags ?? [], subspaces), + spaceName: spaceModel?.modelName ?? '', + )); } }, child: Chip( @@ -129,6 +127,8 @@ class TagChipDisplay extends StatelessWidget { ) : TextButton( onPressed: () async { + Navigator.of(context).pop(); + await showDialog( barrierDismissible: false, context: context, @@ -137,13 +137,8 @@ class TagChipDisplay extends StatelessWidget { subspaces: subspaces, allTags: allTags, spaceName: spaceNameController.text, + pageContext: pageContext, isCreate: true, - onUpdate: (tags, subspaces) { - onLoad?.call(tags, subspaces); - }, - onLoad: (tags, subspaces) { - onLoad?.call(tags, subspaces); - }, ), ); }, diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 7944eb4f..ddb9b5a3 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; @@ -21,28 +22,23 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? allTags; final String spaceName; final bool isCreate; - final void Function( - List? tags, List? subspaces)? onLoad; - final void Function( - List? tags, List? subspaces)? onUpdate; + final List? otherSpaceModels; + final BuildContext? pageContext; - const AddDeviceTypeModelWidget({ - super.key, - this.products, - this.initialSelectedProducts, - this.subspaces, - this.allTags, - this.spaceTagModels, - required this.spaceName, - required this.isCreate, - this.onLoad, - this.onUpdate, - }); + const AddDeviceTypeModelWidget( + {super.key, + this.products, + this.initialSelectedProducts, + this.subspaces, + this.allTags, + this.spaceTagModels, + required this.spaceName, + required this.isCreate, + this.pageContext, + this.otherSpaceModels}); @override Widget build(BuildContext context) { - - final size = MediaQuery.of(context).size; final crossAxisCount = size.width > 1200 ? 8 @@ -142,10 +138,9 @@ class AddDeviceTypeModelWidget extends StatelessWidget { allTags: allTags, spaceName: spaceName, initialTags: state.initialTag, + otherSpaceModels: otherSpaceModels, title: dialogTitle, - onUpdate: (tags, subspaces) { - onUpdate?.call(tags, subspaces); - }, + pageContext: pageContext, ), ); } From 44d95f57011aeb3bb1ead79d9ad4e34b60afecc9 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 21 Jan 2025 20:29:15 +0400 Subject: [PATCH 099/106] fixed index --- .../assign_tag_models/views/assign_tag_models_dialog.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 006a5f7c..d345a8c6 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -133,7 +133,7 @@ class AssignTagModelsDialog extends StatelessWidget { return DataRow( cells: [ - DataCell(Text(index.toString())), + DataCell(Text((index + 1).toString())), DataCell( Row( mainAxisAlignment: From 18afc4f563767f5bc429065015d031f21ffbeb11 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 21 Jan 2025 20:37:21 +0400 Subject: [PATCH 100/106] fixed issues --- .../spaces_management/space_model/view/space_model_page.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 47bea955..33509998 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -63,6 +63,9 @@ class SpaceModelPage extends StatelessWidget { } // Render existing space model final model = spaceModels[index]; + final otherModel = List.from(allSpaceModelNames); + otherModel.remove(model.modelName); + return GestureDetector( onTap: () { showDialog( @@ -72,7 +75,7 @@ class SpaceModelPage extends StatelessWidget { products: products, allTags: allTagValues, spaceModel: model, - otherSpaceModels: allSpaceModelNames, + otherSpaceModels: otherModel, ); }, ); From 7ffdc67016ae96b49e06bb1fb629d5bc929677ca Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 22 Jan 2025 12:48:46 +0400 Subject: [PATCH 101/106] added product comparison --- .../all_spaces/model/product_model.dart | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/pages/spaces_management/all_spaces/model/product_model.dart b/lib/pages/spaces_management/all_spaces/model/product_model.dart index 557c106b..a4ebd550 100644 --- a/lib/pages/spaces_management/all_spaces/model/product_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/product_model.dart @@ -66,4 +66,25 @@ class ProductModel { String toString() { return 'ProductModel(uuid: $uuid, catName: $catName, prodId: $prodId, prodType: $prodType, name: $name, icon: $icon)'; } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ProductModel && + runtimeType == other.runtimeType && + uuid == other.uuid && + catName == other.catName && + prodId == other.prodId && + prodType == other.prodType && + name == other.name && + icon == other.icon; + + @override + int get hashCode => + uuid.hashCode ^ + catName.hashCode ^ + prodId.hashCode ^ + prodType.hashCode ^ + name.hashCode ^ + icon.hashCode; } From f35b699d4c7b2afb433e255d79db53a2ca185e46 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 22 Jan 2025 12:49:47 +0400 Subject: [PATCH 102/106] fixed the edit flow for space model --- .../widgets/device_type_tile_widget.dart | 1 + .../bloc/space_management_state.dart | 15 +- .../widgets/add_device_type_widget.dart | 1 + .../all_spaces/widgets/counter_widget.dart | 25 +- .../views/assign_tag_models_dialog.dart | 37 ++- .../views/create_subspace_model_dialog.dart | 6 +- .../spaces_management/helper/tag_helper.dart | 3 +- .../bloc/create_space_model_bloc.dart | 228 +++++++++++++++++- .../bloc/create_space_model_event.dart | 15 +- .../space_model/bloc/space_model_bloc.dart | 20 ++ .../space_model/bloc/space_model_event.dart | 18 ++ .../create_space_template_body_model.dart | 4 +- .../models/space_template_model.dart | 47 +--- .../space_model/models/tag_update_model.dart | 34 +++ .../space_model/view/space_model_page.dart | 2 +- .../dialog/create_space_model_dialog.dart | 64 ++++- .../widgets/subspace_chip_widget.dart | 3 - .../widgets/subspace_model_create_widget.dart | 34 +-- .../widgets/tag_chips_display_widget.dart | 2 + .../views/add_device_type_model_widget.dart | 47 +++- .../widgets/device_type_tile_widget.dart | 12 +- .../widgets/scrollable_grid_view_widget.dart | 5 +- lib/services/space_model_mang_api.dart | 14 ++ lib/utils/constants/api_const.dart | 7 +- 24 files changed, 517 insertions(+), 127 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/models/tag_update_model.dart diff --git a/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart index 2feea7d9..08ad79ac 100644 --- a/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart +++ b/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart @@ -48,6 +48,7 @@ class DeviceTypeTileWidget extends StatelessWidget { DeviceNameWidget(name: product.name), const SizedBox(height: 4), CounterWidget( + isCreate: false, initialCount: selectedProduct.count, onCountChanged: (newCount) { context.read().add( diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart index 635c244d..571651e5 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart @@ -27,8 +27,7 @@ class SpaceManagementLoaded extends SpaceManagementState { required this.products, this.selectedCommunity, this.selectedSpace, - this.spaceModels - }); + this.spaceModels}); } class SpaceModelManagenetLoaded extends SpaceManagementState { @@ -38,14 +37,10 @@ class SpaceModelManagenetLoaded extends SpaceManagementState { class BlankState extends SpaceManagementState { final List communities; final List products; - List? spaceModels; + List? spaceModels; - - BlankState({ - required this.communities, - required this.products, - this.spaceModels - }); + BlankState( + {required this.communities, required this.products, this.spaceModels}); } class SpaceCreationSuccess extends SpaceManagementState { @@ -67,7 +62,7 @@ class SpaceManagementError extends SpaceManagementState { } class SpaceModelLoaded extends SpaceManagementState { - final List spaceModels; + List spaceModels; final List products; final List communities; diff --git a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart index 351eacce..0e9f4bd1 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart @@ -137,6 +137,7 @@ class _AddDeviceWidgetState extends State { _buildDeviceName(product, size), const SizedBox(height: 4), CounterWidget( + isCreate: false, initialCount: selectedProduct.count, onCountChanged: (newCount) { setState(() { diff --git a/lib/pages/spaces_management/all_spaces/widgets/counter_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/counter_widget.dart index 66935b12..2289819b 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/counter_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/counter_widget.dart @@ -4,12 +4,14 @@ import 'package:syncrow_web/utils/color_manager.dart'; class CounterWidget extends StatefulWidget { final int initialCount; final ValueChanged onCountChanged; + final bool isCreate; - const CounterWidget({ - Key? key, - this.initialCount = 0, - required this.onCountChanged, - }) : super(key: key); + const CounterWidget( + {Key? key, + this.initialCount = 0, + required this.onCountChanged, + required this.isCreate}) + : super(key: key); @override State createState() => _CounterWidgetState(); @@ -53,25 +55,26 @@ class _CounterWidgetState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - _buildCounterButton(Icons.remove, _decrementCounter), + _buildCounterButton(Icons.remove, _decrementCounter,!widget.isCreate ), const SizedBox(width: 8), Text( '$_counter', - style: theme.textTheme.bodyLarge?.copyWith(color: ColorsManager.spaceColor), + style: theme.textTheme.bodyLarge + ?.copyWith(color: ColorsManager.spaceColor), ), const SizedBox(width: 8), - _buildCounterButton(Icons.add, _incrementCounter), + _buildCounterButton(Icons.add, _incrementCounter, false), ], ), ); } - Widget _buildCounterButton(IconData icon, VoidCallback onPressed) { + Widget _buildCounterButton(IconData icon, VoidCallback onPressed, bool isDisabled) { return GestureDetector( - onTap: onPressed, + onTap: isDisabled? null: onPressed, child: Icon( icon, - color: ColorsManager.spaceColor, + color: isDisabled? ColorsManager.spaceColor.withOpacity(0.3): ColorsManager.spaceColor, size: 18, ), ); diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index d345a8c6..fc778436 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -19,6 +19,8 @@ import 'package:syncrow_web/utils/color_manager.dart'; class AssignTagModelsDialog extends StatelessWidget { final List? products; final List? subspaces; + final SpaceTemplateModel? spaceModel; + final List initialTags; final ValueChanged>? onTagsAssigned; final List addedProducts; @@ -39,7 +41,8 @@ class AssignTagModelsDialog extends StatelessWidget { required this.spaceName, required this.title, this.pageContext, - this.otherSpaceModels}) + this.otherSpaceModels, + this.spaceModel}) : super(key: key); @override @@ -210,7 +213,7 @@ class AssignTagModelsDialog extends StatelessWidget { child: DialogDropdown( items: locations, selectedValue: - tag.location ?? 'None', + tag.location ?? 'Main Space', onSelected: (value) { context .read< @@ -281,16 +284,23 @@ class AssignTagModelsDialog extends StatelessWidget { context: context, builder: (dialogContext) => AddDeviceTypeModelWidget( - products: products, - subspaces: subspaces, - isCreate: false, - initialSelectedProducts: addedProducts, - allTags: allTags, - spaceName: spaceName, - otherSpaceModels: otherSpaceModels, - spaceTagModels: state.tags, - pageContext: pageContext, - ), + products: products, + subspaces: subspaces, + isCreate: false, + initialSelectedProducts: + addedProducts, + allTags: allTags, + spaceName: spaceName, + otherSpaceModels: otherSpaceModels, + spaceTagModels: state.tags, + pageContext: pageContext, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + tags: state.tags, + uuid: spaceModel?.uuid, + internalId: + spaceModel?.internalId, + subspaceModels: subspaces)), ); } }, @@ -348,6 +358,9 @@ class AssignTagModelsDialog extends StatelessWidget { spaceModel: SpaceTemplateModel( modelName: spaceName, tags: state.tags, + uuid: spaceModel?.uuid, + internalId: + spaceModel?.internalId, subspaceModels: subspaces), ); }, diff --git a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart index 82aa3684..4c0cb99f 100644 --- a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart @@ -186,8 +186,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget { const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: (state.subSpaces.isEmpty || - state.errorMessage.isNotEmpty) + onPressed: (state.errorMessage.isNotEmpty) ? null : () async { final subSpaces = context @@ -201,8 +200,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget { }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: state.subSpaces.isEmpty || - state.errorMessage.isNotEmpty + foregroundColor: state.errorMessage.isNotEmpty ? ColorsManager.whiteColorsWithOpacity : ColorsManager.whiteColors, child: const Text('OK'), diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index bfff02a9..d4a0ea55 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -37,7 +37,8 @@ class TagHelper { final Map groupedTags = {}; for (var tag in tags) { if (tag.product != null) { - groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1; + final product = tag.product!; + groupedTags[product] = (groupedTags[product] ?? 0) + 1; } } return groupedTags; diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 417ba1fd..40db384a 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -1,12 +1,12 @@ -import 'dart:math'; - import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; +import 'package:syncrow_web/utils/constants/action_enum.dart'; class CreateSpaceModelBloc extends Bloc { @@ -193,5 +193,229 @@ class CreateSpaceModelBloc emit(CreateSpaceModelError("Space template not initialized")); } }); + + on((event, emit) async { + try { + final prevSpaceModel = event.spaceTemplate; + final newSpaceModel = event.updatedSpaceTemplate; + String? spaceModelName; + if (prevSpaceModel.modelName != newSpaceModel.modelName) { + spaceModelName = newSpaceModel.modelName; + } + List tagUpdates = []; + final List subspaceUpdates = []; + + tagUpdates = processTagUpdates(prevSpaceModel.tags, newSpaceModel.tags); + + if (prevSpaceModel.subspaceModels != null) { + for (var prevSubspace in prevSpaceModel.subspaceModels!) { + if (newSpaceModel.subspaceModels == null) { + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.delete, + uuid: prevSubspace.uuid, + )); + continue; + } + + final subspaceExistsInNew = newSpaceModel.subspaceModels! + .any((newSubspace) => newSubspace.uuid == prevSubspace.uuid); + if (!subspaceExistsInNew) { + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.delete, + uuid: prevSubspace.uuid, + )); + } + } + } + + if (newSpaceModel.subspaceModels != null) { + for (var newSubspaceModel in newSpaceModel.subspaceModels!) { + if (newSubspaceModel.uuid == null || + newSubspaceModel.uuid!.isEmpty) { + final List tagUpdatesInSubspace = []; + + if (newSubspaceModel.tags != null) { + for (var tag in newSubspaceModel.tags!) { + tagUpdatesInSubspace.add(TagModelUpdate( + action: Action.add, + uuid: tag.uuid, + tag: tag.tag, + productUuid: tag.product?.uuid, + )); + } + } + + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.add, + subspaceName: newSubspaceModel.subspaceName, + tags: tagUpdatesInSubspace, + )); + } + } + } + + if (newSpaceModel.subspaceModels != null && + prevSpaceModel.subspaceModels != null) { + final prevSubspaceMap = { + for (var subspace in prevSpaceModel.subspaceModels!) + subspace.uuid: subspace + }; + + for (var newSubspace in newSpaceModel.subspaceModels!) { + if (newSubspace.uuid != null && + prevSubspaceMap.containsKey(newSubspace.uuid)) { + final prevSubspace = prevSubspaceMap[newSubspace.uuid]!; + + // Check if modelName has changed + if (newSubspace.subspaceName != prevSubspace.subspaceName) { + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.update, + uuid: newSubspace.uuid, + subspaceName: newSubspace.subspaceName, + )); + } + + // Compare tags within the subspace + final List tagUpdatesInSubspace = []; + if (prevSubspace.tags != null && newSubspace.tags != null) { + final prevTagMap = { + for (var tag in prevSubspace.tags!) tag.uuid: tag + }; + + // Check for deleted tags + for (var prevTag in prevSubspace.tags!) { + if (!newSubspace.tags! + .any((newTag) => newTag.uuid == prevTag.uuid)) { + tagUpdatesInSubspace.add(TagModelUpdate( + action: Action.delete, + uuid: prevTag.uuid, + )); + } + } + + // Check for added tags, including those without a UUID + for (var newTag in newSubspace.tags!) { + if (newTag.uuid == null || newTag.uuid!.isEmpty) { + // Add new tag without UUID + tagUpdatesInSubspace.add(TagModelUpdate( + action: Action.add, + uuid: null, // or generate a new UUID if required + tag: newTag.tag, + productUuid: newTag.product?.uuid, + )); + } else if (!prevSubspace.tags! + .any((prevTag) => prevTag.uuid == newTag.uuid)) { + // Add new tag with UUID + tagUpdatesInSubspace.add(TagModelUpdate( + action: Action.add, + uuid: newTag.uuid, + tag: newTag.tag, + productUuid: newTag.product?.uuid, + )); + } + } + + // Check for updated tags + for (var prevTag in prevSubspace.tags!) { + final newTag = newSubspace.tags!.cast().firstWhere( + (tag) => tag?.uuid == prevTag.uuid, + orElse: () => null, + ); + if (newTag != null && newTag.tag != prevTag.tag) { + tagUpdatesInSubspace.add(TagModelUpdate( + action: Action.update, + uuid: newTag.uuid, + tag: newTag.tag, + )); + } + } + } + + // Add the subspace with updated tags if necessary + if (tagUpdatesInSubspace.isNotEmpty) { + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.update, + uuid: newSubspace.uuid, + subspaceName: newSubspace.subspaceName, + tags: tagUpdatesInSubspace, + )); + } + } + } + } + + final spaceModelBody = CreateSpaceTemplateBodyModel( + modelName: spaceModelName, + tags: tagUpdates, + subspaceModels: subspaceUpdates); + + final res = await _api.updateSpaceModel( + spaceModelBody, prevSpaceModel.uuid ?? ''); + + if (res != null) { + emit(CreateSpaceModelLoaded(newSpaceModel)); + if (event.onUpdate != null) { + event.onUpdate!(event.updatedSpaceTemplate); + } + } + } catch (e) { + emit(CreateSpaceModelError('Error creating space model')); + } + }); + } + + List processTagUpdates( + List? prevTags, + List? newTags, + ) { + final List tagUpdates = []; + final processedTags = {}; + + if (newTags != null || prevTags != null) { + // Case 1: Tags deleted + if (prevTags != null && newTags != null) { + for (var prevTag in prevTags!) { + final existsInNew = + newTags!.any((newTag) => newTag.uuid == prevTag.uuid); + if (!existsInNew) { + tagUpdates + .add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid)); + } + } + } + + // Case 2: Tags added + if (newTags != null) { + for (var newTag in newTags!) { + // Tag without UUID + if ((newTag.uuid == null || newTag.uuid!.isEmpty) && + !processedTags.contains(newTag.tag)) { + tagUpdates.add(TagModelUpdate( + action: Action.add, + tag: newTag.tag, + productUuid: newTag.product?.uuid)); + processedTags.add(newTag.tag); + } + } + } + + // Case 3: Tags updated + if (prevTags != null && newTags != null) { + final newTagMap = {for (var tag in newTags!) tag.uuid: tag}; + + for (var prevTag in prevTags!) { + final newTag = newTagMap[prevTag.uuid]; + if (newTag != null) { + tagUpdates.add(TagModelUpdate( + action: Action.update, + uuid: newTag.uuid, + tag: newTag.tag, + )); + } else {} + } + } + } + + return tagUpdates; } } diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index a78ae78f..22828941 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -22,7 +22,6 @@ class CreateSpaceTemplate extends CreateSpaceModelEvent { final SpaceTemplateModel spaceTemplate; final Function(SpaceTemplateModel)? onCreate; - const CreateSpaceTemplate({ required this.spaceTemplate, this.onCreate, @@ -39,7 +38,7 @@ class UpdateSpaceTemplateName extends CreateSpaceModelEvent { UpdateSpaceTemplateName({required this.name, required this.allModels}); @override - List get props => [name,allModels]; + List get props => [name, allModels]; } class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { @@ -54,9 +53,19 @@ class AddTagsToSpaceTemplate extends CreateSpaceModelEvent { AddTagsToSpaceTemplate(this.tags); } - class ValidateSpaceTemplateName extends CreateSpaceModelEvent { final String name; ValidateSpaceTemplateName({required this.name}); } + +class ModifySpaceTemplate extends CreateSpaceModelEvent { + final SpaceTemplateModel spaceTemplate; + final SpaceTemplateModel updatedSpaceTemplate; + final Function(SpaceTemplateModel)? onUpdate; + + ModifySpaceTemplate( + {required this.spaceTemplate, + required this.updatedSpaceTemplate, + this.onUpdate}); +} diff --git a/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart index e383610d..090dfa13 100644 --- a/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart @@ -12,6 +12,7 @@ class SpaceModelBloc extends Bloc { required List initialSpaceModels, }) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) { on(_onCreateSpaceModel); + on(_onUpdateSpaceModel); } Future _onCreateSpaceModel( @@ -33,4 +34,23 @@ class SpaceModelBloc extends Bloc { } } } + + Future _onUpdateSpaceModel( + UpdateSpaceModel event, Emitter emit) async { + final currentState = state; + if (currentState is SpaceModelLoaded) { + try { + final newSpaceModel = + await api.getSpaceModel(event.spaceModelUuid ?? ''); + if (newSpaceModel != null) { + final updatedSpaceModels = currentState.spaceModels.map((model) { + return model.uuid == event.spaceModelUuid ? newSpaceModel : model; + }).toList(); + emit(SpaceModelLoaded(spaceModels: updatedSpaceModels)); + } + } catch (e) { + emit(SpaceModelError(message: e.toString())); + } + } + } } diff --git a/lib/pages/spaces_management/space_model/bloc/space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/space_model_event.dart index 78331f3c..8f71e611 100644 --- a/lib/pages/spaces_management/space_model/bloc/space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/space_model_event.dart @@ -16,3 +16,21 @@ class CreateSpaceModel extends SpaceModelEvent { @override List get props => [newSpaceModel]; } + +class GetSpaceModel extends SpaceModelEvent { + final String spaceModelUuid; + + GetSpaceModel({required this.spaceModelUuid}); + + @override + List get props => [spaceModelUuid]; +} + +class UpdateSpaceModel extends SpaceModelEvent { + final String spaceModelUuid; + + UpdateSpaceModel({required this.spaceModelUuid}); + + @override + List get props => [spaceModelUuid]; +} diff --git a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart index e481a8b8..cb8d0aac 100644 --- a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart +++ b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart @@ -30,12 +30,12 @@ class CreateSubspaceTemplateModel { } class CreateSpaceTemplateBodyModel { - final String modelName; + final String? modelName; final List? tags; final List? subspaceModels; CreateSpaceTemplateBodyModel({ - required this.modelName, + this.modelName, this.tags, this.subspaceModels, }); diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart index 84f568a5..5edf912f 100644 --- a/lib/pages/spaces_management/space_model/models/space_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; import 'package:uuid/uuid.dart'; @@ -70,14 +71,14 @@ class SpaceTemplateModel extends Equatable { } class UpdateSubspaceTemplateModel { - final String uuid; + final String? uuid; final Action action; final String? subspaceName; - final List? tags; + final List? tags; UpdateSubspaceTemplateModel({ required this.action, - required this.uuid, + this.uuid, this.subspaceName, this.tags, }); @@ -88,7 +89,7 @@ class UpdateSubspaceTemplateModel { uuid: json['uuid'] ?? '', subspaceName: json['subspaceName'] ?? '', tags: (json['tags'] as List) - .map((item) => UpdateTagModel.fromJson(item)) + .map((item) => TagModelUpdate.fromJson(item)) .toList(), ); } @@ -103,44 +104,6 @@ class UpdateSubspaceTemplateModel { } } -class UpdateTagModel { - final Action action; - final String? uuid; - final String tag; - final bool disabled; - final ProductModel? product; - - UpdateTagModel({ - required this.action, - this.uuid, - required this.tag, - required this.disabled, - this.product, - }); - - factory UpdateTagModel.fromJson(Map json) { - return UpdateTagModel( - action: ActionExtension.fromValue(json['action']), - uuid: json['uuid'] ?? '', - tag: json['tag'] ?? '', - disabled: json['disabled'] ?? false, - product: json['product'] != null - ? ProductModel.fromMap(json['product']) - : null, - ); - } - - Map toJson() { - return { - 'action': action.value, - 'uuid': uuid, - 'tag': tag, - 'disabled': disabled, - 'product': product?.toMap(), - }; - } -} - extension SpaceTemplateExtensions on SpaceTemplateModel { List listAllTagValues() { final List tagValues = []; diff --git a/lib/pages/spaces_management/space_model/models/tag_update_model.dart b/lib/pages/spaces_management/space_model/models/tag_update_model.dart new file mode 100644 index 00000000..c7190dc8 --- /dev/null +++ b/lib/pages/spaces_management/space_model/models/tag_update_model.dart @@ -0,0 +1,34 @@ +import 'package:syncrow_web/utils/constants/action_enum.dart'; + +class TagModelUpdate { + final Action action; + final String? uuid; + final String? tag; + final String? productUuid; + + TagModelUpdate({ + required this.action, + this.uuid, + this.tag, + this.productUuid, + }); + + factory TagModelUpdate.fromJson(Map json) { + return TagModelUpdate( + action: json['action'], + uuid: json['uuid'], + tag: json['tag'], + productUuid: json['productUuid'], + ); + } + + // Method to convert an instance to JSON + Map toJson() { + return { + 'action': action.value, + 'uuid': uuid, // Nullable field + 'tag': tag, + 'productUuid': productUuid, + }; + } +} diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 33509998..ae623e81 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -65,7 +65,6 @@ class SpaceModelPage extends StatelessWidget { final model = spaceModels[index]; final otherModel = List.from(allSpaceModelNames); otherModel.remove(model.modelName); - return GestureDetector( onTap: () { showDialog( @@ -76,6 +75,7 @@ class SpaceModelPage extends StatelessWidget { allTags: allTagValues, spaceModel: model, otherSpaceModels: otherModel, + pageContext: context, ); }, ); diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 4c4b8a7c..c1bea0fd 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart'; @@ -58,7 +59,9 @@ class CreateSpaceModelDialog extends StatelessWidget { } spaceNameController.addListener(() { - bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text,allModels: otherSpaceModels ??[])); + bloc.add(UpdateSpaceTemplateName( + name: spaceNameController.text, + allModels: otherSpaceModels ?? [])); }); return bloc; @@ -153,15 +156,12 @@ class CreateSpaceModelDialog extends StatelessWidget { onPressed: state.errorMessage == null || isNameValid ? () { + final updatedSpaceTemplate = + updatedSpaceModel.copyWith( + modelName: + spaceNameController.text.trim(), + ); if (updatedSpaceModel.uuid == null) { - final updatedSpaceTemplate = - updatedSpaceModel.copyWith( - modelName: - spaceNameController.text.trim(), - ); - if (updatedSpaceTemplate.uuid != - null) {} - context .read() .add( @@ -181,6 +181,52 @@ class CreateSpaceModelDialog extends StatelessWidget { }, ), ); + } else { + if (pageContext != null) { + final currentState = pageContext! + .read() + .state; + if (currentState + is SpaceModelLoaded) { + final spaceModels = + List.from( + currentState.spaceModels); + + final SpaceTemplateModel? + currentSpaceModel = spaceModels + .cast() + .firstWhere( + (sm) => + sm?.uuid == + updatedSpaceModel + .uuid, + orElse: () => null, + ); + if (currentSpaceModel != null) { + context + .read() + .add(ModifySpaceTemplate( + spaceTemplate: + currentSpaceModel, + updatedSpaceTemplate: + updatedSpaceTemplate, + onUpdate: (newModel) { + if (pageContext != + null) { + pageContext! + .read< + SpaceModelBloc>() + .add(UpdateSpaceModel( + spaceModelUuid: + newModel.uuid ?? + '')); + } + Navigator.of(context) + .pop(); + })); + } + } + } } } : null, diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart index 8f987c51..70ac6e24 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart @@ -1,9 +1,6 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:flutter/material.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; - class SubspaceChipWidget extends StatelessWidget { final String subspace; diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 7781bb5e..0dda53a6 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -46,23 +46,23 @@ class SubspaceModelCreate extends StatelessWidget { spacing: 8.0, runSpacing: 8.0, children: [ - ...subspaces.map( - (subspace) => Chip( - label: Text( - subspace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor), // Text color - ), - backgroundColor: - ColorsManager.whiteColors, // Chip background color - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16), // Rounded chip - side: const BorderSide( - color: ColorsManager.spaceColor), // Border color - ), - ), - ), + ...subspaces.map((subspace) => Container( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 4.0), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: ColorsManager.transparentColor), + ), + child: Text( + subspace.subspaceName, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + ), + )), GestureDetector( onTap: () async { await _openDialog(context, 'Edit Sub-space'); diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 0c46076f..d4111031 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -98,6 +98,7 @@ class TagChipDisplay extends StatelessWidget { subspaces: subspaces, pageContext: pageContext, allTags: allTags, + spaceModel: spaceModel, initialTags: TagHelper.generateInitialTags( subspaces: subspaces, spaceTagModels: spaceModel?.tags ?? []), @@ -139,6 +140,7 @@ class TagChipDisplay extends StatelessWidget { spaceName: spaceNameController.text, pageContext: pageContext, isCreate: true, + spaceModel: spaceModel, ), ); }, diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index ddb9b5a3..a9d40147 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -5,9 +5,10 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart'; @@ -24,6 +25,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final bool isCreate; final List? otherSpaceModels; final BuildContext? pageContext; + final SpaceTemplateModel? spaceModel; const AddDeviceTypeModelWidget( {super.key, @@ -35,7 +37,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { required this.spaceName, required this.isCreate, this.pageContext, - this.otherSpaceModels}); + this.otherSpaceModels, + this.spaceModel}); @override Widget build(BuildContext context) { @@ -75,6 +78,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 20.0), child: ScrollableGridViewWidget( + isCreate: isCreate, products: products, crossAxisCount: crossAxisCount, initialProductCounts: state.selectedProducts, @@ -98,6 +102,44 @@ class AddDeviceTypeModelWidget extends StatelessWidget { onPressed: () async { if (isCreate) { Navigator.of(context).pop(); + await showDialog( + context: context, + builder: (BuildContext dialogContext) { + return CreateSpaceModelDialog( + products: products, + allTags: allTags, + pageContext: pageContext, + otherSpaceModels: otherSpaceModels, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + tags: spaceModel?.tags ?? [], + uuid: spaceModel?.uuid, + internalId: spaceModel?.internalId, + subspaceModels: subspaces), + ); + }, + ); + } else { + final initialTags = generateInitialTags( + spaceTagModels: spaceTagModels, + subspaces: subspaces, + ); + + Navigator.of(context).pop(); + await showDialog( + context: context, + builder: (context) => AssignTagModelsDialog( + products: products, + subspaces: subspaces, + addedProducts: initialSelectedProducts ?? [], + allTags: allTags, + spaceName: spaceName, + initialTags: initialTags, + otherSpaceModels: otherSpaceModels, + title: 'Edit Device', + spaceModel: spaceModel, + pageContext: pageContext, + )); } }, ), @@ -140,6 +182,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { initialTags: state.initialTag, otherSpaceModels: otherSpaceModels, title: dialogTitle, + spaceModel: spaceModel, pageContext: pageContext, ), ); diff --git a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart index c2d38d0b..7d103cdb 100644 --- a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart @@ -13,12 +13,13 @@ import 'package:syncrow_web/utils/constants/assets.dart'; class DeviceTypeTileWidget extends StatelessWidget { final ProductModel product; final List productCounts; + final bool isCreate; - const DeviceTypeTileWidget({ - super.key, - required this.product, - required this.productCounts, - }); + const DeviceTypeTileWidget( + {super.key, + required this.product, + required this.productCounts, + required this.isCreate}); @override Widget build(BuildContext context) { @@ -48,6 +49,7 @@ class DeviceTypeTileWidget extends StatelessWidget { DeviceNameWidget(name: product.name), const SizedBox(height: 4), CounterWidget( + isCreate: isCreate, initialCount: selectedProduct.count, onCountChanged: (newCount) { context.read().add( diff --git a/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart index 3e32ccd8..d1775c66 100644 --- a/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart @@ -10,12 +10,14 @@ class ScrollableGridViewWidget extends StatelessWidget { final List? products; final int crossAxisCount; final List? initialProductCounts; + final bool isCreate; const ScrollableGridViewWidget({ super.key, required this.products, required this.crossAxisCount, this.initialProductCounts, + required this.isCreate }); @override @@ -30,7 +32,7 @@ class ScrollableGridViewWidget extends StatelessWidget { final productCounts = state is AddDeviceModelLoaded ? state.selectedProducts : []; - + return GridView.builder( controller: scrollController, shrinkWrap: true, @@ -47,6 +49,7 @@ class ScrollableGridViewWidget extends StatelessWidget { return DeviceTypeTileWidget( product: product, + isCreate: isCreate, productCounts: initialProductCount != null ? [...productCounts, initialProductCount] : productCounts, diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index ee241189..eb896432 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -34,6 +34,20 @@ class SpaceModelManagementApi { return response; } + + Future updateSpaceModel( + CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid) async { + final response = await HTTPService().put( + path: ApiEndpoints.updateSpaceModel + .replaceAll('{projectId}', TempConst.projectId).replaceAll('{spaceModelUuid}', spaceModelUuid), + body: spaceModel.toJson(), + expectedResponseModel: (json) { + return json['message']; + }, + ); + return response; + } + Future getSpaceModel(String spaceModelUuid) async { final response = await HTTPService().get( path: ApiEndpoints.getSpaceModel diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 92a2581c..35020151 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -101,8 +101,11 @@ abstract class ApiEndpoints { //space model static const String listSpaceModels = '/projects/{projectId}/space-models'; static const String createSpaceModel = '/projects/{projectId}/space-models'; - static const String getSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}'; - + static const String getSpaceModel = + '/projects/{projectId}/space-models/{spaceModelUuid}'; + static const String updateSpaceModel = + '/projects/{projectId}/space-models/{spaceModelUuid}'; + static const String roleTypes = '/role/types'; static const String permission = '/permission/{roleUuid}'; static const String inviteUser = '/invite-user'; From 513175ed1e99b48ef28318243b1707316cba4e95 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 22 Jan 2025 15:44:46 +0300 Subject: [PATCH 103/106] user_agreement_dialog --- lib/pages/auth/model/user_model.dart | 38 ++++ lib/pages/home/bloc/home_bloc.dart | 40 ++++ lib/pages/home/bloc/home_event.dart | 6 +- lib/pages/home/bloc/home_state.dart | 6 + .../view/agreement_and_privacy_dialog.dart | 176 ++++++++++++++++++ lib/pages/home/view/home_page_web.dart | 36 +++- lib/services/home_api.dart | 29 +++ lib/utils/constants/api_const.dart | 5 +- pubspec.yaml | 2 + 9 files changed, 331 insertions(+), 7 deletions(-) create mode 100644 lib/pages/home/view/agreement_and_privacy_dialog.dart diff --git a/lib/pages/auth/model/user_model.dart b/lib/pages/auth/model/user_model.dart index 84d4661f..5090b0e0 100644 --- a/lib/pages/auth/model/user_model.dart +++ b/lib/pages/auth/model/user_model.dart @@ -10,6 +10,10 @@ class UserModel { final String? phoneNumber; final bool? isEmailVerified; final bool? isAgreementAccepted; + final bool? hasAcceptedWebAgreement; + final DateTime? webAgreementAcceptedAt; + final UserRole? role; + UserModel({ required this.uuid, required this.email, @@ -19,6 +23,9 @@ class UserModel { required this.phoneNumber, required this.isEmailVerified, required this.isAgreementAccepted, + required this.hasAcceptedWebAgreement, + required this.webAgreementAcceptedAt, + required this.role, }); factory UserModel.fromJson(Map json) { @@ -31,6 +38,11 @@ class UserModel { phoneNumber: json['phoneNumber'], isEmailVerified: json['isEmailVerified'], isAgreementAccepted: json['isAgreementAccepted'], + hasAcceptedWebAgreement: json['hasAcceptedWebAgreement'], + webAgreementAcceptedAt: json['webAgreementAcceptedAt'] != null + ? DateTime.parse(json['webAgreementAcceptedAt']) + : null, + role: json['role'] != null ? UserRole.fromJson(json['role']) : null, ); } @@ -41,6 +53,9 @@ class UserModel { Map tempJson = Token.decodeToken(token.accessToken); return UserModel( + hasAcceptedWebAgreement: null, + role: null, + webAgreementAcceptedAt: null, uuid: tempJson['uuid'].toString(), email: tempJson['email'], firstName: null, @@ -65,3 +80,26 @@ class UserModel { }; } } + +class UserRole { + final String uuid; + final DateTime createdAt; + final DateTime updatedAt; + final String type; + + UserRole({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.type, + }); + + factory UserRole.fromJson(Map json) { + return UserRole( + uuid: json['uuid'], + createdAt: DateTime.parse(json['createdAt']), + updatedAt: DateTime.parse(json['updatedAt']), + type: json['type'], + ); + } +} diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 416e9d92..8c887810 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -18,10 +18,15 @@ class HomeBloc extends Bloc { List sourcesList = []; List destinationsList = []; UserModel? user; + String terms = ''; + String policy = ''; HomeBloc() : super((HomeInitial())) { on(_createNode); on(_fetchUserInfo); + on(_fetchTerms); + on(_fetchPolicy); + on(_confirmUserAgreement); } void _createNode(CreateNewNode event, Emitter emit) async { @@ -45,12 +50,47 @@ class HomeBloc extends Bloc { var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); user = await HomeApi().fetchUserInfo(uuid); + add(FetchTermEvent()); + add(FetchPolicyEvent()); emit(HomeInitial()); } catch (e) { return; } } + Future _fetchTerms(FetchTermEvent event, Emitter emit) async { + try { + emit(LoadingHome()); + terms = await HomeApi().fetchTerms(); + emit(TermsAgreement()); + } catch (e) { + return; + } + } + + Future _fetchPolicy(FetchPolicyEvent event, Emitter emit) async { + try { + emit(LoadingHome()); + policy = await HomeApi().fetchPolicy(); + emit(PolicyAgreement()); + } catch (e) { + return; + } + } + + Future _confirmUserAgreement( + ConfirmUserAgreementEvent event, Emitter emit) async { + try { + emit(LoadingHome()); + var uuid = + await const FlutterSecureStorage().read(key: UserModel.userUuidKey); + policy = await HomeApi().confirmUserAgreements(uuid); + emit(PolicyAgreement()); + } catch (e) { + return; + } + } + // static Future fetchUserInfo() async { // try { // var uuid = diff --git a/lib/pages/home/bloc/home_event.dart b/lib/pages/home/bloc/home_event.dart index 963202b9..91b3bee8 100644 --- a/lib/pages/home/bloc/home_event.dart +++ b/lib/pages/home/bloc/home_event.dart @@ -20,4 +20,8 @@ class CreateNewNode extends HomeEvent { class FetchUserInfo extends HomeEvent { const FetchUserInfo(); -} \ No newline at end of file +}class FetchTermEvent extends HomeEvent {} + +class FetchPolicyEvent extends HomeEvent {} + +class ConfirmUserAgreementEvent extends HomeEvent {} \ No newline at end of file diff --git a/lib/pages/home/bloc/home_state.dart b/lib/pages/home/bloc/home_state.dart index 10c50486..5640d550 100644 --- a/lib/pages/home/bloc/home_state.dart +++ b/lib/pages/home/bloc/home_state.dart @@ -7,8 +7,12 @@ abstract class HomeState extends Equatable { @override List get props => []; } +class LoadingHome extends HomeState {} class HomeInitial extends HomeState {} +class TermsAgreement extends HomeState {} + +class PolicyAgreement extends HomeState {} class HomeCounterState extends HomeState { final int counter; @@ -24,3 +28,5 @@ class HomeUpdateTree extends HomeState { @override List get props => [graph, builder]; } + +//FetchTermEvent \ No newline at end of file diff --git a/lib/pages/home/view/agreement_and_privacy_dialog.dart b/lib/pages/home/view/agreement_and_privacy_dialog.dart new file mode 100644 index 00000000..e9371ae9 --- /dev/null +++ b/lib/pages/home/view/agreement_and_privacy_dialog.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:go_router/go_router.dart'; +import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/routes_const.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class AgreementAndPrivacyDialog extends StatefulWidget { + final String terms; + final String policy; + + const AgreementAndPrivacyDialog({ + super.key, + required this.terms, + required this.policy, + }); + + @override + _AgreementAndPrivacyDialogState createState() => + _AgreementAndPrivacyDialogState(); +} + +class _AgreementAndPrivacyDialogState extends State { + final ScrollController _scrollController = ScrollController(); + bool _isAtEnd = false; + int _currentPage = 1; + + @override + void initState() { + super.initState(); + _scrollController.addListener(_onScroll); + WidgetsBinding.instance + .addPostFrameCallback((_) => _checkScrollRequirement()); + } + + void _checkScrollRequirement() { + final scrollPosition = _scrollController.position; + if (scrollPosition.maxScrollExtent <= 0) { + setState(() { + _isAtEnd = true; + }); + } + } + + @override + void dispose() { + _scrollController.removeListener(_onScroll); + _scrollController.dispose(); + super.dispose(); + } + + void _onScroll() { + if (_scrollController.position.atEdge) { + final isAtBottom = _scrollController.position.pixels == + _scrollController.position.maxScrollExtent; + if (isAtBottom && !_isAtEnd) { + setState(() { + _isAtEnd = true; + }); + } + } + } + + String get _dialogTitle => + _currentPage == 2 ? 'User Agreement' : 'Privacy Policy'; + + String get _dialogContent => _currentPage == 2 ? widget.terms : widget.policy; + + Widget _buildScrollableContent() { + return Container( + padding: const EdgeInsets.all(40), + width: MediaQuery.of(context).size.width * 0.8, + height: MediaQuery.of(context).size.height * 0.75, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + child: Scrollbar( + thumbVisibility: true, + trackVisibility: true, + interactive: true, + controller: _scrollController, + child: SingleChildScrollView( + controller: _scrollController, + padding: const EdgeInsets.all(25), + child: Html( + data: _dialogContent, + onLinkTap: (url, attributes, element) async { + if (url != null) { + final uri = Uri.parse(url); + await launchUrl(uri, mode: LaunchMode.externalApplication); + } + }, + style: { + "body": Style( + fontSize: FontSize(14), + color: Colors.black87, + lineHeight: LineHeight(1.5), + ), + }, + ), + ), + ), + ); + } + + Widget _buildActionButton() { + final String buttonText = _currentPage == 2 ? "I Agree" : "Next"; + + return InkWell( + onTap: _isAtEnd + ? () { + if (_currentPage == 1) { + setState(() { + _currentPage = 2; + _isAtEnd = false; + _scrollController.jumpTo(0); + WidgetsBinding.instance + .addPostFrameCallback((_) => _checkScrollRequirement()); + }); + } else { + Navigator.of(context).pop(true); + } + } + : null, + child: Text( + buttonText, + style: TextStyle( + color: _isAtEnd ? ColorsManager.secondaryColor : Colors.grey, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Dialog( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + _dialogTitle, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: ColorsManager.secondaryColor, + ), + ), + ), + const Divider(), + _buildScrollableContent(), + const Divider(), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () { + AuthBloc.logout(); + context.go(RoutesConst.auth); + }, + child: const Text("Cancel"), + ), + _buildActionButton(), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/home/view/home_page_web.dart b/lib/pages/home/view/home_page_web.dart index a198fa76..8e7225d2 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/home/bloc/home_event.dart'; +import 'package:syncrow_web/pages/home/view/agreement_and_privacy_dialog.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_state.dart'; import 'package:syncrow_web/pages/home/view/home_card.dart'; @@ -9,16 +11,40 @@ import 'package:syncrow_web/web_layout/web_scaffold.dart'; class HomeWebPage extends StatelessWidget { const HomeWebPage({super.key}); + @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; + final homeBloc = BlocProvider.of(context); + return PopScope( canPop: false, onPopInvoked: (didPop) => false, child: BlocConsumer( - listener: (BuildContext context, state) {}, + listener: (BuildContext context, state) { + if (state is HomeInitial) { + if (homeBloc.user!.hasAcceptedWebAgreement == false) { + Future.delayed(const Duration(seconds: 1), () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AgreementAndPrivacyDialog( + terms: homeBloc.terms, + policy: homeBloc.policy, + ); + }, + ).then((v) { + if (v != null) { + homeBloc.add(ConfirmUserAgreementEvent()); + homeBloc.add(const FetchUserInfo()); + } + }); + }); + } + } + }, builder: (context, state) { - final homeBloc = BlocProvider.of(context); return WebScaffold( enableMenuSidebar: false, appBarTitle: Row( @@ -52,7 +78,8 @@ class HomeWebPage extends StatelessWidget { width: size.width * 0.68, child: GridView.builder( itemCount: 3, //8 - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 4, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, @@ -64,7 +91,8 @@ class HomeWebPage extends StatelessWidget { active: homeBloc.homeItems[index].active!, name: homeBloc.homeItems[index].title!, img: homeBloc.homeItems[index].icon!, - onTap: () => homeBloc.homeItems[index].onPress(context), + onTap: () => + homeBloc.homeItems[index].onPress(context), ); }, ), diff --git a/lib/services/home_api.dart b/lib/services/home_api.dart index dfbaf4bf..c1e67add 100644 --- a/lib/services/home_api.dart +++ b/lib/services/home_api.dart @@ -12,4 +12,33 @@ class HomeApi { }); return response; } + + Future fetchTerms() async { + final response = await HTTPService().get( + path: ApiEndpoints.terms, + showServerMessage: true, + expectedResponseModel: (json) { + return json['data']; + }); + return response; + } + + Future fetchPolicy() async { + final response = await HTTPService().get( + path: ApiEndpoints.policy, + showServerMessage: true, + expectedResponseModel: (json) { + return json['data']; + }); + return response; + } + + Future confirmUserAgreements(uuid) async { + final response = await HTTPService().patch( + path: ApiEndpoints.userAgreements.replaceAll('{userUuid}', uuid!), + expectedResponseModel: (json) { + return json['data']; + }); + return response; + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index d0331dac..454ec4e7 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -108,6 +108,7 @@ abstract class ApiEndpoints { static const String deleteUser = '/invite-user/{inviteUserUuid}'; static const String changeUserStatus = '/invite-user/{invitedUserUuid}/disable'; - - // static const String updateAutomation = '/automation/{automationId}'; + static const String terms = '/terms'; + static const String policy = '/policy'; + static const String userAgreements = '/user/agreements/web/{userUuid}'; } diff --git a/pubspec.yaml b/pubspec.yaml index 786a39c9..f4108d5c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,6 +54,8 @@ dependencies: time_picker_spinner: ^1.0.0 intl_phone_field: ^3.2.0 number_pagination: ^1.1.6 + url_launcher: ^6.2.5 + flutter_html: ^3.0.0-beta.2 dev_dependencies: flutter_test: From ba7db3a5fb57496b8257ce1d568cd51c6a4c9030 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 22 Jan 2025 17:21:35 +0400 Subject: [PATCH 104/106] updated subspace edit flow --- .../bloc/space_management_bloc.dart | 5 +- .../widgets/community_structure_widget.dart | 21 +-- .../bloc/create_space_model_bloc.dart | 172 ++++++------------ .../create_space_template_body_model.dart | 5 + lib/services/space_model_mang_api.dart | 1 + 5 files changed, 70 insertions(+), 134 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 26e444ca..7e9c6ce3 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -408,7 +408,9 @@ class SpaceManagementBloc Future> saveSpacesHierarchically( List spaces, String communityUuid) async { + print("space"); final orderedSpaces = flattenHierarchy(spaces); + print("parent"); final parentsToDelete = orderedSpaces.where((space) => space.status == SpaceStatus.deleted && @@ -420,9 +422,10 @@ class SpaceManagementBloc await _api.deleteSpace(communityUuid, parent.uuid!); } } catch (e) { - rethrow; // Decide whether to stop execution or continue + rethrow; } } + orderedSpaces.removeWhere((space) => parentsToDelete.contains(space)); for (var space in orderedSpaces) { try { diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 05a80780..dc0ad1cd 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -177,7 +177,7 @@ class _CommunityStructureAreaState extends State { painter: CurvedLinePainter([connection])), ), for (var entry in spaces.asMap().entries) - if (entry.value.status != SpaceStatus.deleted || + if (entry.value.status != SpaceStatus.deleted && entry.value.status != SpaceStatus.parentDeleted) Positioned( left: entry.value.position.dx, @@ -301,7 +301,6 @@ class _CommunityStructureAreaState extends State { List? tags) { setState(() { // Set the first space in the center or use passed position - Offset centerPosition = position ?? _getCenterPosition(screenSize); SpaceModel newSpace = SpaceModel( @@ -385,10 +384,10 @@ class _CommunityStructureAreaState extends State { void flatten(SpaceModel space) { if (space.status == SpaceStatus.deleted || - space.status == SpaceStatus.parentDeleted) return; - + space.status == SpaceStatus.parentDeleted) { + return; + } result.add(space); - for (var child in space.children) { flatten(child); } @@ -456,21 +455,17 @@ class _CommunityStructureAreaState extends State { } void _onDelete() { - if (widget.selectedCommunity != null && - widget.selectedCommunity?.uuid != null && - widget.selectedSpace == null) { - context.read().add(DeleteCommunityEvent( - communityUuid: widget.selectedCommunity!.uuid, - )); - } if (widget.selectedSpace != null) { setState(() { for (var space in spaces) { - if (space.uuid == widget.selectedSpace?.uuid) { + if (space.internalId == widget.selectedSpace?.internalId) { space.status = SpaceStatus.deleted; _markChildrenAsDeleted(space); } } + for (var space in spaces) { + print("space ${space.name} and ${space.status}"); + } _removeConnectionsForDeletedSpaces(); }); } diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 40db384a..d8b39216 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -3,6 +3,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; @@ -204,142 +205,68 @@ class CreateSpaceModelBloc } List tagUpdates = []; final List subspaceUpdates = []; + final List? prevSubspaces = + prevSpaceModel.subspaceModels; + final List? newSubspaces = + newSpaceModel.subspaceModels; tagUpdates = processTagUpdates(prevSpaceModel.tags, newSpaceModel.tags); - if (prevSpaceModel.subspaceModels != null) { - for (var prevSubspace in prevSpaceModel.subspaceModels!) { - if (newSpaceModel.subspaceModels == null) { - subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.delete, - uuid: prevSubspace.uuid, - )); - continue; + if (prevSubspaces != null || newSubspaces != null) { + if (prevSubspaces != null && newSubspaces != null) { + for (var prevSubspace in prevSubspaces!) { + final existsInNew = newSubspaces! + .any((newTag) => newTag.uuid == prevSubspace.uuid); + if (!existsInNew) { + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.delete, uuid: prevSubspace.uuid)); + } } - - final subspaceExistsInNew = newSpaceModel.subspaceModels! - .any((newSubspace) => newSubspace.uuid == prevSubspace.uuid); - if (!subspaceExistsInNew) { + } else if (prevSubspaces != null && newSubspaces == null) { + for (var prevSubspace in prevSubspaces) { subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.delete, - uuid: prevSubspace.uuid, - )); + action: Action.delete, uuid: prevSubspace.uuid)); } } - } - if (newSpaceModel.subspaceModels != null) { - for (var newSubspaceModel in newSpaceModel.subspaceModels!) { - if (newSubspaceModel.uuid == null || - newSubspaceModel.uuid!.isEmpty) { - final List tagUpdatesInSubspace = []; + if (newSubspaces != null) { + for (var newSubspace in newSubspaces!) { + // Tag without UUID + if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) { + final List tagUpdates = []; - if (newSubspaceModel.tags != null) { - for (var tag in newSubspaceModel.tags!) { - tagUpdatesInSubspace.add(TagModelUpdate( + if (newSubspace.tags != null) { + for (var tag in newSubspace.tags!) { + tagUpdates.add(TagModelUpdate( + action: Action.add, + tag: tag.tag, + productUuid: tag.product?.uuid)); + } + } + subspaceUpdates.add(UpdateSubspaceTemplateModel( action: Action.add, - uuid: tag.uuid, - tag: tag.tag, - productUuid: tag.product?.uuid, - )); - } + subspaceName: newSubspace.subspaceName, + tags: tagUpdates)); } - - subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.add, - subspaceName: newSubspaceModel.subspaceName, - tags: tagUpdatesInSubspace, - )); } } - } - if (newSpaceModel.subspaceModels != null && - prevSpaceModel.subspaceModels != null) { - final prevSubspaceMap = { - for (var subspace in prevSpaceModel.subspaceModels!) - subspace.uuid: subspace - }; + if (prevSubspaces != null && newSubspaces != null) { + final newSubspaceMap = { + for (var subspace in newSubspaces!) subspace.uuid: subspace + }; - for (var newSubspace in newSpaceModel.subspaceModels!) { - if (newSubspace.uuid != null && - prevSubspaceMap.containsKey(newSubspace.uuid)) { - final prevSubspace = prevSubspaceMap[newSubspace.uuid]!; - - // Check if modelName has changed - if (newSubspace.subspaceName != prevSubspace.subspaceName) { + for (var prevSubspace in prevSubspaces!) { + final newSubspace = newSubspaceMap[prevSubspace.uuid]; + if (newSubspace != null) { + final List tagSubspaceUpdates = + processTagUpdates(prevSubspace.tags, newSubspace.tags); subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.update, - uuid: newSubspace.uuid, - subspaceName: newSubspace.subspaceName, - )); - } - - // Compare tags within the subspace - final List tagUpdatesInSubspace = []; - if (prevSubspace.tags != null && newSubspace.tags != null) { - final prevTagMap = { - for (var tag in prevSubspace.tags!) tag.uuid: tag - }; - - // Check for deleted tags - for (var prevTag in prevSubspace.tags!) { - if (!newSubspace.tags! - .any((newTag) => newTag.uuid == prevTag.uuid)) { - tagUpdatesInSubspace.add(TagModelUpdate( - action: Action.delete, - uuid: prevTag.uuid, - )); - } - } - - // Check for added tags, including those without a UUID - for (var newTag in newSubspace.tags!) { - if (newTag.uuid == null || newTag.uuid!.isEmpty) { - // Add new tag without UUID - tagUpdatesInSubspace.add(TagModelUpdate( - action: Action.add, - uuid: null, // or generate a new UUID if required - tag: newTag.tag, - productUuid: newTag.product?.uuid, - )); - } else if (!prevSubspace.tags! - .any((prevTag) => prevTag.uuid == newTag.uuid)) { - // Add new tag with UUID - tagUpdatesInSubspace.add(TagModelUpdate( - action: Action.add, - uuid: newTag.uuid, - tag: newTag.tag, - productUuid: newTag.product?.uuid, - )); - } - } - - // Check for updated tags - for (var prevTag in prevSubspace.tags!) { - final newTag = newSubspace.tags!.cast().firstWhere( - (tag) => tag?.uuid == prevTag.uuid, - orElse: () => null, - ); - if (newTag != null && newTag.tag != prevTag.tag) { - tagUpdatesInSubspace.add(TagModelUpdate( - action: Action.update, - uuid: newTag.uuid, - tag: newTag.tag, - )); - } - } - } - - // Add the subspace with updated tags if necessary - if (tagUpdatesInSubspace.isNotEmpty) { - subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.update, - uuid: newSubspace.uuid, - subspaceName: newSubspace.subspaceName, - tags: tagUpdatesInSubspace, - )); - } + action: Action.update, + uuid: newSubspace.uuid, + subspaceName: newSubspace.subspaceName, + tags: tagSubspaceUpdates)); + } else {} } } } @@ -382,6 +309,11 @@ class CreateSpaceModelBloc .add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid)); } } + } else if (prevTags != null && newTags == null) { + for (var prevTag in prevTags) { + tagUpdates + .add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid)); + } } // Case 2: Tags added diff --git a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart index cb8d0aac..9b61f1b0 100644 --- a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart +++ b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart @@ -47,4 +47,9 @@ class CreateSpaceTemplateBodyModel { 'subspaceModels': subspaceModels, }; } + + @override + String toString() { + return toJson().toString(); + } } diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index eb896432..625397c7 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -37,6 +37,7 @@ class SpaceModelManagementApi { Future updateSpaceModel( CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid) async { + print(spaceModel.toJson().toString()); final response = await HTTPService().put( path: ApiEndpoints.updateSpaceModel .replaceAll('{projectId}', TempConst.projectId).replaceAll('{spaceModelUuid}', spaceModelUuid), From 830725254f62d8ad850d17e99b331eb44c64ec68 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 22 Jan 2025 17:22:58 +0400 Subject: [PATCH 105/106] removed logs --- .../all_spaces/bloc/space_management_bloc.dart | 2 -- .../all_spaces/widgets/community_structure_widget.dart | 4 +--- lib/services/space_model_mang_api.dart | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 7e9c6ce3..ff584f52 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -408,9 +408,7 @@ class SpaceManagementBloc Future> saveSpacesHierarchically( List spaces, String communityUuid) async { - print("space"); final orderedSpaces = flattenHierarchy(spaces); - print("parent"); final parentsToDelete = orderedSpaces.where((space) => space.status == SpaceStatus.deleted && diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index dc0ad1cd..f569d252 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -463,9 +463,7 @@ class _CommunityStructureAreaState extends State { _markChildrenAsDeleted(space); } } - for (var space in spaces) { - print("space ${space.name} and ${space.status}"); - } + _removeConnectionsForDeletedSpaces(); }); } diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index 625397c7..eb896432 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -37,7 +37,6 @@ class SpaceModelManagementApi { Future updateSpaceModel( CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid) async { - print(spaceModel.toJson().toString()); final response = await HTTPService().put( path: ApiEndpoints.updateSpaceModel .replaceAll('{projectId}', TempConst.projectId).replaceAll('{spaceModelUuid}', spaceModelUuid), From bc4af6a237bbd213ba26e2e7dd1ffa9bb9958459 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 22 Jan 2025 17:24:19 +0300 Subject: [PATCH 106/106] user_agreement --- lib/pages/home/bloc/home_bloc.dart | 6 ++---- linux/flutter/generated_plugin_registrant.cc | 4 ++++ linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 ++ windows/flutter/generated_plugin_registrant.cc | 3 +++ windows/flutter/generated_plugins.cmake | 1 + 6 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 8c887810..1d4bdf8b 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -51,7 +51,6 @@ class HomeBloc extends Bloc { await const FlutterSecureStorage().read(key: UserModel.userUuidKey); user = await HomeApi().fetchUserInfo(uuid); add(FetchTermEvent()); - add(FetchPolicyEvent()); emit(HomeInitial()); } catch (e) { return; @@ -62,7 +61,7 @@ class HomeBloc extends Bloc { try { emit(LoadingHome()); terms = await HomeApi().fetchTerms(); - emit(TermsAgreement()); + add(FetchPolicyEvent()); } catch (e) { return; } @@ -72,7 +71,6 @@ class HomeBloc extends Bloc { try { emit(LoadingHome()); policy = await HomeApi().fetchPolicy(); - emit(PolicyAgreement()); } catch (e) { return; } @@ -90,7 +88,7 @@ class HomeBloc extends Bloc { return; } } - + // static Future fetchUserInfo() async { // try { // var uuid = diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index d0e7f797..38dd0bc6 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,13 @@ #include "generated_plugin_registrant.h" #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index b29e9ba0..65240e99 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_linux + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 37af1fe0..51aae316 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,9 +8,11 @@ import Foundation import flutter_secure_storage_macos import path_provider_foundation import shared_preferences_foundation +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 0c507538..2048c455 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 4fc759c4..de626cc8 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_windows + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST