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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ Future<void> showDateFilterMenu({
Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB(
overlay.size.width / 3,
overlay.size.width / 2,
240,
0,
overlay.size.height,
@ -40,6 +40,7 @@ Future<void> showDateFilterMenu({
),
title: Text(
"Sort from newest to oldest",
// style: context.textTheme.bodyMedium,
style: TextStyle(
color: isSelected == "NewestToOldest"
? 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(
"Sort A to Z",
// style: context.textTheme.bodyMedium,
style: TextStyle(
color: isSelected == "NewestToOldest"
? 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;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB(
overlay.size.width / 35,
overlay.size.width / 25,
240,
0,
overlay.size.height,
@ -40,6 +40,7 @@ Future<void> showNameMenu({
),
title: Text(
"Sort A to Z",
// style: context.textTheme.bodyMedium,
style: TextStyle(
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_svg/flutter_svg.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart';
class _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 {
final List<String> titles;
final List<List<Widget>> rows;
final void Function(int columnIndex)? onFilter;
const DynamicTableScreen({
required this.titles,
required this.rows,
required this.onFilter,
Key? key,
}) : super(key: key);
DynamicTableScreen(
{required this.titles, required this.rows, required this.onFilter});
@override
_DynamicTableScreenState createState() => _DynamicTableScreenState();
}
class _DynamicTableScreenState extends State<DynamicTableScreen> {
class _DynamicTableScreenState extends State<DynamicTableScreen>
with WidgetsBindingObserver {
late List<double> columnWidths;
final double _minColumnWidth = 100.0;
final double _maxColumnWidth = 300.0;
final double _dividerWidth = 1.0;
double _lastAvailableWidth = 0;
late double totalWidth;
@override
void 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
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeMetrics() {
super.didChangeMetrics();
final newScreenWidth = MediaQuery.of(context).size.width;
setState(() {
double newWidth = columnWidths[index] + delta;
newWidth = newWidth.clamp(_minColumnWidth, _maxColumnWidth);
double actualDelta = newWidth - columnWidths[index];
if (actualDelta == 0) return;
int nextIndex = (index + 1) % columnWidths.length;
columnWidths[index] = newWidth;
columnWidths[nextIndex] = (columnWidths[nextIndex] - actualDelta)
.clamp(_minColumnWidth, _maxColumnWidth);
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() {
return Container(
decoration: containerDecoration.copyWith(
color: ColorsManager.circleRolesBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15),
),
),
child: Row(
children: [
for (int i = 0; i < widget.titles.length; i++)
_HeaderColumn(
title: widget.titles[i],
width: columnWidths[i],
showFilter: i != 1 && i != 9 && i != 8 && i != 5,
onFilter: () => widget.onFilter?.call(i),
onResize: (delta) => _handleColumnResize(i, delta),
),
],
),
);
}
Widget _buildBody() {
if (widget.rows.isEmpty) {
return SizedBox(
height: 300,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(Assets.emptyTable),
const SizedBox(height: 15),
const Text(
'No Users',
style: TextStyle(
color: ColorsManager.lightGrayColor,
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
],
),
),
);
}
return Container(
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15),
),
),
child: Column(
children: [
for (int rowIndex = 0; rowIndex < widget.rows.length; rowIndex++)
_TableRow(
cells: widget.rows[rowIndex],
columnWidths: columnWidths,
isLast: rowIndex == widget.rows.length - 1,
),
],
),
);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final availableWidth = constraints.maxWidth;
final totalDividersWidth = (widget.titles.length - 1) * _dividerWidth;
if (_lastAvailableWidth != availableWidth) {
final equalWidth =
(availableWidth - totalDividersWidth) / widget.titles.length;
final clampedWidth =
equalWidth.clamp(_minColumnWidth, _maxColumnWidth);
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
columnWidths = List.filled(widget.titles.length, clampedWidth);
_lastAvailableWidth = availableWidth;
});
});
final 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;
}
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(),
],
),
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(
color: ColorsManager.circleRolesBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15))),
child: Row(
children: List.generate(widget.titles.length, (index) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
FittedBox(
child: Container(
padding: const EdgeInsets.only(left: 5, right: 5),
width: columnWidths[index],
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
child: Text(
widget.titles[index],
maxLines: 2,
style: const TextStyle(
overflow: TextOverflow.ellipsis,
fontWeight: FontWeight.w400,
fontSize: 13,
color: ColorsManager.grayColor,
),
),
),
if (index != 1 &&
index != 9 &&
index != 8 &&
index != 5)
FittedBox(
child: IconButton(
icon: SvgPicture.asset(
Assets.filterTableIcon,
fit: BoxFit.none,
),
onPressed: () {
if (widget.onFilter != null) {
widget.onFilter!(index);
}
},
),
)
],
),
),
),
GestureDetector(
onHorizontalDragUpdate: (details) {
setState(() {
columnWidths[index] =
(columnWidths[index] + details.delta.dx)
.clamp(150.0, 300.0);
totalWidth = columnWidths.reduce((a, b) => a + b);
});
},
child: MouseRegion(
cursor: SystemMouseCursors.resizeColumn,
child: Container(
color: Colors.green,
child: Container(
color: ColorsManager.boxDivider,
width: 1,
height: 50,
),
),
),
),
],
);
}),
),
),
widget.rows.isEmpty
? SizedBox(
height: MediaQuery.of(context).size.height / 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
SvgPicture.asset(Assets.emptyTable),
const SizedBox(
height: 15,
),
const Text(
'No Users',
style: TextStyle(
color: ColorsManager.lightGrayColor,
fontSize: 16,
fontWeight: FontWeight.w700),
)
],
),
],
),
)
: Center(
child: Container(
width: totalWidth,
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15))),
child: ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: widget.rows.length,
itemBuilder: (context, rowIndex) {
if (columnWidths.every((width) => width == 120.0)) {
columnWidths = List<double>.generate(
widget.titles.length, (index) {
if (index == 1) {
return screenWidth * 0.11;
} else if (index == 9) {
return screenWidth * 0.2;
}
return screenWidth * 0.11;
});
setState(() {});
}
final row = widget.rows[rowIndex];
return Column(
children: [
Container(
child: Padding(
padding: const EdgeInsets.only(
left: 5, top: 10, right: 5, bottom: 10),
child: Row(
children:
List.generate(row.length, (index) {
return SizedBox(
width: columnWidths[index],
child: SizedBox(
child: Padding(
padding: const EdgeInsets.only(
left: 15, right: 10),
child: row[index],
),
),
);
}),
),
),
),
if (rowIndex < widget.rows.length - 1)
Row(
children: List.generate(
widget.titles.length, (index) {
return SizedBox(
width: columnWidths[index],
child: const Divider(
color: ColorsManager.boxDivider,
thickness: 1,
height: 1,
),
);
}),
),
],
);
},
),
),
),
],
),
);
},
),
),
);
}
}

View File

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

View File

@ -33,64 +33,55 @@ class _RoutinesViewState extends State<RoutinesView> {
return Row(
children: [
Expanded(
child:
// SideSpacesView(
// onSelectAction: (String communityId, String spaceId) {
// // context.read<RoutineBloc>()
// // ..add(LoadScenes(spaceId, communityId))
// // ..add(LoadAutomation(spaceId));
// },
// )
SpaceTreeView(
child: SpaceTreeView(
onSelect: () {},
)),
Expanded(
flex: 3,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Create New Routines",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(
height: 10,
),
RoutineViewCard(
onTap: () {
if (context.read<SpaceTreeBloc>().selectedCommunityId.isNotEmpty &&
context.read<SpaceTreeBloc>().selectedSpaceId.isNotEmpty) {
context.read<RoutineBloc>().add(
(ResetRoutineState()),
);
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
} else {
CustomSnackBar.redSnackBar('Please select a space');
}
},
icon: Icons.add,
textString: '',
),
const SizedBox(
height: 15,
),
const Expanded(child: FetchRoutineScenesAutomation()),
],
),
],
flex: 4,
child: ListView(children: [
Container(
padding: const EdgeInsets.all(16),
height: MediaQuery.sizeOf(context).height,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Create New Routines",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(
height: 10,
),
RoutineViewCard(
onTap: () {
if (context.read<SpaceTreeBloc>().selectedCommunityId.isNotEmpty &&
context.read<SpaceTreeBloc>().selectedSpaceId.isNotEmpty) {
context.read<RoutineBloc>().add(
(ResetRoutineState()),
);
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
} else {
CustomSnackBar.redSnackBar('Please select a space');
}
},
icon: Icons.add,
textString: '',
),
const SizedBox(
height: 15,
),
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/style.dart';
class SpaceTreeView extends StatelessWidget {
class SpaceTreeView extends StatefulWidget {
final Function onSelect;
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
Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
@ -21,7 +34,6 @@ class SpaceTreeView extends StatelessWidget {
return Container(
height: MediaQuery.sizeOf(context).height,
decoration: subSectionContainerDecoration,
// padding: const EdgeInsets.all(16.0),
child: state is SpaceTreeLoadingState
? const Center(child: CircularProgressIndicator())
: Column(
@ -33,67 +45,150 @@ class SpaceTreeView extends StatelessWidget {
),
const SizedBox(height: 16),
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,
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: [
Container(
width: MediaQuery.sizeOf(context).width * 0.5,
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,
fontWeight: FontWeight.w400,
),
),
)
: Scrollbar(
scrollbarOrientation: ScrollbarOrientation.left,
thumbVisibility: true,
controller: _scrollController,
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: ListView(
controller: _scrollController,
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));
widget.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));
widget.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(),
),
),
)
: 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(),
),
),
),
),
],
),
),
// 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
.read<SpaceTreeBloc>()
.add(OnSpaceSelected(communityId, child.uuid ?? '', child.children));
onSelect();
widget.onSelect();
},
onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(OnSpaceExpanded(communityId, child.uuid ?? ''));

View File

@ -87,7 +87,6 @@ class SpaceManagementBloc
prevSpaceModels = List<SpaceTemplateModel>.from(
(previousState as dynamic).spaceModels ?? [],
);
allSpaces.addAll(prevSpaceModels);
}
if (prevSpaceModels.isEmpty) {
@ -180,7 +179,6 @@ class SpaceManagementBloc
final updatedCommunities =
await Future.wait(communities.map((community) async {
final spaces = await _fetchSpacesForCommunity(community.uuid);
return CommunityModel(
uuid: community.uuid,
createdAt: community.createdAt,

View File

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

View File

@ -64,11 +64,11 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
);
} else if (state is SpaceModelLoaded) {
return LoadedSpaceView(
communities: state.communities,
products: state.products,
spaceModels: state.spaceModels,
shouldNavigateToSpaceModelPage: true,
);
communities: state.communities,
products: state.products,
spaceModels: state.spaceModels,
shouldNavigateToSpaceModelPage: true,
);
} else if (state is SpaceManagementError) {
return Center(child: Text('Error: ${state.errorMessage}'));
}

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/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/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/utils/color_manager.dart';
@ -133,7 +130,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
communities: widget.communities,
communityName: widget.selectedCommunity?.name,
community: widget.selectedCommunity,
isSave: SpaceHelper.isSave(spaces),
isSave: isSave(spaces),
isEditingName: isEditingName,
nameController: _nameController,
onSave: _saveSpaces,
@ -178,8 +175,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
children: [
for (var connection in connections)
Opacity(
opacity: ConnectionHelper.isHighlightedConnection(
connection, widget.selectedSpace)
opacity: _isHighlightedConnection(connection)
? 1.0
: 0.3, // Adjust opacity
child: CustomPaint(
@ -199,6 +195,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
screenSize,
position:
spaces[index].position + newPosition,
parentIndex: index,
direction: direction,
);
@ -212,8 +209,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
},
buildSpaceContainer: (int index) {
final bool isHighlighted =
SpaceHelper.isHighlightedSpace(
spaces[index], widget.selectedSpace);
_isHighlightedSpace(spaces[index]);
return Opacity(
opacity: isHighlighted ? 1.0 : 0.3,
@ -299,8 +295,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
return CreateSpaceDialog(
products: widget.products,
spaceModels: widget.spaceModels,
allTags:
TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
allTags: _getAllTagValues(spaces),
parentSpace: parentIndex != null ? spaces[parentIndex] : null,
onCreateSpace: (String name,
String icon,
@ -311,7 +306,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
setState(() {
// Set the first space in the center or use passed position
Offset centerPosition =
position ?? ConnectionHelper.getCenterPosition(screenSize);
position ?? _getCenterPosition(screenSize);
SpaceModel newSpace = SpaceModel(
name: name,
icon: icon,
@ -356,15 +351,11 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
spaceModels: widget.spaceModels,
name: widget.selectedSpace!.name,
icon: widget.selectedSpace!.icon,
parentSpace: SpaceHelper.findSpaceByInternalId(
widget.selectedSpace?.parent?.internalId, spaces),
editSpace: widget.selectedSpace,
currentSpaceModel: widget.selectedSpace?.spaceModel,
tags: widget.selectedSpace?.tags,
subspaces: widget.selectedSpace?.subspaces,
isEdit: true,
allTags: TagHelper.getAllTagValues(
widget.communities, widget.spaceModels),
allTags: _getAllTagValues(spaces),
onCreateSpace: (String name,
String icon,
List<SelectedProduct> selectedProducts,
@ -383,15 +374,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
widget.selectedSpace!.status =
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),
@ -470,6 +452,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
}).toList();
if (spacesToSave.isEmpty) {
debugPrint("No new or modified spaces to save.");
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) {
context.read<SpaceManagementBloc>().add(
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) {
final screenWidth = MediaQuery.of(context).size.width;
@ -625,10 +641,17 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
const double horizontalGap = 200.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? duplicatedParent) {
Offset newPosition =
Offset(parentPosition.dx + horizontalGap, original.position.dy);
Offset newPosition = parentPosition + Offset(horizontalGap, 0);
while (spaces.any((s) =>
(s.position - newPosition).distance < horizontalGap &&
@ -636,18 +659,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
newPosition += Offset(horizontalGap, 0);
}
final duplicatedName =
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 duplicatedName = _generateCopyName(original.name);
final duplicated = SpaceModel(
name: duplicatedName,
@ -658,8 +670,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
status: SpaceStatus.newSpace,
parent: duplicatedParent,
spaceModel: original.spaceModel,
subspaces: duplicatedSubspaces,
tags: duplicatedTags,
subspaces: original.subspaces,
tags: original.tags,
);
originalToDuplicate[original] = duplicated;
@ -672,7 +684,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final newConnection = Connection(
startSpace: duplicatedParent,
endSpace: duplicated,
direction: original.incomingConnection?.direction ?? 'down',
direction: "down",
);
connections.add(newConnection);
duplicated.incomingConnection = newConnection;
@ -711,8 +723,10 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
child.incomingConnection?.direction == "down" ?? false;
if (isDownDirection && childrenWithDownDirection.length == 1) {
// Place the only "down" child vertically aligned with the parent
childStartPosition = duplicated.position + Offset(0, verticalGap);
} else if (!isDownDirection) {
// Position children with other directions horizontally
childStartPosition = duplicated.position + Offset(horizontalGap, 0);
}
@ -733,4 +747,14 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
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/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/helper/space_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/space_model/models/space_template_model.dart';
@ -41,7 +40,6 @@ class CreateSpaceDialog extends StatefulWidget {
final List<SubspaceModel>? subspaces;
final List<Tag>? tags;
final List<String>? allTags;
final SpaceTemplateModel? currentSpaceModel;
const CreateSpaceDialog(
{super.key,
@ -56,8 +54,7 @@ class CreateSpaceDialog extends StatefulWidget {
this.selectedProducts = const [],
this.spaceModels,
this.subspaces,
this.tags,
this.currentSpaceModel});
this.tags});
@override
CreateSpaceDialogState createState() => CreateSpaceDialogState();
@ -84,22 +81,12 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty;
if (widget.currentSpaceModel != null) {
subspaces = [];
tags = [];
} else {
tags = widget.tags ?? [];
subspaces = widget.subspaces ?? [];
}
selectedSpaceModel = widget.currentSpaceModel;
tags = widget.tags ?? [];
subspaces = widget.subspaces ?? [];
}
@override
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;
return AlertDialog(
title: widget.isEdit
@ -178,7 +165,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
isNameFieldInvalid = value.isEmpty;
if (!isNameFieldInvalid) {
if (SpaceHelper.isNameConflict(value, widget.parentSpace, widget.editSpace)) {
if (_isNameConflict(value)) {
isNameFieldExist = true;
isOkButtonEnabled = false;
} else {
@ -245,14 +232,11 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
padding: EdgeInsets.zero,
),
onPressed: () {
isSpaceModelDisabled
? null
: _showLinkSpaceModelDialog(context);
_showLinkSpaceModelDialog(context);
},
child: ButtonContentWidget(
child: const ButtonContentWidget(
svgAssets: Assets.link,
label: 'Link a space model',
disabled: isSpaceModelDisabled,
),
)
: Container(
@ -341,15 +325,12 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
overlayColor: ColorsManager.transparentColor,
),
onPressed: () async {
isTagsAndSubspaceModelDisabled
? null
: _showSubSpaceDialog(context, enteredName,
[], false, widget.products, subspaces);
_showSubSpaceDialog(context, enteredName, [],
false, widget.products, subspaces);
},
child: ButtonContentWidget(
child: const ButtonContentWidget(
icon: Icons.add,
label: 'Create Sub Space',
disabled: isTagsAndSubspaceModelDisabled,
),
)
: SizedBox(
@ -476,8 +457,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
context: context,
builder: (context) => AssignTagDialog(
products: widget.products,
subspaces: subspaces,
allTags: widget.allTags,
subspaces: widget.subspaces,
addedProducts: TagHelper
.createInitialSelectedProductsForTags(
tags ?? [], subspaces),
@ -503,22 +483,20 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
)
: TextButton(
onPressed: () {
isTagsAndSubspaceModelDisabled
? null
: _showTagCreateDialog(
context,
enteredName,
widget.isEdit,
widget.products,
);
_showTagCreateDialog(
context,
enteredName,
widget.isEdit,
widget.products,
subspaces,
);
},
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
),
child: ButtonContentWidget(
child: const ButtonContentWidget(
icon: Icons.add,
label: 'Add Devices',
disabled: isTagsAndSubspaceModelDisabled,
))
],
),
@ -592,7 +570,15 @@ 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) {
showDialog(
context: context,
@ -631,26 +617,9 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
products: products,
existingSubSpaces: existingSubSpaces,
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) {
setState(() {
subspaces = slectedSubspaces;
tags?.addAll(tagsToAppendToSpace);
selectedSpaceModel = null;
});
}
@ -660,7 +629,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
}
void _showTagCreateDialog(BuildContext context, String name, bool isEdit,
List<ProductModel>? products) {
List<ProductModel>? products, List<SubspaceModel>? subspaces) {
isEdit
? showDialog(
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/services/space_model_mang_api.dart';
class LoadedSpaceView extends StatefulWidget {
class LoadedSpaceView extends StatelessWidget {
final List<CommunityModel> communities;
final CommunityModel? selectedCommunity;
final SpaceModel? selectedSpace;
@ -26,73 +26,41 @@ class LoadedSpaceView extends StatefulWidget {
this.selectedSpace,
this.products,
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
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
children: [
Row(
children: [
SidebarWidget(
communities: widget.communities,
selectedSpaceUuid: widget.selectedSpace?.uuid ??
widget.selectedCommunity?.uuid ??
'',
communities: communities,
selectedSpaceUuid:
selectedSpace?.uuid ?? selectedCommunity?.uuid ?? '',
),
widget.shouldNavigateToSpaceModelPage
shouldNavigateToSpaceModelPage
? Expanded(
child: BlocProvider(
create: (context) => SpaceModelBloc(
api: SpaceModelManagementApi(),
initialSpaceModels: _spaceModels,
initialSpaceModels: spaceModels ?? [],
),
child: SpaceModelPage(
products: widget.products,
onSpaceModelsUpdated: _onSpaceModelsUpdated,
products: products,
),
),
)
: CommunityStructureArea(
selectedCommunity: widget.selectedCommunity,
selectedSpace: widget.selectedSpace,
spaces: widget.selectedCommunity?.spaces ?? [],
products: widget.products,
communities: widget.communities,
spaceModels: _spaceModels,
selectedCommunity: selectedCommunity,
selectedSpace: selectedSpace,
spaces: selectedCommunity?.spaces ?? [],
products: products,
communities: communities,
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) {
final tags = List<Tag>.from(currentState.tags);
tags[event.index].tag = event.tag;
emit(AssignTagLoaded(
tags: tags,
isSaveEnabled: _validateTags(tags),
@ -79,7 +78,6 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
emit(AssignTagLoaded(
tags: tags,
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
}
});
@ -108,13 +106,12 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
emit(AssignTagLoaded(
tags: updatedTags,
isSaveEnabled: _validateTags(updatedTags),
errorMessage: _getValidationError(updatedTags),
));
} else {
emit(const AssignTagLoaded(
tags: [],
isSaveEnabled: false,
errorMessage: 'Failed to delete tag'));
tags: [],
isSaveEnabled: false,
));
}
});
}
@ -128,10 +125,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
String? _getValidationError(List<Tag> tags) {
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
if (hasEmptyTag) {
return 'Tags cannot be empty.';
}
if (hasEmptyTag) return 'Tags cannot be empty.';
final duplicateTags = tags
.map((tag) => tag.tag?.trim() ?? '')
.fold<Map<String, int>>({}, (map, tag) {

View File

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

View File

@ -71,7 +71,6 @@ class AssignTagDialog extends StatelessWidget {
child: DataTable(
headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey),
key: ValueKey(state.tags.length),
border: TableBorder.all(
color: ColorsManager.dataHeaderGrey,
width: 1,
@ -121,7 +120,6 @@ class AssignTagDialog extends StatelessWidget {
final controller = controllers[index];
final availableTags = getAvailableTags(
allTags ?? [], state.tags, tag);
return DataRow(
cells: [
DataCell(Text((index + 1).toString())),
@ -160,8 +158,6 @@ class AssignTagDialog extends StatelessWidget {
.add(DeleteTag(
tagToDelete: tag,
tags: state.tags));
controllers.removeAt(index);
},
tooltip: 'Delete Tag',
padding: EdgeInsets.zero,
@ -237,13 +233,11 @@ class AssignTagDialog extends StatelessWidget {
label: 'Add New Device',
onPressed: () async {
final updatedTags = List<Tag>.from(state.tags);
final result =
TagHelper.processTags(updatedTags, subspaces);
final result = processTags(updatedTags, subspaces);
final processedTags =
result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceModel>.from(
result['subspaces'] as List<dynamic>);
final processedSubspaces = result['subspaces'];
Navigator.of(context).pop();
@ -259,7 +253,6 @@ class AssignTagDialog extends StatelessWidget {
spaceTags: processedTags,
isCreate: false,
onSave: onSave,
allTags: allTags,
),
);
},
@ -270,21 +263,20 @@ class AssignTagDialog extends StatelessWidget {
Expanded(
child: DefaultButton(
borderRadius: 10,
backgroundColor: ColorsManager.secondaryColor,
foregroundColor: state.isSaveEnabled
? ColorsManager.whiteColors
: ColorsManager.whiteColorsWithOpacity,
backgroundColor: state.isSaveEnabled
? ColorsManager.secondaryColor
: ColorsManager.grayColor,
foregroundColor: ColorsManager.whiteColors,
onPressed: state.isSaveEnabled
? () async {
final updatedTags = List<Tag>.from(state.tags);
final result = TagHelper.processTags(
updatedTags, subspaces);
final result =
processTags(updatedTags, subspaces);
final processedTags =
result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceModel>.from(
result['subspaces'] as List<dynamic>);
result['subspaces'] as List<SubspaceModel>;
onSave?.call(processedTags, processedSubspaces);
Navigator.of(context).pop();
}
@ -309,12 +301,115 @@ class AssignTagDialog extends StatelessWidget {
List<String> getAvailableTags(
List<String> allTags, List<Tag> currentTags, Tag currentTag) {
List<String> availableTagsForTagModel = TagHelper.getAvailableTags<Tag>(
allTags: allTags,
currentTags: currentTags,
currentTag: currentTag,
getTag: (tag) => tag.tag ?? '',
);
return availableTagsForTagModel;
return allTags
.where((tagValue) => !currentTags
.where((e) => e != currentTag) // Exclude the current row
.map((e) => e.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),
);
}
}
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(
headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey),
key: ValueKey(state.tags.length),
border: TableBorder.all(
color: ColorsManager.dataHeaderGrey,
width: 1,
@ -134,9 +133,8 @@ class AssignTagModelsDialog extends StatelessWidget {
: List.generate(state.tags.length, (index) {
final tag = state.tags[index];
final controller = controllers[index];
final availableTags =
TagHelper.getAvailableTagModels(
allTags ?? [], state.tags, tag);
final availableTags = getAvailableTags(
allTags ?? [], state.tags, tag);
return DataRow(
cells: [
@ -177,7 +175,6 @@ class AssignTagModelsDialog extends StatelessWidget {
.add(DeleteTagModel(
tagToDelete: tag,
tags: state.tags));
controllers.removeAt(index);
},
tooltip: 'Delete Tag',
padding: EdgeInsets.zero,
@ -257,15 +254,11 @@ class AssignTagModelsDialog extends StatelessWidget {
final updatedTags =
List<TagModel>.from(state.tags);
final result =
TagHelper.updateSubspaceTagModels(
updatedTags, subspaces);
processTags(updatedTags, subspaces);
final processedTags =
result['updatedTags'] as List<TagModel>;
final processedSubspaces =
List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
final processedSubspaces = result['subspaces'];
if (context.mounted) {
Navigator.of(context).pop();
@ -304,25 +297,22 @@ class AssignTagModelsDialog extends StatelessWidget {
Expanded(
child: DefaultButton(
borderRadius: 10,
backgroundColor: ColorsManager.secondaryColor,
foregroundColor: state.isSaveEnabled
? ColorsManager.whiteColors
: ColorsManager.whiteColorsWithOpacity,
backgroundColor: state.isSaveEnabled
? ColorsManager.secondaryColor
: ColorsManager.grayColor,
foregroundColor: ColorsManager.whiteColors,
onPressed: state.isSaveEnabled
? () async {
final updatedTags =
List<TagModel>.from(state.tags);
final result =
TagHelper.updateSubspaceTagModels(
updatedTags, subspaces);
processTags(updatedTags, subspaces);
final processedTags =
result['updatedTags'] as List<TagModel>;
final processedSubspaces =
List<SubspaceTemplateModel>.from(
result['subspaces']
as List<dynamic>);
result['subspaces']
as List<SubspaceTemplateModel>;
Navigator.of(context)
.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/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/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
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({
List<TagModel>? spaceTagModels,
List<SubspaceTemplateModel>? subspaces,
@ -171,7 +36,7 @@ class TagHelper {
return initialTags;
}
static List<Tag> generateInitialForTags({
static List<Tag> generateInitialForTags({
List<Tag>? spaceTags,
List<SubspaceModel>? subspaces,
}) {
@ -280,85 +145,4 @@ class TagHelper {
))
.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:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';

View File

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

View File

@ -6,64 +6,55 @@ class ButtonContentWidget extends StatelessWidget {
final IconData? icon;
final String label;
final String? svgAssets;
final bool disabled;
const ButtonContentWidget({
Key? key,
this.icon,
required this.label,
this.svgAssets,
this.disabled = false,
}) : super(key: key);
const ButtonContentWidget(
{Key? key, this.icon, required this.label, this.svgAssets})
: super(key: key);
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return Opacity(
opacity: disabled ? 0.5 : 1.0,
child: SizedBox(
width: screenWidth * 0.25,
child: Container(
decoration: BoxDecoration(
color: ColorsManager.textFieldGreyColor,
border: Border.all(
color: ColorsManager.neutralGray,
width: 3.0,
),
borderRadius: BorderRadius.circular(20),
return SizedBox(
width: screenWidth * 0.25,
child: Container(
decoration: BoxDecoration(
color: ColorsManager.textFieldGreyColor,
border: Border.all(
color: ColorsManager.neutralGray,
width: 3.0,
),
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
child: Row(
children: [
if (icon != null)
Icon(
icon,
color: ColorsManager.spaceColor,
),
if (svgAssets != null)
Padding(
padding: const EdgeInsets.only(left: 6.0),
child: SvgPicture.asset(
svgAssets!,
width: screenWidth * 0.015, // Adjust icon size
height: screenWidth * 0.015,
),
),
const SizedBox(width: 10),
Expanded(
child: Text(
label,
style: const TextStyle(
color: ColorsManager.blackColor,
fontSize: 16,
),
borderRadius: BorderRadius.circular(20),
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
child: Row(
children: [
if (icon != null)
Icon(
icon,
color: ColorsManager.spaceColor,
),
if (svgAssets != null)
Padding(
padding: const EdgeInsets.only(left: 6.0),
child: SvgPicture.asset(
svgAssets!,
width: screenWidth * 0.015, // Adjust icon size
height: screenWidth * 0.015,
),
),
],
),
const SizedBox(width: 10),
Expanded(
child: Text(
label,
style: const TextStyle(
color: ColorsManager.blackColor,
fontSize: 16,
),
),
),
],
),
),
),

View File

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

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.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/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart';
import 'package:syncrow_web/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 {
final List<SubspaceTemplateModel> subspaces;
final void Function(
List<SubspaceTemplateModel> newSubspaces, List<TagModel>? tags)?
final void Function(List<SubspaceTemplateModel> newSubspaces)?
onSpaceModelUpdate;
final List<TagModel> tags;
const SubspaceModelCreate({
Key? key,
required this.subspaces,
this.onSpaceModelUpdate,
required this.tags,
}) : super(key: key);
@override
@ -28,13 +24,11 @@ class SubspaceModelCreate extends StatefulWidget {
class _SubspaceModelCreateState extends State<SubspaceModelCreate> {
late List<SubspaceTemplateModel> _subspaces;
String? errorSubspaceId;
late List<TagModel> _tags;
@override
void initState() {
super.initState();
_subspaces = List.from(widget.subspaces);
_tags = List.from(widget.tags);
}
@override
@ -111,26 +105,14 @@ class _SubspaceModelCreateState extends State<SubspaceModelCreate> {
isEdit: true,
dialogTitle: dialogTitle,
existingSubSpaces: _subspaces,
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(() {
_subspaces = subspaceModels;
_tags.addAll(tagsToAppendToSpace);
errorSubspaceId = 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() {
final updatedName = _controller.text;
if (updatedName.isEmpty) {
setState(() {
errorText = 'Subspace name cannot be empty.';
});
return;
}
if (widget.validateName(updatedName)) {
setState(() {
errorText = null;

View File

@ -18,7 +18,8 @@ class UserPermissionApi {
showServerMessage: true,
expectedResponseModel: (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();
},
);
@ -118,7 +119,7 @@ class UserPermissionApi {
);
return response ?? 'Unknown error occurred';
} on DioException catch (e) {
final errorMessage = e.response?.data['error']['message'];
final errorMessage = e.response?.data['error'];
return errorMessage;
} catch (e) {
return e.toString();
@ -204,6 +205,7 @@ class UserPermissionApi {
.replaceAll("{invitedUserUuid}", userUuid),
body: bodya,
expectedResponseModel: (json) {
print('changeUserStatusById==${json['success']}');
return json['success'];
},
);
@ -211,6 +213,7 @@ class UserPermissionApi {
return response;
} catch (e) {
return false;
print(e);
}
}
}