Compare commits

..

4 Commits

74 changed files with 639 additions and 3906 deletions

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/sos/bloc/sos_device_bloc.dart';
class SOSBatchControlView extends StatelessWidget {

View File

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

View File

@ -114,7 +114,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
currentStep++;
if (currentStep == 2) {
_blocRole.add(
CheckStepStatus(isEditUser: false));
const CheckStepStatus(isEditUser: false));
} else if (currentStep == 3) {
_blocRole
.add(const CheckSpacesStepStatus());
@ -151,11 +151,11 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
Widget _getFormContent() {
switch (currentStep) {
case 1:
return BasicsView(
return const BasicsView(
userId: '',
);
case 2:
return SpacesAccessView();
return const SpacesAccessView();
case 3:
return const RolesAndPermission();
default:
@ -172,7 +172,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
bloc.add(const CheckSpacesStepStatus());
currentStep = step;
Future.delayed(const Duration(milliseconds: 500), () {
bloc.add(ValidateBasicsStep());
bloc.add(const ValidateBasicsStep());
});
});
@ -237,10 +237,11 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
onTap: () {
setState(() {
currentStep = step;
bloc.add(CheckStepStatus(isEditUser: false));
bloc.add(const CheckStepStatus(isEditUser: false));
if (step3 == 3) {
bloc.add(const CheckRoleStepStatus());
}
});
},
child: Column(

View File

@ -4,7 +4,6 @@ import 'package:intl_phone_field/countries.dart';
import 'package:intl_phone_field/country_picker_dialog.dart';
import 'package:intl_phone_field/intl_phone_field.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -47,7 +46,9 @@ class BasicsView extends StatelessWidget {
),
Row(
children: [
Expanded(
SizedBox(
width: MediaQuery.of(context).size.width * 0.18,
height: MediaQuery.of(context).size.width * 0.08,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -76,12 +77,12 @@ class BasicsView extends StatelessWidget {
child: TextFormField(
style:
const TextStyle(color: ColorsManager.blackColor),
onChanged: (value) {
Future.delayed(const Duration(milliseconds: 200),
() {
_blocRole.add(ValidateBasicsStep());
});
},
// onChanged: (value) {
// Future.delayed(const Duration(milliseconds: 200),
// () {
// _blocRole.add(const ValidateBasicsStep());
// });
// },
controller: _blocRole.firstNameController,
decoration: inputTextFormDeco(
hintText: "Enter first name",
@ -103,7 +104,9 @@ class BasicsView extends StatelessWidget {
),
),
const SizedBox(width: 10),
Expanded(
SizedBox(
width: MediaQuery.of(context).size.width * 0.18,
height: MediaQuery.of(context).size.width * 0.08,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -128,12 +131,12 @@ class BasicsView extends StatelessWidget {
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
onChanged: (value) {
Future.delayed(const Duration(milliseconds: 200),
() {
_blocRole.add(ValidateBasicsStep());
});
},
// onChanged: (value) {
// Future.delayed(const Duration(milliseconds: 200),
// () {
// _blocRole.add(ValidateBasicsStep());
// });
// },
controller: _blocRole.lastNameController,
style: const TextStyle(color: Colors.black),
decoration:
@ -186,13 +189,13 @@ class BasicsView extends StatelessWidget {
padding: const EdgeInsets.all(8.0),
child: TextFormField(
enabled: userId != '' ? false : true,
onChanged: (value) {
Future.delayed(const Duration(milliseconds: 200), () {
_blocRole.add(CheckStepStatus(
isEditUser: userId != '' ? false : true));
_blocRole.add(ValidateBasicsStep());
});
},
// onChanged: (value) {
// Future.delayed(const Duration(milliseconds: 200), () {
// _blocRole.add(CheckStepStatus(
// isEditUser: userId != '' ? false : true));
// _blocRole.add(ValidateBasicsStep());
// });
// },
controller: _blocRole.emailController,
style: const TextStyle(color: ColorsManager.blackColor),
decoration: inputTextFormDeco(hintText: "name@example.com")

View File

@ -11,7 +11,14 @@ class DeleteUserDialog extends StatefulWidget {
}
class _DeleteUserDialogState extends State<DeleteUserDialog> {
int currentStep = 1;
bool isLoading = false;
bool _isDisposed = false;
@override
void dispose() {
_isDisposed = true;
super.dispose();
}
@override
Widget build(BuildContext context) {
@ -56,7 +63,7 @@ class _DeleteUserDialogState extends State<DeleteUserDialog> {
Expanded(
child: InkWell(
onTap: () {
Navigator.of(context).pop(true);
Navigator.of(context).pop(false); // Return false if canceled
},
child: Container(
padding: const EdgeInsets.all(10),
@ -76,7 +83,26 @@ class _DeleteUserDialogState extends State<DeleteUserDialog> {
)),
Expanded(
child: InkWell(
onTap: widget.onTapDelete,
onTap: isLoading
? null
: () async {
setState(() {
isLoading = true;
});
try {
if (widget.onTapDelete != null) {
await widget.onTapDelete!();
}
} finally {
if (!_isDisposed) {
setState(() {
isLoading = false;
});
}
}
Navigator.of(context).pop(true);
},
child: Container(
padding: const EdgeInsets.all(10),
decoration: const BoxDecoration(
@ -91,13 +117,22 @@ class _DeleteUserDialogState extends State<DeleteUserDialog> {
),
),
),
child: const Center(
child: Text(
'Delete',
style: TextStyle(
color: ColorsManager.red,
),
))),
child: Center(
child: isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
color: ColorsManager.red,
strokeWidth: 2.0,
),
)
: const Text(
'Delete',
style: TextStyle(
color: ColorsManager.red,
),
))),
)),
],
)

View File

@ -128,7 +128,7 @@ class _PermissionManagementState extends State<PermissionManagement> {
),
const SizedBox(width: 8),
Text(
option.title,
' ${option.title.isNotEmpty ? option.title[0].toUpperCase() : ''}${option.title.substring(1)}',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 12,
@ -184,7 +184,7 @@ class _PermissionManagementState extends State<PermissionManagement> {
),
const SizedBox(width: 8),
Text(
subOption.title,
' ${subOption.title.isNotEmpty ? subOption.title[0].toUpperCase() : ''}${subOption.title.substring(1)}',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 12,
@ -246,7 +246,7 @@ class _PermissionManagementState extends State<PermissionManagement> {
),
const SizedBox(width: 8),
Text(
child.title,
' ${child.title.isNotEmpty ? child.title[0].toUpperCase() : ''}${child.title.substring(1)}',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,

View File

@ -5,141 +5,156 @@ import 'package:syncrow_web/utils/style.dart';
Future<void> showPopUpFilterMenu({
required BuildContext context,
Function()? onSortAtoZ,
Function()? onSortZtoA,
required Function(String value) onSortAtoZ,
required Function(String value) onSortZtoA,
Function()? cancelButton,
required Map<String, bool> checkboxStates,
required RelativeRect position,
Function()? onOkPressed,
List<String>? list,
String? isSelected,
}) async {
await showMenu(
context: context,
position:position,
position: position,
color: ColorsManager.whiteColors,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
items: <PopupMenuEntry>[
PopupMenuItem(
onTap: onSortAtoZ,
child: ListTile(
leading: Image.asset(
Assets.AtoZIcon,
width: 25,
),
title: const Text(
"Sort A to Z",
style: TextStyle(color: Colors.blueGrey),
),
),
),
PopupMenuItem(
onTap: onSortZtoA,
child: ListTile(
leading: Image.asset(
Assets.ZtoAIcon,
width: 25,
),
title: const Text(
"Sort Z to A",
style: TextStyle(color: Colors.blueGrey),
),
),
),
const PopupMenuDivider(),
const PopupMenuItem(
child: Text(
"Filter by Status",
style: TextStyle(fontWeight: FontWeight.bold),
)
// Container(
// decoration: containerDecoration.copyWith(
// boxShadow: [],
// borderRadius: const BorderRadius.only(
// topLeft: Radius.circular(10), topRight: Radius.circular(10))),
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: TextFormField(
// onChanged: onTextFieldChanged,
// style: const TextStyle(color: Colors.black),
// decoration: textBoxDecoration(radios: 15)!.copyWith(
// fillColor: ColorsManager.whiteColors,
// errorStyle: const TextStyle(height: 0),
// hintStyle: context.textTheme.titleSmall?.copyWith(
// color: Colors.grey,
// fontSize: 12,
// ),
// hintText: 'Search',
// suffixIcon: SizedBox(
// child: SvgPicture.asset(
// Assets.searchIconUser,
// fit: BoxFit.none,
// ),
// ),
// ),
// // "Filter by Status",
// // style: TextStyle(fontWeight: FontWeight.bold),
// ),
// ),
// ),
),
PopupMenuItem(
child: Container(
decoration: containerDecoration.copyWith(
boxShadow: [],
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10))),
padding: const EdgeInsets.all(10),
height: 200,
width: 400,
child: Container(
padding: const EdgeInsets.all(10),
color: Colors.white,
child: ListView.builder(
itemCount: list?.length ?? 0,
itemBuilder: (context, index) {
final item = list![index];
return CheckboxListTile(
dense: true,
title: Text(item),
value: checkboxStates[item],
onChanged: (bool? newValue) {
checkboxStates[item] = newValue ?? false;
(context as Element).markNeedsBuild();
},
);
},
),
),
),
),
PopupMenuItem(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
Navigator.of(context).pop(); // Close the menu
},
child: const Text("Cancel"),
),
GestureDetector(
onTap: onOkPressed,
child: const Text(
"OK",
style: TextStyle(
color: ColorsManager.spaceColor,
enabled: false,
child: StatefulBuilder(
builder: (context, setState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
child: ListTile(
onTap: () {
setState(() {
if (isSelected == 'Asc') {
isSelected = null;
onSortAtoZ.call('');
} else {
onSortAtoZ.call('Asc');
isSelected = 'Asc';
}
});
},
leading: Image.asset(
Assets.AtoZIcon,
width: 25,
),
title: Text(
"Sort A to Z",
style: TextStyle(
color: isSelected == "Asc"
? ColorsManager.blackColor
: ColorsManager.grayColor),
),
),
),
),
),
],
ListTile(
onTap: () {
setState(() {
if (isSelected == 'Desc') {
isSelected = null;
onSortZtoA.call('');
} else {
onSortZtoA.call('Desc');
isSelected = 'Desc';
}
});
},
leading: Image.asset(
Assets.ZtoAIcon,
width: 25,
),
title: Text(
"Sort Z to A",
style: TextStyle(
color: isSelected == "Desc"
? ColorsManager.blackColor
: ColorsManager.grayColor),
),
),
const Divider(),
const Text(
"Filter by Status",
style: TextStyle(fontWeight: FontWeight.bold),
),
Container(
decoration: containerDecoration.copyWith(
boxShadow: [],
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10))),
padding: const EdgeInsets.all(10),
height: 200,
width: 400,
child: Container(
padding: const EdgeInsets.all(10),
color: Colors.white,
child: ListView.builder(
itemCount: list?.length ?? 0,
itemBuilder: (context, index) {
final item = list![index];
return Row(
children: [
Checkbox(
value: checkboxStates[item],
onChanged: (bool? newValue) {
checkboxStates[item] = newValue ?? false;
(context as Element).markNeedsBuild();
},
),
Text(
item,
style: TextStyle(color: ColorsManager.grayColor),
),
],
);
},
),
),
),
const SizedBox(
height: 10,
),
const Divider(),
const SizedBox(
height: 10,
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
Navigator.of(context).pop(); // Close the menu
},
child: const Text("Cancel"),
),
GestureDetector(
onTap: onOkPressed,
child: const Text(
"OK",
style: TextStyle(
color: ColorsManager.spaceColor,
),
),
),
],
),
const SizedBox(
height: 10,
),
],
);
},
),
),
],

View File

@ -52,14 +52,16 @@ class _RoleDropdownState extends State<RoleDropdown> {
SizedBox(
child: DropdownButtonFormField<String>(
dropdownColor: ColorsManager.whiteColors,
alignment: Alignment.center,
// alignment: Alignment.,
focusColor: Colors.white,
autofocus: true,
value: selectedRole.isNotEmpty ? selectedRole : null,
items: widget.bloc!.roles.map((role) {
return DropdownMenuItem<String>(
value: role.uuid,
child: Text(role.type),
child: Text(
' ${role.type.isNotEmpty ? role.type[0].toUpperCase() : ''}${role.type.substring(1)}',
),
);
}).toList(),
onChanged: (value) {

View File

@ -93,7 +93,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
try {
emit(UsersLoadingState());
bool res = await UserPermissionApi().changeUserStatusById(
event.userId, event.newStatus == "disabled" ? true : false);
event.userId, event.newStatus == "disabled" ? false : true);
if (res == true) {
add(const GetUsers());
// users = users.map((user) {
@ -133,7 +133,10 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} else {
emit(UsersLoadingState());
currentSortOrder = "Asc";
users.sort((a, b) => a.firstName!.compareTo(b.firstName!));
users.sort((a, b) => a.firstName
.toString()
.toLowerCase()
.compareTo(b.firstName.toString().toLowerCase()));
emit(UsersLoadedState(users: users));
}
}
@ -164,6 +167,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
emit(UsersLoadedState(users: users));
} else {
emit(UsersLoadingState());
currentSortOrder = "NewestToOldest";
users.sort((a, b) {
final dateA = _parseDateTime(a.createdDate);
final dateB = _parseDateTime(b.createdDate);
@ -188,6 +192,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
final dateB = _parseDateTime(b.createdDate);
return dateA.compareTo(dateB);
});
currentSortOrder = "OldestToNewest";
emit(UsersLoadedState(users: users));
}
}
@ -210,7 +215,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
final query = event.query.toLowerCase();
final filteredUsers = initialUsers.where((user) {
final fullName = "${user.firstName} ${user.lastName}".toLowerCase();
final email = user.email.toLowerCase() ;
final email = user.email.toLowerCase();
return fullName.contains(query) || email.contains(query);
}).toList();
emit(UsersLoadedState(users: filteredUsers));
@ -256,49 +261,96 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
void _filterUsersByRole(
FilterUsersByRoleEvent event, Emitter<UserTableState> emit) {
selectedRoles = event.selectedRoles.toSet();
selectedRoles = event.selectedRoles!.toSet();
final filteredUsers = initialUsers.where((user) {
if (selectedRoles.isEmpty) return true;
return selectedRoles.contains(user.roleType);
}).toList();
if (event.sortOrder == "Asc") {
currentSortOrder = "Asc";
filteredUsers.sort((a, b) => a.firstName
.toString()
.toLowerCase()
.compareTo(b.firstName.toString().toLowerCase()));
} else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else {
currentSortOrder = "";
}
emit(UsersLoadedState(users: filteredUsers));
}
void _filterUsersByJobTitle(
FilterUsersByJobEvent event, Emitter<UserTableState> emit) {
selectedJobTitles = event.selectedJob.toSet();
selectedJobTitles = event.selectedJob!.toSet();
emit(UsersLoadingState());
final filteredUsers = initialUsers.where((user) {
if (selectedJobTitles.isEmpty) return true;
return selectedJobTitles.contains(user.jobTitle);
}).toList();
if (event.sortOrder == "Asc") {
currentSortOrder = "Asc";
filteredUsers.sort((a, b) => a.firstName
.toString()
.toLowerCase()
.compareTo(b.firstName.toString().toLowerCase()));
} else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else {
currentSortOrder = "";
}
emit(UsersLoadedState(users: filteredUsers));
}
void _filterUsersByCreated(
FilterUsersByCreatedEvent event, Emitter<UserTableState> emit) {
selectedCreatedBy = event.selectedCreatedBy.toSet();
selectedCreatedBy = event.selectedCreatedBy!.toSet();
final filteredUsers = initialUsers.where((user) {
if (selectedCreatedBy.isEmpty) return true;
return selectedCreatedBy.contains(user.invitedBy);
}).toList();
if (event.sortOrder == "Asc") {
currentSortOrder = "Asc";
filteredUsers.sort((a, b) => a.firstName
.toString()
.toLowerCase()
.compareTo(b.firstName.toString().toLowerCase()));
} else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else {
currentSortOrder = "";
}
emit(UsersLoadedState(users: filteredUsers));
}
void _filterUserStatus(
FilterUsersByDeActevateEvent event, Emitter<UserTableState> emit) {
selectedStatuses = event.selectedActivate.toSet();
selectedStatuses = event.selectedActivate!.toSet();
final filteredUsers = initialUsers.where((user) {
if (selectedStatuses.isEmpty) return true;
return selectedStatuses.contains(user.status);
}).toList();
if (event.sortOrder == "Asc") {
currentSortOrder = "Asc";
filteredUsers.sort((a, b) => a.firstName
.toString()
.toLowerCase()
.compareTo(b.firstName.toString().toLowerCase()));
} else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else {
currentSortOrder = "";
}
emit(UsersLoadedState(users: filteredUsers));
}

View File

@ -89,35 +89,36 @@ class DeleteUserEvent extends UserTableEvent {
}
class FilterUsersByRoleEvent extends UserTableEvent {
final List<String> selectedRoles;
final List<String>? selectedRoles;
final String? sortOrder;
FilterUsersByRoleEvent(this.selectedRoles);
@override
List<Object?> get props => [selectedRoles];
const FilterUsersByRoleEvent({this.selectedRoles, this.sortOrder});
List<Object?> get props => [selectedRoles, sortOrder];
}
class FilterUsersByJobEvent extends UserTableEvent {
final List<String> selectedJob;
final List<String>? selectedJob;
final String? sortOrder;
FilterUsersByJobEvent(this.selectedJob);
@override
List<Object?> get props => [selectedJob];
const FilterUsersByJobEvent({this.selectedJob, this.sortOrder});
List<Object?> get props => [selectedJob, sortOrder];
}
class FilterUsersByCreatedEvent extends UserTableEvent {
final List<String> selectedCreatedBy;
final List<String>? selectedCreatedBy;
FilterUsersByCreatedEvent(this.selectedCreatedBy);
@override
List<Object?> get props => [selectedCreatedBy];
final String? sortOrder;
const FilterUsersByCreatedEvent({this.selectedCreatedBy, this.sortOrder});
List<Object?> get props => [selectedCreatedBy, sortOrder];
}
class FilterUsersByDeActevateEvent extends UserTableEvent {
final List<String> selectedActivate;
final List<String>? selectedActivate;
final String? sortOrder;
FilterUsersByDeActevateEvent(this.selectedActivate);
@override
List<Object?> get props => [selectedActivate];
const FilterUsersByDeActevateEvent({this.selectedActivate, this.sortOrder});
List<Object?> get props => [selectedActivate, sortOrder];
}
class FilterOptionsEvent extends UserTableEvent {

View File

@ -19,19 +19,13 @@ class DynamicTableScreen extends StatefulWidget {
class _DynamicTableScreenState extends State<DynamicTableScreen>
with WidgetsBindingObserver {
late List<double> columnWidths;
late double totalWidth;
// @override
// void initState() {
// super.initState();
// // Initialize column widths with default sizes proportional to the screen width
// // Assigning placeholder values here. The actual sizes will be updated in `build`.
// }
@override
void initState() {
super.initState();
setState(() {
columnWidths = List<double>.filled(widget.titles.length, 150.0);
});
columnWidths = List<double>.filled(widget.titles.length, 150.0);
totalWidth = columnWidths.reduce((a, b) => a + b);
WidgetsBinding.instance.addObserver(this);
}
@ -44,7 +38,6 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
@override
void didChangeMetrics() {
super.didChangeMetrics();
// Screen size might have changed
final newScreenWidth = MediaQuery.of(context).size.width;
setState(() {
columnWidths = List<double>.generate(widget.titles.length, (index) {
@ -53,7 +46,7 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
0.12; // 20% of screen width for the second column
} else if (index == 9) {
return newScreenWidth *
0.2; // 25% of screen width for the tenth column
0.1; // 25% of screen width for the tenth column
}
return newScreenWidth *
0.09; // Default to 10% of screen width for other columns
@ -64,293 +57,204 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
// Initialize column widths if they are still set to placeholder values
if (columnWidths.every((width) => width == 120.0)) {
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.2;
return screenWidth * 0.1;
}
return screenWidth * 0.11;
return screenWidth * 0.09;
});
setState(() {});
}
return Container(
child: SingleChildScrollView(
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
child: Container(
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.all(Radius.circular(20))),
child: FittedBox(
child: Column(
children: [
// Header Row with Resizable Columns
Container(
width: MediaQuery.of(context).size.width,
decoration: containerDecoration.copyWith(
color: ColorsManager.circleRolesBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15))),
child: Row(
children: List.generate(widget.titles.length, (index) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
FittedBox(
child: Container(
padding: const EdgeInsets.only(left: 5, right: 5),
width: columnWidths[index],
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
child: Text(
widget.titles[index],
maxLines: 2,
style: const TextStyle(
overflow: TextOverflow.ellipsis,
fontWeight: FontWeight.w400,
fontSize: 13,
color: ColorsManager.grayColor,
),
return SingleChildScrollView(
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
child: Container(
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.all(Radius.circular(20))),
child: FittedBox(
child: Column(
children: [
Container(
width: totalWidth,
decoration: containerDecoration.copyWith(
color: ColorsManager.circleRolesBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15))),
child: Row(
children: List.generate(widget.titles.length, (index) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
FittedBox(
child: Container(
padding: const EdgeInsets.only(left: 5, right: 5),
width: columnWidths[index],
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
child: Text(
widget.titles[index],
maxLines: 2,
style: const TextStyle(
overflow: TextOverflow.ellipsis,
fontWeight: FontWeight.w400,
fontSize: 13,
color: ColorsManager.grayColor,
),
),
if (index != 1 &&
index != 9 &&
index != 8 &&
index != 5)
FittedBox(
child: IconButton(
icon: SvgPicture.asset(
Assets.filterTableIcon,
fit: BoxFit.none,
),
onPressed: () {
if (widget.onFilter != null) {
widget.onFilter!(index);
}
},
),
if (index != 1 &&
index != 9 &&
index != 8 &&
index != 5)
FittedBox(
child: IconButton(
icon: SvgPicture.asset(
Assets.filterTableIcon,
fit: BoxFit.none,
),
)
],
),
),
),
GestureDetector(
onHorizontalDragUpdate: (details) {
setState(() {
columnWidths[index] = (columnWidths[index] +
details.delta.dx)
.clamp(
150.0, 300.0); // Minimum & Maximum size
});
},
child: MouseRegion(
cursor: SystemMouseCursors
.resizeColumn, // Set the cursor to resize
child: Container(
color: Colors.green,
child: Container(
color: ColorsManager.boxDivider,
width: 1,
height: 50, // Height of the header cell
),
),
),
),
],
);
}),
),
),
// Data Rows with Dividers
widget.rows.isEmpty
? Container(
child: SizedBox(
height: MediaQuery.of(context).size.height / 2,
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
SvgPicture.asset(Assets.emptyTable),
const SizedBox(
height: 15,
onPressed: () {
if (widget.onFilter != null) {
widget.onFilter!(index);
}
},
),
const Text(
'No Users',
style: TextStyle(
color: ColorsManager.lightGrayColor,
fontSize: 16,
fontWeight: FontWeight.w700),
)
],
),
)
],
),
),
),
)
: Center(
child: Container(
// height: MediaQuery.of(context).size.height * 0.59,
width: MediaQuery.of(context).size.width,
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15))),
child: ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: widget.rows.length,
itemBuilder: (context, rowIndex) {
if (columnWidths
.every((width) => width == 120.0)) {
columnWidths = List<double>.generate(
widget.titles.length, (index) {
if (index == 1) {
return screenWidth * 0.11;
} else if (index == 9) {
return screenWidth * 0.2;
}
GestureDetector(
onHorizontalDragUpdate: (details) {
setState(() {
columnWidths[index] =
(columnWidths[index] + details.delta.dx)
.clamp(150.0, 300.0);
totalWidth = columnWidths.reduce((a, b) => a + b);
});
},
child: MouseRegion(
cursor: SystemMouseCursors.resizeColumn,
child: Container(
color: Colors.green,
child: Container(
color: ColorsManager.boxDivider,
width: 1,
height: 50,
),
),
),
),
],
);
}),
),
),
widget.rows.isEmpty
? SizedBox(
height: MediaQuery.of(context).size.height / 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
SvgPicture.asset(Assets.emptyTable),
const SizedBox(
height: 15,
),
const Text(
'No Users',
style: TextStyle(
color: ColorsManager.lightGrayColor,
fontSize: 16,
fontWeight: FontWeight.w700),
)
],
),
],
),
)
: Center(
child: Container(
width: totalWidth,
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15))),
child: ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: widget.rows.length,
itemBuilder: (context, rowIndex) {
if (columnWidths.every((width) => width == 120.0)) {
columnWidths = List<double>.generate(
widget.titles.length, (index) {
if (index == 1) {
return screenWidth * 0.11;
});
setState(() {});
}
final row = widget.rows[rowIndex];
return Column(
children: [
Container(
child: Padding(
padding: const EdgeInsets.only(
left: 5,
top: 10,
right: 5,
bottom: 10),
child: Row(
children:
List.generate(row.length, (index) {
return SizedBox(
width: columnWidths[index],
child: SizedBox(
child: Padding(
padding: const EdgeInsets.only(
left: 15, right: 10),
child: row[index],
),
),
);
}),
),
),
),
if (rowIndex < widget.rows.length - 1)
Row(
children: List.generate(
widget.titles.length, (index) {
} else if (index == 9) {
return screenWidth * 0.2;
}
return screenWidth * 0.11;
});
setState(() {});
}
final row = widget.rows[rowIndex];
return Column(
children: [
Container(
child: Padding(
padding: const EdgeInsets.only(
left: 5, top: 10, right: 5, bottom: 10),
child: Row(
children:
List.generate(row.length, (index) {
return SizedBox(
width: columnWidths[index],
child: const Divider(
color: ColorsManager.boxDivider,
thickness: 1,
height: 1,
child: SizedBox(
child: Padding(
padding: const EdgeInsets.only(
left: 15, right: 10),
child: row[index],
),
),
);
}),
),
],
);
},
),
),
),
if (rowIndex < widget.rows.length - 1)
Row(
children: List.generate(
widget.titles.length, (index) {
return SizedBox(
width: columnWidths[index],
child: const Divider(
color: ColorsManager.boxDivider,
thickness: 1,
height: 1,
),
);
}),
),
],
);
},
),
),
],
),
),
],
),
),
),
);
}
}
// Widget build(BuildContext context) {
// return Scaffold(
// body: SingleChildScrollView(
// scrollDirection: Axis.horizontal,
// child: SingleChildScrollView(
// scrollDirection: Axis.vertical,
// child: Column(
// children: [
// // Header Row with Resizable Columns
// Container(
// color: Colors.green,
// child: Row(
// children: List.generate(widget.titles.length, (index) {
// return Row(
// children: [
// Container(
// width: columnWidths[index],
// decoration: const BoxDecoration(
// color: Colors.green,
// ),
// child: Text(
// widget.titles[index],
// style: TextStyle(fontWeight: FontWeight.bold),
// textAlign: TextAlign.center,
// ),
// ),
// GestureDetector(
// onHorizontalDragUpdate: (details) {
// setState(() {
// columnWidths[index] = (columnWidths[index] +
// details.delta.dx)
// .clamp(50.0, 300.0); // Minimum & Maximum size
// });
// },
// child: MouseRegion(
// cursor: SystemMouseCursors
// .resizeColumn, // Set the cursor to resize
// child: Container(
// color: Colors.green,
// child: Container(
// color: Colors.black,
// width: 1,
// height: 50, // Height of the header cell
// ),
// ),
// ),
// ),
// ],
// );
// }),
// ),
// ),
// // Data Rows
// ...widget.rows.map((row) {
// return Row(
// children: List.generate(row.length, (index) {
// return Container(
// width: columnWidths[index],
// child: row[index],
// );
// }),
// );
// }).toList(),
// ],
// ),
// ),
// ),
// );
// }

View File

@ -107,7 +107,6 @@ class UsersPage extends StatelessWidget {
builder: (context, state) {
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());
@ -189,8 +188,6 @@ class UsersPage extends StatelessWidget {
const SizedBox(height: 25),
DynamicTableScreen(
onFilter: (columnIndex) {
_blocRole.add(FilterClearEvent());
if (columnIndex == 0) {
showNameMenu(
context: context,
@ -210,11 +207,12 @@ class UsersPage extends StatelessWidget {
if (columnIndex == 2) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.jobTitle)
item: false, // Initialize with false
item: _blocRole.selectedJobTitles.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 4,
@ -225,26 +223,28 @@ class UsersPage extends StatelessWidget {
list: _blocRole.jobTitle,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder,
onOkPressed: () {
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
_blocRole.add(FilterUsersByJobEvent(selectedItems));
_blocRole.add(FilterUsersByJobEvent(
selectedJob: selectedItems,
sortOrder: _blocRole.currentSortOrder,
));
},
onSortAtoZ: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameAsc());
onSortAtoZ: (v) {
_blocRole.currentSortOrder = v;
},
onSortZtoA: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
onSortZtoA: (v) {
_blocRole.currentSortOrder = v;
},
);
}
if (columnIndex == 3) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.roleTypes)
@ -263,32 +263,31 @@ class UsersPage extends StatelessWidget {
list: _blocRole.roleTypes,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder,
onOkPressed: () {
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
context
.read<UserTableBloc>()
.add(FilterUsersByRoleEvent(selectedItems));
context.read<UserTableBloc>().add(
FilterUsersByRoleEvent(
selectedRoles: selectedItems,
sortOrder: _blocRole.currentSortOrder));
},
onSortAtoZ: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameAsc());
onSortAtoZ: (v) {
_blocRole.currentSortOrder = v;
},
onSortZtoA: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
onSortZtoA: (v) {
_blocRole.currentSortOrder = v;
},
);
}
if (columnIndex == 4) {
showDateFilterMenu(
context: context,
isSelected: _blocRole.currentSortOrderDate,
isSelected: _blocRole.currentSortOrder,
aToZTap: () {
context
.read<UserTableBloc>()
@ -319,32 +318,30 @@ class UsersPage extends StatelessWidget {
list: _blocRole.createdBy,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder,
onOkPressed: () {
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
_blocRole
.add(FilterUsersByCreatedEvent(selectedItems));
_blocRole.add(FilterUsersByCreatedEvent(
selectedCreatedBy: selectedItems,
sortOrder: _blocRole.currentSortOrder));
},
onSortAtoZ: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameAsc());
onSortAtoZ: (v) {
_blocRole.currentSortOrder = v;
},
onSortZtoA: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
onSortZtoA: (v) {
_blocRole.currentSortOrder = v;
},
);
}
if (columnIndex == 7) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.status)
item: _blocRole.selectedCreatedBy.contains(item),
item: _blocRole.selectedStatuses.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
@ -359,24 +356,24 @@ class UsersPage extends StatelessWidget {
list: _blocRole.status,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder,
onOkPressed: () {
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
_blocRole
.add(FilterUsersByCreatedEvent(selectedItems));
_blocRole.add(FilterUsersByDeActevateEvent(
selectedActivate: selectedItems,
sortOrder: _blocRole.currentSortOrder));
},
onSortAtoZ: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameAsc());
onSortAtoZ: (v) {
_blocRole.currentSortOrder = v;
},
onSortZtoA: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
onSortZtoA: (v) {
_blocRole.currentSortOrder = v;
},
);
}
@ -412,8 +409,8 @@ class UsersPage extends StatelessWidget {
rows: state.users.map((user) {
return [
Text('${user.firstName} ${user.lastName}'),
Text(user.email ),
Text(user.jobTitle ?? ''),
Text(user.email),
Text(user.jobTitle ?? '-'),
Text(user.roleType ?? ''),
Text(user.createdDate ?? ''),
Text(user.createdTime ?? ''),
@ -476,11 +473,17 @@ class UsersPage extends StatelessWidget {
barrierDismissible: false,
builder: (BuildContext context) {
return DeleteUserDialog(
onTapDelete: () {
onTapDelete: () async {
try {
_blocRole.add(DeleteUserEvent(
user.uuid, context));
},
);
await Future.delayed(
const Duration(seconds: 2));
return true;
} catch (e) {
return false;
}
});
},
).then((v) {
if (v != null) {
@ -504,6 +507,7 @@ class UsersPage extends StatelessWidget {
SizedBox(
width: 500,
child: NumberPagination(
visiblePagesCount: 4,
buttonRadius: 10,
selectedButtonColor: ColorsManager.secondaryColor,
buttonUnSelectedBorderColor:

View File

@ -4,20 +4,17 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/services/product_api.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
class SpaceManagementBloc
extends Bloc<SpaceManagementEvent, SpaceManagementState> {
final CommunitySpaceManagementApi _api;
final ProductApi _productApi;
final SpaceModelManagementApi _spaceModelApi;
List<ProductModel>? _cachedProducts;
SpaceManagementBloc(this._api, this._productApi, this._spaceModelApi)
SpaceManagementBloc(this._api, this._productApi)
: super(SpaceManagementInitial()) {
on<LoadCommunityAndSpacesEvent>(_onLoadCommunityAndSpaces);
on<UpdateSpacePositionEvent>(_onUpdateSpacePosition);
@ -29,8 +26,6 @@ class SpaceManagementBloc
on<FetchProductsEvent>(_onFetchProducts);
on<SelectSpaceEvent>(_onSelectSpace);
on<NewCommunityEvent>(_onNewCommunity);
on<BlankStateEvent>(_onBlankState);
on<SpaceModelLoadEvent>(_onLoadSpaceModel);
}
void _onUpdateCommunity(
@ -108,45 +103,6 @@ class SpaceManagementBloc
}
}
Future<void> _onBlankState(
BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
try {
final previousState = state;
if (previousState is SpaceManagementLoaded ||
previousState is BlankState) {
final prevCommunities = (previousState as dynamic).communities ?? [];
emit(BlankState(
communities: List<CommunityModel>.from(prevCommunities),
products: _cachedProducts ?? [],
));
return;
}
final communities = await _api.fetchCommunities();
final updatedCommunities =
await Future.wait(communities.map((community) async {
final spaces = await _fetchSpacesForCommunity(community.uuid);
return CommunityModel(
uuid: community.uuid,
createdAt: community.createdAt,
updatedAt: community.updatedAt,
name: community.name,
description: community.description,
spaces: spaces,
region: community.region,
);
}));
emit(BlankState(
communities: updatedCommunities,
products: _cachedProducts ?? [],
));
} catch (error) {
emit(SpaceManagementError('Error loading communities: $error'));
}
}
void _onLoadCommunityAndSpaces(
LoadCommunityAndSpacesEvent event,
Emitter<SpaceManagementState> emit,
@ -239,15 +195,11 @@ class SpaceManagementBloc
SelectCommunityEvent event,
Emitter<SpaceManagementState> emit,
) async {
try {
_handleCommunitySpaceStateUpdate(
emit: emit,
selectedCommunity: event.selectedCommunity,
selectedSpace: null,
);
} catch (e) {
emit(SpaceManagementError('Error updating state: $e'));
}
_handleCommunitySpaceStateUpdate(
emit: emit,
selectedCommunity: event.selectedCommunity,
selectedSpace: null,
);
}
void _onSelectSpace(
@ -271,8 +223,7 @@ class SpaceManagementBloc
try {
if (previousState is SpaceManagementLoaded ||
previousState is BlankState ||
previousState is SpaceModelLoaded) {
previousState is BlankState) {
final communities = List<CommunityModel>.from(
(previousState as dynamic).communities,
);
@ -366,26 +317,26 @@ class SpaceManagementBloc
try {
if (space.uuid != null && space.uuid!.isNotEmpty) {
final response = await _api.updateSpace(
communityId: communityUuid,
spaceId: space.uuid!,
name: space.name,
parentId: space.parent?.uuid,
isPrivate: space.isPrivate,
position: space.position,
icon: space.icon,
direction: space.incomingConnection?.direction,
);
communityId: communityUuid,
spaceId: space.uuid!,
name: space.name,
parentId: space.parent?.uuid,
isPrivate: space.isPrivate,
position: space.position,
icon: space.icon,
direction: space.incomingConnection?.direction,
products: space.selectedProducts);
} else {
// Call create if the space does not have a UUID
final response = await _api.createSpace(
communityId: communityUuid,
name: space.name,
parentId: space.parent?.uuid,
isPrivate: space.isPrivate,
position: space.position,
icon: space.icon,
direction: space.incomingConnection?.direction,
);
communityId: communityUuid,
name: space.name,
parentId: space.parent?.uuid,
isPrivate: space.isPrivate,
position: space.position,
icon: space.icon,
direction: space.incomingConnection?.direction,
products: space.selectedProducts);
space.uuid = response?.uuid;
}
} catch (e) {
@ -419,37 +370,4 @@ class SpaceManagementBloc
}
return result.toList(); // Convert back to a list
}
void _onLoadSpaceModel(
SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
emit(SpaceManagementLoading());
try {
List<CommunityModel> communities = await _api.fetchCommunities();
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces =
await _fetchSpacesForCommunity(community.uuid);
return CommunityModel(
uuid: community.uuid,
createdAt: community.createdAt,
updatedAt: community.updatedAt,
name: community.name,
description: community.description,
spaces: spaces, // New spaces list
region: community.region,
);
}).toList(),
);
List<SpaceTemplateModel> spaceModels =
await _spaceModelApi.listSpaceModels(page: 1);
emit(SpaceModelLoaded(
communities: updatedCommunities,
products: _cachedProducts ?? [],
spaceModels: spaceModels));
} catch (e) {
emit(SpaceManagementError('Error loading communities and spaces: $e'));
}
}
}

View File

@ -140,8 +140,3 @@ class LoadSpaceHierarchyEvent extends SpaceManagementEvent {
@override
List<Object> get props => [communityId];
}
class BlankStateEvent extends SpaceManagementEvent {}
class SpaceModelLoadEvent extends SpaceManagementEvent {}

View File

@ -2,7 +2,6 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
abstract class SpaceManagementState extends Equatable {
const SpaceManagementState();
@ -28,10 +27,6 @@ class SpaceManagementLoaded extends SpaceManagementState {
this.selectedSpace});
}
class SpaceModelManagenetLoaded extends SpaceManagementState {
SpaceModelManagenetLoaded();
}
class BlankState extends SpaceManagementState {
final List<CommunityModel> communities;
final List<ProductModel> products;
@ -59,18 +54,3 @@ class SpaceManagementError extends SpaceManagementState {
@override
List<Object> get props => [errorMessage];
}
class SpaceModelLoaded extends SpaceManagementState {
final List<SpaceTemplateModel> spaceModels;
final List<ProductModel> products;
final List<CommunityModel> communities;
SpaceModelLoaded({
required this.communities,
required this.products,
required this.spaceModels,
});
@override
List<Object> get props => [communities, products, spaceModels];
}

View File

@ -19,6 +19,7 @@ class ProductModel {
// Factory method to create a Product from JSON
factory ProductModel.fromMap(Map<String, dynamic> json) {
String icon = _mapIconToProduct(json['prodType']);
return ProductModel(
uuid: json['uuid'],
catName: json['catName'],

View File

@ -1,18 +1,13 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
class SelectedProduct {
final String productId;
int count;
final String productName;
final ProductModel? product;
SelectedProduct({required this.productId, required this.count, required this.productName, this.product});
SelectedProduct({required this.productId, required this.count});
Map<String, dynamic> toJson() {
return {
'productId': productId,
'count': count,
'productName': productName,
};
}

View File

@ -20,6 +20,7 @@ class SpaceModel {
Offset position;
bool isHovered;
SpaceStatus status;
List<SelectedProduct> selectedProducts;
String internalId;
List<Connection> outgoingConnections = []; // Connections from this space
@ -40,6 +41,7 @@ class SpaceModel {
this.isHovered = false,
this.incomingConnection,
this.status = SpaceStatus.unchanged,
this.selectedProducts = const [],
}) : internalId = internalId ?? const Uuid().v4();
factory SpaceModel.fromJson(Map<String, dynamic> json,
@ -83,6 +85,14 @@ class SpaceModel {
icon: json['icon'] ?? Assets.location,
position: Offset(json['x'] ?? 0, json['y'] ?? 0),
isHovered: false,
selectedProducts: json['spaceProducts'] != null
? (json['spaceProducts'] as List).map((product) {
return SelectedProduct(
productId: product['product']['uuid'],
count: product['productCount'],
);
}).toList()
: [],
);
if (json['incomingConnections'] != null &&

View File

@ -1,15 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart';
import 'package:syncrow_web/pages/spaces_management/structure_selector/view/center_body_widget.dart';
import 'package:syncrow_web/services/product_api.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class SpaceManagementPage extends StatefulWidget {
@ -22,54 +22,48 @@ class SpaceManagementPage extends StatefulWidget {
class SpaceManagementPageState extends State<SpaceManagementPage> {
final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi();
final ProductApi _productApi = ProductApi();
final SpaceModelManagementApi _spaceModelApi = SpaceModelManagementApi();
Map<String, List<SpaceModel>> communitySpaces = {};
List<ProductModel> products = [];
bool isProductDataLoaded = false;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => SpaceManagementBloc(_api, _productApi, _spaceModelApi)
..add(LoadCommunityAndSpacesEvent()),
),
BlocProvider(
create: (_) => CenterBodyBloc(),
),
],
return BlocProvider(
create: (context) => SpaceManagementBloc(_api, _productApi)
..add(LoadCommunityAndSpacesEvent()),
child: WebScaffold(
appBarTitle: Text('Space Management',
style: Theme.of(context).textTheme.headlineLarge),
enableMenuSidebar: false,
centerBody: CenterBodyWidget(),
rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocBuilder<SpaceManagementBloc, SpaceManagementState>(
builder: (context, state) {
if (state is SpaceManagementLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is BlankState) {
return LoadedSpaceView(
communities: state.communities,
selectedCommunity: null,
selectedSpace: null,
products: state.products,
);
} else if (state is SpaceManagementLoaded) {
return LoadedSpaceView(
communities: state.communities,
selectedCommunity: state.selectedCommunity,
selectedSpace: state.selectedSpace,
products: state.products,
);
} else if (state is SpaceModelLoaded) {
return LoadedSpaceView(
communities: state.communities,
products: state.products,
spaceModels: state.spaceModels);
} else if (state is SpaceManagementError) {
return Center(child: Text('Error: ${state.errorMessage}'));
}
return Container();
},
),
builder: (context, state) {
if (state is SpaceManagementLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is BlankState) {
return LoadedSpaceView(
communities: state.communities,
selectedCommunity: null,
selectedSpace: null,
products: state.products,
);
} else if (state is SpaceManagementLoaded) {
return LoadedSpaceView(
communities: state.communities,
selectedCommunity: state.selectedCommunity,
selectedSpace: state.selectedSpace,
products: state.products,
);
} else if (state is SpaceManagementError) {
return Center(child: Text('Error: ${state.errorMessage}'));
}
return Container();
}),
),
);
}

View File

@ -99,7 +99,7 @@ class _AddDeviceWidgetState extends State<AddDeviceWidget> {
_buildActionButton('Cancel', ColorsManager.boxColor, ColorsManager.blackColor, () {
Navigator.of(context).pop();
}),
_buildActionButton('Continue', ColorsManager.secondaryColor, ColorsManager.whiteColors, () {
_buildActionButton('Continue', ColorsManager.secondaryColor, Colors.white, () {
Navigator.of(context).pop();
if (widget.onProductsSelected != null) {
widget.onProductsSelected!(productCounts);
@ -114,7 +114,7 @@ class _AddDeviceWidgetState extends State<AddDeviceWidget> {
Widget _buildDeviceTypeTile(ProductModel product, Size size) {
final selectedProduct = productCounts.firstWhere(
(p) => p.productId == product.uuid,
orElse: () => SelectedProduct(productId: product.uuid, count: 0, productName: product.catName, product: product),
orElse: () => SelectedProduct(productId: product.uuid, count: 0),
);
return SizedBox(
@ -143,7 +143,7 @@ class _AddDeviceWidgetState extends State<AddDeviceWidget> {
if (newCount > 0) {
if (!productCounts.contains(selectedProduct)) {
productCounts
.add(SelectedProduct(productId: product.uuid, count: newCount, productName: product.catName, product: product));
.add(SelectedProduct(productId: product.uuid, count: newCount));
} else {
selectedProduct.count = newCount;
}

View File

@ -178,7 +178,7 @@ class _CommunityStructureHeaderState extends State<CommunityStructureHeader> {
padding: 2.0,
height: buttonHeight,
elevation: 0,
borderColor: ColorsManager.lightGrayColor,
borderColor: Colors.grey.shade300,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [

View File

@ -299,7 +299,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
isPrivate: false,
children: [],
status: SpaceStatus.newSpace,
);
selectedProducts: selectedProducts);
if (parentIndex != null && direction != null) {
SpaceModel parentSpace = spaces[parentIndex];
@ -335,12 +335,14 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
icon: space.icon,
editSpace: space,
isEdit: true,
selectedProducts: space.selectedProducts,
onCreateSpace: (String name, String icon,
List<SelectedProduct> selectedProducts) {
setState(() {
// Update the space's properties
space.name = name;
space.icon = icon;
space.selectedProducts = selectedProducts;
if (space.status != SpaceStatus.newSpace) {
space.status = SpaceStatus.modified; // Mark as modified

View File

@ -104,7 +104,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
width: screenWidth * 0.020,
height: screenWidth * 0.020,
decoration: const BoxDecoration(
color: ColorsManager.whiteColors,
color: Colors.white,
shape: BoxShape.circle,
),
child: SvgPicture.asset(
@ -143,7 +143,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
}
});
},
style: const TextStyle(color: ColorsManager.blackColor),
style: const TextStyle(color: Colors.black),
decoration: InputDecoration(
hintText: 'Please enter the name',
hintStyle: const TextStyle(
@ -212,7 +212,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
);
},
backgroundColor: ColorsManager.textFieldGreyColor,
foregroundColor: ColorsManager.blackColor,
foregroundColor: Colors.black,
borderColor: ColorsManager.neutralGray,
borderRadius: 16.0,
padding: 10.0, // Reduced padding for smaller size

View File

@ -40,8 +40,8 @@ void showDeleteConfirmationDialog(BuildContext context, VoidCallback onConfirm,
Navigator.of(context).pop(); // Close the first dialog
showProcessingPopup(context, isSpace, onConfirm);
},
style: _dialogButtonStyle(ColorsManager.spaceColor),
child: const Text('Continue', style: TextStyle(color: ColorsManager.whiteColors)),
style: _dialogButtonStyle(Colors.blue),
child: const Text('Continue', style: TextStyle(color: Colors.white)),
),
],
),
@ -83,7 +83,7 @@ void showProcessingPopup(BuildContext context, bool isSpace, VoidCallback onDele
ElevatedButton(
onPressed: onDelete,
style: _dialogButtonStyle(ColorsManager.warningRed),
child: const Text('Delete', style: TextStyle(color: ColorsManager.whiteColors)),
child: const Text('Delete', style: TextStyle(color: Colors.white)),
),
CancelButton(
label: 'Cancel',
@ -108,7 +108,7 @@ Widget _buildWarningIcon() {
color: ColorsManager.warningRed,
shape: BoxShape.circle,
),
child: const Icon(Icons.close, color: ColorsManager.whiteColors, size: 40),
child: const Icon(Icons.close, color: Colors.white, size: 40),
);
}

View File

@ -28,7 +28,7 @@ class IconSelectionDialog extends StatelessWidget {
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.2), // Shadow color
color: Colors.black.withOpacity(0.2), // Shadow color
blurRadius: 20, // Spread of the blur
offset: const Offset(0, 8), // Offset of the shadow
),

View File

@ -1,22 +1,16 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/gradient_canvas_border_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
class LoadedSpaceView extends StatefulWidget {
final List<CommunityModel> communities;
final CommunityModel? selectedCommunity;
final SpaceModel? selectedSpace;
final List<ProductModel>? products;
final List<SpaceTemplateModel>? spaceModels;
const LoadedSpaceView({
super.key,
@ -24,7 +18,6 @@ class LoadedSpaceView extends StatefulWidget {
this.selectedCommunity,
this.selectedSpace,
this.products,
this.spaceModels,
});
@override
@ -34,9 +27,6 @@ class LoadedSpaceView extends StatefulWidget {
class _LoadedStateViewState extends State<LoadedSpaceView> {
@override
Widget build(BuildContext context) {
final bool hasSpaceModels =
widget.spaceModels != null && widget.spaceModels!.isNotEmpty;
return Stack(
clipBehavior: Clip.none,
children: [
@ -48,25 +38,13 @@ class _LoadedStateViewState extends State<LoadedSpaceView> {
widget.selectedCommunity?.uuid ??
'',
),
hasSpaceModels
? Expanded(
child: BlocProvider(
create: (context) => SpaceModelBloc(
api: SpaceModelManagementApi(),
initialSpaceModels: widget.spaceModels ?? [],
),
child: SpaceModelPage(
products: widget.products,
),
),
)
: CommunityStructureArea(
selectedCommunity: widget.selectedCommunity,
selectedSpace: widget.selectedSpace,
spaces: widget.selectedCommunity?.spaces ?? [],
products: widget.products,
communities: widget.communities,
),
CommunityStructureArea(
selectedCommunity: widget.selectedCommunity,
selectedSpace: widget.selectedSpace,
spaces: widget.selectedCommunity?.spaces ?? [],
products: widget.products,
communities: widget.communities,
),
],
),
const GradientCanvasBorderWidget(),

View File

@ -45,7 +45,7 @@ class PlusButtonWidget extends StatelessWidget {
color: ColorsManager.spaceColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.add, color: ColorsManager.whiteColors, size: 20),
child: const Icon(Icons.add, color: Colors.white, size: 20),
),
),
);

View File

@ -8,8 +8,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_m
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_tile.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart';
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart';
@ -120,7 +118,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
children: [
Text('Communities',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: ColorsManager.blackColor,
color: Colors.black,
)),
GestureDetector(
onTap: () => _navigateToBlank(context),
@ -188,8 +186,6 @@ class _SidebarWidgetState extends State<SidebarWidget> {
_selectedSpaceUuid = null; // Update the selected community
});
context.read<CenterBodyBloc>().add(CommunitySelectedEvent());
context.read<SpaceManagementBloc>().add(
SelectCommunityEvent(selectedCommunity: community),
);

View File

@ -78,7 +78,7 @@ class SpaceContainerWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: ColorsManager.lightGrayColor.withOpacity(0.5),
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: const Offset(0, 3), // Shadow position

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class SpaceWidget extends StatelessWidget {
final String name;
@ -24,11 +23,11 @@ class SpaceWidget extends StatelessWidget {
Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: ColorsManager.lightGrayColor.withOpacity(0.5),
color: Colors.grey.withOpacity(0.5),
spreadRadius: 5,
blurRadius: 7,
offset: const Offset(0, 3),
@ -37,7 +36,7 @@ class SpaceWidget extends StatelessWidget {
),
child: Row(
children: [
const Icon(Icons.location_on, color: ColorsManager.spaceColor),
const Icon(Icons.location_on, color: Colors.blue),
const SizedBox(width: 8),
Text(name, style: const TextStyle(fontSize: 16)),
],

View File

@ -1,153 +0,0 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
class AssignTagModelBloc
extends Bloc<AssignTagModelEvent, AssignTagModelState> {
AssignTagModelBloc() : super(AssignTagModelInitial()) {
on<InitializeTagModels>((event, emit) {
final initialTags = event.initialTags ?? [];
final existingTagCounts = <String, int>{};
for (var tag in initialTags) {
if (tag.product != null) {
existingTagCounts[tag.product!.uuid] =
(existingTagCounts[tag.product!.uuid] ?? 0) + 1;
}
}
final allTags = <TagModel>[];
for (var selectedProduct in event.addedProducts) {
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
if (selectedProduct.count == 0 ||
selectedProduct.count <= existingCount) {
allTags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
continue;
}
final missingCount = selectedProduct.count - existingCount;
allTags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (missingCount > 0) {
allTags.addAll(List.generate(
missingCount,
(index) => TagModel(
tag: '',
product: selectedProduct.product,
location: 'None',
),
));
}
}
emit(AssignTagModelLoaded(
tags: allTags,
isSaveEnabled: _validateTags(allTags),
));
});
on<UpdateTag>((event, emit) {
final currentState = state;
if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) {
final tags = List<TagModel>.from(currentState.tags);
tags[event.index].tag = event.tag;
emit(AssignTagModelLoaded(
tags: tags,
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
}
});
on<UpdateLocation>((event, emit) {
final currentState = state;
if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) {
final tags = List<TagModel>.from(currentState.tags);
// Use copyWith for immutability
tags[event.index] =
tags[event.index].copyWith(location: event.location);
emit(AssignTagModelLoaded(
tags: tags,
isSaveEnabled: _validateTags(tags),
));
}
});
on<ValidateTagModels>((event, emit) {
final currentState = state;
if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) {
final tags = List<TagModel>.from(currentState.tags);
emit(AssignTagModelLoaded(
tags: tags,
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
}
});
on<DeleteTagModel>((event, emit) {
final currentState = state;
if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) {
final updatedTags = List<TagModel>.from(currentState.tags)
..remove(event.tagToDelete);
emit(AssignTagModelLoaded(
tags: updatedTags,
isSaveEnabled: _validateTags(updatedTags),
));
} else {
emit(const AssignTagModelLoaded(
tags: [],
isSaveEnabled: false,
));
}
});
}
bool _validateTags(List<TagModel> tags) {
if (tags.isEmpty) {
return false;
}
final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet();
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
return uniqueTags.length == tags.length && !hasEmptyTag;
}
String? _getValidationError(List<TagModel> tags) {
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
if (hasEmptyTag) return 'Tags cannot be empty.';
final duplicateTags = tags
.map((tag) => tag.tag?.trim() ?? '')
.fold<Map<String, int>>({}, (map, tag) {
map[tag] = (map[tag] ?? 0) + 1;
return map;
})
.entries
.where((entry) => entry.value > 1)
.map((entry) => entry.key)
.toList();
if (duplicateTags.isNotEmpty) {
return 'Duplicate tags found: ${duplicateTags.join(', ')}';
}
return null;
}
}

View File

@ -1,55 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
abstract class AssignTagModelEvent extends Equatable {
const AssignTagModelEvent();
@override
List<Object> get props => [];
}
class InitializeTagModels extends AssignTagModelEvent {
final List<TagModel>? initialTags;
final List<SelectedProduct> addedProducts;
const InitializeTagModels({
required this.initialTags,
required this.addedProducts,
});
@override
List<Object> get props => [initialTags ?? [], addedProducts];
}
class UpdateTag extends AssignTagModelEvent {
final int index;
final String tag;
const UpdateTag({required this.index, required this.tag});
@override
List<Object> get props => [index, tag];
}
class UpdateLocation extends AssignTagModelEvent {
final int index;
final String location;
const UpdateLocation({required this.index, required this.location});
@override
List<Object> get props => [index, location];
}
class ValidateTagModels extends AssignTagModelEvent {}
class DeleteTagModel extends AssignTagModelEvent {
final TagModel tagToDelete;
final List<TagModel> tags;
const DeleteTagModel({required this.tagToDelete, required this.tags});
@override
List<Object> get props => [tagToDelete, tags];
}

View File

@ -1,37 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
abstract class AssignTagModelState extends Equatable {
const AssignTagModelState();
@override
List<Object> get props => [];
}
class AssignTagModelInitial extends AssignTagModelState {}
class AssignTagModelLoading extends AssignTagModelState {}
class AssignTagModelLoaded extends AssignTagModelState {
final List<TagModel> tags;
final bool isSaveEnabled;
final String? errorMessage;
const AssignTagModelLoaded({
required this.tags,
required this.isSaveEnabled,
this.errorMessage,
});
@override
List<Object> get props => [tags, isSaveEnabled];
}
class AssignTagModelError extends AssignTagModelState {
final String errorMessage;
const AssignTagModelError(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}

View File

@ -1,364 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class AssignTagModelsDialog extends StatelessWidget {
final List<ProductModel>? products;
final List<SubspaceTemplateModel>? subspaces;
final List<TagModel>? initialTags;
final ValueChanged<List<TagModel>>? onTagsAssigned;
final List<SelectedProduct> addedProducts;
final List<String>? allTags;
final String spaceName;
final String title;
const AssignTagModelsDialog({
Key? key,
required this.products,
required this.subspaces,
required this.addedProducts,
this.initialTags,
this.onTagsAssigned,
this.allTags,
required this.spaceName,
required this.title
}) : super(key: key);
@override
Widget build(BuildContext context) {
final List<String> locations =
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList();
return BlocProvider(
create: (_) => AssignTagModelBloc()
..add(InitializeTagModels(
initialTags: initialTags,
addedProducts: addedProducts,
)),
child: BlocBuilder<AssignTagModelBloc, AssignTagModelState>(
builder: (context, state) {
if (state is AssignTagModelLoaded) {
final controllers = List.generate(
state.tags.length,
(index) => TextEditingController(text: state.tags[index].tag),
);
return AlertDialog(
title: Text(title),
backgroundColor: ColorsManager.whiteColors,
content: SingleChildScrollView(
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: DataTable(
headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey),
border: TableBorder.all(
color: ColorsManager.dataHeaderGrey,
width: 1,
borderRadius: BorderRadius.circular(20),
),
columns: [
DataColumn(
label: Text('#',
style:
Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Device',
style:
Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Tag',
style:
Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Location',
style:
Theme.of(context).textTheme.bodyMedium)),
],
rows: state.tags.isEmpty
? [
const DataRow(cells: [
DataCell(
Center(
child: Text(
'No Data Available',
style: TextStyle(
fontSize: 14,
color: ColorsManager.lightGrayColor,
),
),
),
),
DataCell(SizedBox()),
DataCell(SizedBox()),
DataCell(SizedBox()),
])
]
: List.generate(state.tags.length, (index) {
final tag = state.tags[index];
final controller = controllers[index];
return DataRow(
cells: [
DataCell(Text(index.toString())),
DataCell(
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
tag.product?.name ?? 'Unknown',
overflow: TextOverflow.ellipsis,
)),
IconButton(
icon: const Icon(Icons.close,
color: ColorsManager.warningRed,
size: 16),
onPressed: () {
context
.read<AssignTagModelBloc>()
.add(DeleteTagModel(
tagToDelete: tag,
tags: state.tags));
},
tooltip: 'Delete Tag',
)
],
),
),
DataCell(
Row(
children: [
Expanded(
child: TextFormField(
controller: controller,
onChanged: (value) {
context
.read<AssignTagModelBloc>()
.add(UpdateTag(
index: index,
tag: value.trim(),
));
},
decoration: const InputDecoration(
hintText: 'Enter Tag',
border: InputBorder.none,
),
style: const TextStyle(
fontSize: 14,
color: ColorsManager.blackColor,
),
),
),
SizedBox(
width: MediaQuery.of(context)
.size
.width *
0.15,
child: PopupMenuButton<String>(
color: ColorsManager.whiteColors,
icon: const Icon(
Icons.arrow_drop_down,
color: ColorsManager.blackColor),
onSelected: (value) {
controller.text = value;
context
.read<AssignTagModelBloc>()
.add(UpdateTag(
index: index,
tag: value,
));
},
itemBuilder: (context) {
return (allTags ?? [])
.where((tagValue) => !state
.tags
.map((e) => e.tag)
.contains(tagValue))
.map((tagValue) {
return PopupMenuItem<String>(
textStyle: const TextStyle(
color: ColorsManager
.textPrimaryColor),
value: tagValue,
child: ConstrainedBox(
constraints:
BoxConstraints(
minWidth: MediaQuery.of(
context)
.size
.width *
0.15,
maxWidth: MediaQuery.of(
context)
.size
.width *
0.15,
),
child: Text(
tagValue,
overflow: TextOverflow
.ellipsis,
),
));
}).toList();
},
),
),
],
),
),
DataCell(
DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: tag.location ?? 'None',
dropdownColor: ColorsManager
.whiteColors, // Dropdown background
style: const TextStyle(
color: Colors
.black), // Style for selected text
items: [
const DropdownMenuItem<String>(
value: 'None',
child: Text(
'None',
style: TextStyle(
color: ColorsManager
.textPrimaryColor),
),
),
...locations.map((location) {
return DropdownMenuItem<String>(
value: location,
child: Text(
location,
style: const TextStyle(
color: ColorsManager
.textPrimaryColor),
),
);
}).toList(),
],
onChanged: (value) {
if (value != null) {
context
.read<AssignTagModelBloc>()
.add(UpdateLocation(
index: index,
location: value,
));
}
},
),
),
),
],
);
}),
),
),
if (state.errorMessage != null)
Text(
state.errorMessage!,
style: const TextStyle(color: ColorsManager.warningRed),
),
],
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const SizedBox(width: 10),
Expanded(
child: CancelButton(
label: 'Cancel',
onPressed: () async {
Navigator.of(context).pop();
await showDialog(
barrierDismissible: false,
context: context,
builder: (context) => CreateSpaceModelDialog(
products: products,
allTags: allTags,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
subspaceModels: subspaces,
tags: initialTags),
),
);
},
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
borderRadius: 10,
backgroundColor: state.isSaveEnabled
? ColorsManager.secondaryColor
: ColorsManager.grayColor,
foregroundColor: ColorsManager.whiteColors,
onPressed: state.isSaveEnabled
? () async {
Navigator.of(context).pop();
final assignedTags = <TagModel>{};
for (var tag in state.tags) {
if (tag.location == null ||
subspaces == null) {
continue;
}
for (var subspace in subspaces!) {
if (tag.location == subspace.subspaceName) {
subspace.tags ??= [];
subspace.tags!.add(tag);
assignedTags.add(tag);
break;
}
}
}
state.tags.removeWhere(assignedTags.contains);
await showDialog(
barrierDismissible: false,
context: context,
builder: (context) => CreateSpaceModelDialog(
products: products,
allTags: allTags,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
subspaceModels: subspaces,
tags: state.tags),
),
);
}
: null,
child: const Text('Save'),
),
),
const SizedBox(width: 10),
],
),
],
);
} else if (state is AssignTagModelLoading) {
return const Center(child: CircularProgressIndicator());
} else {
return const Center(child: Text('Something went wrong.'));
}
},
),
);
}
}

View File

@ -1,85 +0,0 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart';
class SubSpaceModelBloc extends Bloc<SubSpaceModelEvent, SubSpaceModelState> {
SubSpaceModelBloc() : super(SubSpaceModelState([], [], '')) {
// Handle AddSubSpaceModel Event
on<AddSubSpaceModel>((event, emit) {
// Check for duplicate names (case-insensitive)
final existingNames =
state.subSpaces.map((e) => e.subspaceName.toLowerCase()).toSet();
if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) {
// Emit state with an error message if duplicate name exists
emit(SubSpaceModelState(
state.subSpaces,
state.updatedSubSpaceModels,
'Subspace name already exists.',
));
} else {
// Add subspace if no duplicate exists
final updatedSubSpaces =
List<SubspaceTemplateModel>.from(state.subSpaces)
..add(event.subSpace);
emit(SubSpaceModelState(
updatedSubSpaces,
state.updatedSubSpaceModels,
'', // Clear error message
));
}
});
// Handle RemoveSubSpaceModel Event
on<RemoveSubSpaceModel>((event, emit) {
final updatedSubSpaces = List<SubspaceTemplateModel>.from(state.subSpaces)
..remove(event.subSpace);
final updatedSubspaceModels = List<UpdateSubspaceTemplateModel>.from(
state.updatedSubSpaceModels,
);
if (event.subSpace.uuid?.isNotEmpty ?? false) {
updatedSubspaceModels.add(UpdateSubspaceTemplateModel(
action: Action.delete,
uuid: event.subSpace.uuid!,
));
}
emit(SubSpaceModelState(
updatedSubSpaces,
updatedSubspaceModels,
'', // Clear error message
));
});
// Handle UpdateSubSpaceModel Event
on<UpdateSubSpaceModel>((event, emit) {
final updatedSubSpaces = state.subSpaces.map((subSpace) {
if (subSpace.uuid == event.updatedSubSpace.uuid) {
return event.updatedSubSpace;
}
return subSpace;
}).toList();
final updatedSubspaceModels = List<UpdateSubspaceTemplateModel>.from(
state.updatedSubSpaceModels,
);
updatedSubspaceModels.add(UpdateSubspaceTemplateModel(
action: Action.update,
uuid: event.updatedSubSpace.uuid!,
));
emit(SubSpaceModelState(
updatedSubSpaces,
updatedSubspaceModels,
'', // Clear error message
));
});
}
}

View File

@ -1,18 +0,0 @@
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
abstract class SubSpaceModelEvent {}
class AddSubSpaceModel extends SubSpaceModelEvent {
final SubspaceTemplateModel subSpace;
AddSubSpaceModel(this.subSpace);
}
class RemoveSubSpaceModel extends SubSpaceModelEvent {
final SubspaceTemplateModel subSpace;
RemoveSubSpaceModel(this.subSpace);
}
class UpdateSubSpaceModel extends SubSpaceModelEvent {
final SubspaceTemplateModel updatedSubSpace;
UpdateSubSpaceModel(this.updatedSubSpace);
}

View File

@ -1,26 +0,0 @@
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
class SubSpaceModelState {
final List<SubspaceTemplateModel> subSpaces;
final List<UpdateSubspaceTemplateModel> updatedSubSpaceModels;
final String errorMessage;
SubSpaceModelState(
this.subSpaces,
this.updatedSubSpaceModels,
this.errorMessage,
);
SubSpaceModelState copyWith({
List<SubspaceTemplateModel>? subSpaces,
List<UpdateSubspaceTemplateModel>? updatedSubSpaceModels,
String? errorMessage,
}) {
return SubSpaceModelState(
subSpaces ?? this.subSpaces,
updatedSubSpaceModels ?? this.updatedSubSpaceModels,
errorMessage ?? this.errorMessage,
);
}
}

View File

@ -1,231 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CreateSubSpaceModelDialog extends StatelessWidget {
final bool isEdit;
final String dialogTitle;
final List<SubspaceTemplateModel>? existingSubSpaces;
final String? spaceName;
final List<TagModel>? spaceTagModels;
final List<String>? allTags;
final List<ProductModel>? products;
final SpaceTemplateModel? spaceModel;
const CreateSubSpaceModelDialog({
Key? key,
required this.isEdit,
required this.dialogTitle,
this.existingSubSpaces,
required this.allTags,
required this.spaceName,
required this.spaceTagModels,
required this.products,
required this.spaceModel,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final textController = TextEditingController();
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: BlocProvider(
create: (_) {
final bloc = SubSpaceModelBloc();
if (existingSubSpaces != null) {
for (var subSpace in existingSubSpaces!) {
bloc.add(AddSubSpaceModel(subSpace));
}
}
return bloc;
},
child: BlocBuilder<SubSpaceModelBloc, SubSpaceModelState>(
builder: (context, state) {
return Container(
color: ColorsManager.whiteColors,
child: SizedBox(
width: screenWidth * 0.35,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
dialogTitle,
style: Theme.of(context)
.textTheme
.headlineLarge
?.copyWith(color: ColorsManager.blackColor),
),
const SizedBox(height: 16),
Container(
width: screenWidth * 0.35,
padding: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 16.0),
decoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10),
),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
...state.subSpaces.map(
(subSpace) => Chip(
label: Text(
subSpace.subspaceName,
style: const TextStyle(
color: ColorsManager.spaceColor),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: const BorderSide(
color: ColorsManager.transparentColor,
width: 0,
),
),
deleteIcon: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.lightGrayColor,
width: 1.5,
),
),
child: const Icon(
Icons.close,
size: 16,
color: ColorsManager.lightGrayColor,
),
),
onDeleted: () => context
.read<SubSpaceModelBloc>()
.add(RemoveSubSpaceModel(subSpace)),
),
),
SizedBox(
width: 200,
child: TextField(
controller: textController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: state.subSpaces.isEmpty
? 'Please enter the name'
: null,
hintStyle: const TextStyle(
color: ColorsManager.lightGrayColor),
),
onSubmitted: (value) {
if (value.trim().isNotEmpty) {
context.read<SubSpaceModelBloc>().add(
AddSubSpaceModel(
SubspaceTemplateModel(
subspaceName: value.trim(),
disabled: false)));
textController.clear();
}
},
style: const TextStyle(
color: ColorsManager.blackColor),
),
),
if (state.errorMessage.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
state.errorMessage,
style: const TextStyle(
color: ColorsManager.warningRed,
fontSize: 12,
),
),
),
],
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: CancelButton(
label: 'Cancel',
onPressed: () async {
Navigator.of(context).pop();
await showDialog(
barrierDismissible: false,
context: context,
builder: (context) =>
CreateSpaceModelDialog(
products: products,
allTags: allTags,
spaceModel: SpaceTemplateModel(
modelName: spaceName ?? '',
subspaceModels: existingSubSpaces,
tags: spaceTagModels,
),
),
);
},
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
onPressed: () async {
final subSpaces = context
.read<SubSpaceModelBloc>()
.state
.subSpaces;
Navigator.of(context).pop();
await showDialog(
barrierDismissible: false,
context: context,
builder: (context) =>
CreateSpaceModelDialog(
products: products,
allTags: allTags,
spaceModel: SpaceTemplateModel(
modelName: spaceName ?? '',
subspaceModels: subSpaces,
tags: spaceTagModels,
),
),
);
},
backgroundColor: ColorsManager.secondaryColor,
borderRadius: 10,
foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'),
),
),
],
),
],
),
),
));
},
),
),
);
}
}

View File

@ -1,107 +0,0 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
class CreateSpaceModelBloc
extends Bloc<CreateSpaceModelEvent, CreateSpaceModelState> {
SpaceTemplateModel? _space;
final SpaceModelManagementApi _api;
CreateSpaceModelBloc(this._api) : super(CreateSpaceModelInitial()) {
on<CreateSpaceTemplate>((event, emit) async {
try {
late SpaceTemplateModel spaceTemplate = event.spaceTemplate;
final tagBodyModels =
spaceTemplate.tags?.map((tag) => tag.toTagBodyModel()).toList() ??
[];
final subspaceTemplateBodyModels =
spaceTemplate.subspaceModels?.map((subspaceModel) {
final tagsubspaceBodyModels = subspaceModel.tags
?.map((tag) => tag.toTagBodyModel())
.toList() ??
[];
return CreateSubspaceTemplateModel()
..subspaceName = subspaceModel.subspaceName
..tags = tagsubspaceBodyModels;
}).toList() ??
[];
final spaceModelBody = CreateSpaceTemplateBodyModel(
modelName: spaceTemplate.modelName,
tags: tagBodyModels,
subspaceModels: subspaceTemplateBodyModels);
final newSpaceTemplate = await _api.createSpaceModel(spaceModelBody);
spaceTemplate.uuid = newSpaceTemplate?.uuid ?? '';
if (newSpaceTemplate != null) {
emit(CreateSpaceModelLoaded(spaceTemplate));
if (event.onCreate != null) {
event.onCreate!(spaceTemplate);
}
}
} catch (e) {
emit(CreateSpaceModelError('Error creating space model'));
}
});
on<LoadSpaceTemplate>((event, emit) {
emit(CreateSpaceModelLoading());
Future.delayed(const Duration(seconds: 1), () {
if (_space != null) {
emit(CreateSpaceModelLoaded(_space!));
} else {
emit(CreateSpaceModelError("No space template found"));
}
});
});
on<UpdateSpaceTemplate>((event, emit) {
_space = event.spaceTemplate;
emit(CreateSpaceModelLoaded(_space!));
});
on<AddSubspacesToSpaceTemplate>((event, emit) {
final currentState = state;
if (currentState is CreateSpaceModelLoaded) {
final updatedSpace = currentState.space.copyWith(
subspaceModels: [
...(_space!.subspaceModels ?? []),
...event.subspaces,
],
);
emit(CreateSpaceModelLoaded(updatedSpace));
} else {
emit(CreateSpaceModelError("Space template not initialized"));
}
});
on<UpdateSpaceTemplateName>((event, emit) {
final currentState = state;
if (currentState is CreateSpaceModelLoaded) {
if (event.name.trim().isEmpty) {
emit(CreateSpaceModelLoaded(
currentState.space,
errorMessage: "Model name cannot be empty",
));
} else {
final updatedSpaceModel =
currentState.space.copyWith(modelName: event.name);
emit(CreateSpaceModelLoaded(updatedSpaceModel));
}
} else {
emit(CreateSpaceModelError("Space template not initialized"));
}
});
}
}

View File

@ -1,52 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
abstract class CreateSpaceModelEvent extends Equatable {
const CreateSpaceModelEvent();
@override
List<Object> get props => [];
}
class LoadSpaceTemplate extends CreateSpaceModelEvent {}
class UpdateSpaceTemplate extends CreateSpaceModelEvent {
final SpaceTemplateModel spaceTemplate;
UpdateSpaceTemplate(this.spaceTemplate);
}
class CreateSpaceTemplate extends CreateSpaceModelEvent {
final SpaceTemplateModel spaceTemplate;
final Function(SpaceTemplateModel)? onCreate;
const CreateSpaceTemplate({
required this.spaceTemplate,
this.onCreate,
});
@override
List<Object> get props => [spaceTemplate];
}
class UpdateSpaceTemplateName extends CreateSpaceModelEvent {
final String name;
UpdateSpaceTemplateName({required this.name});
@override
List<Object> get props => [name];
}
class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent {
final List<SubspaceTemplateModel> subspaces;
AddSubspacesToSpaceTemplate(this.subspaces);
}
class ValidateSpaceTemplateName extends CreateSpaceModelEvent {
final String name;
ValidateSpaceTemplateName({required this.name});
}

View File

@ -1,29 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
abstract class CreateSpaceModelState extends Equatable {
const CreateSpaceModelState();
@override
List<Object?> get props => [];
}
class CreateSpaceModelInitial extends CreateSpaceModelState {}
class CreateSpaceModelLoading extends CreateSpaceModelState {}
class CreateSpaceModelLoaded extends CreateSpaceModelState {
final SpaceTemplateModel space;
final String? errorMessage;
CreateSpaceModelLoaded(this.space, {this.errorMessage});
@override
List<Object?> get props => [space, errorMessage];
}
class CreateSpaceModelError extends CreateSpaceModelState {
final String message;
CreateSpaceModelError(this.message);
}

View File

@ -1,36 +0,0 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
final SpaceModelManagementApi api;
SpaceModelBloc({
required this.api,
required List<SpaceTemplateModel> initialSpaceModels,
}) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) {
on<CreateSpaceModel>(_onCreateSpaceModel);
}
Future<void> _onCreateSpaceModel(
CreateSpaceModel event, Emitter<SpaceModelState> emit) async {
final currentState = state;
if (currentState is SpaceModelLoaded) {
try {
final newSpaceModel =
await api.getSpaceModel(event.newSpaceModel.uuid ?? '');
if (newSpaceModel != null) {
final updatedSpaceModels =
List<SpaceTemplateModel>.from(currentState.spaceModels)
..add(newSpaceModel);
emit(SpaceModelLoaded(spaceModels: updatedSpaceModels));
}
} catch (e) {
emit(SpaceModelError(message: e.toString()));
}
}
}
}

View File

@ -1,18 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
abstract class SpaceModelEvent extends Equatable {
@override
List<Object?> get props => [];
}
class LoadSpaceModels extends SpaceModelEvent {}
class CreateSpaceModel extends SpaceModelEvent {
final SpaceTemplateModel newSpaceModel;
CreateSpaceModel({required this.newSpaceModel});
@override
List<Object?> get props => [newSpaceModel];
}

View File

@ -1,29 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
abstract class SpaceModelState extends Equatable {
@override
List<Object?> get props => [];
}
class SpaceModelInitial extends SpaceModelState {}
class SpaceModelLoading extends SpaceModelState {}
class SpaceModelLoaded extends SpaceModelState {
final List<SpaceTemplateModel> spaceModels;
SpaceModelLoaded({required this.spaceModels});
@override
List<Object?> get props => [spaceModels];
}
class SpaceModelError extends SpaceModelState {
final String message;
SpaceModelError({required this.message});
@override
List<Object?> get props => [message];
}

View File

@ -1,50 +0,0 @@
class TagBodyModel {
late String uuid;
late String tag;
late final String? productUuid;
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'tag': tag,
'productUuid': productUuid,
};
}
@override
String toString() {
return toJson().toString();
}
}
class CreateSubspaceTemplateModel {
late String subspaceName;
late List<TagBodyModel>? tags;
Map<String, dynamic> toJson() {
return {
'subspaceName': subspaceName,
'tags': tags?.map((tag) => tag.toJson()).toList(),
};
}
}
class CreateSpaceTemplateBodyModel {
final String modelName;
final List<dynamic>? tags;
final List<dynamic>? subspaceModels;
CreateSpaceTemplateBodyModel({
required this.modelName,
this.tags,
this.subspaceModels,
});
Map<String, dynamic> toJson() {
return {
'modelName': modelName,
'tags': tags,
'subspaceModels': subspaceModels,
};
}
}

View File

@ -1,167 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart';
import 'package:uuid/uuid.dart';
class SpaceTemplateModel extends Equatable {
String? uuid;
String modelName;
List<SubspaceTemplateModel>? subspaceModels;
final List<TagModel>? tags;
String internalId;
@override
List<Object?> get props => [modelName, subspaceModels];
SpaceTemplateModel({
this.uuid,
String? internalId,
required this.modelName,
this.subspaceModels,
this.tags,
}) : internalId = internalId ?? const Uuid().v4();
factory SpaceTemplateModel.fromJson(Map<String, dynamic> json) {
final String internalId = json['internalId'] ?? const Uuid().v4();
return SpaceTemplateModel(
uuid: json['uuid'] ?? '',
internalId: internalId,
modelName: json['modelName'] ?? '',
subspaceModels: (json['subspaceModels'] as List<dynamic>?)
?.where((e) => e is Map<String, dynamic>) // Validate type
.map((e) =>
SubspaceTemplateModel.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
tags: (json['tags'] as List<dynamic>?)
?.where((item) => item is Map<String, dynamic>) // Validate type
.map((item) => TagModel.fromJson(item as Map<String, dynamic>))
.toList() ??
[],
);
}
SpaceTemplateModel copyWith({
String? uuid,
String? modelName,
List<SubspaceTemplateModel>? subspaceModels,
List<TagModel>? tags,
String? internalId,
}) {
return SpaceTemplateModel(
uuid: uuid ?? this.uuid,
modelName: modelName ?? this.modelName,
subspaceModels: subspaceModels ?? this.subspaceModels,
tags: tags ?? this.tags,
internalId: internalId ?? this.internalId,
);
}
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'modelName': modelName,
'subspaceModels': subspaceModels?.map((e) => e.toJson()).toList(),
'tags': tags?.map((e) => e.toJson()).toList(),
};
}
}
class UpdateSubspaceTemplateModel {
final String uuid;
final Action action;
final String? subspaceName;
final List<UpdateTagModel>? tags;
UpdateSubspaceTemplateModel({
required this.action,
required this.uuid,
this.subspaceName,
this.tags,
});
factory UpdateSubspaceTemplateModel.fromJson(Map<String, dynamic> json) {
return UpdateSubspaceTemplateModel(
action: ActionExtension.fromValue(json['action']),
uuid: json['uuid'] ?? '',
subspaceName: json['subspaceName'] ?? '',
tags: (json['tags'] as List)
.map((item) => UpdateTagModel.fromJson(item))
.toList(),
);
}
Map<String, dynamic> toJson() {
return {
'action': action.value,
'uuid': uuid,
'subspaceName': subspaceName,
'tags': tags?.map((e) => e.toJson()).toList() ?? [],
};
}
}
class UpdateTagModel {
final Action action;
final String? uuid;
final String tag;
final bool disabled;
final ProductModel? product;
UpdateTagModel({
required this.action,
this.uuid,
required this.tag,
required this.disabled,
this.product,
});
factory UpdateTagModel.fromJson(Map<String, dynamic> json) {
return UpdateTagModel(
action: ActionExtension.fromValue(json['action']),
uuid: json['uuid'] ?? '',
tag: json['tag'] ?? '',
disabled: json['disabled'] ?? false,
product: json['product'] != null
? ProductModel.fromMap(json['product'])
: null,
);
}
Map<String, dynamic> toJson() {
return {
'action': action.value,
'uuid': uuid,
'tag': tag,
'disabled': disabled,
'product': product?.toMap(),
};
}
}
extension SpaceTemplateExtensions on SpaceTemplateModel {
List<String> listAllTagValues() {
final List<String> tagValues = [];
if (tags != null) {
tagValues.addAll(
tags!.map((tag) => tag.tag ?? '').where((tag) => tag.isNotEmpty));
}
if (subspaceModels != null) {
for (final subspace in subspaceModels!) {
if (subspace.tags != null) {
tagValues.addAll(
subspace.tags!
.map((tag) => tag.tag ?? '')
.where((tag) => tag.isNotEmpty),
);
}
}
}
return tagValues;
}
}

View File

@ -1,36 +0,0 @@
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
class SubspaceTemplateModel {
final String? uuid;
String subspaceName;
final bool disabled;
List<TagModel>? tags;
SubspaceTemplateModel({
this.uuid,
required this.subspaceName,
required this.disabled,
this.tags,
});
factory SubspaceTemplateModel.fromJson(Map<String, dynamic> json) {
return SubspaceTemplateModel(
uuid: json['uuid'] ?? '',
subspaceName: json['subspaceName'] ?? '',
disabled: json['disabled'] ?? false,
tags: (json['tags'] as List<dynamic>?)
?.map((item) => TagModel.fromJson(item))
.toList() ??
[],
);
}
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'subspaceName': subspaceName,
'disabled': disabled,
'tags': tags?.map((e) => e.toJson()).toList() ?? [],
};
}
}

View File

@ -1,61 +0,0 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
import 'package:uuid/uuid.dart';
class TagModel {
String? uuid;
String? tag;
final ProductModel? product;
String internalId;
String? location;
TagModel(
{this.uuid,
required this.tag,
this.product,
String? internalId,
this.location})
: internalId = internalId ?? const Uuid().v4();
factory TagModel.fromJson(Map<String, dynamic> json) {
final String internalId = json['internalId'] ?? const Uuid().v4();
return TagModel(
uuid: json['uuid'] ?? '',
internalId: internalId,
tag: json['tag'] ?? '',
product: json['product'] != null
? ProductModel.fromMap(json['product'])
: null,
);
}
TagModel copyWith({
String? tag,
ProductModel? product,
String? location,
}) {
return TagModel(
tag: tag ?? this.tag,
product: product ?? this.product,
location: location ?? this.location,
);
}
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'tag': tag,
'product': product?.toMap(),
};
}
}
extension TagModelExtensions on TagModel {
TagBodyModel toTagBodyModel() {
return TagBodyModel()
..uuid = uuid ?? ''
..tag = tag ?? ''
..productUuid = product?.uuid;
}
}

View File

@ -1,117 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/add_space_model_widget.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class SpaceModelPage extends StatelessWidget {
final List<ProductModel>? products;
const SpaceModelPage({Key? key, this.products}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocBuilder<SpaceModelBloc, SpaceModelState>(
builder: (context, state) {
if (state is SpaceModelLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is SpaceModelLoaded) {
final spaceModels = state.spaceModels;
final allTagValues = _getAllTagValues(spaceModels);
return Scaffold(
backgroundColor: ColorsManager.whiteColors,
body: Padding(
padding: const EdgeInsets.fromLTRB(20.0, 16.0, 16.0, 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
childAspectRatio: _calculateChildAspectRatio(context),
),
itemCount: spaceModels.length + 1,
itemBuilder: (context, index) {
if (index == spaceModels.length) {
// Add Button
return GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return CreateSpaceModelDialog(
products: products,
allTags: allTagValues,
onLoad: (newModel) {
context.read<SpaceModelBloc>().add(
CreateSpaceModel(
newSpaceModel: newModel),
);
},
);
},
);
},
child: const AddSpaceModelWidget(),
);
}
// Render existing space model
final model = spaceModels[index];
return Container(
margin: const EdgeInsets.all(8.0),
child: SpaceModelCardWidget(model: model),
);
},
),
),
],
),
),
);
} else if (state is SpaceModelError) {
return Center(
child: Text(
'Error: ${state.message}',
style: const TextStyle(color: ColorsManager.warningRed),
),
);
}
return const Center(child: Text('Initializing...'));
},
);
}
double _calculateChildAspectRatio(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
if (screenWidth > 1600) {
return 2; // Taller cards for larger screens
}
if (screenWidth > 1200) {
return 3; // Adjusted height for medium screens
} else if (screenWidth > 800) {
return 3.5; // Adjusted height for smaller screens
} else {
return 4.0; // Default ratio for smallest screens
}
}
List<String> _getAllTagValues(List<SpaceTemplateModel> spaceModels) {
final List<String> allTags = [];
for (final spaceModel in spaceModels) {
if (spaceModel.tags != null) {
allTags.addAll(spaceModel.listAllTagValues());
}
}
return allTags;
}
}

View File

@ -1,50 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class AddSpaceModelWidget extends StatelessWidget {
const AddSpaceModelWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(20),
boxShadow: const [
BoxShadow(
color: ColorsManager.semiTransparentBlackColor,
blurRadius: 15,
offset: Offset(0, 4),
spreadRadius: 0,
),
BoxShadow(
color: ColorsManager.semiTransparentBlackColor,
blurRadius: 25,
offset: Offset(0, 15),
spreadRadius: -5,
),
],
),
child: Center(
child: Container(
width: 60,
height: 60, // Set a proper height here
decoration: BoxDecoration(
shape: BoxShape.circle,
color: ColorsManager.neutralGray,
border: Border.all(
color: ColorsManager.textFieldGreyColor,
width: 2.0,
),
),
child: const Icon(
Icons.add,
size: 40,
color: ColorsManager.spaceColor,
),
),
),
);
}
}

View File

@ -1,53 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class ButtonContentWidget extends StatelessWidget {
final IconData icon;
final String label;
const ButtonContentWidget({
Key? key,
required this.icon,
required this.label,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return SizedBox(
width: screenWidth * 0.25,
child: Container(
decoration: BoxDecoration(
color: ColorsManager.textFieldGreyColor,
border: Border.all(
color: ColorsManager.neutralGray,
width: 3.0,
),
borderRadius: BorderRadius.circular(20),
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
child: Row(
children: [
Icon(
icon,
color: ColorsManager.spaceColor,
),
const SizedBox(width: 10),
Expanded(
child: Text(
label,
style: const TextStyle(
color: ColorsManager.blackColor,
fontSize: 16,
),
),
),
],
),
),
),
);
}
}

View File

@ -1,188 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CreateSpaceModelDialog extends StatelessWidget {
final List<ProductModel>? products;
final List<String>? allTags;
final SpaceTemplateModel? spaceModel;
final void Function(SpaceTemplateModel newModel)? onLoad;
const CreateSpaceModelDialog({
Key? key,
this.products,
this.allTags,
this.spaceModel,
this.onLoad,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final SpaceModelManagementApi _spaceModelApi = SpaceModelManagementApi();
final screenWidth = MediaQuery.of(context).size.width;
final TextEditingController spaceNameController = TextEditingController(
text: spaceModel?.modelName ?? '',
);
return AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
backgroundColor: ColorsManager.whiteColors,
content: SizedBox(
width: screenWidth * 0.3,
child: BlocProvider(
create: (_) {
final bloc = CreateSpaceModelBloc(_spaceModelApi);
if (spaceModel != null) {
bloc.add(UpdateSpaceTemplate(spaceModel!));
} else {
bloc.add(UpdateSpaceTemplate(SpaceTemplateModel(
modelName: '',
subspaceModels: const [],
)));
}
spaceNameController.addListener(() {
bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text));
});
return bloc;
},
child: BlocBuilder<CreateSpaceModelBloc, CreateSpaceModelState>(
builder: (context, state) {
if (state is CreateSpaceModelLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is CreateSpaceModelLoaded) {
final updatedSpaceModel = state.space;
final subspaces = updatedSpaceModel.subspaceModels ?? [];
final isNameValid = spaceNameController.text.trim().isNotEmpty;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Create New Space Model',
style: Theme.of(context)
.textTheme
.headlineLarge
?.copyWith(color: ColorsManager.blackColor),
),
const SizedBox(height: 16),
SizedBox(
width: screenWidth * 0.25,
child: TextField(
controller: spaceNameController,
onChanged: (value) {
context
.read<CreateSpaceModelBloc>()
.add(UpdateSpaceTemplateName(name: value));
},
style: const TextStyle(color: ColorsManager.blackColor),
decoration: InputDecoration(
filled: true,
fillColor: ColorsManager.textFieldGreyColor,
hintText: 'Please enter the name',
errorText: state.errorMessage,
hintStyle: const TextStyle(
color: ColorsManager.lightGrayColor),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(
vertical: 12.0,
horizontal: 16.0,
),
),
),
),
const SizedBox(height: 16),
SubspaceModelCreate(context,
subspaces: state.space.subspaceModels ?? [],
allTags: allTags,
products: products,
spaceModel: spaceModel,
spaceTagModels: spaceModel?.tags ?? [],
spaceNameController: spaceNameController),
const SizedBox(height: 10),
TagChipDisplay(context,
screenWidth: screenWidth,
spaceModel: updatedSpaceModel,
products: products,
subspaces: subspaces,
allTags: allTags,
spaceNameController: spaceNameController),
const SizedBox(height: 20),
SizedBox(
width: screenWidth * 0.25,
child: Row(
children: [
Expanded(
child: CancelButton(
label: 'Cancel',
onPressed: () => Navigator.of(context).pop(),
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
onPressed: state.errorMessage == null ||
isNameValid
? () {
final updatedSpaceTemplate =
updatedSpaceModel.copyWith(
modelName:
spaceNameController.text.trim(),
);
context.read<CreateSpaceModelBloc>().add(
CreateSpaceTemplate(
spaceTemplate:
updatedSpaceTemplate,
onCreate: (newModel) {
onLoad!(newModel);
Navigator.of(context)
.pop(); // Close the dialog
},
),
);
}
: null,
backgroundColor: ColorsManager.secondaryColor,
borderRadius: 10,
foregroundColor: isNameValid
? ColorsManager.whiteColors
: ColorsManager.whiteColorsWithOpacity,
child: const Text('OK'),
),
),
],
),
),
],
);
} else if (state is CreateSpaceModelError) {
return Text(
'Error: ${state.message}',
style: const TextStyle(color: ColorsManager.warningRed),
);
}
return const Center(child: Text('Initializing...'));
},
),
),
),
);
}
}

View File

@ -1,142 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class SpaceModelCardWidget extends StatelessWidget {
final SpaceTemplateModel model;
const SpaceModelCardWidget({Key? key, required this.model}) : super(key: key);
@override
Widget build(BuildContext context) {
final Map<String, int> productTagCount = {};
if (model.tags != null) {
for (var tag in model.tags!) {
final prodIcon = tag.product?.icon ?? 'Unknown';
productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1;
}
}
if (model.subspaceModels != null) {
for (var subspace in model.subspaceModels!) {
if (subspace.tags != null) {
for (var tag in subspace.tags!) {
final prodIcon = tag.product?.icon ?? 'Unknown';
productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1;
}
}
}
}
return Container(
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: ColorsManager.lightGrayColor.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: const Offset(0, 3),
),
],
),
padding: const EdgeInsets.fromLTRB(16.0, 14.0, 8.0, 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: Text(
model.modelName,
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
color: ColorsManager.blackColor,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(height: 10),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Wrap(
spacing: 3.0,
runSpacing: 3.0,
children: [
for (var subspace in model.subspaceModels!
.take(calculateTakeCount(context)))
SubspaceChipWidget(subspace: subspace.subspaceName),
],
),
),
if (productTagCount.isNotEmpty)
Container(
width: 1,
height: double.infinity,
color: ColorsManager.softGray,
margin: const EdgeInsets.symmetric(horizontal: 4.0),
),
const SizedBox(width: 7),
Expanded(
child: Wrap(
spacing: 4.0,
runSpacing: 4.0,
children: productTagCount.entries.map((entry) {
final prodType = entry.key;
final count = entry.value;
return Chip(
label: Row(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
prodType,
width: 15,
height: 16,
),
const SizedBox(width: 4),
Text(
'x$count', // Product count
style: const TextStyle(fontSize: 12),
),
],
),
backgroundColor: ColorsManager.textFieldGreyColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: const BorderSide(
color: ColorsManager.transparentColor,
width: 0,
),
),
);
}).toList(),
),
),
],
),
),
const SizedBox(height: 5),
],
),
);
}
int calculateTakeCount(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
// Adjust the count based on the screen width
if (screenWidth > 1500) {
return 3; // For large screens
} else if (screenWidth > 1200) {
return 2;
} else {
return 1; // For smaller screens
}
}
}

View File

@ -1,34 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class SubspaceChipWidget extends StatelessWidget {
final String subspace;
const SubspaceChipWidget({
Key? key,
required this.subspace,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Chip(
label: Text(
subspace,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
backgroundColor: ColorsManager.textFieldGreyColor,
labelStyle: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.spaceColor),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: const BorderSide(
color: ColorsManager.transparentColor,
width: 0,
),
),
);
}
}

View File

@ -1,139 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class SubspaceModelCreate extends StatelessWidget {
final List<SubspaceTemplateModel> subspaces;
final TextEditingController spaceNameController;
final List<TagModel>? spaceTagModels;
final List<String>? allTags;
final List<ProductModel>? products;
final SpaceTemplateModel? spaceModel;
const SubspaceModelCreate(
BuildContext context, {
Key? key,
required this.subspaces,
this.spaceTagModels,
required this.allTags,
required this.products,
required this.spaceModel,
required this.spaceNameController,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return Container(
child: subspaces.isEmpty
? TextButton(
style: TextButton.styleFrom(
overlayColor: ColorsManager.transparentColor,
),
onPressed: () async {
Navigator.of(context).pop();
await showDialog<List<SubspaceTemplateModel>>(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return CreateSubSpaceModelDialog(
allTags: allTags,
spaceName: spaceNameController.text,
spaceModel: spaceModel,
spaceTagModels: spaceTagModels,
products: products,
isEdit: true,
dialogTitle: subspaces.isEmpty
? 'Create Sub-space'
: 'Edit Sub-space',
existingSubSpaces: subspaces,
);
},
);
},
child: const ButtonContentWidget(
icon: Icons.add,
label: 'Create Sub Space',
),
)
: SizedBox(
width: screenWidth * 0.25,
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: ColorsManager.textFieldGreyColor,
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: ColorsManager.textFieldGreyColor,
width: 3.0, // Border width
),
),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
...subspaces.map(
(subspace) => Chip(
label: Text(
subspace.subspaceName,
style: const TextStyle(
color: ColorsManager.spaceColor), // Text color
),
backgroundColor: ColorsManager.whiteColors, // Chip background color
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(16), // Rounded chip
side: const BorderSide(
color: ColorsManager.spaceColor), // Border color
),
),
),
GestureDetector(
onTap: () async {
Navigator.of(context).pop();
await showDialog<List<SubspaceTemplateModel>>(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return CreateSubSpaceModelDialog(
isEdit: true,
dialogTitle: 'Edit Sub-space',
existingSubSpaces: subspaces,
allTags: allTags,
spaceName: spaceNameController.text,
spaceTagModels: spaceTagModels,
products: products,
spaceModel: spaceModel,
);
},
);
},
child: Chip(
label: const Text(
'Edit',
style: TextStyle(
color: ColorsManager.spaceColor),
),
backgroundColor:
ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(16),
side: const BorderSide(
color: ColorsManager.spaceColor),
),
),
),
],
),
),
),
);
}
}

View File

@ -1,192 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class TagChipDisplay extends StatelessWidget {
final double screenWidth;
final SpaceTemplateModel? spaceModel;
final List<ProductModel>? products;
final List<SubspaceTemplateModel>? subspaces;
final List<String>? allTags;
final TextEditingController spaceNameController;
const TagChipDisplay(
BuildContext context, {
Key? key,
required this.screenWidth,
required this.spaceModel,
required this.products,
required this.subspaces,
required this.allTags,
required this.spaceNameController,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return (spaceModel?.tags?.isNotEmpty == true ||
spaceModel?.subspaceModels
?.any((subspace) => subspace.tags?.isNotEmpty == true) ==
true)
? SizedBox(
width: screenWidth * 0.25,
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: ColorsManager.textFieldGreyColor,
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: ColorsManager.textFieldGreyColor,
width: 3.0, // Border width
),
),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
// Combine tags from spaceModel and subspaces
..._groupTags([
...?spaceModel?.tags,
...?spaceModel?.subspaceModels
?.expand((subspace) => subspace.tags ?? [])
]).entries.map(
(entry) => Chip(
avatar: SizedBox(
width: 24,
height: 24,
child: SvgPicture.asset(
entry.key.icon ?? 'assets/icons/gateway.svg',
fit: BoxFit.contain,
),
),
label: Text(
'x${entry.value}', // Show count
style: const TextStyle(
color: ColorsManager.spaceColor,
),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(
color: ColorsManager.spaceColor,
),
),
),
),
GestureDetector(
onTap: () async {
Navigator.of(context).pop();
await showDialog<bool>(
barrierDismissible: false,
context: context,
builder: (context) => AddDeviceTypeModelWidget(
products: products,
subspaces: subspaces,
allTags: allTags,
spaceName: spaceNameController.text,
spaceTagModels: spaceModel?.tags,
initialSelectedProducts:
_createInitialSelectedProducts(
spaceModel?.tags, spaceModel?.subspaceModels),
),
);
// Edit action
},
child: Chip(
label: const Text(
'Edit',
style: TextStyle(
color: ColorsManager.spaceColor),
),
backgroundColor:
ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(
color: ColorsManager.spaceColor),
),
),
),
],
),
),
)
: TextButton(
onPressed: () async {
Navigator.of(context).pop();
final result = await showDialog<bool>(
barrierDismissible: false,
context: context,
builder: (context) => AddDeviceTypeModelWidget(
products: products,
subspaces: subspaces,
allTags: allTags,
spaceName: spaceNameController.text,
),
);
if (result == true) {}
},
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
),
child: const ButtonContentWidget(
icon: Icons.add,
label: 'Add Devices',
),
);
}
Map<ProductModel, int> _groupTags(List<TagModel> tags) {
final Map<ProductModel, int> groupedTags = {};
for (var tag in tags) {
if (tag.product != null) {
groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1;
}
}
return groupedTags;
}
List<SelectedProduct> _createInitialSelectedProducts(
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces) {
final Map<ProductModel, int> productCounts = {};
if (tags != null) {
for (var tag in tags) {
if (tag.product != null) {
productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1;
}
}
}
if (subspaces != null) {
for (var subspace in subspaces) {
if (subspace.tags != null) {
for (var tag in subspace.tags!) {
if (tag.product != null) {
productCounts[tag.product!] =
(productCounts[tag.product!] ?? 0) + 1;
}
}
}
}
}
return productCounts.entries
.map((entry) => SelectedProduct(
productId: entry.key.uuid,
count: entry.value,
productName: entry.key.name ?? 'Unnamed',
product: entry.key,
))
.toList();
}
}

View File

@ -1,20 +0,0 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart';
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_state.dart';
// Bloc Implementation
class CenterBodyBloc extends Bloc<CenterBodyEvent, CenterBodyState> {
CenterBodyBloc() : super(InitialState()) {
on<CommunityStructureSelectedEvent>((event, emit) {
emit(CommunityStructureState());
});
on<SpaceModelSelectedEvent>((event, emit) {
emit(SpaceModelState());
});
on<CommunitySelectedEvent>((event, emit) {
emit(CommunitySelectedState());
});
}
}

View File

@ -1,8 +0,0 @@
// Define Events
abstract class CenterBodyEvent {}
class CommunityStructureSelectedEvent extends CenterBodyEvent {}
class SpaceModelSelectedEvent extends CenterBodyEvent {}
class CommunitySelectedEvent extends CenterBodyEvent {}

View File

@ -1,9 +0,0 @@
abstract class CenterBodyState {}
class InitialState extends CenterBodyState {}
class CommunityStructureState extends CenterBodyState {}
class SpaceModelState extends CenterBodyState {}
class CommunitySelectedState extends CenterBodyState {}

View File

@ -1,81 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart';
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_state.dart';
import '../bloc/center_body_bloc.dart';
class CenterBodyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<CenterBodyBloc, CenterBodyState>(
builder: (context, state) {
if (state is InitialState) {
context.read<CenterBodyBloc>().add(CommunityStructureSelectedEvent());
}
if (state is CommunityStructureState) {
context.read<SpaceManagementBloc>().add(BlankStateEvent());
}
if (state is SpaceModelState) {
context.read<SpaceManagementBloc>().add(SpaceModelLoadEvent());
}
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () {
context.read<CenterBodyBloc>().add(CommunityStructureSelectedEvent());
},
child: Text(
'Community Structure',
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontWeight: state is CommunityStructureState || state is CommunitySelectedState
? FontWeight.bold
: FontWeight.normal,
color: state is CommunityStructureState || state is CommunitySelectedState
? Theme.of(context).textTheme.bodyLarge!.color
: Theme.of(context)
.textTheme
.bodyLarge!
.color!
.withOpacity(0.5),
),
),
),
const SizedBox(width: 20),
GestureDetector(
onTap: () {
context.read<CenterBodyBloc>().add(SpaceModelSelectedEvent());
},
child: Text(
'Space Model',
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontWeight: state is SpaceModelState
? FontWeight.bold
: FontWeight.normal,
color: state is SpaceModelState
? Theme.of(context).textTheme.bodyLarge!.color
: Theme.of(context)
.textTheme
.bodyLarge!
.color!
.withOpacity(0.5),
),
),
),
],
),
],
),
);
},
);
}
}

View File

@ -1,38 +0,0 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart';
class AddDeviceTypeModelBloc
extends Bloc<AddDeviceTypeModelEvent, List<SelectedProduct>> {
AddDeviceTypeModelBloc(List<SelectedProduct> initialProducts)
: super(initialProducts) {
on<UpdateProductCountEvent>(_onUpdateProductCount);
}
void _onUpdateProductCount(
UpdateProductCountEvent event, Emitter<List<SelectedProduct>> emit) {
final existingProduct = state.firstWhere(
(p) => p.productId == event.productId,
orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName,product: event.product ),
);
if (event.count > 0) {
if (!state.contains(existingProduct)) {
emit([
...state,
SelectedProduct(productId: event.productId, count: event.count, productName: event.productName, product: event.product)
]);
} else {
final updatedList = state.map((p) {
if (p.productId == event.productId) {
return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName,product: p.product);
}
return p;
}).toList();
emit(updatedList);
}
} else {
emit(state.where((p) => p.productId != event.productId).toList());
}
}
}

View File

@ -1,19 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
abstract class AddDeviceTypeModelEvent extends Equatable {
@override
List<Object> get props => [];
}
class UpdateProductCountEvent extends AddDeviceTypeModelEvent {
final String productId;
final int count;
final String productName;
final ProductModel product;
UpdateProductCountEvent({required this.productId, required this.count, required this.productName, required this.product});
@override
List<Object> get props => [productId, count];
}

View File

@ -1,156 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class AddDeviceTypeModelWidget extends StatelessWidget {
final List<ProductModel>? products;
final ValueChanged<List<SelectedProduct>>? onProductsSelected;
final List<SelectedProduct>? initialSelectedProducts;
final List<SubspaceTemplateModel>? subspaces;
final List<TagModel>? spaceTagModels;
final List<String>? allTags;
final String spaceName;
const AddDeviceTypeModelWidget(
{super.key,
this.products,
this.initialSelectedProducts,
this.onProductsSelected,
this.subspaces,
this.allTags,
this.spaceTagModels,
required this.spaceName});
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final crossAxisCount = size.width > 1200
? 8
: size.width > 800
? 5
: 3;
return BlocProvider(
create: (_) => AddDeviceTypeModelBloc(initialSelectedProducts ?? []),
child: Builder(
builder: (context) => AlertDialog(
title: const Text('Add Devices'),
backgroundColor: ColorsManager.whiteColors,
content: SingleChildScrollView(
child: Container(
width: size.width * 0.9,
height: size.height * 0.65,
color: ColorsManager.textFieldGreyColor,
child: Column(
children: [
const SizedBox(height: 16),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: ScrollableGridViewWidget(
products: products, crossAxisCount: crossAxisCount),
),
),
],
),
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CancelButton(
label: 'Cancel',
onPressed: () async {
Navigator.of(context).pop();
await showDialog(
barrierDismissible: false,
context: context,
builder: (context) => CreateSpaceModelDialog(
products: products,
allTags: allTags,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
subspaceModels: subspaces,
tags: spaceTagModels,
)),
);
},
),
ActionButton(
label: 'Continue',
backgroundColor: ColorsManager.secondaryColor,
foregroundColor: ColorsManager.whiteColors,
onPressed: () async {
final currentState =
context.read<AddDeviceTypeModelBloc>().state;
Navigator.of(context).pop();
if (currentState.isNotEmpty) {
final initialTags = generateInitialTags(
spaceTagModels: spaceTagModels,
subspaces: subspaces,
);
final dialogTitle = initialTags.isNotEmpty
? 'Edit Device'
: 'Assign Tags';
await showDialog<bool>(
barrierDismissible: false,
context: context,
builder: (context) => AssignTagModelsDialog(
products: products,
subspaces: subspaces,
addedProducts: currentState,
allTags: allTags,
spaceName: spaceName,
initialTags: initialTags,
title: dialogTitle,
),
);
}
},
),
],
),
],
),
));
}
List<TagModel> generateInitialTags({
List<TagModel>? spaceTagModels,
List<SubspaceTemplateModel>? subspaces,
}) {
final List<TagModel> initialTags = [];
if (spaceTagModels != null) {
initialTags.addAll(spaceTagModels);
}
if (subspaces != null) {
for (var subspace in subspaces) {
if (subspace.tags != null) {
initialTags.addAll(
subspace.tags!.map(
(tag) => tag.copyWith(location: subspace.subspaceName),
),
);
}
}
}
return initialTags;
}
}

View File

@ -1,30 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
class ActionButton extends StatelessWidget {
final String label;
final Color backgroundColor;
final Color foregroundColor;
final VoidCallback onPressed;
const ActionButton({
super.key,
required this.label,
required this.backgroundColor,
required this.foregroundColor,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 120,
child: DefaultButton(
onPressed: onPressed,
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
child: Text(label),
),
);
}
}

View File

@ -1,36 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class DeviceIconWidget extends StatelessWidget {
final String? icon;
const DeviceIconWidget({
super.key,
required this.icon,
});
@override
Widget build(BuildContext context) {
return Container(
height: 50,
width: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: ColorsManager.textFieldGreyColor,
border: Border.all(
color: ColorsManager.neutralGray,
width: 2,
),
),
child: Center(
child: SvgPicture.asset(
icon ?? Assets.sensors,
width: 30,
height: 30,
),
),
);
}
}

View File

@ -1,28 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DeviceNameWidget extends StatelessWidget {
final String? name;
const DeviceNameWidget({
super.key,
required this.name,
});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 35,
child: Text(
name ?? '',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.blackColor),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
);
}
}

View File

@ -1,67 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_icon_widget.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_name_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class DeviceTypeTileWidget extends StatelessWidget {
final ProductModel product;
final List<SelectedProduct> productCounts;
const DeviceTypeTileWidget({
super.key,
required this.product,
required this.productCounts,
});
@override
Widget build(BuildContext context) {
final selectedProduct = productCounts.firstWhere(
(p) => p.productId == product.uuid,
orElse: () => SelectedProduct(
productId: product.uuid,
count: 0,
productName: product.catName,
product: product),
);
return Card(
elevation: 2,
color: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
DeviceIconWidget(icon: product.icon ?? Assets.doorSensor),
const SizedBox(height: 4),
DeviceNameWidget(name: product.name),
const SizedBox(height: 4),
CounterWidget(
initialCount: selectedProduct.count,
onCountChanged: (newCount) {
context.read<AddDeviceTypeModelBloc>().add(
UpdateProductCountEvent(
productId: product.uuid,
count: newCount,
productName: product.catName,
product: product),
);
},
),
],
),
),
);
}
}

View File

@ -1,72 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart';
class ScrollableGridViewWidget extends StatelessWidget {
final List<ProductModel>? products;
final int crossAxisCount;
final List<SelectedProduct>? initialProductCounts;
const ScrollableGridViewWidget({
super.key,
required this.products,
required this.crossAxisCount,
this.initialProductCounts,
});
@override
Widget build(BuildContext context) {
final scrollController = ScrollController();
return Scrollbar(
controller: scrollController,
thumbVisibility: true,
child: BlocBuilder<AddDeviceTypeModelBloc, List<SelectedProduct>>(
builder: (context, productCounts) {
return GridView.builder(
controller: scrollController,
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
mainAxisSpacing: 6,
crossAxisSpacing: 4,
childAspectRatio: .8,
),
itemCount: products?.length ?? 0,
itemBuilder: (context, index) {
final product = products![index];
final initialProductCount = _findInitialProductCount(product);
return DeviceTypeTileWidget(
product: product,
productCounts: initialProductCount != null
? [...productCounts, initialProductCount]
: productCounts,
);
},
);
},
),
);
}
SelectedProduct? _findInitialProductCount(ProductModel product) {
// Check if the product exists in initialProductCounts
if (initialProductCounts == null) return null;
final matchingProduct = initialProductCounts!.firstWhere(
(selectedProduct) => selectedProduct.productId == product.uuid,
orElse: () => SelectedProduct(
productId: '',
count: 0,
productName: '',
product: null,
),
);
// Check if the product was actually found
return matchingProduct.productId.isNotEmpty ? matchingProduct : null;
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart';
import 'package:syncrow_web/services/api/http_service.dart';
@ -166,6 +167,7 @@ class CommunitySpaceManagementApi {
bool isPrivate = false,
required Offset position,
String? icon,
required List<SelectedProduct> products,
}) async {
try {
final body = {
@ -175,6 +177,7 @@ class CommunitySpaceManagementApi {
'y': position.dy,
'direction': direction,
'icon': icon,
'products': products.map((product) => product.toJson()).toList(),
};
if (parentId != null) {
body['parentUuid'] = parentId;
@ -204,6 +207,7 @@ class CommunitySpaceManagementApi {
String? direction,
bool isPrivate = false,
required Offset position,
required List<SelectedProduct> products,
}) async {
try {
final body = {
@ -213,6 +217,7 @@ class CommunitySpaceManagementApi {
'y': position.dy,
'direction': direction,
'icon': icon,
'products': products.map((product) => product.toJson()).toList(),
};
if (parentId != null) {
body['parentUuid'] = parentId;

View File

@ -1,77 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
class SpaceModelManagementApi {
Future<List<SpaceTemplateModel>> listSpaceModels({int page = 1}) async {
try {
List<SpaceTemplateModel> spaceModels = [];
bool hasNext = true;
while (hasNext) {
await HTTPService().get(
path: ApiEndpoints.listSpaceModels
.replaceAll('{projectId}', TempConst.projectId),
queryParameters: {'page': page},
expectedResponseModel: (json) {
List<dynamic> jsonData = json['data'];
hasNext = json['hasNext'] ?? false;
int currentPage = json['page'] ?? 1;
List<SpaceTemplateModel> spaceModelList = jsonData.map((jsonItem) {
return SpaceTemplateModel.fromJson(jsonItem);
}).toList();
spaceModels.addAll(spaceModelList);
page = currentPage + 1;
return spaceModelList;
},
);
}
return spaceModels;
} catch (e) {
debugPrint('Error fetching space models: $e');
return [];
}
}
Future<SpaceTemplateModel?> createSpaceModel(
CreateSpaceTemplateBodyModel spaceModel) async {
try {
final response = await HTTPService().post(
path: ApiEndpoints.createSpaceModel
.replaceAll('{projectId}', TempConst.projectId),
showServerMessage: true,
body: spaceModel.toJson(),
expectedResponseModel: (json) {
return SpaceTemplateModel.fromJson(json['data']);
},
);
return response;
} catch (e) {
debugPrint('Error creating space model: $e');
return null;
}
}
Future<SpaceTemplateModel?> getSpaceModel(String spaceModelUuid) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.getSpaceModel
.replaceAll('{projectId}', TempConst.projectId)
.replaceAll('{spaceModelUuid}', spaceModelUuid),
showServerMessage: true,
expectedResponseModel: (json) {
debugPrint('Response JSON: $json');
return SpaceTemplateModel.fromJson(json['data']);
},
);
return response;
} catch (e) {
debugPrint('Error getting space model: $e');
return null;
}
}
}

View File

@ -71,8 +71,8 @@ class UserPermissionApi {
"firstName": firstName,
"lastName": lastName,
"email": email,
"jobTitle": jobTitle != '' ? jobTitle : " ",
"phoneNumber": phoneNumber != '' ? phoneNumber : " ",
"jobTitle": jobTitle != '' ? jobTitle : null,
"phoneNumber": phoneNumber != '' ? phoneNumber : null,
"roleUuid": roleUuid,
"projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c",
"spaceUuids": spaceUuids,
@ -119,13 +119,8 @@ class UserPermissionApi {
);
return response ?? 'Unknown error occurred';
} on DioException catch (e) {
if (e.response != null) {
final errorMessage = e.response?.data['error'];
return errorMessage is String
? errorMessage
: 'Error occurred while checking email';
}
return 'Error occurred while checking email';
final errorMessage = e.response?.data['error'];
return errorMessage;
} catch (e) {
return e.toString();
}

View File

@ -8,8 +8,6 @@ abstract class ColorsManager {
static Color primaryColorWithOpacity =
const Color(0xFF023DFE).withOpacity(0.6);
static const Color whiteColors = Colors.white;
static Color whiteColorsWithOpacity = Colors.white.withOpacity(0.6);
static const Color secondaryColor = Color(0xFF023DFE);
static const Color onSecondaryColor = Color(0xFF023DFE);
static Color shadowBlackColor = Colors.black.withOpacity(0.2);
@ -56,10 +54,6 @@ abstract class ColorsManager {
static const Color neutralGray = Color(0xFFE5E5E5);
static const Color warningRed = Color(0xFFFF6465);
static const Color borderColor = Color(0xFFE5E5E5);
static const Color CircleImageBackground = Color(0xFFF4F4F4);
static const Color softGray = Color(0xFFD5D5D5);
static const Color semiTransparentBlack = Color(0x19000000);
static const Color dataHeaderGrey = Color(0x33999999);
static const Color circleImageBackground = Color(0xFFF4F4F4);
static const Color circleRolesBackground = Color(0xFFF8F8F8);
static const Color activeGreen = Color(0xFF99FF93);

View File

@ -1,31 +0,0 @@
enum Action {
update,
add,
delete,
}
extension ActionExtension on Action {
String get value {
switch (this) {
case Action.update:
return 'update';
case Action.add:
return 'add';
case Action.delete:
return 'delete';
}
}
static Action fromValue(String value) {
switch (value) {
case 'update':
return Action.update;
case 'add':
return Action.add;
case 'delete':
return Action.delete;
default:
throw ArgumentError('Invalid action: $value');
}
}
}

View File

@ -97,12 +97,6 @@ abstract class ApiEndpoints {
static const String updateScene = '/scene/tap-to-run/{sceneId}';
static const String updateAutomation = '/automation/{automationId}';
//space model
static const String listSpaceModels = '/projects/{projectId}/space-models';
static const String createSpaceModel = '/projects/{projectId}/space-models';
static const String getSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}';
static const String roleTypes = '/role/types';
static const String permission = '/permission/{roleUuid}';
static const String inviteUser = '/invite-user';