Compare commits

..

1 Commits

Author SHA1 Message Date
132cafcaa2 Enhanced the side tree design 2025-02-05 11:52:44 +03:00
38 changed files with 932 additions and 1099 deletions

View File

@ -31,8 +31,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
////////////////////////////// forget password ////////////////////////////////// ////////////////////////////// forget password //////////////////////////////////
final TextEditingController forgetEmailController = TextEditingController(); final TextEditingController forgetEmailController = TextEditingController();
final TextEditingController forgetPasswordController = final TextEditingController forgetPasswordController = TextEditingController();
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>();
@ -49,8 +48,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
return; return;
} }
_remainingTime = 1; _remainingTime = 1;
add(UpdateTimerEvent( add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
remainingTime: _remainingTime, isButtonEnabled: false));
try { try {
forgetEmailValidate = ''; forgetEmailValidate = '';
_remainingTime = (await AuthenticationAPI.sendOtp( _remainingTime = (await AuthenticationAPI.sendOtp(
@ -87,8 +85,7 @@ 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( add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
remainingTime: _remainingTime, isButtonEnabled: false));
} }
}); });
} }
@ -98,8 +95,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
} }
Future<void> changePassword( Future<void> changePassword(ChangePasswordEvent event, Emitter<AuthState> emit) async {
ChangePasswordEvent event, Emitter<AuthState> emit) async {
emit(LoadingForgetState()); emit(LoadingForgetState());
try { try {
var response = await AuthenticationAPI.verifyOtp( var response = await AuthenticationAPI.verifyOtp(
@ -115,8 +111,7 @@ 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 = String errorMessage = errorData['error']['message'] ?? 'something went wrong';
errorData['error']['message'] ?? 'something went wrong';
validate = errorMessage; validate = errorMessage;
emit(AuthInitialState()); emit(AuthInitialState());
} }
@ -130,9 +125,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} }
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) { void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
emit(TimerState( emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime));
isButtonEnabled: event.isButtonEnabled,
remainingTime: event.remainingTime));
} }
///////////////////////////////////// login ///////////////////////////////////// ///////////////////////////////////// login /////////////////////////////////////
@ -168,23 +161,15 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
password: event.password, password: event.password,
), ),
); );
} on DioException catch (e) { } catch (failure) {
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( await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken);
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());
@ -342,14 +327,12 @@ 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 = await SharedPreferencesHelper.readBoolFromSP( final firstLaunch =
StringsManager.firstLaunch) ?? await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
true;
if (firstLaunch) { if (firstLaunch) {
storage.deleteAll(); storage.deleteAll();
} }
await SharedPreferencesHelper.saveBoolToSP( await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false);
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';
@ -402,9 +385,7 @@ 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 hours.toString().padLeft(2, '0'), // Show hours if there are days or 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,9 +21,7 @@ class LoginWithEmailModel {
return { return {
'email': email, 'email': email,
'password': password, 'password': password,
"platform": "web"
// 'regionUuid': regionUuid, // 'regionUuid': regionUuid,
}; };
} }
} }
//tst@tst.com

View File

@ -69,7 +69,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
}, },
)), )),
Expanded( Expanded(
flex: 3, flex: 4,
child: state is DeviceManagementLoading child: state is DeviceManagementLoading
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: Column( : Column(

View File

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

View File

@ -63,11 +63,9 @@ class _AgreementAndPrivacyDialogState extends State<AgreementAndPrivacyDialog> {
} }
String get _dialogTitle => String get _dialogTitle =>
_currentPage == 1 ? 'User Agreement' : 'Privacy Policy'; _currentPage == 2 ? 'User Agreement' : 'Privacy Policy';
String get _dialogContent => _currentPage == 1 ? widget.terms : widget.policy; String get _dialogContent => _currentPage == 2 ? 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(
@ -87,7 +85,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 $staticText", data: _dialogContent,
onLinkTap: (url, attributes, element) async { onLinkTap: (url, attributes, element) async {
if (url != null) { if (url != null) {
final uri = Uri.parse(url); final uri = Uri.parse(url);

View File

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

View File

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

View File

@ -79,14 +79,13 @@ 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 =
@ -103,6 +102,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
}).toList(), }).toList(),
); );
emit(const SpacesLoadedState()); emit(const SpacesLoadedState());
return updatedCommunities;
} catch (e) { } catch (e) {
emit(ErrorState('Error loading communities and spaces: $e')); emit(ErrorState('Error loading communities and spaces: $e'));
} }
@ -177,6 +177,7 @@ 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'));
@ -207,13 +208,10 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
return anyMatch; return anyMatch;
} }
void _sendInvitUser(SendInviteUsers event, Emitter<UsersState> emit) async { _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,
@ -223,8 +221,7 @@ 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,14 +248,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

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

View File

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

View File

@ -40,7 +40,9 @@ 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);
@ -55,12 +57,15 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
for (var user in users) { for (var user in users) {
createdBy.add(user.invitedBy.toString()); createdBy.add(user.invitedBy.toString());
} }
// for (var user in users) {
// deActivate.add(user.status.toString());
// }
initialUsers = List.from(users); initialUsers = List.from(users);
roleTypes = roleTypes.toSet().toList(); roleTypes = roleTypes.toSet().toList();
jobTitle = jobTitle.toSet().toList(); jobTitle = jobTitle.toSet().toList();
createdBy = createdBy.toSet().toList(); createdBy = createdBy.toSet().toList();
// deActivate = deActivate.toSet().toList();
_handlePageChange(ChangePage(1), emit); _handlePageChange(ChangePage(1), emit);
add(ChangePage(currentPage));
emit(UsersLoadedState(users: users)); emit(UsersLoadedState(users: users));
} catch (e) { } catch (e) {
emit(ErrorState(e.toString())); emit(ErrorState(e.toString()));
@ -120,10 +125,6 @@ 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 = "";
@ -142,16 +143,13 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
void _toggleSortUsersByNameDesc( void _toggleSortUsersByNameDesc(
SortUsersByNameDesc event, Emitter<UserTableState> emit) { SortUsersByNameDesc event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
if (currentSortOrder == "Desc") { if (currentSortOrder == "Desc") {
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = ""; currentSortOrder = "";
users = List.from(initialUsers); users = List.from(initialUsers); // Reset to saved initial state
emit(UsersLoadedState(users: users)); emit(UsersLoadedState(users: users));
} else { } else {
// Sort descending
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = "Desc"; currentSortOrder = "Desc";
users.sort((a, b) => b.firstName!.compareTo(a.firstName!)); users.sort((a, b) => b.firstName!.compareTo(a.firstName!));
@ -161,10 +159,6 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
void _toggleSortUsersByDateNewestToOldest( void _toggleSortUsersByDateNewestToOldest(
DateNewestToOldestEvent event, Emitter<UserTableState> emit) { DateNewestToOldestEvent event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
if (currentSortOrderDate == "NewestToOldest") { if (currentSortOrderDate == "NewestToOldest") {
emit(UsersLoadingState()); emit(UsersLoadingState());
currentSortOrder = ""; currentSortOrder = "";
@ -185,10 +179,6 @@ 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 = "";
@ -347,20 +337,7 @@ 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";
@ -374,11 +351,9 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} else { } else {
currentSortOrder = ""; currentSortOrder = "";
} }
emit(UsersLoadedState(users: filteredUsers)); emit(UsersLoadedState(users: filteredUsers));
} }
void _resetAllFilters(Emitter<UserTableState> emit) { void _resetAllFilters(Emitter<UserTableState> emit) {
selectedRoles.clear(); selectedRoles.clear();
selectedJobTitles.clear(); selectedJobTitles.clear();

View File

@ -12,7 +12,7 @@ Future<void> showDateFilterMenu({
Overlay.of(context).context.findRenderObject() as RenderBox; Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect( final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB( Rect.fromLTRB(
overlay.size.width / 3, overlay.size.width / 2,
240, 240,
0, 0,
overlay.size.height, overlay.size.height,
@ -40,6 +40,7 @@ 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
@ -64,5 +65,9 @@ Future<void> showDateFilterMenu({
), ),
), ),
], ],
).then((value) {}); ).then((value) {
// setState(() {
// _isDropdownOpen = false;
// });
});
} }

View File

@ -40,6 +40,7 @@ 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
@ -64,5 +65,9 @@ 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 / 35, overlay.size.width / 25,
240, 240,
0, 0,
overlay.size.height, overlay.size.height,
@ -40,6 +40,7 @@ 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),
), ),
@ -60,5 +61,9 @@ Future<void> showNameMenu({
), ),
), ),
], ],
).then((value) {}); ).then((value) {
// setState(() {
// _isDropdownOpen = false;
// });
});
} }

View File

@ -1,264 +1,260 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/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 _HeaderColumn extends StatelessWidget {
final String title;
final double width;
final bool showFilter;
final VoidCallback? onFilter;
final Function(double) onResize;
const _HeaderColumn({
required this.title,
required this.width,
required this.showFilter,
required this.onResize,
this.onFilter,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return MouseRegion(
cursor: SystemMouseCursors.resizeColumn,
child: GestureDetector(
onHorizontalDragUpdate: (details) => onResize(details.delta.dx),
child: Container(
width: width,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: const BoxDecoration(
border: Border(right: BorderSide(color: ColorsManager.boxDivider)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.w400,
fontSize: 13,
color: ColorsManager.grayColor,
),
),
),
if (showFilter)
IconButton(
icon: SvgPicture.asset(Assets.filterTableIcon),
onPressed: onFilter,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
),
),
);
}
}
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 { class DynamicTableScreen extends StatefulWidget {
final List<String> titles; final List<String> titles;
final List<List<Widget>> rows; final List<List<Widget>> rows;
final void Function(int columnIndex)? onFilter; final void Function(int columnIndex)? onFilter;
const DynamicTableScreen({ DynamicTableScreen(
required this.titles, {required this.titles, required this.rows, required this.onFilter});
required this.rows,
required this.onFilter,
Key? key,
}) : super(key: key);
@override @override
_DynamicTableScreenState createState() => _DynamicTableScreenState(); _DynamicTableScreenState createState() => _DynamicTableScreenState();
} }
class _DynamicTableScreenState extends State<DynamicTableScreen> { class _DynamicTableScreenState extends State<DynamicTableScreen>
with WidgetsBindingObserver {
late List<double> columnWidths; late List<double> columnWidths;
final double _minColumnWidth = 100.0; late double totalWidth;
final double _maxColumnWidth = 300.0;
final double _dividerWidth = 1.0;
double _lastAvailableWidth = 0;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
columnWidths = List.filled(widget.titles.length, _minColumnWidth); columnWidths = List<double>.filled(widget.titles.length, 150.0);
totalWidth = columnWidths.reduce((a, b) => a + b);
WidgetsBinding.instance.addObserver(this);
} }
void _handleColumnResize(int index, double delta) { @override
setState(() { void dispose() {
double newWidth = columnWidths[index] + delta; WidgetsBinding.instance.removeObserver(this);
newWidth = newWidth.clamp(_minColumnWidth, _maxColumnWidth); super.dispose();
double actualDelta = newWidth - columnWidths[index]; }
if (actualDelta == 0) return;
int nextIndex = (index + 1) % columnWidths.length; @override
columnWidths[index] = newWidth; void didChangeMetrics() {
columnWidths[nextIndex] = (columnWidths[nextIndex] - actualDelta) super.didChangeMetrics();
.clamp(_minColumnWidth, _maxColumnWidth); 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
});
}); });
} }
Widget _buildHeader() { @override
return Container( Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
if (columnWidths.every((width) => width == screenWidth * 7)) {
columnWidths = List<double>.generate(widget.titles.length, (index) {
if (index == 1) {
return screenWidth * 0.11;
} else if (index == 9) {
return screenWidth * 0.1;
}
return screenWidth * 0.09;
});
setState(() {});
}
return SingleChildScrollView(
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
child: Container(
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.all(Radius.circular(20))),
child: FittedBox(
child: Column(
children: [
Container(
width: totalWidth,
decoration: containerDecoration.copyWith( decoration: containerDecoration.copyWith(
color: ColorsManager.circleRolesBackground, color: ColorsManager.circleRolesBackground,
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15), topLeft: Radius.circular(15),
topRight: Radius.circular(15), topRight: Radius.circular(15))),
),
),
child: Row( child: Row(
children: List.generate(widget.titles.length, (index) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
for (int i = 0; i < widget.titles.length; i++) FittedBox(
_HeaderColumn( child: Container(
title: widget.titles[i], padding: const EdgeInsets.only(left: 5, right: 5),
width: columnWidths[i], width: columnWidths[index],
showFilter: i != 1 && i != 9 && i != 8 && i != 5, child: Row(
onFilter: () => widget.onFilter?.call(i), mainAxisAlignment: MainAxisAlignment.spaceBetween,
onResize: (delta) => _handleColumnResize(i, delta), crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
child: Text(
widget.titles[index],
maxLines: 2,
style: const TextStyle(
overflow: TextOverflow.ellipsis,
fontWeight: FontWeight.w400,
fontSize: 13,
color: ColorsManager.grayColor,
), ),
),
),
if (index != 1 &&
index != 9 &&
index != 8 &&
index != 5)
FittedBox(
child: IconButton(
icon: SvgPicture.asset(
Assets.filterTableIcon,
fit: BoxFit.none,
),
onPressed: () {
if (widget.onFilter != null) {
widget.onFilter!(index);
}
},
),
)
], ],
), ),
),
),
GestureDetector(
onHorizontalDragUpdate: (details) {
setState(() {
columnWidths[index] =
(columnWidths[index] + details.delta.dx)
.clamp(150.0, 300.0);
totalWidth = columnWidths.reduce((a, b) => a + b);
});
},
child: MouseRegion(
cursor: SystemMouseCursors.resizeColumn,
child: Container(
color: Colors.green,
child: Container(
color: ColorsManager.boxDivider,
width: 1,
height: 50,
),
),
),
),
],
); );
} }),
),
Widget _buildBody() { ),
if (widget.rows.isEmpty) { widget.rows.isEmpty
return SizedBox( ? SizedBox(
height: 300, height: MediaQuery.of(context).size.height / 2,
child: Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [ children: [
SvgPicture.asset(Assets.emptyTable), SvgPicture.asset(Assets.emptyTable),
const SizedBox(height: 15), const SizedBox(
height: 15,
),
const Text( const Text(
'No Users', 'No Users',
style: TextStyle( style: TextStyle(
color: ColorsManager.lightGrayColor, color: ColorsManager.lightGrayColor,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700),
), )
],
), ),
], ],
), ),
), )
); : Center(
} child: Container(
width: totalWidth,
return Container(
decoration: containerDecoration.copyWith( decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(15), bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15), bottomRight: Radius.circular(15))),
), child: ListView.builder(
), physics: const NeverScrollableScrollPhysics(),
child: Column( 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: [ children: [
for (int rowIndex = 0; rowIndex < widget.rows.length; rowIndex++) Container(
_TableRow( child: Padding(
cells: widget.rows[rowIndex], padding: const EdgeInsets.only(
columnWidths: columnWidths, left: 5, top: 10, right: 5, bottom: 10),
isLast: rowIndex == widget.rows.length - 1, 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],
), ),
],
), ),
); );
} }),
),
@override ),
Widget build(BuildContext context) { ),
return LayoutBuilder( if (rowIndex < widget.rows.length - 1)
builder: (context, constraints) { Row(
final availableWidth = constraints.maxWidth; children: List.generate(
final totalDividersWidth = (widget.titles.length - 1) * _dividerWidth; widget.titles.length, (index) {
return SizedBox(
if (_lastAvailableWidth != availableWidth) { width: columnWidths[index],
final equalWidth = child: const Divider(
(availableWidth - totalDividersWidth) / widget.titles.length; color: ColorsManager.boxDivider,
final clampedWidth = thickness: 1,
equalWidth.clamp(_minColumnWidth, _maxColumnWidth); height: 1,
),
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,6 +108,7 @@ 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(
@ -214,7 +215,7 @@ class UsersPage extends StatelessWidget {
showPopUpFilterMenu( showPopUpFilterMenu(
position: RelativeRect.fromLTRB( position: RelativeRect.fromLTRB(
overlay.size.width / 5.3, overlay.size.width / 4,
240, 240,
overlay.size.width / 4, overlay.size.width / 4,
0, 0,
@ -224,7 +225,6 @@ class UsersPage extends StatelessWidget {
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortOrder,
onOkPressed: () { onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent()); _blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries final selectedItems = checkboxStates.entries
.where((entry) => entry.value) .where((entry) => entry.value)
@ -265,7 +265,6 @@ class UsersPage extends StatelessWidget {
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortOrder,
onOkPressed: () { onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent()); _blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries final selectedItems = checkboxStates.entries
.where((entry) => entry.value) .where((entry) => entry.value)
@ -321,7 +320,6 @@ class UsersPage extends StatelessWidget {
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortOrder,
onOkPressed: () { onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent()); _blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries final selectedItems = checkboxStates.entries
.where((entry) => entry.value) .where((entry) => entry.value)
@ -345,7 +343,6 @@ 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;
@ -353,7 +350,7 @@ class UsersPage extends StatelessWidget {
position: RelativeRect.fromLTRB( position: RelativeRect.fromLTRB(
overlay.size.width / 0, overlay.size.width / 0,
240, 240,
overlay.size.width / 5, overlay.size.width / 4,
0, 0,
), ),
list: _blocRole.status, list: _blocRole.status,
@ -361,8 +358,8 @@ class UsersPage extends StatelessWidget {
checkboxStates: checkboxStates, checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder, isSelected: _blocRole.currentSortOrder,
onOkPressed: () { onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent()); _blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries final selectedItems = checkboxStates.entries
.where((entry) => entry.value) .where((entry) => entry.value)
.map((entry) => entry.key) .map((entry) => entry.key)
@ -413,7 +410,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 ?? ''),
@ -430,6 +427,11 @@ 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,
@ -441,6 +443,10 @@ class UsersPage extends StatelessWidget {
), ),
Row( Row(
children: [ children: [
// actionButton(
// title: "Activity Log",
// onTap: () {},
// ),
actionButton( actionButton(
title: "Edit", title: "Edit",
onTap: () { onTap: () {
@ -480,9 +486,11 @@ class UsersPage extends StatelessWidget {
}); });
}, },
).then((v) { ).then((v) {
if (v != null) {
if (v != null) { if (v != null) {
_blocRole.add(const GetUsers()); _blocRole.add(const GetUsers());
} }
}
}); });
}, },
), ),

View File

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

View File

@ -10,10 +10,23 @@ 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 StatelessWidget { class SpaceTreeView extends StatefulWidget {
final Function onSelect; final Function onSelect;
const SpaceTreeView({required this.onSelect, 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) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) { return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
@ -21,7 +34,6 @@ class SpaceTreeView extends StatelessWidget {
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(
@ -33,55 +45,71 @@ class SpaceTreeView extends StatelessWidget {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(
child: Padding( child: ListView(
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, // Gray when not selected color: ColorsManager.lightGrayColor,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
), ),
) )
: ListView( : Scrollbar(
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: isSelected: state.selectedCommunities
state.selectedCommunities.contains(community.uuid), .contains(community.uuid),
isSoldCheck: isSoldCheck: state.selectedCommunities
state.selectedCommunities.contains(community.uuid), .contains(community.uuid),
onExpansionChanged: () { onExpansionChanged: () {
context context
.read<SpaceTreeBloc>() .read<SpaceTreeBloc>()
.add(OnCommunityExpanded(community.uuid)); .add(OnCommunityExpanded(community.uuid));
}, },
isExpanded: isExpanded: state.expandedCommunities
state.expandedCommunities.contains(community.uuid), .contains(community.uuid),
onItemSelected: () { onItemSelected: () {
context.read<SpaceTreeBloc>().add( context.read<SpaceTreeBloc>().add(
OnCommunitySelected(community.uuid, community.spaces)); OnCommunitySelected(
community.uuid, community.spaces));
onSelect(); widget.onSelect();
}, },
children: community.spaces.map((space) { children: community.spaces.map((space) {
return CustomExpansionTileSpaceTree( return CustomExpansionTileSpaceTree(
title: space.name, title: space.name,
isExpanded: state.expandedSpaces.contains(space.uuid), isExpanded:
state.expandedSpaces.contains(space.uuid),
onItemSelected: () { onItemSelected: () {
context.read<SpaceTreeBloc>().add(OnSpaceSelected( context.read<SpaceTreeBloc>().add(
community.uuid, space.uuid ?? '', space.children)); OnSpaceSelected(community.uuid,
onSelect(); space.uuid ?? '', space.children));
widget.onSelect();
}, },
onExpansionChanged: () { onExpansionChanged: () {
context.read<SpaceTreeBloc>().add( context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(community.uuid, space.uuid ?? '')); OnSpaceExpanded(
community.uuid, space.uuid ?? ''));
}, },
isSelected: state.selectedSpaces.contains(space.uuid) || isSelected:
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(
@ -94,6 +122,73 @@ class SpaceTreeView extends StatelessWidget {
), ),
), ),
), ),
),
],
),
),
// 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(),
// ),
// ),
// ),
], ],
), ),
); );
@ -113,7 +208,7 @@ class SpaceTreeView extends StatelessWidget {
context context
.read<SpaceTreeBloc>() .read<SpaceTreeBloc>()
.add(OnSpaceSelected(communityId, child.uuid ?? '', child.children)); .add(OnSpaceSelected(communityId, child.uuid ?? '', child.children));
onSelect(); widget.onSelect();
}, },
onExpansionChanged: () { onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(OnSpaceExpanded(communityId, child.uuid ?? '')); context.read<SpaceTreeBloc>().add(OnSpaceExpanded(communityId, child.uuid ?? ''));

View File

@ -87,7 +87,6 @@ class SpaceManagementBloc
prevSpaceModels = List<SpaceTemplateModel>.from( prevSpaceModels = List<SpaceTemplateModel>.from(
(previousState as dynamic).spaceModels ?? [], (previousState as dynamic).spaceModels ?? [],
); );
allSpaces.addAll(prevSpaceModels);
} }
if (prevSpaceModels.isEmpty) { if (prevSpaceModels.isEmpty) {
@ -180,7 +179,6 @@ 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,

View File

@ -95,9 +95,6 @@ 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,9 +22,6 @@ 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/connection_helper.dart';
import 'package:syncrow_web/pages/spaces_management/helper/space_helper.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@ -133,7 +130,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
communities: widget.communities, communities: widget.communities,
communityName: widget.selectedCommunity?.name, communityName: widget.selectedCommunity?.name,
community: widget.selectedCommunity, community: widget.selectedCommunity,
isSave: SpaceHelper.isSave(spaces), isSave: isSave(spaces),
isEditingName: isEditingName, isEditingName: isEditingName,
nameController: _nameController, nameController: _nameController,
onSave: _saveSpaces, onSave: _saveSpaces,
@ -178,8 +175,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
children: [ children: [
for (var connection in connections) for (var connection in connections)
Opacity( Opacity(
opacity: ConnectionHelper.isHighlightedConnection( opacity: _isHighlightedConnection(connection)
connection, widget.selectedSpace)
? 1.0 ? 1.0
: 0.3, // Adjust opacity : 0.3, // Adjust opacity
child: CustomPaint( child: CustomPaint(
@ -199,6 +195,7 @@ 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,
); );
@ -212,8 +209,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
}, },
buildSpaceContainer: (int index) { buildSpaceContainer: (int index) {
final bool isHighlighted = final bool isHighlighted =
SpaceHelper.isHighlightedSpace( _isHighlightedSpace(spaces[index]);
spaces[index], widget.selectedSpace);
return Opacity( return Opacity(
opacity: isHighlighted ? 1.0 : 0.3, opacity: isHighlighted ? 1.0 : 0.3,
@ -299,8 +295,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
return CreateSpaceDialog( return CreateSpaceDialog(
products: widget.products, products: widget.products,
spaceModels: widget.spaceModels, spaceModels: widget.spaceModels,
allTags: allTags: _getAllTagValues(spaces),
TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
parentSpace: parentIndex != null ? spaces[parentIndex] : null, parentSpace: parentIndex != null ? spaces[parentIndex] : null,
onCreateSpace: (String name, onCreateSpace: (String name,
String icon, String icon,
@ -311,7 +306,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
setState(() { setState(() {
// Set the first space in the center or use passed position // Set the first space in the center or use passed position
Offset centerPosition = Offset centerPosition =
position ?? ConnectionHelper.getCenterPosition(screenSize); position ?? _getCenterPosition(screenSize);
SpaceModel newSpace = SpaceModel( SpaceModel newSpace = SpaceModel(
name: name, name: name,
icon: icon, icon: icon,
@ -356,15 +351,11 @@ 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,
allTags: TagHelper.getAllTagValues( allTags: _getAllTagValues(spaces),
widget.communities, widget.spaceModels),
onCreateSpace: (String name, onCreateSpace: (String name,
String icon, String icon,
List<SelectedProduct> selectedProducts, List<SelectedProduct> selectedProducts,
@ -383,15 +374,6 @@ 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),
@ -470,6 +452,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
}).toList(); }).toList();
if (spacesToSave.isEmpty) { if (spacesToSave.isEmpty) {
debugPrint("No new or modified spaces to save.");
return; return;
} }
@ -533,6 +516,17 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
); );
} }
bool _isHighlightedSpace(SpaceModel space) {
final selectedSpace = widget.selectedSpace;
if (selectedSpace == null) return true;
return space == selectedSpace ||
selectedSpace.parent?.internalId == space.internalId ||
selectedSpace.children
?.any((child) => child.internalId == space.internalId) ==
true;
}
void _deselectSpace(BuildContext context) { void _deselectSpace(BuildContext context) {
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent( SelectSpaceEvent(
@ -540,6 +534,28 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
); );
} }
bool _isHighlightedConnection(Connection connection) {
if (widget.selectedSpace == null) return true;
return connection.startSpace == widget.selectedSpace ||
connection.endSpace == widget.selectedSpace;
}
Offset _getCenterPosition(Size screenSize) {
return Offset(
screenSize.width / 2 - 260,
screenSize.height / 2 - 200,
);
}
bool isSave(List<SpaceModel> spaces) {
return spaces.isNotEmpty &&
spaces.any((space) =>
space.status == SpaceStatus.newSpace ||
space.status == SpaceStatus.modified ||
space.status == SpaceStatus.deleted);
}
void _onDuplicate(BuildContext parentContext) { void _onDuplicate(BuildContext parentContext) {
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
@ -625,10 +641,17 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
const double horizontalGap = 200.0; const double horizontalGap = 200.0;
const double verticalGap = 100.0; const double verticalGap = 100.0;
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 = Offset newPosition = parentPosition + Offset(horizontalGap, 0);
Offset(parentPosition.dx + horizontalGap, original.position.dy);
while (spaces.any((s) => while (spaces.any((s) =>
(s.position - newPosition).distance < horizontalGap && (s.position - newPosition).distance < horizontalGap &&
@ -636,18 +659,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
newPosition += Offset(horizontalGap, 0); newPosition += Offset(horizontalGap, 0);
} }
final duplicatedName = final duplicatedName = _generateCopyName(original.name);
SpaceHelper.generateUniqueSpaceName(original.name, spaces);
final List<SubspaceModel>? duplicatedSubspaces;
final List<Tag>? duplicatedTags;
if (original.spaceModel != null) {
duplicatedTags = [];
duplicatedSubspaces = [];
} else {
duplicatedTags = original.tags;
duplicatedSubspaces = original.subspaces;
}
final duplicated = SpaceModel( final duplicated = SpaceModel(
name: duplicatedName, name: duplicatedName,
@ -658,8 +670,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
status: SpaceStatus.newSpace, status: SpaceStatus.newSpace,
parent: duplicatedParent, parent: duplicatedParent,
spaceModel: original.spaceModel, spaceModel: original.spaceModel,
subspaces: duplicatedSubspaces, subspaces: original.subspaces,
tags: duplicatedTags, tags: original.tags,
); );
originalToDuplicate[original] = duplicated; originalToDuplicate[original] = duplicated;
@ -672,7 +684,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final newConnection = Connection( final newConnection = Connection(
startSpace: duplicatedParent, startSpace: duplicatedParent,
endSpace: duplicated, endSpace: duplicated,
direction: original.incomingConnection?.direction ?? 'down', direction: "down",
); );
connections.add(newConnection); connections.add(newConnection);
duplicated.incomingConnection = newConnection; duplicated.incomingConnection = newConnection;
@ -711,8 +723,10 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
child.incomingConnection?.direction == "down" ?? false; child.incomingConnection?.direction == "down" ?? false;
if (isDownDirection && childrenWithDownDirection.length == 1) { if (isDownDirection && childrenWithDownDirection.length == 1) {
// Place the only "down" child vertically aligned with the parent
childStartPosition = duplicated.position + Offset(0, verticalGap); childStartPosition = duplicated.position + Offset(0, verticalGap);
} else if (!isDownDirection) { } else if (!isDownDirection) {
// Position children with other directions horizontally
childStartPosition = duplicated.position + Offset(horizontalGap, 0); childStartPosition = duplicated.position + Offset(horizontalGap, 0);
} }
@ -733,4 +747,14 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
duplicateRecursive(space, space.position, duplicatedParent); duplicateRecursive(space, space.position, duplicatedParent);
} }
} }
List<String> _getAllTagValues(List<SpaceModel> spaces) {
final List<String> allTags = [];
for (final space in spaces) {
if (space.tags != null) {
allTags.addAll(space.listAllTagValues());
}
}
return allTags;
}
} }

View File

@ -12,7 +12,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/helper/space_helper.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
@ -41,7 +40,6 @@ 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,
@ -56,8 +54,7 @@ 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();
@ -84,22 +81,12 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled = isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty; enteredName.isNotEmpty || nameController.text.isNotEmpty;
if (widget.currentSpaceModel != null) {
subspaces = [];
tags = [];
} else {
tags = widget.tags ?? []; tags = widget.tags ?? [];
subspaces = widget.subspaces ?? []; subspaces = widget.subspaces ?? [];
} }
selectedSpaceModel = widget.currentSpaceModel;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isSpaceModelDisabled = (tags != null && tags!.isNotEmpty ||
subspaces != null && subspaces!.isNotEmpty);
bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null);
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
return AlertDialog( return AlertDialog(
title: widget.isEdit title: widget.isEdit
@ -178,7 +165,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
isNameFieldInvalid = value.isEmpty; isNameFieldInvalid = value.isEmpty;
if (!isNameFieldInvalid) { if (!isNameFieldInvalid) {
if (SpaceHelper.isNameConflict(value, widget.parentSpace, widget.editSpace)) { if (_isNameConflict(value)) {
isNameFieldExist = true; isNameFieldExist = true;
isOkButtonEnabled = false; isOkButtonEnabled = false;
} else { } else {
@ -245,14 +232,11 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
), ),
onPressed: () { onPressed: () {
isSpaceModelDisabled _showLinkSpaceModelDialog(context);
? null
: _showLinkSpaceModelDialog(context);
}, },
child: ButtonContentWidget( child: const ButtonContentWidget(
svgAssets: Assets.link, svgAssets: Assets.link,
label: 'Link a space model', label: 'Link a space model',
disabled: isSpaceModelDisabled,
), ),
) )
: Container( : Container(
@ -341,15 +325,12 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
overlayColor: ColorsManager.transparentColor, overlayColor: ColorsManager.transparentColor,
), ),
onPressed: () async { onPressed: () async {
isTagsAndSubspaceModelDisabled _showSubSpaceDialog(context, enteredName, [],
? null false, widget.products, subspaces);
: _showSubSpaceDialog(context, enteredName,
[], false, widget.products, subspaces);
}, },
child: ButtonContentWidget( child: const ButtonContentWidget(
icon: Icons.add, icon: Icons.add,
label: 'Create Sub Space', label: 'Create Sub Space',
disabled: isTagsAndSubspaceModelDisabled,
), ),
) )
: SizedBox( : SizedBox(
@ -476,8 +457,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
context: context, context: context,
builder: (context) => AssignTagDialog( builder: (context) => AssignTagDialog(
products: widget.products, products: widget.products,
subspaces: subspaces, subspaces: widget.subspaces,
allTags: widget.allTags,
addedProducts: TagHelper addedProducts: TagHelper
.createInitialSelectedProductsForTags( .createInitialSelectedProductsForTags(
tags ?? [], subspaces), tags ?? [], subspaces),
@ -503,22 +483,20 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
) )
: TextButton( : TextButton(
onPressed: () { onPressed: () {
isTagsAndSubspaceModelDisabled _showTagCreateDialog(
? null
: _showTagCreateDialog(
context, context,
enteredName, enteredName,
widget.isEdit, widget.isEdit,
widget.products, widget.products,
subspaces,
); );
}, },
style: TextButton.styleFrom( style: TextButton.styleFrom(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
), ),
child: ButtonContentWidget( child: const ButtonContentWidget(
icon: Icons.add, icon: Icons.add,
label: 'Add Devices', label: 'Add Devices',
disabled: isTagsAndSubspaceModelDisabled,
)) ))
], ],
), ),
@ -592,6 +570,14 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
bool _isNameConflict(String value) {
return (widget.parentSpace?.children.any((child) => child.name == value) ??
false) ||
(widget.parentSpace?.name == value) ||
(widget.editSpace?.parent?.name == value) ||
(widget.editSpace?.children.any((child) => child.name == value) ??
false);
}
void _showLinkSpaceModelDialog(BuildContext context) { void _showLinkSpaceModelDialog(BuildContext context) {
showDialog( showDialog(
@ -631,26 +617,9 @@ 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;
}); });
} }
@ -660,7 +629,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<ProductModel>? products, List<SubspaceModel>? subspaces) {
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 StatefulWidget { class LoadedSpaceView extends StatelessWidget {
final List<CommunityModel> communities; final List<CommunityModel> communities;
final CommunityModel? selectedCommunity; final CommunityModel? selectedCommunity;
final SpaceModel? selectedSpace; final SpaceModel? selectedSpace;
@ -26,73 +26,41 @@ class LoadedSpaceView extends StatefulWidget {
this.selectedSpace, this.selectedSpace,
this.products, this.products,
this.spaceModels, this.spaceModels,
required this.shouldNavigateToSpaceModelPage, required this.shouldNavigateToSpaceModelPage
}); });
@override
_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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( return Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
Row( Row(
children: [ children: [
SidebarWidget( SidebarWidget(
communities: widget.communities, communities: communities,
selectedSpaceUuid: widget.selectedSpace?.uuid ?? selectedSpaceUuid:
widget.selectedCommunity?.uuid ?? selectedSpace?.uuid ?? selectedCommunity?.uuid ?? '',
'',
), ),
widget.shouldNavigateToSpaceModelPage 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: widget.products, products: products,
onSpaceModelsUpdated: _onSpaceModelsUpdated,
), ),
), ),
) )
: CommunityStructureArea( : CommunityStructureArea(
selectedCommunity: widget.selectedCommunity, selectedCommunity: selectedCommunity,
selectedSpace: widget.selectedSpace, selectedSpace: selectedSpace,
spaces: widget.selectedCommunity?.spaces ?? [], spaces: selectedCommunity?.spaces ?? [],
products: widget.products, products: products,
communities: widget.communities, communities: communities,
spaceModels: _spaceModels, spaceModels: spaceModels,
), ),
], ],
), ),
@ -100,4 +68,13 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
], ],
); );
} }
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

@ -57,7 +57,6 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags); final tags = List<Tag>.from(currentState.tags);
tags[event.index].tag = event.tag; tags[event.index].tag = event.tag;
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
isSaveEnabled: _validateTags(tags), isSaveEnabled: _validateTags(tags),
@ -79,7 +78,6 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
isSaveEnabled: _validateTags(tags), isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
)); ));
} }
}); });
@ -108,13 +106,12 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: updatedTags, tags: updatedTags,
isSaveEnabled: _validateTags(updatedTags), isSaveEnabled: _validateTags(updatedTags),
errorMessage: _getValidationError(updatedTags),
)); ));
} else { } else {
emit(const AssignTagLoaded( emit(const AssignTagLoaded(
tags: [], tags: [],
isSaveEnabled: false, isSaveEnabled: false,
errorMessage: 'Failed to delete tag')); ));
} }
}); });
} }
@ -128,10 +125,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
String? _getValidationError(List<Tag> tags) { String? _getValidationError(List<Tag> tags) {
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
if (hasEmptyTag) { if (hasEmptyTag) return 'Tags cannot be empty.';
return 'Tags cannot be empty.';
}
final duplicateTags = tags final duplicateTags = tags
.map((tag) => tag.tag?.trim() ?? '') .map((tag) => tag.tag?.trim() ?? '')
.fold<Map<String, int>>({}, (map, tag) { .fold<Map<String, int>>({}, (map, tag) {

View File

@ -21,11 +21,11 @@ class AssignTagLoaded extends AssignTagState {
const AssignTagLoaded({ const AssignTagLoaded({
required this.tags, required this.tags,
required this.isSaveEnabled, required this.isSaveEnabled,
required this.errorMessage, this.errorMessage,
}); });
@override @override
List<Object> get props => [tags, isSaveEnabled, errorMessage ?? '']; List<Object> get props => [tags, isSaveEnabled];
} }
class AssignTagError extends AssignTagState { class AssignTagError extends AssignTagState {

View File

@ -71,7 +71,6 @@ class AssignTagDialog extends StatelessWidget {
child: DataTable( child: DataTable(
headingRowColor: WidgetStateProperty.all( headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey), ColorsManager.dataHeaderGrey),
key: ValueKey(state.tags.length),
border: TableBorder.all( border: TableBorder.all(
color: ColorsManager.dataHeaderGrey, color: ColorsManager.dataHeaderGrey,
width: 1, width: 1,
@ -121,7 +120,6 @@ class AssignTagDialog extends StatelessWidget {
final controller = controllers[index]; final controller = controllers[index];
final availableTags = getAvailableTags( final availableTags = getAvailableTags(
allTags ?? [], state.tags, tag); allTags ?? [], state.tags, tag);
return DataRow( return DataRow(
cells: [ cells: [
DataCell(Text((index + 1).toString())), DataCell(Text((index + 1).toString())),
@ -160,8 +158,6 @@ class AssignTagDialog extends StatelessWidget {
.add(DeleteTag( .add(DeleteTag(
tagToDelete: tag, tagToDelete: tag,
tags: state.tags)); tags: state.tags));
controllers.removeAt(index);
}, },
tooltip: 'Delete Tag', tooltip: 'Delete Tag',
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -237,13 +233,11 @@ 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 = final result = processTags(updatedTags, subspaces);
TagHelper.processTags(updatedTags, subspaces);
final processedTags = final processedTags =
result['updatedTags'] as List<Tag>; result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceModel>.from( final processedSubspaces = result['subspaces'];
result['subspaces'] as List<dynamic>);
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -259,7 +253,6 @@ class AssignTagDialog extends StatelessWidget {
spaceTags: processedTags, spaceTags: processedTags,
isCreate: false, isCreate: false,
onSave: onSave, onSave: onSave,
allTags: allTags,
), ),
); );
}, },
@ -270,21 +263,20 @@ class AssignTagDialog extends StatelessWidget {
Expanded( Expanded(
child: DefaultButton( child: DefaultButton(
borderRadius: 10, borderRadius: 10,
backgroundColor: ColorsManager.secondaryColor, backgroundColor: state.isSaveEnabled
foregroundColor: state.isSaveEnabled ? ColorsManager.secondaryColor
? ColorsManager.whiteColors : ColorsManager.grayColor,
: ColorsManager.whiteColorsWithOpacity, foregroundColor: ColorsManager.whiteColors,
onPressed: state.isSaveEnabled onPressed: state.isSaveEnabled
? () async { ? () async {
final updatedTags = List<Tag>.from(state.tags); final updatedTags = List<Tag>.from(state.tags);
final result = TagHelper.processTags( final result =
updatedTags, subspaces); processTags(updatedTags, subspaces);
final processedTags = final processedTags =
result['updatedTags'] as List<Tag>; result['updatedTags'] as List<Tag>;
final processedSubspaces = final processedSubspaces =
List<SubspaceModel>.from( result['subspaces'] as List<SubspaceModel>;
result['subspaces'] as List<dynamic>);
onSave?.call(processedTags, processedSubspaces); onSave?.call(processedTags, processedSubspaces);
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
@ -309,12 +301,115 @@ 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) {
List<String> availableTagsForTagModel = TagHelper.getAvailableTags<Tag>( return allTags
allTags: allTags, .where((tagValue) => !currentTags
currentTags: currentTags, .where((e) => e != currentTag) // Exclude the current row
currentTag: currentTag, .map((e) => e.tag)
getTag: (tag) => tag.tag ?? '', .contains(tagValue))
.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

@ -82,7 +82,6 @@ class AssignTagModelsDialog extends StatelessWidget {
child: DataTable( child: DataTable(
headingRowColor: WidgetStateProperty.all( headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey), ColorsManager.dataHeaderGrey),
key: ValueKey(state.tags.length),
border: TableBorder.all( border: TableBorder.all(
color: ColorsManager.dataHeaderGrey, color: ColorsManager.dataHeaderGrey,
width: 1, width: 1,
@ -134,8 +133,7 @@ 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 = final availableTags = getAvailableTags(
TagHelper.getAvailableTagModels(
allTags ?? [], state.tags, tag); allTags ?? [], state.tags, tag);
return DataRow( return DataRow(
@ -177,7 +175,6 @@ class AssignTagModelsDialog extends StatelessWidget {
.add(DeleteTagModel( .add(DeleteTagModel(
tagToDelete: tag, tagToDelete: tag,
tags: state.tags)); tags: state.tags));
controllers.removeAt(index);
}, },
tooltip: 'Delete Tag', tooltip: 'Delete Tag',
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -257,15 +254,11 @@ class AssignTagModelsDialog extends StatelessWidget {
final updatedTags = final updatedTags =
List<TagModel>.from(state.tags); List<TagModel>.from(state.tags);
final result = final result =
TagHelper.updateSubspaceTagModels( processTags(updatedTags, subspaces);
updatedTags, subspaces);
final processedTags = final processedTags =
result['updatedTags'] as List<TagModel>; result['updatedTags'] as List<TagModel>;
final processedSubspaces = final processedSubspaces = result['subspaces'];
List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
if (context.mounted) { if (context.mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -304,25 +297,22 @@ class AssignTagModelsDialog extends StatelessWidget {
Expanded( Expanded(
child: DefaultButton( child: DefaultButton(
borderRadius: 10, borderRadius: 10,
backgroundColor: ColorsManager.secondaryColor, backgroundColor: state.isSaveEnabled
foregroundColor: state.isSaveEnabled ? ColorsManager.secondaryColor
? ColorsManager.whiteColors : ColorsManager.grayColor,
: ColorsManager.whiteColorsWithOpacity, foregroundColor: ColorsManager.whiteColors,
onPressed: state.isSaveEnabled onPressed: state.isSaveEnabled
? () async { ? () async {
final updatedTags = final updatedTags =
List<TagModel>.from(state.tags); List<TagModel>.from(state.tags);
final result = final result =
TagHelper.updateSubspaceTagModels( processTags(updatedTags, subspaces);
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<dynamic>); as List<SubspaceTemplateModel>;
Navigator.of(context) Navigator.of(context)
.popUntil((route) => route.isFirst); .popUntil((route) => route.isFirst);
@ -366,4 +356,120 @@ 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

@ -1,21 +0,0 @@
import 'dart:ui';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
class ConnectionHelper {
static Offset getCenterPosition(Size screenSize) {
return Offset(
screenSize.width / 2 - 260,
screenSize.height / 2 - 200,
);
}
static bool isHighlightedConnection(
Connection connection, SpaceModel? selectedSpace) {
if (selectedSpace == null) return true;
return connection.startSpace == selectedSpace ||
connection.endSpace == selectedSpace;
}
}

View File

@ -1,94 +0,0 @@
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})";
}
static bool isSave(List<SpaceModel> spaces) {
return spaces.isNotEmpty &&
spaces.any((space) =>
space.status == SpaceStatus.newSpace ||
space.status == SpaceStatus.modified ||
space.status == SpaceStatus.deleted);
}
static bool isHighlightedSpace(SpaceModel space, SpaceModel? selectedSpace) {
if (selectedSpace == null) return true;
return space == selectedSpace ||
selectedSpace.parent?.internalId == space.internalId ||
selectedSpace.children
?.any((child) => child.internalId == space.internalId) ==
true;
}
static bool isNameConflict(
String value, SpaceModel? parentSpace, SpaceModel? editSpace) {
final siblings = parentSpace?.children
.where((child) => child.internalId != editSpace?.internalId)
.toList() ??
[];
final editSiblings = editSpace?.parent?.children
.where((child) => child.internalId != editSpace.internalId)
.toList() ??
[];
final editSiblingConflict =
editSiblings.any((child) => child.name == value);
final siblingConflict = siblings.any((child) => child.name == value);
final parentConflict = parentSpace?.name == value &&
parentSpace?.internalId != editSpace?.internalId;
final parentOfEditSpaceConflict = editSpace?.parent?.name == value &&
editSpace?.parent?.internalId != editSpace?.internalId;
final childConflict =
editSpace?.children.any((child) => child.name == value) ?? false;
return siblingConflict ||
parentConflict ||
editSiblingConflict ||
parentOfEditSpaceConflict ||
childConflict;
}
}

View File

@ -1,147 +1,12 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/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';
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,
@ -280,85 +145,4 @@ 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,
);
}
static List<String> getAllTagValues(
List<CommunityModel> communities, List<SpaceTemplateModel>? spaceModels) {
final Set<String> allTags = {};
if (spaceModels != null) {
for (var model in spaceModels) {
allTags.addAll(model.listAllTagValues());
}
}
for (final community in communities) {
for (final space in community.spaces) {
if (space.tags != null) {
allTags.addAll(space.listAllTagValues());
}
}
}
return allTags.toList();
}
} }

View File

@ -1,4 +1,5 @@
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,10 +11,8 @@ 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, this.onSpaceModelsUpdated}) const SpaceModelPage({Key? key, this.products}) : super(key: key);
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -27,10 +25,6 @@ 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

@ -6,23 +6,16 @@ class ButtonContentWidget extends StatelessWidget {
final IconData? icon; final IconData? icon;
final String label; final String label;
final String? svgAssets; final String? svgAssets;
final bool disabled;
const ButtonContentWidget({ const ButtonContentWidget(
Key? key, {Key? key, this.icon, required this.label, this.svgAssets})
this.icon, : super(key: key);
required this.label,
this.svgAssets,
this.disabled = false,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
return Opacity( return SizedBox(
opacity: disabled ? 0.5 : 1.0,
child: SizedBox(
width: screenWidth * 0.25, width: screenWidth * 0.25,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -34,8 +27,7 @@ class ButtonContentWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: Padding( child: Padding(
padding: padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
child: Row( child: Row(
children: [ children: [
if (icon != null) if (icon != null)
@ -66,7 +58,6 @@ class ButtonContentWidget extends StatelessWidget {
), ),
), ),
), ),
),
); );
} }
} }

View File

@ -129,16 +129,10 @@ class CreateSpaceModelDialog extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
SubspaceModelCreate( SubspaceModelCreate(
subspaces: state.space.subspaceModels ?? [], subspaces: state.space.subspaceModels ?? [],
tags: state.space.tags ?? [], onSpaceModelUpdate: (updatedSubspaces) {
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,7 +1,6 @@
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';
@ -9,16 +8,13 @@ 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( final void Function(List<SubspaceTemplateModel> newSubspaces)?
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
@ -28,13 +24,11 @@ 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
@ -111,26 +105,14 @@ 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;
_tags.addAll(tagsToAppendToSpace); errorSubspaceId = null;
}); });
if (widget.onSpaceModelUpdate != null) { if (widget.onSpaceModelUpdate != null) {
widget.onSpaceModelUpdate!(subspaceModels, _tags); widget.onSpaceModelUpdate!(subspaceModels);
} }
}, },
); );

View File

@ -52,13 +52,6 @@ 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

@ -18,7 +18,8 @@ class UserPermissionApi {
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
debugPrint('fetchUsers Response: $json'); debugPrint('fetchUsers Response: $json');
final List<dynamic> data = json['data'] ?? []; final List<dynamic> 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();
}, },
); );
@ -118,7 +119,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']['message']; final errorMessage = e.response?.data['error'];
return errorMessage; return errorMessage;
} catch (e) { } catch (e) {
return e.toString(); return e.toString();
@ -204,6 +205,7 @@ 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'];
}, },
); );
@ -211,6 +213,7 @@ class UserPermissionApi {
return response; return response;
} catch (e) { } catch (e) {
return false; return false;
print(e);
} }
} }
} }