Compare commits

..

9 Commits

Author SHA1 Message Date
5ab9664318 Merged with dev 2025-02-02 11:24:45 +03:00
d3bf4de0ca Merge pull request #78 from SyncrowIOT/side_tree
fixed issues in the space tree
2025-02-02 02:48:17 +03:00
e6fa9c2391 Merge pull request #76 from SyncrowIOT/bugfix/empty-subspace
add empty subspace validation
2025-01-31 10:58:11 +04:00
916b606cb1 deleting subspace won't remove tag 2025-01-30 23:26:26 +04:00
29c444eede updated tags 2025-01-30 22:01:08 +04:00
9dd6c9e1e7 updated logic of adding tag to subspace 2025-01-30 21:15:00 +04:00
b070884bd9 Fix bugs related to the user table, privacy policy, and table filter. 2025-01-30 16:43:45 +03:00
bf5b39e742 add empty subspace validation 2025-01-30 15:00:40 +04:00
7d05a33c52 Merge pull request #75 from SyncrowIOT/side_tree
Side tree
2025-01-30 12:26:48 +03:00
33 changed files with 733 additions and 805 deletions

View File

@ -31,7 +31,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
////////////////////////////// forget password ////////////////////////////////// ////////////////////////////// forget password //////////////////////////////////
final TextEditingController forgetEmailController = TextEditingController(); final TextEditingController forgetEmailController = TextEditingController();
final TextEditingController forgetPasswordController = TextEditingController(); final TextEditingController forgetPasswordController =
TextEditingController();
final TextEditingController forgetOtp = TextEditingController(); final TextEditingController forgetOtp = TextEditingController();
final forgetFormKey = GlobalKey<FormState>(); final forgetFormKey = GlobalKey<FormState>();
final forgetEmailKey = GlobalKey<FormState>(); final forgetEmailKey = GlobalKey<FormState>();
@ -48,7 +49,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
return; return;
} }
_remainingTime = 1; _remainingTime = 1;
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
try { try {
forgetEmailValidate = ''; forgetEmailValidate = '';
_remainingTime = (await AuthenticationAPI.sendOtp( _remainingTime = (await AuthenticationAPI.sendOtp(
@ -85,7 +87,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
_timer?.cancel(); _timer?.cancel();
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true)); add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
} else { } else {
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
} }
}); });
} }
@ -95,7 +98,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
} }
Future<void> changePassword(ChangePasswordEvent event, Emitter<AuthState> emit) async { Future<void> changePassword(
ChangePasswordEvent event, Emitter<AuthState> emit) async {
emit(LoadingForgetState()); emit(LoadingForgetState());
try { try {
var response = await AuthenticationAPI.verifyOtp( var response = await AuthenticationAPI.verifyOtp(
@ -111,7 +115,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} }
} on DioException catch (e) { } on DioException catch (e) {
final errorData = e.response!.data; final errorData = e.response!.data;
String errorMessage = errorData['error']['message'] ?? 'something went wrong'; String errorMessage =
errorData['error']['message'] ?? 'something went wrong';
validate = errorMessage; validate = errorMessage;
emit(AuthInitialState()); emit(AuthInitialState());
} }
@ -125,7 +130,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} }
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) { void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime)); emit(TimerState(
isButtonEnabled: event.isButtonEnabled,
remainingTime: event.remainingTime));
} }
///////////////////////////////////// login ///////////////////////////////////// ///////////////////////////////////// login /////////////////////////////////////
@ -161,15 +168,23 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
password: event.password, password: event.password,
), ),
); );
} catch (failure) { } on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['error']['message'];
if (errorMessage == "Access denied for web platform") {
validate = errorMessage;
} else {
validate = 'Invalid Credentials!'; validate = 'Invalid Credentials!';
}
emit(LoginInitial()); emit(LoginInitial());
return; return;
} }
if (token.accessTokenIsNotEmpty) { if (token.accessTokenIsNotEmpty) {
FlutterSecureStorage storage = const FlutterSecureStorage(); FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken); await storage.write(
key: Token.loginAccessTokenKey, value: token.accessToken);
const FlutterSecureStorage().write( const FlutterSecureStorage().write(
key: UserModel.userUuidKey, key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString()); value: Token.decodeToken(token.accessToken)['uuid'].toString());
@ -327,12 +342,14 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
static Future<String> getTokenAndValidate() async { static Future<String> getTokenAndValidate() async {
try { try {
const storage = FlutterSecureStorage(); const storage = FlutterSecureStorage();
final firstLaunch = final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(
await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true; StringsManager.firstLaunch) ??
true;
if (firstLaunch) { if (firstLaunch) {
storage.deleteAll(); storage.deleteAll();
} }
await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false); await SharedPreferencesHelper.saveBoolToSP(
StringsManager.firstLaunch, false);
final value = await storage.read(key: Token.loginAccessTokenKey) ?? ''; final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
if (value.isEmpty) { if (value.isEmpty) {
return 'Token not found'; return 'Token not found';
@ -385,7 +402,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
final String formattedTime = [ final String formattedTime = [
if (days > 0) '${days}d', // Append 'd' for days if (days > 0) '${days}d', // Append 'd' for days
if (days > 0 || hours > 0) if (days > 0 || hours > 0)
hours.toString().padLeft(2, '0'), // Show hours if there are days or hours hours
.toString()
.padLeft(2, '0'), // Show hours if there are days or hours
minutes.toString().padLeft(2, '0'), minutes.toString().padLeft(2, '0'),
seconds.toString().padLeft(2, '0'), seconds.toString().padLeft(2, '0'),
].join(':'); ].join(':');

View File

@ -21,7 +21,9 @@ class LoginWithEmailModel {
return { return {
'email': email, 'email': email,
'password': password, 'password': password,
"platform": "web"
// 'regionUuid': regionUuid, // 'regionUuid': regionUuid,
}; };
} }
} }
//tst@tst.com

View File

@ -1,8 +1,6 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/devices_mang_api.dart';
part 'device_managment_event.dart'; part 'device_managment_event.dart';
@ -34,20 +32,7 @@ class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementS
Future<void> _onFetchDevices(FetchDevices event, Emitter<DeviceManagementState> emit) async { Future<void> _onFetchDevices(FetchDevices event, Emitter<DeviceManagementState> emit) async {
emit(DeviceManagementLoading()); emit(DeviceManagementLoading());
try { try {
List<AllDevicesModel> devices = []; final devices = await DevicesManagementApi().fetchDevices(event.communityId, event.spaceId);
_devices.clear();
var spaceBloc = event.context.read<SpaceTreeBloc>();
if (spaceBloc.state.selectedCommunities.isEmpty) {
devices = await DevicesManagementApi().fetchDevices('', '');
} else {
for (var community in spaceBloc.state.selectedCommunities) {
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
for (var space in spacesList) {
devices.addAll(await DevicesManagementApi().fetchDevices(community, space));
}
}
}
_selectedDevices.clear(); _selectedDevices.clear();
_devices = devices; _devices = devices;
_filteredDevices = devices; _filteredDevices = devices;

View File

@ -8,13 +8,12 @@ abstract class DeviceManagementEvent extends Equatable {
} }
class FetchDevices extends DeviceManagementEvent { class FetchDevices extends DeviceManagementEvent {
// final Map<String, List<String>> selectedCommunitiesSpaces; final String communityId;
// final String spaceId; final String spaceId;
final BuildContext context;
const FetchDevices(this.context); const FetchDevices(this.communityId, this.spaceId);
@override @override
List<Object?> get props => [context]; List<Object?> get props => [communityId, spaceId];
} }
class FilterDevices extends DeviceManagementEvent { class FilterDevices extends DeviceManagementEvent {

View File

@ -19,7 +19,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider( BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices(context)), create: (context) => DeviceManagementBloc()..add(const FetchDevices('', '')),
), ),
], ],
child: WebScaffold( child: WebScaffold(

View File

@ -8,7 +8,6 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/utils/format_date_time.dart'; import 'package:syncrow_web/utils/format_date_time.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -63,13 +62,14 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
return Row( return Row(
children: [ children: [
Expanded(child: SpaceTreeView( const Expanded(
onSelect: () { child: SpaceTreeView(
context.read<DeviceManagementBloc>().add(FetchDevices(context)); // onSelectAction: (String communityId, String spaceId) {
}, // context.read<DeviceManagementBloc>().add(FetchDevices(communityId, spaceId));
// },
)), )),
Expanded( Expanded(
flex: 4, flex: 3,
child: state is DeviceManagementLoading child: state is DeviceManagementLoading
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: Column( : Column(

View File

@ -85,7 +85,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters> with HelperRe
productNameController.clear(); productNameController.clear();
context.read<DeviceManagementBloc>() context.read<DeviceManagementBloc>()
..add(ResetFilters()) ..add(ResetFilters())
..add(FetchDevices(context)); ..add(const FetchDevices('', ''));
}, },
); );
} }

View File

@ -62,6 +62,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
emit(LoadingHome()); emit(LoadingHome());
terms = await HomeApi().fetchTerms(); terms = await HomeApi().fetchTerms();
add(FetchPolicyEvent()); add(FetchPolicyEvent());
emit(PolicyAgreement());
} catch (e) { } catch (e) {
return; return;
} }
@ -71,6 +72,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
try { try {
emit(LoadingHome()); emit(LoadingHome());
policy = await HomeApi().fetchPolicy(); policy = await HomeApi().fetchPolicy();
emit(PolicyAgreement());
} catch (e) { } catch (e) {
return; return;
} }

View File

@ -63,9 +63,11 @@ class _AgreementAndPrivacyDialogState extends State<AgreementAndPrivacyDialog> {
} }
String get _dialogTitle => String get _dialogTitle =>
_currentPage == 2 ? 'User Agreement' : 'Privacy Policy'; _currentPage == 1 ? 'User Agreement' : 'Privacy Policy';
String get _dialogContent => _currentPage == 2 ? widget.terms : widget.policy; String get _dialogContent => _currentPage == 1 ? widget.terms : widget.policy;
final String staticText =
'<h5 style="color: #FF5722;">If you cancel you will be logged out.</h5>';
Widget _buildScrollableContent() { Widget _buildScrollableContent() {
return Container( return Container(
@ -85,7 +87,7 @@ class _AgreementAndPrivacyDialogState extends State<AgreementAndPrivacyDialog> {
controller: _scrollController, controller: _scrollController,
padding: const EdgeInsets.all(25), padding: const EdgeInsets.all(25),
child: Html( child: Html(
data: _dialogContent, data: "$_dialogContent $staticText",
onLinkTap: (url, attributes, element) async { onLinkTap: (url, attributes, element) async {
if (url != null) { if (url != null) {
final uri = Uri.parse(url); final uri = Uri.parse(url);

View File

@ -24,7 +24,7 @@ class HomeWebPage extends StatelessWidget {
listener: (BuildContext context, state) { listener: (BuildContext context, state) {
if (state is HomeInitial) { if (state is HomeInitial) {
if (homeBloc.user!.hasAcceptedWebAgreement == false) { if (homeBloc.user!.hasAcceptedWebAgreement == false) {
Future.delayed(const Duration(seconds: 1), () { Future.delayed(const Duration(seconds: 2), () {
showDialog( showDialog(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,

View File

@ -42,7 +42,9 @@ class RolesUserModel {
invitedBy: invitedBy:
json['invitedBy'].toString().toLowerCase().replaceAll("_", " "), json['invitedBy'].toString().toLowerCase().replaceAll("_", " "),
phoneNumber: json['phoneNumber'], phoneNumber: json['phoneNumber'],
jobTitle: json['jobTitle'] ?? "-", jobTitle: json['jobTitle'] == null || json['jobTitle'] == " "
? "_"
: json['jobTitle'],
createdDate: json['createdDate'], createdDate: json['createdDate'],
createdTime: json['createdTime'], createdTime: json['createdTime'],
); );

View File

@ -79,13 +79,14 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
List<TreeNode> updatedCommunities = []; List<TreeNode> updatedCommunities = [];
List<TreeNode> spacesNodes = []; List<TreeNode> spacesNodes = [];
List<String> communityIds = [];
_onLoadCommunityAndSpaces( _onLoadCommunityAndSpaces(
LoadCommunityAndSpacesEvent event, Emitter<UsersState> emit) async { LoadCommunityAndSpacesEvent event, Emitter<UsersState> emit) async {
try { try {
emit(UsersLoadingState()); emit(UsersLoadingState());
List<CommunityModel> communities = List<CommunityModel> communities =
await CommunitySpaceManagementApi().fetchCommunities(); await CommunitySpaceManagementApi().fetchCommunities();
communityIds = communities.map((community) => community.uuid).toList();
updatedCommunities = await Future.wait( updatedCommunities = await Future.wait(
communities.map((community) async { communities.map((community) async {
List<SpaceModel> spaces = List<SpaceModel> spaces =
@ -102,7 +103,6 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
}).toList(), }).toList(),
); );
emit(const SpacesLoadedState()); emit(const SpacesLoadedState());
return updatedCommunities;
} catch (e) { } catch (e) {
emit(ErrorState('Error loading communities and spaces: $e')); emit(ErrorState('Error loading communities and spaces: $e'));
} }
@ -177,7 +177,6 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
try { try {
emit(UsersLoadingState()); emit(UsersLoadingState());
roles = await UserPermissionApi().fetchRoles(); roles = await UserPermissionApi().fetchRoles();
// add(PermissionEvent(roleUuid: roles.first.uuid));
emit(RolePermissionInitial()); emit(RolePermissionInitial());
} catch (e) { } catch (e) {
emit(ErrorState('Error loading communities and spaces: $e')); emit(ErrorState('Error loading communities and spaces: $e'));
@ -208,10 +207,13 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
return anyMatch; return anyMatch;
} }
_sendInvitUser(SendInviteUsers event, Emitter<UsersState> emit) async { void _sendInvitUser(SendInviteUsers event, Emitter<UsersState> emit) async {
try { try {
emit(UsersLoadingState()); emit(UsersLoadingState());
List<String> selectedIds = getSelectedIds(updatedCommunities); List<String> selectedIds = getSelectedIds(updatedCommunities)
.where((id) => !communityIds.contains(id))
.toList();
bool res = await UserPermissionApi().sendInviteUser( bool res = await UserPermissionApi().sendInviteUser(
email: emailController.text, email: emailController.text,
firstName: firstNameController.text, firstName: firstNameController.text,
@ -221,7 +223,8 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
roleUuid: roleSelected, roleUuid: roleSelected,
spaceUuids: selectedIds, spaceUuids: selectedIds,
); );
if (res == true) {
if (res) {
showCustomDialog( showCustomDialog(
barrierDismissible: false, barrierDismissible: false,
context: event.context, context: event.context,
@ -248,10 +251,14 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
} }
} }
_editInviteUser(EditInviteUsers event, Emitter<UsersState> emit) async { _editInviteUser(EditInviteUsers event, Emitter<UsersState> emit) async {
try { try {
emit(UsersLoadingState()); emit(UsersLoadingState());
List<String> selectedIds = getSelectedIds(updatedCommunities); List<String> selectedIds = getSelectedIds(updatedCommunities)
.where((id) => !communityIds.contains(id))
.toList();
bool res = await UserPermissionApi().editInviteUser( bool res = await UserPermissionApi().editInviteUser(
userId: event.userId, userId: event.userId,
firstName: firstNameController.text, firstName: firstNameController.text,

View File

@ -218,7 +218,7 @@ class BasicsView extends StatelessWidget {
if (_blocRole.checkEmailValid != "Valid email") { if (_blocRole.checkEmailValid != "Valid email") {
return _blocRole.checkEmailValid; return _blocRole.checkEmailValid;
} }
return null; // return null;
}, },
), ),
), ),

View File

@ -81,7 +81,7 @@ Future<void> showPopUpFilterMenu({
), ),
const Divider(), const Divider(),
const Text( const Text(
"Filter by Status", "Filter by ",
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ),
Container( Container(

View File

@ -40,9 +40,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
roleTypes.clear(); roleTypes.clear();
jobTitle.clear(); jobTitle.clear();
createdBy.clear(); createdBy.clear();
// deActivate.clear();
users = await UserPermissionApi().fetchUsers(); users = await UserPermissionApi().fetchUsers();
users.sort((a, b) { users.sort((a, b) {
final dateA = _parseDateTime(a.createdDate); final dateA = _parseDateTime(a.createdDate);
final dateB = _parseDateTime(b.createdDate); final dateB = _parseDateTime(b.createdDate);
@ -57,15 +55,12 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
for (var user in users) { for (var user in users) {
createdBy.add(user.invitedBy.toString()); createdBy.add(user.invitedBy.toString());
} }
// for (var user in users) {
// deActivate.add(user.status.toString());
// }
initialUsers = List.from(users); initialUsers = List.from(users);
roleTypes = roleTypes.toSet().toList(); roleTypes = roleTypes.toSet().toList();
jobTitle = jobTitle.toSet().toList(); jobTitle = jobTitle.toSet().toList();
createdBy = createdBy.toSet().toList(); createdBy = createdBy.toSet().toList();
// deActivate = deActivate.toSet().toList();
_handlePageChange(ChangePage(1), emit); _handlePageChange(ChangePage(1), emit);
add(ChangePage(currentPage));
emit(UsersLoadedState(users: users)); emit(UsersLoadedState(users: users));
} catch (e) { } catch (e) {
emit(ErrorState(e.toString())); emit(ErrorState(e.toString()));
@ -125,6 +120,10 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
void _toggleSortUsersByNameAsc( void _toggleSortUsersByNameAsc(
SortUsersByNameAsc event, Emitter<UserTableState> emit) { SortUsersByNameAsc event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
if (currentSortOrder == "Asc") { if (currentSortOrder == "Asc") {
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = ""; currentSortOrder = "";
@ -143,13 +142,16 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
void _toggleSortUsersByNameDesc( void _toggleSortUsersByNameDesc(
SortUsersByNameDesc event, Emitter<UserTableState> emit) { SortUsersByNameDesc event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
if (currentSortOrder == "Desc") { if (currentSortOrder == "Desc") {
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = ""; currentSortOrder = "";
users = List.from(initialUsers); // Reset to saved initial state users = List.from(initialUsers);
emit(UsersLoadedState(users: users)); emit(UsersLoadedState(users: users));
} else { } else {
// Sort descending
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = "Desc"; currentSortOrder = "Desc";
users.sort((a, b) => b.firstName!.compareTo(a.firstName!)); users.sort((a, b) => b.firstName!.compareTo(a.firstName!));
@ -159,6 +161,10 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
void _toggleSortUsersByDateNewestToOldest( void _toggleSortUsersByDateNewestToOldest(
DateNewestToOldestEvent event, Emitter<UserTableState> emit) { DateNewestToOldestEvent event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
if (currentSortOrderDate == "NewestToOldest") { if (currentSortOrderDate == "NewestToOldest") {
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = ""; currentSortOrder = "";
@ -179,6 +185,10 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
void _toggleSortUsersByDateOldestToNewest( void _toggleSortUsersByDateOldestToNewest(
DateOldestToNewestEvent event, Emitter<UserTableState> emit) { DateOldestToNewestEvent event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
if (currentSortOrderDate == "OldestToNewest") { if (currentSortOrderDate == "OldestToNewest") {
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = ""; currentSortOrder = "";
@ -337,7 +347,20 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
final filteredUsers = initialUsers.where((user) { final filteredUsers = initialUsers.where((user) {
if (selectedStatuses.isEmpty) return true; if (selectedStatuses.isEmpty) return true;
return selectedStatuses.contains(user.status);
return selectedStatuses.any((status) {
final userStatus = user.status?.toLowerCase() ?? '';
switch (status.toLowerCase()) {
case 'active':
return user.isEnabled == true && userStatus != 'invited';
case 'disabled':
return user.isEnabled == false;
case 'invited':
return userStatus == 'invited';
default:
return false;
}
});
}).toList(); }).toList();
if (event.sortOrder == "Asc") { if (event.sortOrder == "Asc") {
currentSortOrder = "Asc"; currentSortOrder = "Asc";
@ -351,9 +374,11 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} else { } else {
currentSortOrder = ""; currentSortOrder = "";
} }
emit(UsersLoadedState(users: filteredUsers)); emit(UsersLoadedState(users: filteredUsers));
} }
void _resetAllFilters(Emitter<UserTableState> emit) { void _resetAllFilters(Emitter<UserTableState> emit) {
selectedRoles.clear(); selectedRoles.clear();
selectedJobTitles.clear(); selectedJobTitles.clear();

View File

@ -12,7 +12,7 @@ Future<void> showDateFilterMenu({
Overlay.of(context).context.findRenderObject() as RenderBox; Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect( final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB( Rect.fromLTRB(
overlay.size.width / 2, overlay.size.width / 3,
240, 240,
0, 0,
overlay.size.height, overlay.size.height,
@ -40,7 +40,6 @@ Future<void> showDateFilterMenu({
), ),
title: Text( title: Text(
"Sort from newest to oldest", "Sort from newest to oldest",
// style: context.textTheme.bodyMedium,
style: TextStyle( style: TextStyle(
color: isSelected == "NewestToOldest" color: isSelected == "NewestToOldest"
? Colors.black ? Colors.black
@ -65,9 +64,5 @@ Future<void> showDateFilterMenu({
), ),
), ),
], ],
).then((value) { ).then((value) {});
// setState(() {
// _isDropdownOpen = false;
// });
});
} }

View File

@ -40,7 +40,6 @@ Future<void> showDeActivateFilterMenu({
), ),
title: Text( title: Text(
"Sort A to Z", "Sort A to Z",
// style: context.textTheme.bodyMedium,
style: TextStyle( style: TextStyle(
color: isSelected == "NewestToOldest" color: isSelected == "NewestToOldest"
? Colors.black ? Colors.black
@ -65,9 +64,5 @@ Future<void> showDeActivateFilterMenu({
), ),
), ),
], ],
).then((value) { ).then((value) {});
// setState(() {
// _isDropdownOpen = false;
// });
});
} }

View File

@ -12,7 +12,7 @@ Future<void> showNameMenu({
Overlay.of(context).context.findRenderObject() as RenderBox; Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect( final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB( Rect.fromLTRB(
overlay.size.width / 25, overlay.size.width / 35,
240, 240,
0, 0,
overlay.size.height, overlay.size.height,
@ -40,7 +40,6 @@ Future<void> showNameMenu({
), ),
title: Text( title: Text(
"Sort A to Z", "Sort A to Z",
// style: context.textTheme.bodyMedium,
style: TextStyle( style: TextStyle(
color: isSelected == "Asc" ? Colors.black : Colors.blueGrey), color: isSelected == "Asc" ? Colors.black : Colors.blueGrey),
), ),
@ -61,9 +60,5 @@ Future<void> showNameMenu({
), ),
), ),
], ],
).then((value) { ).then((value) {});
// setState(() {
// _isDropdownOpen = false;
// });
});
} }

View File

@ -1,260 +1,264 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class DynamicTableScreen extends StatefulWidget { class _HeaderColumn extends StatelessWidget {
final List<String> titles; final String title;
final List<List<Widget>> rows; final double width;
final void Function(int columnIndex)? onFilter; final bool showFilter;
final VoidCallback? onFilter;
final Function(double) onResize;
DynamicTableScreen( const _HeaderColumn({
{required this.titles, required this.rows, required this.onFilter}); required this.title,
required this.width,
@override required this.showFilter,
_DynamicTableScreenState createState() => _DynamicTableScreenState(); required this.onResize,
} this.onFilter,
Key? key,
class _DynamicTableScreenState extends State<DynamicTableScreen> }) : super(key: key);
with WidgetsBindingObserver {
late List<double> columnWidths;
late double totalWidth;
@override
void initState() {
super.initState();
columnWidths = List<double>.filled(widget.titles.length, 150.0);
totalWidth = columnWidths.reduce((a, b) => a + b);
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeMetrics() {
super.didChangeMetrics();
final newScreenWidth = MediaQuery.of(context).size.width;
setState(() {
columnWidths = List<double>.generate(widget.titles.length, (index) {
if (index == 1) {
return newScreenWidth *
0.12; // 20% of screen width for the second column
} else if (index == 9) {
return newScreenWidth *
0.1; // 25% of screen width for the tenth column
}
return newScreenWidth *
0.09; // Default to 10% of screen width for other columns
});
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width; return MouseRegion(
if (columnWidths.every((width) => width == screenWidth * 7)) { cursor: SystemMouseCursors.resizeColumn,
columnWidths = List<double>.generate(widget.titles.length, (index) { child: GestureDetector(
if (index == 1) { onHorizontalDragUpdate: (details) => onResize(details.delta.dx),
return screenWidth * 0.11;
} else if (index == 9) {
return screenWidth * 0.1;
}
return screenWidth * 0.09;
});
setState(() {});
}
return SingleChildScrollView(
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
child: Container( child: Container(
decoration: containerDecoration.copyWith( width: width,
color: ColorsManager.whiteColors, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
borderRadius: const BorderRadius.all(Radius.circular(20))), decoration: const BoxDecoration(
child: FittedBox( border: Border(right: BorderSide(color: ColorsManager.boxDivider)),
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( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
SizedBox( Expanded(
child: Text( child: Text(
widget.titles[index], title,
maxLines: 2, maxLines: 2,
style: const TextStyle(
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
fontSize: 13, fontSize: 13,
color: ColorsManager.grayColor, color: ColorsManager.grayColor,
), ),
), ),
), ),
if (index != 1 && if (showFilter)
index != 9 && IconButton(
index != 8 && icon: SvgPicture.asset(Assets.filterTableIcon),
index != 5) onPressed: onFilter,
FittedBox( padding: EdgeInsets.zero,
child: IconButton( constraints: const BoxConstraints(),
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) class _TableRow extends StatelessWidget {
.clamp(150.0, 300.0); final List<Widget> cells;
totalWidth = columnWidths.reduce((a, b) => a + b); final List<double> columnWidths;
}); final bool isLast;
},
child: MouseRegion( const _TableRow({
cursor: SystemMouseCursors.resizeColumn, required this.cells,
child: Container( required this.columnWidths,
color: Colors.green, required this.isLast,
child: Container( Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
children: [
for (int i = 0; i < cells.length; i++)
Container(
width: columnWidths[i],
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
// decoration: BoxDecoration(
// border: Border(
// right: BorderSide(color: ColorsManager.boxDivider),
// ),
// ),
child: cells[i],
),
],
),
if (!isLast)
Divider(
height: 1,
thickness: 1,
color: ColorsManager.boxDivider, color: ColorsManager.boxDivider,
width: 1,
height: 50,
),
),
),
), ),
], ],
); );
}), }
}
//===========================================================================
class DynamicTableScreen extends StatefulWidget {
final List<String> titles;
final List<List<Widget>> rows;
final void Function(int columnIndex)? onFilter;
const DynamicTableScreen({
required this.titles,
required this.rows,
required this.onFilter,
Key? key,
}) : super(key: key);
@override
_DynamicTableScreenState createState() => _DynamicTableScreenState();
}
class _DynamicTableScreenState extends State<DynamicTableScreen> {
late List<double> columnWidths;
final double _minColumnWidth = 100.0;
final double _maxColumnWidth = 300.0;
final double _dividerWidth = 1.0;
double _lastAvailableWidth = 0;
@override
void initState() {
super.initState();
columnWidths = List.filled(widget.titles.length, _minColumnWidth);
}
void _handleColumnResize(int index, double delta) {
setState(() {
double newWidth = columnWidths[index] + delta;
newWidth = newWidth.clamp(_minColumnWidth, _maxColumnWidth);
double actualDelta = newWidth - columnWidths[index];
if (actualDelta == 0) return;
int nextIndex = (index + 1) % columnWidths.length;
columnWidths[index] = newWidth;
columnWidths[nextIndex] = (columnWidths[nextIndex] - actualDelta)
.clamp(_minColumnWidth, _maxColumnWidth);
});
}
Widget _buildHeader() {
return Container(
decoration: containerDecoration.copyWith(
color: ColorsManager.circleRolesBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15),
), ),
), ),
widget.rows.isEmpty child: Row(
? SizedBox(
height: MediaQuery.of(context).size.height / 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Column( for (int i = 0; i < widget.titles.length; i++)
_HeaderColumn(
title: widget.titles[i],
width: columnWidths[i],
showFilter: i != 1 && i != 9 && i != 8 && i != 5,
onFilter: () => widget.onFilter?.call(i),
onResize: (delta) => _handleColumnResize(i, delta),
),
],
),
);
}
Widget _buildBody() {
if (widget.rows.isEmpty) {
return SizedBox(
height: 300,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
SvgPicture.asset(Assets.emptyTable), SvgPicture.asset(Assets.emptyTable),
const SizedBox( const SizedBox(height: 15),
height: 15,
),
const Text( const Text(
'No Users', 'No Users',
style: TextStyle( style: TextStyle(
color: ColorsManager.lightGrayColor, color: ColorsManager.lightGrayColor,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700), fontWeight: FontWeight.w700,
) ),
],
), ),
], ],
), ),
) ),
: Center( );
child: Container( }
width: totalWidth,
return Container(
decoration: containerDecoration.copyWith( decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(15), bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15))), bottomRight: Radius.circular(15),
child: ListView.builder( ),
physics: const NeverScrollableScrollPhysics(), ),
shrinkWrap: true, child: Column(
itemCount: widget.rows.length,
itemBuilder: (context, rowIndex) {
if (columnWidths.every((width) => width == 120.0)) {
columnWidths = List<double>.generate(
widget.titles.length, (index) {
if (index == 1) {
return screenWidth * 0.11;
} else if (index == 9) {
return screenWidth * 0.2;
}
return screenWidth * 0.11;
});
setState(() {});
}
final row = widget.rows[rowIndex];
return Column(
children: [ children: [
Container( for (int rowIndex = 0; rowIndex < widget.rows.length; rowIndex++)
child: Padding( _TableRow(
padding: const EdgeInsets.only( cells: widget.rows[rowIndex],
left: 5, top: 10, right: 5, bottom: 10), columnWidths: columnWidths,
child: Row( isLast: rowIndex == widget.rows.length - 1,
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,
),
);
}),
), ),
], ],
),
);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final availableWidth = constraints.maxWidth;
final totalDividersWidth = (widget.titles.length - 1) * _dividerWidth;
if (_lastAvailableWidth != availableWidth) {
final equalWidth =
(availableWidth - totalDividersWidth) / widget.titles.length;
final clampedWidth =
equalWidth.clamp(_minColumnWidth, _maxColumnWidth);
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
columnWidths = List.filled(widget.titles.length, clampedWidth);
_lastAvailableWidth = availableWidth;
});
});
}
final totalTableWidth =
columnWidths.fold(0.0, (sum, w) => sum + w) + totalDividersWidth;
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Container(
width: totalTableWidth,
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
_buildBody(),
],
),
),
); );
}, },
),
),
),
],
),
),
),
); );
} }
} }

View File

@ -108,7 +108,6 @@ class UsersPage extends StatelessWidget {
final screenSize = MediaQuery.of(context).size; final screenSize = MediaQuery.of(context).size;
final _blocRole = BlocProvider.of<UserTableBloc>(context); final _blocRole = BlocProvider.of<UserTableBloc>(context);
if (state is UsersLoadingState) { if (state is UsersLoadingState) {
_blocRole.add(ChangePage(_blocRole.currentPage));
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else if (state is UsersLoadedState) { } else if (state is UsersLoadedState) {
return Padding( return Padding(
@ -215,7 +214,7 @@ class UsersPage extends StatelessWidget {
showPopUpFilterMenu( showPopUpFilterMenu(
position: RelativeRect.fromLTRB( position: RelativeRect.fromLTRB(
overlay.size.width / 4, overlay.size.width / 5.3,
240, 240,
overlay.size.width / 4, overlay.size.width / 4,
0, 0,
@ -225,6 +224,7 @@ class UsersPage extends StatelessWidget {
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortOrder,
onOkPressed: () { onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent()); _blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries final selectedItems = checkboxStates.entries
.where((entry) => entry.value) .where((entry) => entry.value)
@ -265,6 +265,7 @@ class UsersPage extends StatelessWidget {
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortOrder,
onOkPressed: () { onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent()); _blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries final selectedItems = checkboxStates.entries
.where((entry) => entry.value) .where((entry) => entry.value)
@ -320,6 +321,7 @@ class UsersPage extends StatelessWidget {
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortOrder,
onOkPressed: () { onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent()); _blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries final selectedItems = checkboxStates.entries
.where((entry) => entry.value) .where((entry) => entry.value)
@ -343,6 +345,7 @@ class UsersPage extends StatelessWidget {
for (var item in _blocRole.status) for (var item in _blocRole.status)
item: _blocRole.selectedStatuses.contains(item), item: _blocRole.selectedStatuses.contains(item),
}; };
final RenderBox overlay = Overlay.of(context) final RenderBox overlay = Overlay.of(context)
.context .context
.findRenderObject() as RenderBox; .findRenderObject() as RenderBox;
@ -350,7 +353,7 @@ class UsersPage extends StatelessWidget {
position: RelativeRect.fromLTRB( position: RelativeRect.fromLTRB(
overlay.size.width / 0, overlay.size.width / 0,
240, 240,
overlay.size.width / 4, overlay.size.width / 5,
0, 0,
), ),
list: _blocRole.status, list: _blocRole.status,
@ -358,8 +361,8 @@ class UsersPage extends StatelessWidget {
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortOrder,
onOkPressed: () { onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent()); _blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries final selectedItems = checkboxStates.entries
.where((entry) => entry.value) .where((entry) => entry.value)
.map((entry) => entry.key) .map((entry) => entry.key)
@ -410,7 +413,7 @@ class UsersPage extends StatelessWidget {
return [ return [
Text('${user.firstName} ${user.lastName}'), Text('${user.firstName} ${user.lastName}'),
Text(user.email), Text(user.email),
Text(user.jobTitle ?? '-'), Text(user.jobTitle),
Text(user.roleType ?? ''), Text(user.roleType ?? ''),
Text(user.createdDate ?? ''), Text(user.createdDate ?? ''),
Text(user.createdTime ?? ''), Text(user.createdTime ?? ''),
@ -427,11 +430,6 @@ class UsersPage extends StatelessWidget {
userId: user.uuid, userId: user.uuid,
onTap: user.status != "invited" onTap: user.status != "invited"
? () { ? () {
// final newStatus = user.status == 'active'
// ? 'disabled'
// : user.status == 'disabled'
// ? 'invited'
// : 'active';
context.read<UserTableBloc>().add( context.read<UserTableBloc>().add(
ChangeUserStatus( ChangeUserStatus(
userId: user.uuid, userId: user.uuid,
@ -443,10 +441,6 @@ class UsersPage extends StatelessWidget {
), ),
Row( Row(
children: [ children: [
// actionButton(
// title: "Activity Log",
// onTap: () {},
// ),
actionButton( actionButton(
title: "Edit", title: "Edit",
onTap: () { onTap: () {
@ -486,11 +480,9 @@ class UsersPage extends StatelessWidget {
}); });
}, },
).then((v) { ).then((v) {
if (v != null) {
if (v != null) { if (v != null) {
_blocRole.add(const GetUsers()); _blocRole.add(const GetUsers());
} }
}
}); });
}, },
), ),

View File

@ -816,7 +816,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async { FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true)); emit(state.copyWith(isLoading: true));
try { try {
final devices = await DevicesManagementApi().fetchDevices('', ''); final devices = await DevicesManagementApi().fetchDevices(communityId, spaceId);
emit(state.copyWith(isLoading: false, devices: devices)); emit(state.copyWith(isLoading: false, devices: devices));
} catch (e) { } catch (e) {

View File

@ -32,17 +32,23 @@ class _RoutinesViewState extends State<RoutinesView> {
} }
return Row( return Row(
children: [ children: [
const Expanded(
child:
// SideSpacesView(
// onSelectAction: (String communityId, String spaceId) {
// // context.read<RoutineBloc>()
// // ..add(LoadScenes(spaceId, communityId))
// // ..add(LoadAutomation(spaceId));
// },
// )
SpaceTreeView()),
Expanded( Expanded(
child: SpaceTreeView( flex: 3,
onSelect: () {}, child: Padding(
)),
Expanded(
flex: 4,
child: ListView(children: [
Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
height: MediaQuery.sizeOf(context).height, child: Row(
child: Column( children: [
Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@ -80,8 +86,9 @@ class _RoutinesViewState extends State<RoutinesView> {
const Expanded(child: FetchRoutineScenesAutomation()), const Expanded(child: FetchRoutineScenesAutomation()),
], ],
), ),
],
),
), ),
]),
), ),
], ],
); );

View File

@ -6,8 +6,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model
import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart';
class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> { class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
String selectedCommunityId = 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9'; String selectedCommunityId = '';
String selectedSpaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6'; String selectedSpaceId = '';
SpaceTreeBloc() : super(const SpaceTreeState()) { SpaceTreeBloc() : super(const SpaceTreeState()) {
on<InitialEvent>(_fetchSpaces); on<InitialEvent>(_fetchSpaces);
@ -87,7 +87,6 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
List.from(state.selectedCommunities.toSet().toList()); List.from(state.selectedCommunities.toSet().toList());
List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList()); List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList());
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList()); List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
List<String> childrenIds = _getAllChildIds(event.children); List<String> childrenIds = _getAllChildIds(event.children);
@ -102,13 +101,10 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
updatedSoldChecks.removeWhere(childrenIds.contains); updatedSoldChecks.removeWhere(childrenIds.contains);
} }
communityAndSpaces[event.communityId] = updatedSelectedSpaces;
emit(state.copyWith( emit(state.copyWith(
selectedCommunities: updatedSelectedCommunities, selectedCommunities: updatedSelectedCommunities,
selectedSpaces: updatedSelectedSpaces, selectedSpaces: updatedSelectedSpaces,
soldCheck: updatedSoldChecks, soldCheck: updatedSoldChecks));
selectedCommunityAndSpaces: communityAndSpaces));
} catch (e) { } catch (e) {
emit(const SpaceTreeErrorState('Something went wrong')); emit(const SpaceTreeErrorState('Something went wrong'));
} }
@ -120,7 +116,6 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
List.from(state.selectedCommunities.toSet().toList()); List.from(state.selectedCommunities.toSet().toList());
List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList()); List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList());
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList()); List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
List<String> childrenIds = _getAllChildIds(event.children); List<String> childrenIds = _getAllChildIds(event.children);
bool isChildSelected = false; bool isChildSelected = false;
@ -171,13 +166,10 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
} }
} }
communityAndSpaces[event.communityId] = updatedSelectedSpaces;
emit(state.copyWith( emit(state.copyWith(
selectedCommunities: updatedSelectedCommunities, selectedCommunities: updatedSelectedCommunities,
selectedSpaces: updatedSelectedSpaces, selectedSpaces: updatedSelectedSpaces,
soldCheck: updatedSoldChecks, soldCheck: updatedSoldChecks));
selectedCommunityAndSpaces: communityAndSpaces));
emit(state.copyWith(selectedSpaces: updatedSelectedSpaces)); emit(state.copyWith(selectedSpaces: updatedSelectedSpaces));
} catch (e) { } catch (e) {
emit(const SpaceTreeErrorState('Something went wrong')); emit(const SpaceTreeErrorState('Something went wrong'));

View File

@ -2,7 +2,6 @@ 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/community_model.dart';
class SpaceTreeState extends Equatable { class SpaceTreeState extends Equatable {
final Map<String, List<String>> selectedCommunityAndSpaces;
final List<CommunityModel> communityList; final List<CommunityModel> communityList;
final List<CommunityModel> filteredCommunity; final List<CommunityModel> filteredCommunity;
final List<String> expandedCommunities; final List<String> expandedCommunities;
@ -20,8 +19,7 @@ class SpaceTreeState extends Equatable {
this.selectedCommunities = const [], this.selectedCommunities = const [],
this.selectedSpaces = const [], this.selectedSpaces = const [],
this.soldCheck = const [], this.soldCheck = const [],
this.isSearching = false, this.isSearching = false});
this.selectedCommunityAndSpaces = const {}});
SpaceTreeState copyWith( SpaceTreeState copyWith(
{List<CommunityModel>? communitiesList, {List<CommunityModel>? communitiesList,
@ -31,8 +29,7 @@ class SpaceTreeState extends Equatable {
List<String>? selectedCommunities, List<String>? selectedCommunities,
List<String>? selectedSpaces, List<String>? selectedSpaces,
List<String>? soldCheck, List<String>? soldCheck,
bool? isSearching, bool? isSearching}) {
Map<String, List<String>>? selectedCommunityAndSpaces}) {
return SpaceTreeState( return SpaceTreeState(
communityList: communitiesList ?? this.communityList, communityList: communitiesList ?? this.communityList,
filteredCommunity: filteredCommunity ?? this.filteredCommunity, filteredCommunity: filteredCommunity ?? this.filteredCommunity,
@ -41,8 +38,7 @@ class SpaceTreeState extends Equatable {
selectedCommunities: selectedCommunities ?? this.selectedCommunities, selectedCommunities: selectedCommunities ?? this.selectedCommunities,
selectedSpaces: selectedSpaces ?? this.selectedSpaces, selectedSpaces: selectedSpaces ?? this.selectedSpaces,
soldCheck: soldCheck ?? this.soldCheck, soldCheck: soldCheck ?? this.soldCheck,
isSearching: isSearching ?? this.isSearching, isSearching: isSearching ?? this.isSearching);
selectedCommunityAndSpaces: selectedCommunityAndSpaces ?? this.selectedCommunityAndSpaces);
} }
@override @override
@ -54,8 +50,7 @@ class SpaceTreeState extends Equatable {
selectedCommunities, selectedCommunities,
selectedSpaces, selectedSpaces,
soldCheck, soldCheck,
isSearching, isSearching
selectedCommunityAndSpaces
]; ];
} }

View File

@ -10,22 +10,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class SpaceTreeView extends StatefulWidget { class SpaceTreeView extends StatelessWidget {
final Function onSelect; const SpaceTreeView({super.key});
const SpaceTreeView({required this.onSelect, super.key});
@override
State<SpaceTreeView> createState() => _SpaceTreeViewState();
}
class _SpaceTreeViewState extends State<SpaceTreeView> {
final ScrollController _scrollController = ScrollController();
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -34,6 +20,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
return Container( return Container(
height: MediaQuery.sizeOf(context).height, height: MediaQuery.sizeOf(context).height,
decoration: subSectionContainerDecoration, decoration: subSectionContainerDecoration,
// padding: const EdgeInsets.all(16.0),
child: state is SpaceTreeLoadingState child: state is SpaceTreeLoadingState
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: Column( : Column(
@ -45,71 +32,52 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(
child: ListView( child: Padding(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: [
Container(
width: MediaQuery.sizeOf(context).width * 0.5,
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: list.isEmpty child: list.isEmpty
? Center( ? Center(
child: Text( child: Text(
'No results found', 'No results found',
style: Theme.of(context).textTheme.bodySmall!.copyWith( style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.lightGrayColor, color: ColorsManager.lightGrayColor, // Gray when not selected
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
), ),
) )
: Scrollbar( : ListView(
scrollbarOrientation: ScrollbarOrientation.left,
thumbVisibility: true,
controller: _scrollController,
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: ListView(
controller: _scrollController,
shrinkWrap: true, shrinkWrap: true,
children: list children: list
.map( .map(
(community) => CustomExpansionTileSpaceTree( (community) => CustomExpansionTileSpaceTree(
title: community.name, title: community.name,
isSelected: state.selectedCommunities isSelected:
.contains(community.uuid), state.selectedCommunities.contains(community.uuid),
isSoldCheck: state.selectedCommunities isSoldCheck:
.contains(community.uuid), state.selectedCommunities.contains(community.uuid),
onExpansionChanged: () { onExpansionChanged: () {
context context
.read<SpaceTreeBloc>() .read<SpaceTreeBloc>()
.add(OnCommunityExpanded(community.uuid)); .add(OnCommunityExpanded(community.uuid));
}, },
isExpanded: state.expandedCommunities isExpanded:
.contains(community.uuid), state.expandedCommunities.contains(community.uuid),
onItemSelected: () { onItemSelected: () {
context.read<SpaceTreeBloc>().add( context.read<SpaceTreeBloc>().add(
OnCommunitySelected( OnCommunitySelected(community.uuid, community.spaces));
community.uuid, community.spaces));
widget.onSelect();
}, },
children: community.spaces.map((space) { children: community.spaces.map((space) {
return CustomExpansionTileSpaceTree( return CustomExpansionTileSpaceTree(
title: space.name, title: space.name,
isExpanded: isExpanded: state.expandedSpaces.contains(space.uuid),
state.expandedSpaces.contains(space.uuid),
onItemSelected: () { onItemSelected: () {
context.read<SpaceTreeBloc>().add( context.read<SpaceTreeBloc>().add(OnSpaceSelected(
OnSpaceSelected(community.uuid, community.uuid, space.uuid ?? '', space.children));
space.uuid ?? '', space.children));
widget.onSelect();
}, },
onExpansionChanged: () { onExpansionChanged: () {
context.read<SpaceTreeBloc>().add( context.read<SpaceTreeBloc>().add(
OnSpaceExpanded( OnSpaceExpanded(community.uuid, space.uuid ?? ''));
community.uuid, space.uuid ?? ''));
}, },
isSelected: isSelected: state.selectedSpaces.contains(space.uuid) ||
state.selectedSpaces.contains(space.uuid) ||
state.soldCheck.contains(space.uuid), state.soldCheck.contains(space.uuid),
isSoldCheck: state.soldCheck.contains(space.uuid), isSoldCheck: state.soldCheck.contains(space.uuid),
children: _buildNestedSpaces( children: _buildNestedSpaces(
@ -122,73 +90,6 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
), ),
), ),
), ),
),
],
),
),
// Expanded(
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: list.isEmpty
// ? Center(
// child: Text(
// 'No results found',
// style: Theme.of(context).textTheme.bodySmall!.copyWith(
// color: ColorsManager.lightGrayColor, // Gray when not selected
// fontWeight: FontWeight.w400,
// ),
// ),
// )
// : ListView(
// shrinkWrap: true,
// children: list
// .map(
// (community) => CustomExpansionTileSpaceTree(
// title: community.name,
// isSelected:
// state.selectedCommunities.contains(community.uuid),
// isSoldCheck:
// state.selectedCommunities.contains(community.uuid),
// onExpansionChanged: () {
// context
// .read<SpaceTreeBloc>()
// .add(OnCommunityExpanded(community.uuid));
// },
// isExpanded:
// state.expandedCommunities.contains(community.uuid),
// onItemSelected: () {
// context.read<SpaceTreeBloc>().add(
// OnCommunitySelected(community.uuid, community.spaces));
// onSelect();
// },
// children: community.spaces.map((space) {
// return CustomExpansionTileSpaceTree(
// title: space.name,
// isExpanded: state.expandedSpaces.contains(space.uuid),
// onItemSelected: () {
// context.read<SpaceTreeBloc>().add(OnSpaceSelected(
// community.uuid, space.uuid ?? '', space.children));
// onSelect();
// },
// onExpansionChanged: () {
// context.read<SpaceTreeBloc>().add(
// OnSpaceExpanded(community.uuid, space.uuid ?? ''));
// },
// isSelected: state.selectedSpaces.contains(space.uuid) ||
// state.soldCheck.contains(space.uuid),
// isSoldCheck: state.soldCheck.contains(space.uuid),
// children: _buildNestedSpaces(
// context, state, space, community.uuid),
// );
// }).toList(),
// ),
// )
// .toList(),
// ),
// ),
// ),
], ],
), ),
); );
@ -208,7 +109,6 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
context context
.read<SpaceTreeBloc>() .read<SpaceTreeBloc>()
.add(OnSpaceSelected(communityId, child.uuid ?? '', child.children)); .add(OnSpaceSelected(communityId, child.uuid ?? '', child.children));
widget.onSelect();
}, },
onExpansionChanged: () { onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(OnSpaceExpanded(communityId, child.uuid ?? '')); context.read<SpaceTreeBloc>().add(OnSpaceExpanded(communityId, child.uuid ?? ''));

View File

@ -237,7 +237,8 @@ class AssignTagDialog extends StatelessWidget {
final processedTags = final processedTags =
result['updatedTags'] as List<Tag>; result['updatedTags'] as List<Tag>;
final processedSubspaces = result['subspaces']; final processedSubspaces = List<SubspaceModel>.from(
result['subspaces'] as List<dynamic>);
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -276,7 +277,8 @@ class AssignTagDialog extends StatelessWidget {
final processedTags = final processedTags =
result['updatedTags'] as List<Tag>; result['updatedTags'] as List<Tag>;
final processedSubspaces = final processedSubspaces =
result['subspaces'] as List<SubspaceModel>; List<SubspaceModel>.from(
result['subspaces'] as List<dynamic>);
onSave?.call(processedTags, processedSubspaces); onSave?.call(processedTags, processedSubspaces);
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
@ -301,105 +303,31 @@ class AssignTagDialog extends StatelessWidget {
List<String> getAvailableTags( List<String> getAvailableTags(
List<String> allTags, List<Tag> currentTags, Tag currentTag) { List<String> allTags, List<Tag> currentTags, Tag currentTag) {
return allTags List<String> availableTagsForTagModel = TagHelper.getAvailableTags<Tag>(
.where((tagValue) => !currentTags allTags: allTags,
.where((e) => e != currentTag) // Exclude the current row currentTags: currentTags,
.map((e) => e.tag) currentTag: currentTag,
.contains(tagValue)) getTag: (tag) => tag.tag ?? '',
.toList(); );
return availableTagsForTagModel;
} }
Map<String, dynamic> processTags( Map<String, dynamic> processTags(
List<Tag> updatedTags, List<SubspaceModel>? subspaces) { List<Tag> updatedTags, List<SubspaceModel>? subspaces) {
final modifiedTags = List<Tag>.from(updatedTags); return TagHelper.updateTags<Tag>(
final modifiedSubspaces = List<SubspaceModel>.from(subspaces ?? []); updatedTags: updatedTags,
subspaces: subspaces,
if (subspaces != null) { getInternalId: (tag) => tag.internalId,
for (var subspace in subspaces) { getLocation: (tag) => tag.location,
subspace.tags?.removeWhere( setLocation: (tag, location) => tag.location = location,
(tag) => !modifiedTags getSubspaceName: (subspace) => subspace.subspaceName,
.any((updatedTag) => updatedTag.internalId == tag.internalId), getSubspaceTags: (subspace) => subspace.tags,
setSubspaceTags: (subspace, tags) => subspace.tags = tags,
checkTagExistInSubspace: checkTagExistInSubspace,
); );
} }
}
for (var tag in modifiedTags.toList()) {
if (modifiedSubspaces.isEmpty) continue;
final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces); int? checkTagExistInSubspace(Tag tag, List<dynamic>? subspaces) {
if ((tag.location == 'Main Space' || tag.location == null) &&
(prevIndice == null ||
modifiedSubspaces[prevIndice].subspaceName == 'Main Space')) {
continue;
}
if ((tag.location == 'Main Space' || tag.location == null) &&
prevIndice != null) {
modifiedSubspaces[prevIndice]
.tags
?.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location != 'Main Space' && tag.location != null) &&
prevIndice == null) {
final newIndex = modifiedSubspaces
.indexWhere((subspace) => subspace.subspaceName == tag.location);
if (newIndex != -1) {
if (modifiedSubspaces[newIndex]
.tags
?.any((t) => t.internalId == tag.internalId) !=
true) {
tag.location = modifiedSubspaces[newIndex].subspaceName;
modifiedSubspaces[newIndex].tags?.add(tag);
}
}
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location != 'Main Space' && tag.location != null) &&
tag.location != modifiedSubspaces[prevIndice!].subspaceName) {
modifiedSubspaces[prevIndice]
.tags
?.removeWhere((t) => t.internalId == tag.internalId);
final newIndex = modifiedSubspaces
.indexWhere((subspace) => subspace.subspaceName == tag.location);
if (newIndex != -1) {
if (modifiedSubspaces[newIndex]
.tags
?.any((t) => t.internalId == tag.internalId) !=
true) {
tag.location = modifiedSubspaces[newIndex].subspaceName;
modifiedSubspaces[newIndex].tags?.add(tag);
}
}
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location != 'Main Space' && tag.location != null) &&
tag.location == modifiedSubspaces[prevIndice!].subspaceName) {
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location == 'Main Space' || tag.location == null) &&
prevIndice != null) {
modifiedSubspaces[prevIndice]
.tags
?.removeWhere((t) => t.internalId == tag.internalId);
}
}
return {
'updatedTags': modifiedTags,
'subspaces': modifiedSubspaces,
};
}
int? checkTagExistInSubspace(Tag tag, List<SubspaceModel>? subspaces) {
if (subspaces == null) return null; if (subspaces == null) return null;
for (int i = 0; i < subspaces.length; i++) { for (int i = 0; i < subspaces.length; i++) {
final subspace = subspaces[i]; final subspace = subspaces[i];

View File

@ -133,7 +133,8 @@ class AssignTagModelsDialog extends StatelessWidget {
: List.generate(state.tags.length, (index) { : List.generate(state.tags.length, (index) {
final tag = state.tags[index]; final tag = state.tags[index];
final controller = controllers[index]; final controller = controllers[index];
final availableTags = getAvailableTags( final availableTags =
TagHelper.getAvailableTagModels(
allTags ?? [], state.tags, tag); allTags ?? [], state.tags, tag);
return DataRow( return DataRow(
@ -254,11 +255,15 @@ class AssignTagModelsDialog extends StatelessWidget {
final updatedTags = final updatedTags =
List<TagModel>.from(state.tags); List<TagModel>.from(state.tags);
final result = final result =
processTags(updatedTags, subspaces); TagHelper.updateSubspaceTagModels(
updatedTags, subspaces);
final processedTags = final processedTags =
result['updatedTags'] as List<TagModel>; result['updatedTags'] as List<TagModel>;
final processedSubspaces = result['subspaces']; final processedSubspaces =
List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
if (context.mounted) { if (context.mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -305,14 +310,17 @@ class AssignTagModelsDialog extends StatelessWidget {
? () async { ? () async {
final updatedTags = final updatedTags =
List<TagModel>.from(state.tags); List<TagModel>.from(state.tags);
final result = final result =
processTags(updatedTags, subspaces); TagHelper.updateSubspaceTagModels(
updatedTags, subspaces);
final processedTags = final processedTags =
result['updatedTags'] as List<TagModel>; result['updatedTags'] as List<TagModel>;
final processedSubspaces = final processedSubspaces =
List<SubspaceTemplateModel>.from(
result['subspaces'] result['subspaces']
as List<SubspaceTemplateModel>; as List<dynamic>);
Navigator.of(context) Navigator.of(context)
.popUntil((route) => route.isFirst); .popUntil((route) => route.isFirst);
@ -356,120 +364,4 @@ class AssignTagModelsDialog extends StatelessWidget {
), ),
)); ));
} }
List<String> getAvailableTags(
List<String> allTags, List<TagModel> currentTags, TagModel currentTag) {
return allTags
.where((tagValue) => !currentTags
.where((e) => e != currentTag) // Exclude the current row
.map((e) => e.tag)
.contains(tagValue))
.toList();
}
int? checkTagExistInSubspace(
TagModel tag, List<SubspaceTemplateModel>? subspaces) {
if (subspaces == null) return null;
for (int i = 0; i < subspaces.length; i++) {
final subspace = subspaces[i];
if (subspace.tags == null) continue;
for (var t in subspace.tags!) {
if (tag.internalId == t.internalId) {
return i;
}
}
}
return null;
}
Map<String, dynamic> processTags(
List<TagModel> updatedTags, List<SubspaceTemplateModel>? subspaces) {
final modifiedTags = List<TagModel>.from(updatedTags);
final modifiedSubspaces = List<SubspaceTemplateModel>.from(subspaces ?? []);
if (subspaces != null) {
for (var subspace in subspaces) {
subspace.tags?.removeWhere(
(tag) => !modifiedTags
.any((updatedTag) => updatedTag.internalId == tag.internalId),
);
}
}
for (var tag in modifiedTags.toList()) {
if (modifiedSubspaces.isEmpty) continue;
final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces);
if ((tag.location == 'Main Space' || tag.location == null) &&
(prevIndice == null ||
modifiedSubspaces[prevIndice].subspaceName == 'Main Space')) {
continue;
}
if ((tag.location == 'Main Space' || tag.location == null) &&
prevIndice != null) {
modifiedSubspaces[prevIndice]
.tags
?.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location != 'Main Space' && tag.location != null) &&
prevIndice == null) {
final newIndex = modifiedSubspaces
.indexWhere((subspace) => subspace.subspaceName == tag.location);
if (newIndex != -1) {
if (modifiedSubspaces[newIndex]
.tags
?.any((t) => t.internalId == tag.internalId) !=
true) {
tag.location = modifiedSubspaces[newIndex].subspaceName;
modifiedSubspaces[newIndex].tags?.add(tag);
}
}
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location != 'Main Space' && tag.location != null) &&
tag.location != modifiedSubspaces[prevIndice!].subspaceName) {
modifiedSubspaces[prevIndice]
.tags
?.removeWhere((t) => t.internalId == tag.internalId);
final newIndex = modifiedSubspaces
.indexWhere((subspace) => subspace.subspaceName == tag.location);
if (newIndex != -1) {
if (modifiedSubspaces[newIndex]
.tags
?.any((t) => t.internalId == tag.internalId) !=
true) {
tag.location = modifiedSubspaces[newIndex].subspaceName;
modifiedSubspaces[newIndex].tags?.add(tag);
}
}
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location != 'Main Space' && tag.location != null) &&
tag.location == modifiedSubspaces[prevIndice!].subspaceName) {
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
continue;
}
if ((tag.location == 'Main Space' || tag.location == null) &&
prevIndice != null) {
modifiedSubspaces[prevIndice]
.tags
?.removeWhere((t) => t.internalId == tag.internalId);
}
}
return {
'updatedTags': modifiedTags,
'subspaces': modifiedSubspaces,
};
}
} }

View File

@ -7,6 +7,138 @@ 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/models/tag_model.dart';
class TagHelper { class TagHelper {
static Map<String, dynamic> updateTags<T>({
required List<T> updatedTags,
required List<dynamic>? subspaces,
required String Function(T) getInternalId,
required String? Function(T) getLocation,
required void Function(T, String) setLocation,
required String Function(dynamic) getSubspaceName,
required List<T>? Function(dynamic) getSubspaceTags,
required void Function(dynamic, List<T>?) setSubspaceTags,
required int? Function(T, List<dynamic>) checkTagExistInSubspace,
}) {
final modifiedTags = List<T>.from(updatedTags);
final modifiedSubspaces = List<dynamic>.from(subspaces ?? []);
if (subspaces != null) {
for (var subspace in subspaces) {
getSubspaceTags(subspace)?.removeWhere(
(tag) => !modifiedTags.any(
(updatedTag) => getInternalId(updatedTag) == getInternalId(tag)),
);
}
}
for (var tag in modifiedTags.toList()) {
if (modifiedSubspaces.isEmpty) continue;
final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces);
final tagLocation = getLocation(tag);
if ((tagLocation == 'Main Space' || tagLocation == null) &&
(prevIndice == null ||
getSubspaceName(modifiedSubspaces[prevIndice]) == 'Main Space')) {
continue;
}
if ((tagLocation == 'Main Space' || tagLocation == null) &&
prevIndice != null) {
getSubspaceTags(modifiedSubspaces[prevIndice])
?.removeWhere((t) => getInternalId(t) == getInternalId(tag));
continue;
}
if ((tagLocation != 'Main Space' && tagLocation != null) &&
prevIndice == null) {
final newIndex = modifiedSubspaces
.indexWhere((subspace) => getSubspaceName(subspace) == tagLocation);
if (newIndex != -1) {
if (getSubspaceTags(modifiedSubspaces[newIndex])
?.any((t) => getInternalId(t) == getInternalId(tag)) !=
true) {
setLocation(tag, getSubspaceName(modifiedSubspaces[newIndex]));
final subspaceTags =
getSubspaceTags(modifiedSubspaces[newIndex]) ?? [];
subspaceTags.add(tag);
setSubspaceTags(modifiedSubspaces[newIndex], subspaceTags);
}
}
modifiedTags.removeWhere((t) => getInternalId(t) == getInternalId(tag));
continue;
}
if ((tagLocation != 'Main Space' && tagLocation != null) &&
tagLocation != getSubspaceName(modifiedSubspaces[prevIndice!])) {
getSubspaceTags(modifiedSubspaces[prevIndice])
?.removeWhere((t) => getInternalId(t) == getInternalId(tag));
final newIndex = modifiedSubspaces
.indexWhere((subspace) => getSubspaceName(subspace) == tagLocation);
if (newIndex != -1) {
if (getSubspaceTags(modifiedSubspaces[newIndex])
?.any((t) => getInternalId(t) == getInternalId(tag)) !=
true) {
setLocation(tag, getSubspaceName(modifiedSubspaces[newIndex]));
final subspaceTags =
getSubspaceTags(modifiedSubspaces[newIndex]) ?? [];
subspaceTags.add(tag);
setSubspaceTags(modifiedSubspaces[newIndex], subspaceTags);
}
}
modifiedTags.removeWhere((t) => getInternalId(t) == getInternalId(tag));
continue;
}
if ((tagLocation != 'Main Space' && tagLocation != null) &&
tagLocation == getSubspaceName(modifiedSubspaces[prevIndice!])) {
modifiedTags.removeWhere((t) => getInternalId(t) == getInternalId(tag));
continue;
}
if ((tagLocation == 'Main Space' || tagLocation == null) &&
prevIndice != null) {
getSubspaceTags(modifiedSubspaces[prevIndice])
?.removeWhere((t) => getInternalId(t) == getInternalId(tag));
}
}
return {
'updatedTags': modifiedTags,
'subspaces': modifiedSubspaces,
};
}
static List<String> getAvailableTags<T>({
required List<String> allTags,
required List<T> currentTags,
required T currentTag,
required String? Function(T) getTag, // Allow nullable return type
}) {
return allTags
.where((tagValue) => !currentTags
.where((e) => e != currentTag) // Exclude the current row
.map((e) => getTag(e) ?? '') // Handle null values gracefully
.contains(tagValue))
.toList();
}
static List<String> getAvailableTagModels(
List<String> allTags, List<TagModel> currentTags, TagModel currentTag) {
List<String> availableTagsForTagModel =
TagHelper.getAvailableTags<TagModel>(
allTags: allTags,
currentTags: currentTags,
currentTag: currentTag,
getTag: (tag) => tag.tag ?? '',
);
return availableTagsForTagModel;
}
static List<TagModel> generateInitialTags({ static List<TagModel> generateInitialTags({
List<TagModel>? spaceTagModels, List<TagModel>? spaceTagModels,
List<SubspaceTemplateModel>? subspaces, List<SubspaceTemplateModel>? subspaces,
@ -145,4 +277,35 @@ class TagHelper {
)) ))
.toList(); .toList();
} }
static int? checkTagExistInSubspaceModels(TagModel tag, List<dynamic>? subspaces) {
if (subspaces == null) return null;
for (int i = 0; i < subspaces.length; i++) {
final subspace = subspaces[i] as SubspaceTemplateModel; // Explicit cast
if (subspace.tags == null) continue;
for (var t in subspace.tags!) {
if (tag.internalId == t.internalId) {
return i;
}
}
}
return null;
}
static Map<String, dynamic> updateSubspaceTagModels(
List<TagModel> updatedTags, List<SubspaceTemplateModel>? subspaces) {
return TagHelper.updateTags<TagModel>(
updatedTags: updatedTags,
subspaces: subspaces,
getInternalId: (tag) => tag.internalId,
getLocation: (tag) => tag.location,
setLocation: (tag, location) => tag.location = location,
getSubspaceName: (subspace) => subspace.subspaceName,
getSubspaceTags: (subspace) => subspace.tags,
setSubspaceTags: (subspace, tags) => subspace.tags = tags,
checkTagExistInSubspace: checkTagExistInSubspaceModels,
);
}
} }

View File

@ -129,10 +129,16 @@ class CreateSpaceModelDialog extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
SubspaceModelCreate( SubspaceModelCreate(
subspaces: state.space.subspaceModels ?? [], subspaces: state.space.subspaceModels ?? [],
onSpaceModelUpdate: (updatedSubspaces) { tags: state.space.tags ?? [],
onSpaceModelUpdate: (updatedSubspaces,updatedTags) {
context context
.read<CreateSpaceModelBloc>() .read<CreateSpaceModelBloc>()
.add(AddSubspacesToSpaceTemplate(updatedSubspaces)); .add(AddSubspacesToSpaceTemplate(updatedSubspaces));
if(updatedTags!=null){
context
.read<CreateSpaceModelBloc>()
.add(AddTagsToSpaceTemplate(updatedTags));
}
}, },
), ),
const SizedBox(height: 10), const SizedBox(height: 10),

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/common/edit_chip.dart'; import 'package:syncrow_web/common/edit_chip.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/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/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/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart';
@ -8,13 +9,16 @@ import 'package:syncrow_web/utils/color_manager.dart';
class SubspaceModelCreate extends StatefulWidget { class SubspaceModelCreate extends StatefulWidget {
final List<SubspaceTemplateModel> subspaces; final List<SubspaceTemplateModel> subspaces;
final void Function(List<SubspaceTemplateModel> newSubspaces)? final void Function(
List<SubspaceTemplateModel> newSubspaces, List<TagModel>? tags)?
onSpaceModelUpdate; onSpaceModelUpdate;
final List<TagModel> tags;
const SubspaceModelCreate({ const SubspaceModelCreate({
Key? key, Key? key,
required this.subspaces, required this.subspaces,
this.onSpaceModelUpdate, this.onSpaceModelUpdate,
required this.tags,
}) : super(key: key); }) : super(key: key);
@override @override
@ -24,11 +28,13 @@ class SubspaceModelCreate extends StatefulWidget {
class _SubspaceModelCreateState extends State<SubspaceModelCreate> { class _SubspaceModelCreateState extends State<SubspaceModelCreate> {
late List<SubspaceTemplateModel> _subspaces; late List<SubspaceTemplateModel> _subspaces;
String? errorSubspaceId; String? errorSubspaceId;
late List<TagModel> _tags;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_subspaces = List.from(widget.subspaces); _subspaces = List.from(widget.subspaces);
_tags = List.from(widget.tags);
} }
@override @override
@ -105,14 +111,26 @@ class _SubspaceModelCreateState extends State<SubspaceModelCreate> {
isEdit: true, isEdit: true,
dialogTitle: dialogTitle, dialogTitle: dialogTitle,
existingSubSpaces: _subspaces, existingSubSpaces: _subspaces,
onUpdate: (subspaceModels) { onUpdate: (subspaceModels) {
final updatedIds = subspaceModels.map((s) => s.internalId).toSet();
final deletedSubspaces = _subspaces
.where((s) => !updatedIds.contains(s.internalId))
.toList();
final List<TagModel> tagsToAppendToSpace = [];
for (var s in deletedSubspaces) {
if (s.tags != null) {
tagsToAppendToSpace.addAll(s.tags!);
}
}
setState(() { setState(() {
_subspaces = subspaceModels; _subspaces = subspaceModels;
errorSubspaceId = null; _tags.addAll(tagsToAppendToSpace);
}); });
if (widget.onSpaceModelUpdate != null) { if (widget.onSpaceModelUpdate != null) {
widget.onSpaceModelUpdate!(subspaceModels); widget.onSpaceModelUpdate!(subspaceModels, _tags);
} }
}, },
); );

View File

@ -52,6 +52,13 @@ class _SubspaceNameDisplayWidgetState extends State<SubspaceNameDisplayWidget> {
void _handleValidationAndSave() { void _handleValidationAndSave() {
final updatedName = _controller.text; final updatedName = _controller.text;
if (updatedName.isEmpty) {
setState(() {
errorText = 'Subspace name cannot be empty.';
});
return;
}
if (widget.validateName(updatedName)) { if (widget.validateName(updatedName)) {
setState(() { setState(() {
errorText = null; errorText = null;

View File

@ -23,8 +23,7 @@ class DevicesManagementApi {
: ApiEndpoints.getAllDevices, : ApiEndpoints.getAllDevices,
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
List<dynamic> jsonData = List<dynamic> jsonData = json;
communityId.isNotEmpty && spaceId.isNotEmpty ? json['data'] : json;
List<AllDevicesModel> devicesList = jsonData.map((jsonItem) { List<AllDevicesModel> devicesList = jsonData.map((jsonItem) {
return AllDevicesModel.fromJson(jsonItem); return AllDevicesModel.fromJson(jsonItem);
}).toList(); }).toList();

View File

@ -18,8 +18,7 @@ class UserPermissionApi {
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
debugPrint('fetchUsers Response: $json'); debugPrint('fetchUsers Response: $json');
final List<dynamic> data = final List<dynamic> data = json['data'] ?? [];
json['data'] ?? []; // Default to an empty list if no data
return data.map((item) => RolesUserModel.fromJson(item)).toList(); return data.map((item) => RolesUserModel.fromJson(item)).toList();
}, },
); );
@ -119,7 +118,7 @@ class UserPermissionApi {
); );
return response ?? 'Unknown error occurred'; return response ?? 'Unknown error occurred';
} on DioException catch (e) { } on DioException catch (e) {
final errorMessage = e.response?.data['error']; final errorMessage = e.response?.data['error']['message'];
return errorMessage; return errorMessage;
} catch (e) { } catch (e) {
return e.toString(); return e.toString();
@ -205,7 +204,6 @@ class UserPermissionApi {
.replaceAll("{invitedUserUuid}", userUuid), .replaceAll("{invitedUserUuid}", userUuid),
body: bodya, body: bodya,
expectedResponseModel: (json) { expectedResponseModel: (json) {
print('changeUserStatusById==${json['success']}');
return json['success']; return json['success'];
}, },
); );
@ -213,7 +211,6 @@ class UserPermissionApi {
return response; return response;
} catch (e) { } catch (e) {
return false; return false;
print(e);
} }
} }
} }