Compare commits

..

16 Commits

Author SHA1 Message Date
9a6bf5cbaf privacy policy fixes 2025-02-05 18:16:09 +03:00
51fbe64209 fixes bugs 2025-02-05 16:53:36 +03:00
a18e8443d0 Merge pull request #77 from SyncrowIOT/web_bugs_fixes
Fix bugs related to the user table, privacy policy, and table filter.
2025-02-03 14:39:38 +03:00
ab3edbaf57 Merge pull request #79 from SyncrowIOT/bugfix/fix-duplicate-space
Bugfix/fix-duplicate-space
2025-02-02 23:39:37 +04:00
64e3fb7f34 removed print 2025-02-02 23:38:04 +04:00
e6e46be9b4 fixed issues in create space and duplicate 2025-02-02 23:16:34 +04:00
91dfd53477 fixed issue in creating space 2025-02-02 21:02:58 +04:00
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
43 changed files with 1458 additions and 1237 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

@ -1,3 +1,4 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@ -51,6 +52,8 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
await const FlutterSecureStorage().read(key: UserModel.userUuidKey); await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
user = await HomeApi().fetchUserInfo(uuid); user = await HomeApi().fetchUserInfo(uuid);
add(FetchTermEvent()); add(FetchTermEvent());
add(FetchPolicyEvent());
emit(HomeInitial()); emit(HomeInitial());
} catch (e) { } catch (e) {
return; return;
@ -61,7 +64,9 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
try { try {
emit(LoadingHome()); emit(LoadingHome());
terms = await HomeApi().fetchTerms(); terms = await HomeApi().fetchTerms();
add(FetchPolicyEvent()); emit(HomeInitial());
// emit(PolicyAgreement());
} catch (e) { } catch (e) {
return; return;
} }
@ -71,7 +76,11 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
try { try {
emit(LoadingHome()); emit(LoadingHome());
policy = await HomeApi().fetchPolicy(); policy = await HomeApi().fetchPolicy();
debugPrint("Fetched policy: $policy");
// Emit a state to trigger the UI update
emit(HomeInitial());
} catch (e) { } catch (e) {
debugPrint("Error fetching policy: $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

@ -9,9 +9,24 @@ import 'package:syncrow_web/pages/home/view/home_card.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
class HomeWebPage extends StatelessWidget { class HomeWebPage extends StatefulWidget {
const HomeWebPage({super.key}); const HomeWebPage({super.key});
@override
State<HomeWebPage> createState() => _HomeWebPageState();
}
class _HomeWebPageState extends State<HomeWebPage> {
// Flag to track whether the dialog is already shown.
bool _dialogShown = false;
@override
void initState() {
super.initState();
final homeBloc = BlocProvider.of<HomeBloc>(context);
homeBloc.add(FetchUserInfo());
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size; Size size = MediaQuery.of(context).size;
@ -22,8 +37,12 @@ class HomeWebPage extends StatelessWidget {
onPopInvoked: (didPop) => false, onPopInvoked: (didPop) => false,
child: BlocConsumer<HomeBloc, HomeState>( child: BlocConsumer<HomeBloc, HomeState>(
listener: (BuildContext context, state) { listener: (BuildContext context, state) {
print('state=$state');
if (state is HomeInitial) { if (state is HomeInitial) {
if (homeBloc.user!.hasAcceptedWebAgreement == false) { if (homeBloc.user!.hasAcceptedWebAgreement == false &&
!_dialogShown) {
_dialogShown =
true; // Set the flag to true to indicate the dialog is showing.
Future.delayed(const Duration(seconds: 1), () { Future.delayed(const Duration(seconds: 1), () {
showDialog( showDialog(
context: context, context: context,
@ -35,6 +54,7 @@ class HomeWebPage extends StatelessWidget {
); );
}, },
).then((v) { ).then((v) {
_dialogShown = false;
if (v != null) { if (v != null) {
homeBloc.add(ConfirmUserAgreementEvent()); homeBloc.add(ConfirmUserAgreementEvent());
homeBloc.add(const FetchUserInfo()); homeBloc.add(const FetchUserInfo());
@ -80,9 +100,10 @@ class HomeWebPage extends StatelessWidget {
height: size.height * 0.6, height: size.height * 0.6,
width: size.width * 0.68, width: size.width * 0.68,
child: GridView.builder( child: GridView.builder(
itemCount: 3, //8 itemCount: 3, // Change this count if needed.
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate:
crossAxisCount: 3, //4 const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // Adjust as needed.
crossAxisSpacing: 20.0, crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0, mainAxisSpacing: 20.0,
childAspectRatio: 1.5, childAspectRatio: 1.5,
@ -93,7 +114,8 @@ class HomeWebPage extends StatelessWidget {
active: homeBloc.homeItems[index].active!, active: homeBloc.homeItems[index].active!,
name: homeBloc.homeItems[index].title!, name: homeBloc.homeItems[index].title!,
img: homeBloc.homeItems[index].icon!, img: homeBloc.homeItems[index].icon!,
onTap: () => homeBloc.homeItems[index].onPress(context), onTap: () =>
homeBloc.homeItems[index].onPress(context),
); );
}, },
), ),
@ -106,6 +128,7 @@ class HomeWebPage extends StatelessWidget {
), ),
); );
}, },
)); ),
);
} }
} }

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 =
@ -101,13 +102,19 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
); );
}).toList(), }).toList(),
); );
originalCommunities = updatedCommunities;
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'));
} }
} }
// This variable holds the full original list.
List<TreeNode> originalCommunities = [];
// This variable holds the working list that may be filtered.
// Build tree nodes from your data model.
List<TreeNode> _buildTreeNodes(List<SpaceModel> spaces) { List<TreeNode> _buildTreeNodes(List<SpaceModel> spaces) {
return spaces.map((space) { return spaces.map((space) {
List<TreeNode> childNodes = List<TreeNode> childNodes =
@ -123,12 +130,39 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
}).toList(); }).toList();
} }
// Optional helper method to deep clone a TreeNode.
TreeNode _cloneNode(TreeNode node) {
return TreeNode(
uuid: node.uuid,
title: node.title,
isChecked: node.isChecked,
isHighlighted: node.isHighlighted,
isExpanded: node.isExpanded,
children: node.children.map(_cloneNode).toList(),
);
}
// Clone an entire list of tree nodes.
List<TreeNode> _cloneNodes(List<TreeNode> nodes) {
return nodes.map(_cloneNode).toList();
}
// Your search event handler.
void searchTreeNode(SearchAnode event, Emitter<UsersState> emit) { void searchTreeNode(SearchAnode event, Emitter<UsersState> emit) {
emit(UsersLoadingState()); emit(UsersLoadingState());
// If the search term is empty, restore the original list.
if (event.searchTerm!.isEmpty) { if (event.searchTerm!.isEmpty) {
// Clear any highlights on the restored copy.
updatedCommunities = _cloneNodes(originalCommunities);
_clearHighlights(updatedCommunities); _clearHighlights(updatedCommunities);
} else { } else {
_searchAndHighlightNodes(updatedCommunities, event.searchTerm!); // Start with a fresh clone of the original tree.
List<TreeNode> freshClone = _cloneNodes(originalCommunities);
_searchAndHighlightNodes(freshClone, event.searchTerm!);
updatedCommunities = _filterNodes(freshClone, event.searchTerm!);
} }
emit(ChangeStatusSteps()); emit(ChangeStatusSteps());
} }
@ -155,6 +189,91 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
return anyMatch; return anyMatch;
} }
List<TreeNode> _filterNodes(List<TreeNode> nodes, String searchTerm) {
List<TreeNode> filteredNodes = [];
for (var node in nodes) {
bool isMatch =
node.title.toLowerCase().contains(searchTerm.toLowerCase());
List<TreeNode> filteredChildren = _filterNodes(node.children, searchTerm);
if (isMatch || filteredChildren.isNotEmpty) {
node.isHighlighted = isMatch;
node.children = filteredChildren;
filteredNodes.add(node);
}
}
return filteredNodes;
}
// List<TreeNode> _buildTreeNodes(List<SpaceModel> spaces) {
// return spaces.map((space) {
// List<TreeNode> childNodes =
// space.children.isNotEmpty ? _buildTreeNodes(space.children) : [];
// return TreeNode(
// uuid: space.uuid!,
// title: space.name,
// isChecked: false,
// isHighlighted: false,
// isExpanded: childNodes.isNotEmpty,
// children: childNodes,
// );
// }).toList();
// }
// void searchTreeNode(SearchAnode event, Emitter<UsersState> emit) {
// emit(UsersLoadingState());
// if (event.searchTerm!.isEmpty) {
// _clearHighlights(updatedCommunities);
// } else {
// _searchAndHighlightNodes(updatedCommunities, event.searchTerm!);
// updatedCommunities = _filterNodes(updatedCommunities, event.searchTerm!);
// }
// emit(ChangeStatusSteps());
// }
// void _clearHighlights(List<TreeNode> nodes) {
// for (var node in nodes) {
// node.isHighlighted = false;
// if (node.children.isNotEmpty) {
// _clearHighlights(node.children);
// }
// }
// }
// bool _searchAndHighlightNodes(List<TreeNode> nodes, String searchTerm) {
// bool anyMatch = false;
// for (var node in nodes) {
// bool isMatch =
// node.title.toLowerCase().contains(searchTerm.toLowerCase());
// bool childMatch = _searchAndHighlightNodes(node.children, searchTerm);
// node.isHighlighted = isMatch || childMatch;
// anyMatch = anyMatch || node.isHighlighted;
// }
// return anyMatch;
// }
// List<TreeNode> _filterNodes(List<TreeNode> nodes, String searchTerm) {
// List<TreeNode> filteredNodes = [];
// for (var node in nodes) {
// // Check if the current node's title contains the search term.
// bool isMatch =
// node.title.toLowerCase().contains(searchTerm.toLowerCase());
// // Recursively filter the children.
// List<TreeNode> filteredChildren = _filterNodes(node.children, searchTerm);
// // If the current node is a match or any of its children are, include it.
// if (isMatch || filteredChildren.isNotEmpty) {
// // Optionally, update any properties (like isHighlighted) if you still need them.
// node.isHighlighted = isMatch;
// // Replace the children with the filtered ones.
// node.children = filteredChildren;
// filteredNodes.add(node);
// }
// }
// return filteredNodes;
// }
List<String> selectedIds = []; List<String> selectedIds = [];
List<String> getSelectedIds(List<TreeNode> nodes) { List<String> getSelectedIds(List<TreeNode> nodes) {
@ -177,7 +296,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 +326,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 +342,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,
@ -251,7 +373,10 @@ 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

@ -26,9 +26,13 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
create: (BuildContext context) => UsersBloc() create: (BuildContext context) => UsersBloc()
..add(const LoadCommunityAndSpacesEvent()) ..add(const LoadCommunityAndSpacesEvent())
..add(const RoleEvent()), ..add(const RoleEvent()),
child: BlocConsumer<UsersBloc, UsersState>( child: BlocConsumer<UsersBloc, UsersState>(listener: (context, state) {
listener: (context, state) {}, // print('88888==${state}');
builder: (context, state) { // if (state is SpacesLoadedState) {
// print('object');
// _blocRole.add(const CheckRoleStepStatus());
// }
}, builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context); final _blocRole = BlocProvider.of<UsersBloc>(context);
return Dialog( return Dialog(
@ -116,12 +120,10 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
_blocRole.add( _blocRole.add(
const CheckStepStatus(isEditUser: false)); const CheckStepStatus(isEditUser: false));
} else if (currentStep == 3) { } else if (currentStep == 3) {
_blocRole _blocRole.add(const CheckSpacesStepStatus());
.add(const CheckSpacesStepStatus());
} }
} else { } else {
_blocRole _blocRole.add(SendInviteUsers(context: context));
.add(SendInviteUsers(context: context));
} }
}); });
}, },
@ -129,10 +131,8 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
currentStep < 3 ? "Next" : "Save", currentStep < 3 ? "Next" : "Save",
style: TextStyle( style: TextStyle(
color: (_blocRole.isCompleteSpaces == false || color: (_blocRole.isCompleteSpaces == false ||
_blocRole.isCompleteBasics == _blocRole.isCompleteBasics == false ||
false || _blocRole.isCompleteRolePermissions ==
_blocRole
.isCompleteRolePermissions ==
false) && false) &&
currentStep == 3 currentStep == 3
? ColorsManager.grayColor ? ColorsManager.grayColor
@ -236,12 +236,16 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
setState(() { setState(() {
currentStep = step;
bloc.add(const CheckStepStatus(isEditUser: false)); bloc.add(const CheckStepStatus(isEditUser: false));
currentStep = step;
Future.delayed(const Duration(milliseconds: 500), () {
bloc.add(const CheckStepStatus(isEditUser: false));
});
if (step3 == 3) { if (step3 == 3) {
Future.delayed(const Duration(seconds: 1), () {
bloc.add(const CheckRoleStepStatus()); bloc.add(const CheckRoleStepStatus());
});
} }
}); });
}, },
child: Column( child: Column(

View File

@ -46,8 +46,9 @@ class BasicsView extends StatelessWidget {
), ),
Row( Row(
children: [ children: [
SizedBox( Flexible(
width: MediaQuery.of(context).size.width * 0.18, child: SizedBox(
// width: MediaQuery.of(context).size.width * 0.18,
height: MediaQuery.of(context).size.width * 0.08, height: MediaQuery.of(context).size.width * 0.08,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -75,8 +76,8 @@ class BasicsView extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: TextFormField( child: TextFormField(
style: style: const TextStyle(
const TextStyle(color: ColorsManager.blackColor), color: ColorsManager.blackColor),
// onChanged: (value) { // onChanged: (value) {
// Future.delayed(const Duration(milliseconds: 200), // Future.delayed(const Duration(milliseconds: 200),
// () { // () {
@ -103,9 +104,11 @@ class BasicsView extends StatelessWidget {
], ],
), ),
), ),
),
const SizedBox(width: 10), const SizedBox(width: 10),
SizedBox( Flexible(
width: MediaQuery.of(context).size.width * 0.18, child: SizedBox(
// width: MediaQuery.of(context).size.width * 0.18,
height: MediaQuery.of(context).size.width * 0.08, height: MediaQuery.of(context).size.width * 0.08,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -142,8 +145,7 @@ class BasicsView extends StatelessWidget {
decoration: decoration:
inputTextFormDeco(hintText: "Enter last name") inputTextFormDeco(hintText: "Enter last name")
.copyWith( .copyWith(
hintStyle: context hintStyle: context.textTheme.bodyMedium
.textTheme.bodyMedium
?.copyWith( ?.copyWith(
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
fontSize: 12, fontSize: 12,
@ -159,6 +161,7 @@ class BasicsView extends StatelessWidget {
], ],
), ),
), ),
),
], ],
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
@ -218,7 +221,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

@ -27,7 +27,16 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
int currentPage = 1; int currentPage = 1;
List<RolesUserModel> users = []; List<RolesUserModel> users = [];
List<RolesUserModel> initialUsers = []; List<RolesUserModel> initialUsers = [];
List<RolesUserModel> totalUsersCount = [];
String currentSortOrder = ''; String currentSortOrder = '';
String currentSortJopTitle = '';
String currentSortRole = '';
String currentSortCreatedDate = '';
String currentSortStatus = '';
String currentSortCreatedBy = '';
String currentSortOrderDate = ''; String currentSortOrderDate = '';
List<String> roleTypes = []; List<String> roleTypes = [];
List<String> jobTitle = []; List<String> jobTitle = [];
@ -40,9 +49,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 +64,13 @@ 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);
totalUsersCount = initialUsers;
add(ChangePage(currentPage));
emit(UsersLoadedState(users: users)); emit(UsersLoadedState(users: users));
} catch (e) { } catch (e) {
emit(ErrorState(e.toString())); emit(ErrorState(e.toString()));
@ -96,26 +101,6 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
event.userId, event.newStatus == "disabled" ? false : true); event.userId, event.newStatus == "disabled" ? false : true);
if (res == true) { if (res == true) {
add(const GetUsers()); 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)); emit(UsersLoadedState(users: users));
} catch (e) { } catch (e) {
@ -125,11 +110,14 @@ 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 = "";
users = List.from(users); users = List.from(users);
emit(UsersLoadedState(users: users));
} else { } else {
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = "Asc"; currentSortOrder = "Asc";
@ -137,28 +125,42 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
.toString() .toString()
.toLowerCase() .toLowerCase()
.compareTo(b.firstName.toString().toLowerCase())); .compareTo(b.firstName.toString().toLowerCase()));
emit(UsersLoadedState(users: users));
} }
currentSortJopTitle = '';
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
emit(UsersLoadedState(users: users));
} }
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));
} 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!));
emit(UsersLoadedState(users: users));
} }
currentSortJopTitle = '';
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
emit(UsersLoadedState(users: users));
} }
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 +181,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 = "";
@ -212,6 +218,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
Future<void> _searchUsers( Future<void> _searchUsers(
SearchUsers event, Emitter<UserTableState> emit) async { SearchUsers event, Emitter<UserTableState> emit) async {
try { try {
emit(TableSearch());
final query = event.query.toLowerCase(); final query = event.query.toLowerCase();
final filteredUsers = initialUsers.where((user) { final filteredUsers = initialUsers.where((user) {
final fullName = "${user.firstName} ${user.lastName}".toLowerCase(); final fullName = "${user.firstName} ${user.lastName}".toLowerCase();
@ -240,7 +247,8 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} }
void _handlePageChange(ChangePage event, Emitter<UserTableState> emit) { void _handlePageChange(ChangePage event, Emitter<UserTableState> emit) {
const itemsPerPage = 10; currentPage = event.pageNumber;
const itemsPerPage = 20;
final startIndex = (event.pageNumber - 1) * itemsPerPage; final startIndex = (event.pageNumber - 1) * itemsPerPage;
final endIndex = startIndex + itemsPerPage; final endIndex = startIndex + itemsPerPage;
if (startIndex >= users.length) { if (startIndex >= users.length) {
@ -277,9 +285,15 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} else if (event.sortOrder == "Desc") { } else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc"; currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else { } else {}
currentSortOrder = ""; currentSortOrder = "";
} currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
currentSortJopTitle = '';
currentSortOrderDate = "";
totalUsersCount = filteredUsers;
emit(UsersLoadedState(users: filteredUsers)); emit(UsersLoadedState(users: filteredUsers));
} }
@ -301,9 +315,16 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} else if (event.sortOrder == "Desc") { } else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc"; currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else { } else {}
currentSortOrder = ""; currentSortOrder = "";
} currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
currentSortRole = '';
currentSortOrderDate = "";
totalUsersCount = filteredUsers;
emit(UsersLoadedState(users: filteredUsers)); emit(UsersLoadedState(users: filteredUsers));
} }
@ -325,9 +346,15 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} else if (event.sortOrder == "Desc") { } else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc"; currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else { } else {}
currentSortOrder = ""; currentSortOrder = '';
} currentSortRole = '';
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortOrderDate = "";
totalUsersCount = filteredUsers;
emit(UsersLoadedState(users: filteredUsers)); emit(UsersLoadedState(users: filteredUsers));
} }
@ -337,7 +364,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";
@ -348,9 +388,14 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} else if (event.sortOrder == "Desc") { } else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc"; currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else { totalUsersCount = filteredUsers;
currentSortOrder = ""; } else {}
} currentSortOrder = '';
currentSortRole = '';
currentSortCreatedDate = '';
currentSortCreatedBy = '';
currentSortOrderDate = "";
emit(UsersLoadedState(users: filteredUsers)); emit(UsersLoadedState(users: filteredUsers));
} }

View File

@ -9,7 +9,10 @@ final class TableInitial extends UserTableState {
@override @override
List<Object> get props => []; List<Object> get props => [];
} }
final class TableSearch extends UserTableState {
@override
List<Object> get props => [];
}
final class RolesLoadingState extends UserTableState { final class RolesLoadingState extends UserTableState {
@override @override
List<Object> get props => []; List<Object> get props => [];

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,255 +1,58 @@
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)
.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<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: [
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,
),
);
}),
),
],
);
},
),
),
), ),
], ],
), ),
@ -258,3 +61,204 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
); );
} }
} }
class _TableRow extends StatelessWidget {
final List<Widget> cells;
final List<double> columnWidths;
final bool isLast;
const _TableRow({
required this.cells,
required this.columnWidths,
required this.isLast,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
children: [
for (int i = 0; i < cells.length; i++)
Container(
width: columnWidths[i],
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
// decoration: BoxDecoration(
// border: Border(
// right: BorderSide(color: ColorsManager.boxDivider),
// ),
// ),
child: cells[i],
),
],
),
if (!isLast)
Divider(
height: 1,
thickness: 1,
color: ColorsManager.boxDivider,
),
],
);
}
}
//===========================================================================
class DynamicTableScreen extends StatefulWidget {
final List<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),
),
),
child: Row(
children: [
for (int i = 0; i < widget.titles.length; i++)
_HeaderColumn(
title: widget.titles[i],
width: columnWidths[i],
showFilter: i != 1 && i != 9 && i != 8 && i != 5,
onFilter: () => widget.onFilter?.call(i),
onResize: (delta) => _handleColumnResize(i, delta),
),
],
),
);
}
Widget _buildBody() {
if (widget.rows.isEmpty) {
return SizedBox(
height: 300,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(Assets.emptyTable),
const SizedBox(height: 15),
const Text(
'No Users',
style: TextStyle(
color: ColorsManager.lightGrayColor,
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
],
),
),
);
}
return Container(
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15),
),
),
child: Column(
children: [
for (int rowIndex = 0; rowIndex < widget.rows.length; rowIndex++)
_TableRow(
cells: widget.rows[rowIndex],
columnWidths: columnWidths,
isLast: rowIndex == widget.rows.length - 1,
),
],
),
);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final availableWidth = constraints.maxWidth;
final totalDividersWidth = (widget.titles.length - 1) * _dividerWidth;
if (_lastAvailableWidth != availableWidth) {
final equalWidth =
(availableWidth - totalDividersWidth) / widget.titles.length;
final clampedWidth =
equalWidth.clamp(_minColumnWidth, _maxColumnWidth);
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
columnWidths = List.filled(widget.titles.length, clampedWidth);
_lastAvailableWidth = availableWidth;
});
});
}
final totalTableWidth =
columnWidths.fold(0.0, (sum, w) => sum + w) + totalDividersWidth;
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Container(
width: totalTableWidth,
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
_buildBody(),
],
),
),
);
},
);
}
}

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(
@ -130,9 +129,12 @@ class UsersPage extends StatelessWidget {
child: TextFormField( child: TextFormField(
controller: searchController, controller: searchController,
onChanged: (value) { onChanged: (value) {
context final bloc = context.read<UserTableBloc>();
.read<UserTableBloc>() bloc.add(FilterClearEvent());
.add(SearchUsers(value)); bloc.add(SearchUsers(value));
if (value == '') {
bloc.add(ChangePage(1));
}
}, },
style: const TextStyle(color: Colors.black), style: const TextStyle(color: Colors.black),
decoration: textBoxDecoration(radios: 15)!.copyWith( decoration: textBoxDecoration(radios: 15)!.copyWith(
@ -215,7 +217,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,
@ -223,8 +225,9 @@ class UsersPage extends StatelessWidget {
list: _blocRole.jobTitle, list: _blocRole.jobTitle,
context: context, context: context,
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortJopTitle,
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)
@ -233,14 +236,14 @@ class UsersPage extends StatelessWidget {
Navigator.of(context).pop(); Navigator.of(context).pop();
_blocRole.add(FilterUsersByJobEvent( _blocRole.add(FilterUsersByJobEvent(
selectedJob: selectedItems, selectedJob: selectedItems,
sortOrder: _blocRole.currentSortOrder, sortOrder: _blocRole.currentSortJopTitle,
)); ));
}, },
onSortAtoZ: (v) { onSortAtoZ: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortJopTitle = v;
}, },
onSortZtoA: (v) { onSortZtoA: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortJopTitle = v;
}, },
); );
} }
@ -263,8 +266,9 @@ class UsersPage extends StatelessWidget {
list: _blocRole.roleTypes, list: _blocRole.roleTypes,
context: context, context: context,
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortRole,
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)
@ -274,13 +278,13 @@ class UsersPage extends StatelessWidget {
context.read<UserTableBloc>().add( context.read<UserTableBloc>().add(
FilterUsersByRoleEvent( FilterUsersByRoleEvent(
selectedRoles: selectedItems, selectedRoles: selectedItems,
sortOrder: _blocRole.currentSortOrder)); sortOrder: _blocRole.currentSortRole));
}, },
onSortAtoZ: (v) { onSortAtoZ: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortRole = v;
}, },
onSortZtoA: (v) { onSortZtoA: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortRole = v;
}, },
); );
} }
@ -318,8 +322,9 @@ class UsersPage extends StatelessWidget {
list: _blocRole.createdBy, list: _blocRole.createdBy,
context: context, context: context,
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortCreatedBy,
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)
@ -328,13 +333,13 @@ class UsersPage extends StatelessWidget {
Navigator.of(context).pop(); Navigator.of(context).pop();
_blocRole.add(FilterUsersByCreatedEvent( _blocRole.add(FilterUsersByCreatedEvent(
selectedCreatedBy: selectedItems, selectedCreatedBy: selectedItems,
sortOrder: _blocRole.currentSortOrder)); sortOrder: _blocRole.currentSortCreatedBy));
}, },
onSortAtoZ: (v) { onSortAtoZ: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortCreatedBy = v;
}, },
onSortZtoA: (v) { onSortZtoA: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortCreatedBy = v;
}, },
); );
} }
@ -343,6 +348,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,16 +356,16 @@ 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,
context: context, context: context,
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortStatus,
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)
@ -367,13 +373,13 @@ class UsersPage extends StatelessWidget {
Navigator.of(context).pop(); Navigator.of(context).pop();
_blocRole.add(FilterUsersByDeActevateEvent( _blocRole.add(FilterUsersByDeActevateEvent(
selectedActivate: selectedItems, selectedActivate: selectedItems,
sortOrder: _blocRole.currentSortOrder)); sortOrder: _blocRole.currentSortStatus));
}, },
onSortAtoZ: (v) { onSortAtoZ: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortStatus = v;
}, },
onSortZtoA: (v) { onSortZtoA: (v) {
_blocRole.currentSortOrder = v; _blocRole.currentSortStatus = v;
}, },
); );
} }
@ -410,7 +416,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 +433,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 +444,6 @@ class UsersPage extends StatelessWidget {
), ),
Row( Row(
children: [ children: [
// actionButton(
// title: "Activity Log",
// onTap: () {},
// ),
actionButton( actionButton(
title: "Edit", title: "Edit",
onTap: () { onTap: () {
@ -486,11 +483,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());
} }
}
}); });
}, },
), ),
@ -516,12 +511,11 @@ class UsersPage extends StatelessWidget {
const Icon(Icons.keyboard_double_arrow_right), const Icon(Icons.keyboard_double_arrow_right),
firstPageIcon: firstPageIcon:
const Icon(Icons.keyboard_double_arrow_left), const Icon(Icons.keyboard_double_arrow_left),
totalPages: (_blocRole.users.length / totalPages: (_blocRole.totalUsersCount.length /
_blocRole.itemsPerPage) _blocRole.itemsPerPage)
.ceil(), .ceil(),
currentPage: _blocRole.currentPage, currentPage: _blocRole.currentPage,
onPageChanged: (int pageNumber) { onPageChanged: (int pageNumber) {
_blocRole.currentPage = pageNumber;
context context
.read<UserTableBloc>() .read<UserTableBloc>()
.add(ChangePage(pageNumber)); .add(ChangePage(pageNumber));

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

@ -179,6 +179,7 @@ class SpaceManagementBloc
final updatedCommunities = final updatedCommunities =
await Future.wait(communities.map((community) async { await Future.wait(communities.map((community) async {
final spaces = await _fetchSpacesForCommunity(community.uuid); final spaces = await _fetchSpacesForCommunity(community.uuid);
return CommunityModel( return CommunityModel(
uuid: community.uuid, uuid: community.uuid,
createdAt: community.createdAt, createdAt: community.createdAt,
@ -313,6 +314,7 @@ class SpaceManagementBloc
SelectSpaceEvent event, SelectSpaceEvent event,
Emitter<SpaceManagementState> emit, Emitter<SpaceManagementState> emit,
) { ) {
_handleCommunitySpaceStateUpdate( _handleCommunitySpaceStateUpdate(
emit: emit, emit: emit,
selectedCommunity: event.selectedCommunity, selectedCommunity: event.selectedCommunity,

View File

@ -95,6 +95,9 @@ class SpaceModel {
icon: json['icon'] ?? Assets.location, icon: json['icon'] ?? Assets.location,
position: Offset(json['x'] ?? 0, json['y'] ?? 0), position: Offset(json['x'] ?? 0, json['y'] ?? 0),
isHovered: false, isHovered: false,
spaceModel: json['spaceModel'] != null
? SpaceTemplateModel.fromJson(json['spaceModel'])
: null,
tags: (json['tags'] as List<dynamic>?) tags: (json['tags'] as List<dynamic>?)
?.where((item) => item is Map<String, dynamic>) // Validate type ?.where((item) => item is Map<String, dynamic>) // Validate type
.map((item) => Tag.fromJson(item as Map<String, dynamic>)) .map((item) => Tag.fromJson(item as Map<String, dynamic>))

View File

@ -22,6 +22,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/curved_li
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.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_card_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart';
import 'package:syncrow_web/pages/spaces_management/helper/space_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/space_template_model.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@ -195,7 +196,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
screenSize, screenSize,
position: position:
spaces[index].position + newPosition, spaces[index].position + newPosition,
parentIndex: index, parentIndex: index,
direction: direction, direction: direction,
); );
@ -351,7 +351,10 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
spaceModels: widget.spaceModels, spaceModels: widget.spaceModels,
name: widget.selectedSpace!.name, name: widget.selectedSpace!.name,
icon: widget.selectedSpace!.icon, icon: widget.selectedSpace!.icon,
parentSpace: SpaceHelper.findSpaceByInternalId(
widget.selectedSpace?.parent?.internalId, spaces),
editSpace: widget.selectedSpace, editSpace: widget.selectedSpace,
currentSpaceModel: widget.selectedSpace?.spaceModel,
tags: widget.selectedSpace?.tags, tags: widget.selectedSpace?.tags,
subspaces: widget.selectedSpace?.subspaces, subspaces: widget.selectedSpace?.subspaces,
isEdit: true, isEdit: true,
@ -374,6 +377,15 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
widget.selectedSpace!.status = widget.selectedSpace!.status =
SpaceStatus.modified; // Mark as modified SpaceStatus.modified; // Mark as modified
} }
for (var space in spaces) {
if (space.internalId == widget.selectedSpace?.internalId) {
space.status = SpaceStatus.modified;
space.subspaces = subspaces;
space.tags = tags;
space.name = name;
}
}
}); });
}, },
key: Key(widget.selectedSpace!.name), key: Key(widget.selectedSpace!.name),
@ -452,7 +464,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
}).toList(); }).toList();
if (spacesToSave.isEmpty) { if (spacesToSave.isEmpty) {
debugPrint("No new or modified spaces to save.");
return; return;
} }
@ -643,12 +654,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final Map<String, int> nameCounters = {}; final Map<String, int> nameCounters = {};
String _generateCopyName(String originalName) {
final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim();
nameCounters[baseName] = (nameCounters[baseName] ?? 0) + 1;
return "$baseName(${nameCounters[baseName]})";
}
SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition, SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition,
SpaceModel? duplicatedParent) { SpaceModel? duplicatedParent) {
Offset newPosition = parentPosition + Offset(horizontalGap, 0); Offset newPosition = parentPosition + Offset(horizontalGap, 0);
@ -659,7 +664,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
newPosition += Offset(horizontalGap, 0); newPosition += Offset(horizontalGap, 0);
} }
final duplicatedName = _generateCopyName(original.name); final duplicatedName =
SpaceHelper.generateUniqueSpaceName(original.name, spaces);
final duplicated = SpaceModel( final duplicated = SpaceModel(
name: duplicatedName, name: duplicatedName,

View File

@ -40,6 +40,7 @@ class CreateSpaceDialog extends StatefulWidget {
final List<SubspaceModel>? subspaces; final List<SubspaceModel>? subspaces;
final List<Tag>? tags; final List<Tag>? tags;
final List<String>? allTags; final List<String>? allTags;
final SpaceTemplateModel? currentSpaceModel;
const CreateSpaceDialog( const CreateSpaceDialog(
{super.key, {super.key,
@ -54,7 +55,8 @@ class CreateSpaceDialog extends StatefulWidget {
this.selectedProducts = const [], this.selectedProducts = const [],
this.spaceModels, this.spaceModels,
this.subspaces, this.subspaces,
this.tags}); this.tags,
this.currentSpaceModel});
@override @override
CreateSpaceDialogState createState() => CreateSpaceDialogState(); CreateSpaceDialogState createState() => CreateSpaceDialogState();
@ -83,6 +85,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
enteredName.isNotEmpty || nameController.text.isNotEmpty; enteredName.isNotEmpty || nameController.text.isNotEmpty;
tags = widget.tags ?? []; tags = widget.tags ?? [];
subspaces = widget.subspaces ?? []; subspaces = widget.subspaces ?? [];
selectedSpaceModel = widget.currentSpaceModel;
} }
@override @override
@ -457,7 +460,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
context: context, context: context,
builder: (context) => AssignTagDialog( builder: (context) => AssignTagDialog(
products: widget.products, products: widget.products,
subspaces: widget.subspaces, subspaces: subspaces,
addedProducts: TagHelper addedProducts: TagHelper
.createInitialSelectedProductsForTags( .createInitialSelectedProductsForTags(
tags ?? [], subspaces), tags ?? [], subspaces),
@ -488,7 +491,6 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
enteredName, enteredName,
widget.isEdit, widget.isEdit,
widget.products, widget.products,
subspaces,
); );
}, },
style: TextButton.styleFrom( style: TextButton.styleFrom(
@ -571,12 +573,23 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
} }
bool _isNameConflict(String value) { bool _isNameConflict(String value) {
return (widget.parentSpace?.children.any((child) => child.name == value) ?? final parentSpace = widget.parentSpace;
false) || final editSpace = widget.editSpace;
(widget.parentSpace?.name == value) || final siblings = parentSpace?.children
(widget.editSpace?.parent?.name == value) || .where((child) => child.uuid != editSpace?.uuid)
(widget.editSpace?.children.any((child) => child.name == value) ?? .toList() ??
false); [];
final siblingConflict = siblings.any((child) => child.name == value);
final parentConflict =
parentSpace?.name == value && parentSpace?.uuid != editSpace?.uuid;
final parentOfEditSpaceConflict = editSpace?.parent?.name == value &&
editSpace?.parent?.uuid != editSpace?.uuid;
final childConflict =
editSpace?.children.any((child) => child.name == value) ?? false;
return siblingConflict ||
parentConflict ||
parentOfEditSpaceConflict ||
childConflict;
} }
void _showLinkSpaceModelDialog(BuildContext context) { void _showLinkSpaceModelDialog(BuildContext context) {
@ -617,9 +630,26 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
products: products, products: products,
existingSubSpaces: existingSubSpaces, existingSubSpaces: existingSubSpaces,
onSave: (slectedSubspaces) { onSave: (slectedSubspaces) {
final List<Tag> tagsToAppendToSpace = [];
if (slectedSubspaces != null) {
final updatedIds =
slectedSubspaces.map((s) => s.internalId).toSet();
if (existingSubSpaces != null) {
final deletedSubspaces = existingSubSpaces
.where((s) => !updatedIds.contains(s.internalId))
.toList();
for (var s in deletedSubspaces) {
if (s.tags != null) {
tagsToAppendToSpace.addAll(s.tags!);
}
}
}
}
if (slectedSubspaces != null) { if (slectedSubspaces != null) {
setState(() { setState(() {
subspaces = slectedSubspaces; subspaces = slectedSubspaces;
tags?.addAll(tagsToAppendToSpace);
selectedSpaceModel = null; selectedSpaceModel = null;
}); });
} }
@ -629,7 +659,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
} }
void _showTagCreateDialog(BuildContext context, String name, bool isEdit, void _showTagCreateDialog(BuildContext context, String name, bool isEdit,
List<ProductModel>? products, List<SubspaceModel>? subspaces) { List<ProductModel>? products) {
isEdit isEdit
? showDialog( ? showDialog(
context: context, context: context,

View File

@ -11,7 +11,7 @@ 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/pages/spaces_management/space_model/view/space_model_page.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart';
class LoadedSpaceView extends StatelessWidget { class LoadedSpaceView extends StatefulWidget {
final List<CommunityModel> communities; final List<CommunityModel> communities;
final CommunityModel? selectedCommunity; final CommunityModel? selectedCommunity;
final SpaceModel? selectedSpace; final SpaceModel? selectedSpace;
@ -26,41 +26,73 @@ class LoadedSpaceView extends StatelessWidget {
this.selectedSpace, this.selectedSpace,
this.products, this.products,
this.spaceModels, this.spaceModels,
required this.shouldNavigateToSpaceModelPage required this.shouldNavigateToSpaceModelPage,
}); });
@override @override
Widget build(BuildContext context) { _LoadedSpaceViewState createState() => _LoadedSpaceViewState();
}
class _LoadedSpaceViewState extends State<LoadedSpaceView> {
late List<SpaceTemplateModel> _spaceModels;
@override
void initState() {
super.initState();
_spaceModels = List.from(widget.spaceModels ?? []);
}
@override
void didUpdateWidget(covariant LoadedSpaceView oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.spaceModels != oldWidget.spaceModels) {
setState(() {
_spaceModels = List.from(widget.spaceModels ?? []);
});
}
}
void _onSpaceModelsUpdated(List<SpaceTemplateModel> updatedModels) {
if (mounted && updatedModels != _spaceModels) {
setState(() {
_spaceModels = updatedModels;
});
}
}
@override
Widget build(BuildContext context) {
return Stack( return Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
Row( Row(
children: [ children: [
SidebarWidget( SidebarWidget(
communities: communities, communities: widget.communities,
selectedSpaceUuid: selectedSpaceUuid: widget.selectedSpace?.uuid ??
selectedSpace?.uuid ?? selectedCommunity?.uuid ?? '', widget.selectedCommunity?.uuid ??
'',
), ),
shouldNavigateToSpaceModelPage widget.shouldNavigateToSpaceModelPage
? Expanded( ? Expanded(
child: BlocProvider( child: BlocProvider(
create: (context) => SpaceModelBloc( create: (context) => SpaceModelBloc(
api: SpaceModelManagementApi(), api: SpaceModelManagementApi(),
initialSpaceModels: spaceModels ?? [], initialSpaceModels: _spaceModels,
), ),
child: SpaceModelPage( child: SpaceModelPage(
products: products, products: widget.products,
onSpaceModelsUpdated: _onSpaceModelsUpdated,
), ),
), ),
) )
: CommunityStructureArea( : CommunityStructureArea(
selectedCommunity: selectedCommunity, selectedCommunity: widget.selectedCommunity,
selectedSpace: selectedSpace, selectedSpace: widget.selectedSpace,
spaces: selectedCommunity?.spaces ?? [], spaces: widget.selectedCommunity?.spaces ?? [],
products: products, products: widget.products,
communities: communities, communities: widget.communities,
spaceModels: spaceModels, spaceModels: _spaceModels,
), ),
], ],
), ),
@ -68,13 +100,4 @@ class LoadedSpaceView extends StatelessWidget {
], ],
); );
} }
SpaceModel? findSpaceByUuid(String? uuid, List<CommunityModel> communities) {
for (var community in communities) {
for (var space in community.spaces) {
if (space.uuid == uuid) return space;
}
}
return null;
}
} }

View File

@ -233,11 +233,13 @@ class AssignTagDialog extends StatelessWidget {
label: 'Add New Device', label: 'Add New Device',
onPressed: () async { onPressed: () async {
final updatedTags = List<Tag>.from(state.tags); final updatedTags = List<Tag>.from(state.tags);
final result = processTags(updatedTags, subspaces); final result =
TagHelper.processTags(updatedTags, subspaces);
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();
@ -270,13 +272,14 @@ class AssignTagDialog extends StatelessWidget {
onPressed: state.isSaveEnabled onPressed: state.isSaveEnabled
? () async { ? () async {
final updatedTags = List<Tag>.from(state.tags); final updatedTags = List<Tag>.from(state.tags);
final result = final result = TagHelper.processTags(
processTags(updatedTags, subspaces); updatedTags, subspaces);
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,115 +304,12 @@ 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();
}
Map<String, dynamic> processTags(
List<Tag> updatedTags, List<SubspaceModel>? subspaces) {
final modifiedTags = List<Tag>.from(updatedTags);
final modifiedSubspaces = List<SubspaceModel>.from(subspaces ?? []);
if (subspaces != null) {
for (var subspace in subspaces) {
subspace.tags?.removeWhere(
(tag) => !modifiedTags
.any((updatedTag) => updatedTag.internalId == tag.internalId),
); );
} return availableTagsForTagModel;
}
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,
};
}
int? checkTagExistInSubspace(Tag tag, List<SubspaceModel>? 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;
} }
} }

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

@ -0,0 +1,43 @@
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';
class SpaceHelper {
static SpaceModel? findSpaceByUuid(
String? uuid, List<CommunityModel> communities) {
for (var community in communities) {
for (var space in community.spaces) {
if (space.uuid == uuid) return space;
}
}
return null;
}
static SpaceModel? findSpaceByInternalId(
String? internalId, List<SpaceModel> spaces) {
if (internalId != null) {
for (var space in spaces) {
if (space.internalId == internalId) return space;
}
}
return null;
}
static String generateUniqueSpaceName(
String originalName, List<SpaceModel> spaces) {
final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim();
int maxNumber = 0;
for (var space in spaces) {
final match = RegExp(r'^(.*?)\((\d+)\)$').firstMatch(space.name);
if (match != null && match.group(1)?.trim() == baseName) {
int existingNumber = int.parse(match.group(2)!);
if (existingNumber > maxNumber) {
maxNumber = existingNumber;
}
}
}
return "$baseName(${maxNumber + 1})";
}
}

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,64 @@ 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,
);
}
static int? checkTagExistInSubspace(Tag tag, List<dynamic>? 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;
}
static Map<String, dynamic> processTags(
List<Tag> updatedTags, List<SubspaceModel>? subspaces) {
return TagHelper.updateTags<Tag>(
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: checkTagExistInSubspace,
);
}
} }

View File

@ -1,5 +1,4 @@
import 'package:equatable/equatable.dart'; 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/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_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';

View File

@ -11,8 +11,10 @@ import 'package:syncrow_web/utils/color_manager.dart';
class SpaceModelPage extends StatelessWidget { class SpaceModelPage extends StatelessWidget {
final List<ProductModel>? products; final List<ProductModel>? products;
final Function(List<SpaceTemplateModel>)? onSpaceModelsUpdated;
const SpaceModelPage({Key? key, this.products}) : super(key: key); const SpaceModelPage({Key? key, this.products, this.onSpaceModelsUpdated})
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -25,6 +27,10 @@ class SpaceModelPage extends StatelessWidget {
final allTagValues = _getAllTagValues(spaceModels); final allTagValues = _getAllTagValues(spaceModels);
final allSpaceModelNames = _getAllSpaceModelName(spaceModels); final allSpaceModelNames = _getAllSpaceModelName(spaceModels);
if (onSpaceModelsUpdated != null) {
onSpaceModelsUpdated!(spaceModels);
}
return Scaffold( return Scaffold(
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
body: Padding( body: Padding(

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);
} }
} }
} }