mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-11 15:47:44 +00:00
Compare commits
26 Commits
side_tree
...
bugfix/fix
Author | SHA1 | Date | |
---|---|---|---|
af4c0f84cb | |||
c2b77ad1fc | |||
d5fcbe2601 | |||
1fa33a271f | |||
09e2564183 | |||
5dee6c2842 | |||
c5c5088724 | |||
d1d570b40f | |||
a43ff3c07d | |||
5e5f127a4b | |||
6f51c2d2b6 | |||
a18e8443d0 | |||
72241cba6c | |||
ab3edbaf57 | |||
64e3fb7f34 | |||
e6e46be9b4 | |||
91dfd53477 | |||
5ab9664318 | |||
d3bf4de0ca | |||
e6fa9c2391 | |||
916b606cb1 | |||
29c444eede | |||
9dd6c9e1e7 | |||
b070884bd9 | |||
bf5b39e742 | |||
7d05a33c52 |
@ -31,7 +31,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
|
|
||||||
////////////////////////////// forget password //////////////////////////////////
|
////////////////////////////// forget password //////////////////////////////////
|
||||||
final TextEditingController forgetEmailController = TextEditingController();
|
final TextEditingController forgetEmailController = TextEditingController();
|
||||||
final TextEditingController forgetPasswordController = TextEditingController();
|
final TextEditingController forgetPasswordController =
|
||||||
|
TextEditingController();
|
||||||
final TextEditingController forgetOtp = TextEditingController();
|
final TextEditingController forgetOtp = TextEditingController();
|
||||||
final forgetFormKey = GlobalKey<FormState>();
|
final forgetFormKey = GlobalKey<FormState>();
|
||||||
final forgetEmailKey = GlobalKey<FormState>();
|
final forgetEmailKey = GlobalKey<FormState>();
|
||||||
@ -48,7 +49,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_remainingTime = 1;
|
_remainingTime = 1;
|
||||||
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
|
add(UpdateTimerEvent(
|
||||||
|
remainingTime: _remainingTime, isButtonEnabled: false));
|
||||||
try {
|
try {
|
||||||
forgetEmailValidate = '';
|
forgetEmailValidate = '';
|
||||||
_remainingTime = (await AuthenticationAPI.sendOtp(
|
_remainingTime = (await AuthenticationAPI.sendOtp(
|
||||||
@ -85,7 +87,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
|
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
|
||||||
} else {
|
} else {
|
||||||
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
|
add(UpdateTimerEvent(
|
||||||
|
remainingTime: _remainingTime, isButtonEnabled: false));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -95,7 +98,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> changePassword(ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
Future<void> changePassword(
|
||||||
|
ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
||||||
emit(LoadingForgetState());
|
emit(LoadingForgetState());
|
||||||
try {
|
try {
|
||||||
var response = await AuthenticationAPI.verifyOtp(
|
var response = await AuthenticationAPI.verifyOtp(
|
||||||
@ -111,7 +115,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
}
|
}
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
final errorData = e.response!.data;
|
final errorData = e.response!.data;
|
||||||
String errorMessage = errorData['error']['message'] ?? 'something went wrong';
|
String errorMessage =
|
||||||
|
errorData['error']['message'] ?? 'something went wrong';
|
||||||
validate = errorMessage;
|
validate = errorMessage;
|
||||||
emit(AuthInitialState());
|
emit(AuthInitialState());
|
||||||
}
|
}
|
||||||
@ -125,7 +130,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
|
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
|
||||||
emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime));
|
emit(TimerState(
|
||||||
|
isButtonEnabled: event.isButtonEnabled,
|
||||||
|
remainingTime: event.remainingTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////// login /////////////////////////////////////
|
///////////////////////////////////// login /////////////////////////////////////
|
||||||
@ -161,15 +168,23 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
password: event.password,
|
password: event.password,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (failure) {
|
} on DioException catch (e) {
|
||||||
validate = 'Invalid Credentials!';
|
final errorData = e.response!.data;
|
||||||
|
String errorMessage = errorData['error']['message'];
|
||||||
|
if (errorMessage == "Access denied for web platform") {
|
||||||
|
validate = errorMessage;
|
||||||
|
} else {
|
||||||
|
validate = 'Invalid Credentials!';
|
||||||
|
}
|
||||||
emit(LoginInitial());
|
emit(LoginInitial());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (token.accessTokenIsNotEmpty) {
|
if (token.accessTokenIsNotEmpty) {
|
||||||
FlutterSecureStorage storage = const FlutterSecureStorage();
|
FlutterSecureStorage storage = const FlutterSecureStorage();
|
||||||
await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken);
|
await storage.write(
|
||||||
|
key: Token.loginAccessTokenKey, value: token.accessToken);
|
||||||
const FlutterSecureStorage().write(
|
const FlutterSecureStorage().write(
|
||||||
key: UserModel.userUuidKey,
|
key: UserModel.userUuidKey,
|
||||||
value: Token.decodeToken(token.accessToken)['uuid'].toString());
|
value: Token.decodeToken(token.accessToken)['uuid'].toString());
|
||||||
@ -327,12 +342,14 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
static Future<String> getTokenAndValidate() async {
|
static Future<String> getTokenAndValidate() async {
|
||||||
try {
|
try {
|
||||||
const storage = FlutterSecureStorage();
|
const storage = FlutterSecureStorage();
|
||||||
final firstLaunch =
|
final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(
|
||||||
await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
|
StringsManager.firstLaunch) ??
|
||||||
|
true;
|
||||||
if (firstLaunch) {
|
if (firstLaunch) {
|
||||||
storage.deleteAll();
|
storage.deleteAll();
|
||||||
}
|
}
|
||||||
await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false);
|
await SharedPreferencesHelper.saveBoolToSP(
|
||||||
|
StringsManager.firstLaunch, false);
|
||||||
final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
|
final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
|
||||||
if (value.isEmpty) {
|
if (value.isEmpty) {
|
||||||
return 'Token not found';
|
return 'Token not found';
|
||||||
@ -385,7 +402,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
final String formattedTime = [
|
final String formattedTime = [
|
||||||
if (days > 0) '${days}d', // Append 'd' for days
|
if (days > 0) '${days}d', // Append 'd' for days
|
||||||
if (days > 0 || hours > 0)
|
if (days > 0 || hours > 0)
|
||||||
hours.toString().padLeft(2, '0'), // Show hours if there are days or hours
|
hours
|
||||||
|
.toString()
|
||||||
|
.padLeft(2, '0'), // Show hours if there are days or hours
|
||||||
minutes.toString().padLeft(2, '0'),
|
minutes.toString().padLeft(2, '0'),
|
||||||
seconds.toString().padLeft(2, '0'),
|
seconds.toString().padLeft(2, '0'),
|
||||||
].join(':');
|
].join(':');
|
||||||
|
@ -21,7 +21,9 @@ class LoginWithEmailModel {
|
|||||||
return {
|
return {
|
||||||
'email': email,
|
'email': email,
|
||||||
'password': password,
|
'password': password,
|
||||||
|
"platform": "web"
|
||||||
// 'regionUuid': regionUuid,
|
// 'regionUuid': regionUuid,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//tst@tst.com
|
@ -69,7 +69,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
|||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 4,
|
flex: 3,
|
||||||
child: state is DeviceManagementLoading
|
child: state is DeviceManagementLoading
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: Column(
|
: Column(
|
||||||
|
@ -62,6 +62,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
|||||||
emit(LoadingHome());
|
emit(LoadingHome());
|
||||||
terms = await HomeApi().fetchTerms();
|
terms = await HomeApi().fetchTerms();
|
||||||
add(FetchPolicyEvent());
|
add(FetchPolicyEvent());
|
||||||
|
emit(PolicyAgreement());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -71,6 +72,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
|||||||
try {
|
try {
|
||||||
emit(LoadingHome());
|
emit(LoadingHome());
|
||||||
policy = await HomeApi().fetchPolicy();
|
policy = await HomeApi().fetchPolicy();
|
||||||
|
emit(PolicyAgreement());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ class _AgreementAndPrivacyDialogState extends State<AgreementAndPrivacyDialog> {
|
|||||||
final scrollPosition = _scrollController.position;
|
final scrollPosition = _scrollController.position;
|
||||||
if (scrollPosition.maxScrollExtent <= 0) {
|
if (scrollPosition.maxScrollExtent <= 0) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isAtEnd = true;
|
_isAtEnd = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,9 +63,11 @@ class _AgreementAndPrivacyDialogState extends State<AgreementAndPrivacyDialog> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String get _dialogTitle =>
|
String get _dialogTitle =>
|
||||||
_currentPage == 2 ? 'User Agreement' : 'Privacy Policy';
|
_currentPage == 1 ? 'User Agreement' : 'Privacy Policy';
|
||||||
|
|
||||||
String get _dialogContent => _currentPage == 2 ? widget.terms : widget.policy;
|
String get _dialogContent => _currentPage == 1 ? widget.terms : widget.policy;
|
||||||
|
final String staticText =
|
||||||
|
'<h5 style="color: #FF5722;">If you cancel you will be logged out.</h5>';
|
||||||
|
|
||||||
Widget _buildScrollableContent() {
|
Widget _buildScrollableContent() {
|
||||||
return Container(
|
return Container(
|
||||||
@ -85,7 +87,7 @@ class _AgreementAndPrivacyDialogState extends State<AgreementAndPrivacyDialog> {
|
|||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
padding: const EdgeInsets.all(25),
|
padding: const EdgeInsets.all(25),
|
||||||
child: Html(
|
child: Html(
|
||||||
data: _dialogContent,
|
data: "$_dialogContent $staticText",
|
||||||
onLinkTap: (url, attributes, element) async {
|
onLinkTap: (url, attributes, element) async {
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
final uri = Uri.parse(url);
|
final uri = Uri.parse(url);
|
||||||
|
@ -24,7 +24,7 @@ class HomeWebPage extends StatelessWidget {
|
|||||||
listener: (BuildContext context, state) {
|
listener: (BuildContext context, state) {
|
||||||
if (state is HomeInitial) {
|
if (state is HomeInitial) {
|
||||||
if (homeBloc.user!.hasAcceptedWebAgreement == false) {
|
if (homeBloc.user!.hasAcceptedWebAgreement == false) {
|
||||||
Future.delayed(const Duration(seconds: 1), () {
|
Future.delayed(const Duration(seconds: 2), () {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
|
@ -42,7 +42,9 @@ class RolesUserModel {
|
|||||||
invitedBy:
|
invitedBy:
|
||||||
json['invitedBy'].toString().toLowerCase().replaceAll("_", " "),
|
json['invitedBy'].toString().toLowerCase().replaceAll("_", " "),
|
||||||
phoneNumber: json['phoneNumber'],
|
phoneNumber: json['phoneNumber'],
|
||||||
jobTitle: json['jobTitle'] ?? "-",
|
jobTitle: json['jobTitle'] == null || json['jobTitle'] == " "
|
||||||
|
? "_"
|
||||||
|
: json['jobTitle'],
|
||||||
createdDate: json['createdDate'],
|
createdDate: json['createdDate'],
|
||||||
createdTime: json['createdTime'],
|
createdTime: json['createdTime'],
|
||||||
);
|
);
|
||||||
|
@ -79,13 +79,14 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
|||||||
|
|
||||||
List<TreeNode> updatedCommunities = [];
|
List<TreeNode> updatedCommunities = [];
|
||||||
List<TreeNode> spacesNodes = [];
|
List<TreeNode> spacesNodes = [];
|
||||||
|
List<String> communityIds = [];
|
||||||
_onLoadCommunityAndSpaces(
|
_onLoadCommunityAndSpaces(
|
||||||
LoadCommunityAndSpacesEvent event, Emitter<UsersState> emit) async {
|
LoadCommunityAndSpacesEvent event, Emitter<UsersState> emit) async {
|
||||||
try {
|
try {
|
||||||
emit(UsersLoadingState());
|
emit(UsersLoadingState());
|
||||||
List<CommunityModel> communities =
|
List<CommunityModel> communities =
|
||||||
await CommunitySpaceManagementApi().fetchCommunities();
|
await CommunitySpaceManagementApi().fetchCommunities();
|
||||||
|
communityIds = communities.map((community) => community.uuid).toList();
|
||||||
updatedCommunities = await Future.wait(
|
updatedCommunities = await Future.wait(
|
||||||
communities.map((community) async {
|
communities.map((community) async {
|
||||||
List<SpaceModel> spaces =
|
List<SpaceModel> spaces =
|
||||||
@ -102,7 +103,6 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
|||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
emit(const SpacesLoadedState());
|
emit(const SpacesLoadedState());
|
||||||
return updatedCommunities;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(ErrorState('Error loading communities and spaces: $e'));
|
emit(ErrorState('Error loading communities and spaces: $e'));
|
||||||
}
|
}
|
||||||
@ -177,7 +177,6 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
|||||||
try {
|
try {
|
||||||
emit(UsersLoadingState());
|
emit(UsersLoadingState());
|
||||||
roles = await UserPermissionApi().fetchRoles();
|
roles = await UserPermissionApi().fetchRoles();
|
||||||
// add(PermissionEvent(roleUuid: roles.first.uuid));
|
|
||||||
emit(RolePermissionInitial());
|
emit(RolePermissionInitial());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(ErrorState('Error loading communities and spaces: $e'));
|
emit(ErrorState('Error loading communities and spaces: $e'));
|
||||||
@ -208,10 +207,13 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
|||||||
return anyMatch;
|
return anyMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendInvitUser(SendInviteUsers event, Emitter<UsersState> emit) async {
|
void _sendInvitUser(SendInviteUsers event, Emitter<UsersState> emit) async {
|
||||||
try {
|
try {
|
||||||
emit(UsersLoadingState());
|
emit(UsersLoadingState());
|
||||||
List<String> selectedIds = getSelectedIds(updatedCommunities);
|
List<String> selectedIds = getSelectedIds(updatedCommunities)
|
||||||
|
.where((id) => !communityIds.contains(id))
|
||||||
|
.toList();
|
||||||
|
|
||||||
bool res = await UserPermissionApi().sendInviteUser(
|
bool res = await UserPermissionApi().sendInviteUser(
|
||||||
email: emailController.text,
|
email: emailController.text,
|
||||||
firstName: firstNameController.text,
|
firstName: firstNameController.text,
|
||||||
@ -219,9 +221,10 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
|||||||
lastName: lastNameController.text,
|
lastName: lastNameController.text,
|
||||||
phoneNumber: phoneController.text,
|
phoneNumber: phoneController.text,
|
||||||
roleUuid: roleSelected,
|
roleUuid: roleSelected,
|
||||||
spaceUuids: selectedIds,
|
spaceUuids: selectedIds,
|
||||||
);
|
);
|
||||||
if (res == true) {
|
|
||||||
|
if (res) {
|
||||||
showCustomDialog(
|
showCustomDialog(
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
context: event.context,
|
context: event.context,
|
||||||
@ -248,10 +251,14 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_editInviteUser(EditInviteUsers event, Emitter<UsersState> emit) async {
|
_editInviteUser(EditInviteUsers event, Emitter<UsersState> emit) async {
|
||||||
try {
|
try {
|
||||||
emit(UsersLoadingState());
|
emit(UsersLoadingState());
|
||||||
List<String> selectedIds = getSelectedIds(updatedCommunities);
|
List<String> selectedIds = getSelectedIds(updatedCommunities)
|
||||||
|
.where((id) => !communityIds.contains(id))
|
||||||
|
.toList();
|
||||||
|
|
||||||
bool res = await UserPermissionApi().editInviteUser(
|
bool res = await UserPermissionApi().editInviteUser(
|
||||||
userId: event.userId,
|
userId: event.userId,
|
||||||
firstName: firstNameController.text,
|
firstName: firstNameController.text,
|
||||||
|
@ -218,7 +218,7 @@ class BasicsView extends StatelessWidget {
|
|||||||
if (_blocRole.checkEmailValid != "Valid email") {
|
if (_blocRole.checkEmailValid != "Valid email") {
|
||||||
return _blocRole.checkEmailValid;
|
return _blocRole.checkEmailValid;
|
||||||
}
|
}
|
||||||
return null;
|
// return null;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -81,7 +81,7 @@ Future<void> showPopUpFilterMenu({
|
|||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const Text(
|
const Text(
|
||||||
"Filter by Status",
|
"Filter by ",
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
|
@ -40,9 +40,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
|||||||
roleTypes.clear();
|
roleTypes.clear();
|
||||||
jobTitle.clear();
|
jobTitle.clear();
|
||||||
createdBy.clear();
|
createdBy.clear();
|
||||||
// deActivate.clear();
|
|
||||||
users = await UserPermissionApi().fetchUsers();
|
users = await UserPermissionApi().fetchUsers();
|
||||||
|
|
||||||
users.sort((a, b) {
|
users.sort((a, b) {
|
||||||
final dateA = _parseDateTime(a.createdDate);
|
final dateA = _parseDateTime(a.createdDate);
|
||||||
final dateB = _parseDateTime(b.createdDate);
|
final dateB = _parseDateTime(b.createdDate);
|
||||||
@ -57,15 +55,12 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
|||||||
for (var user in users) {
|
for (var user in users) {
|
||||||
createdBy.add(user.invitedBy.toString());
|
createdBy.add(user.invitedBy.toString());
|
||||||
}
|
}
|
||||||
// for (var user in users) {
|
|
||||||
// deActivate.add(user.status.toString());
|
|
||||||
// }
|
|
||||||
initialUsers = List.from(users);
|
initialUsers = List.from(users);
|
||||||
roleTypes = roleTypes.toSet().toList();
|
roleTypes = roleTypes.toSet().toList();
|
||||||
jobTitle = jobTitle.toSet().toList();
|
jobTitle = jobTitle.toSet().toList();
|
||||||
createdBy = createdBy.toSet().toList();
|
createdBy = createdBy.toSet().toList();
|
||||||
// deActivate = deActivate.toSet().toList();
|
|
||||||
_handlePageChange(ChangePage(1), emit);
|
_handlePageChange(ChangePage(1), emit);
|
||||||
|
add(ChangePage(currentPage));
|
||||||
emit(UsersLoadedState(users: users));
|
emit(UsersLoadedState(users: users));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(ErrorState(e.toString()));
|
emit(ErrorState(e.toString()));
|
||||||
@ -125,6 +120,10 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
|||||||
|
|
||||||
void _toggleSortUsersByNameAsc(
|
void _toggleSortUsersByNameAsc(
|
||||||
SortUsersByNameAsc event, Emitter<UserTableState> emit) {
|
SortUsersByNameAsc event, Emitter<UserTableState> emit) {
|
||||||
|
selectedRoles.clear();
|
||||||
|
selectedJobTitles.clear();
|
||||||
|
selectedCreatedBy.clear();
|
||||||
|
selectedStatuses.clear();
|
||||||
if (currentSortOrder == "Asc") {
|
if (currentSortOrder == "Asc") {
|
||||||
emit(UsersLoadingState());
|
emit(UsersLoadingState());
|
||||||
currentSortOrder = "";
|
currentSortOrder = "";
|
||||||
@ -143,13 +142,16 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
|||||||
|
|
||||||
void _toggleSortUsersByNameDesc(
|
void _toggleSortUsersByNameDesc(
|
||||||
SortUsersByNameDesc event, Emitter<UserTableState> emit) {
|
SortUsersByNameDesc event, Emitter<UserTableState> emit) {
|
||||||
|
selectedRoles.clear();
|
||||||
|
selectedJobTitles.clear();
|
||||||
|
selectedCreatedBy.clear();
|
||||||
|
selectedStatuses.clear();
|
||||||
if (currentSortOrder == "Desc") {
|
if (currentSortOrder == "Desc") {
|
||||||
emit(UsersLoadingState());
|
emit(UsersLoadingState());
|
||||||
currentSortOrder = "";
|
currentSortOrder = "";
|
||||||
users = List.from(initialUsers); // Reset to saved initial state
|
users = List.from(initialUsers);
|
||||||
emit(UsersLoadedState(users: users));
|
emit(UsersLoadedState(users: users));
|
||||||
} else {
|
} else {
|
||||||
// Sort descending
|
|
||||||
emit(UsersLoadingState());
|
emit(UsersLoadingState());
|
||||||
currentSortOrder = "Desc";
|
currentSortOrder = "Desc";
|
||||||
users.sort((a, b) => b.firstName!.compareTo(a.firstName!));
|
users.sort((a, b) => b.firstName!.compareTo(a.firstName!));
|
||||||
@ -159,6 +161,10 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
|||||||
|
|
||||||
void _toggleSortUsersByDateNewestToOldest(
|
void _toggleSortUsersByDateNewestToOldest(
|
||||||
DateNewestToOldestEvent event, Emitter<UserTableState> emit) {
|
DateNewestToOldestEvent event, Emitter<UserTableState> emit) {
|
||||||
|
selectedRoles.clear();
|
||||||
|
selectedJobTitles.clear();
|
||||||
|
selectedCreatedBy.clear();
|
||||||
|
selectedStatuses.clear();
|
||||||
if (currentSortOrderDate == "NewestToOldest") {
|
if (currentSortOrderDate == "NewestToOldest") {
|
||||||
emit(UsersLoadingState());
|
emit(UsersLoadingState());
|
||||||
currentSortOrder = "";
|
currentSortOrder = "";
|
||||||
@ -179,6 +185,10 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
|||||||
|
|
||||||
void _toggleSortUsersByDateOldestToNewest(
|
void _toggleSortUsersByDateOldestToNewest(
|
||||||
DateOldestToNewestEvent event, Emitter<UserTableState> emit) {
|
DateOldestToNewestEvent event, Emitter<UserTableState> emit) {
|
||||||
|
selectedRoles.clear();
|
||||||
|
selectedJobTitles.clear();
|
||||||
|
selectedCreatedBy.clear();
|
||||||
|
selectedStatuses.clear();
|
||||||
if (currentSortOrderDate == "OldestToNewest") {
|
if (currentSortOrderDate == "OldestToNewest") {
|
||||||
emit(UsersLoadingState());
|
emit(UsersLoadingState());
|
||||||
currentSortOrder = "";
|
currentSortOrder = "";
|
||||||
@ -337,7 +347,20 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
|||||||
|
|
||||||
final filteredUsers = initialUsers.where((user) {
|
final filteredUsers = initialUsers.where((user) {
|
||||||
if (selectedStatuses.isEmpty) return true;
|
if (selectedStatuses.isEmpty) return true;
|
||||||
return selectedStatuses.contains(user.status);
|
|
||||||
|
return selectedStatuses.any((status) {
|
||||||
|
final userStatus = user.status?.toLowerCase() ?? '';
|
||||||
|
switch (status.toLowerCase()) {
|
||||||
|
case 'active':
|
||||||
|
return user.isEnabled == true && userStatus != 'invited';
|
||||||
|
case 'disabled':
|
||||||
|
return user.isEnabled == false;
|
||||||
|
case 'invited':
|
||||||
|
return userStatus == 'invited';
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
}).toList();
|
}).toList();
|
||||||
if (event.sortOrder == "Asc") {
|
if (event.sortOrder == "Asc") {
|
||||||
currentSortOrder = "Asc";
|
currentSortOrder = "Asc";
|
||||||
@ -351,9 +374,11 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
|||||||
} else {
|
} else {
|
||||||
currentSortOrder = "";
|
currentSortOrder = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(UsersLoadedState(users: filteredUsers));
|
emit(UsersLoadedState(users: filteredUsers));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void _resetAllFilters(Emitter<UserTableState> emit) {
|
void _resetAllFilters(Emitter<UserTableState> emit) {
|
||||||
selectedRoles.clear();
|
selectedRoles.clear();
|
||||||
selectedJobTitles.clear();
|
selectedJobTitles.clear();
|
||||||
|
@ -12,7 +12,7 @@ Future<void> showDateFilterMenu({
|
|||||||
Overlay.of(context).context.findRenderObject() as RenderBox;
|
Overlay.of(context).context.findRenderObject() as RenderBox;
|
||||||
final RelativeRect position = RelativeRect.fromRect(
|
final RelativeRect position = RelativeRect.fromRect(
|
||||||
Rect.fromLTRB(
|
Rect.fromLTRB(
|
||||||
overlay.size.width / 2,
|
overlay.size.width / 3,
|
||||||
240,
|
240,
|
||||||
0,
|
0,
|
||||||
overlay.size.height,
|
overlay.size.height,
|
||||||
@ -40,7 +40,6 @@ Future<void> showDateFilterMenu({
|
|||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
"Sort from newest to oldest",
|
"Sort from newest to oldest",
|
||||||
// style: context.textTheme.bodyMedium,
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isSelected == "NewestToOldest"
|
color: isSelected == "NewestToOldest"
|
||||||
? Colors.black
|
? Colors.black
|
||||||
@ -65,9 +64,5 @@ Future<void> showDateFilterMenu({
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).then((value) {
|
).then((value) {});
|
||||||
// setState(() {
|
|
||||||
// _isDropdownOpen = false;
|
|
||||||
// });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,6 @@ Future<void> showDeActivateFilterMenu({
|
|||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
"Sort A to Z",
|
"Sort A to Z",
|
||||||
// style: context.textTheme.bodyMedium,
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isSelected == "NewestToOldest"
|
color: isSelected == "NewestToOldest"
|
||||||
? Colors.black
|
? Colors.black
|
||||||
@ -65,9 +64,5 @@ Future<void> showDeActivateFilterMenu({
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).then((value) {
|
).then((value) {});
|
||||||
// setState(() {
|
|
||||||
// _isDropdownOpen = false;
|
|
||||||
// });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ Future<void> showNameMenu({
|
|||||||
Overlay.of(context).context.findRenderObject() as RenderBox;
|
Overlay.of(context).context.findRenderObject() as RenderBox;
|
||||||
final RelativeRect position = RelativeRect.fromRect(
|
final RelativeRect position = RelativeRect.fromRect(
|
||||||
Rect.fromLTRB(
|
Rect.fromLTRB(
|
||||||
overlay.size.width / 25,
|
overlay.size.width / 35,
|
||||||
240,
|
240,
|
||||||
0,
|
0,
|
||||||
overlay.size.height,
|
overlay.size.height,
|
||||||
@ -40,7 +40,6 @@ Future<void> showNameMenu({
|
|||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
"Sort A to Z",
|
"Sort A to Z",
|
||||||
// style: context.textTheme.bodyMedium,
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isSelected == "Asc" ? Colors.black : Colors.blueGrey),
|
color: isSelected == "Asc" ? Colors.black : Colors.blueGrey),
|
||||||
),
|
),
|
||||||
@ -61,9 +60,5 @@ Future<void> showNameMenu({
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).then((value) {
|
).then((value) {});
|
||||||
// setState(() {
|
|
||||||
// _isDropdownOpen = false;
|
|
||||||
// });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -1,256 +1,59 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
import 'package:syncrow_web/utils/style.dart';
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
class DynamicTableScreen extends StatefulWidget {
|
class _HeaderColumn extends StatelessWidget {
|
||||||
final List<String> titles;
|
final String title;
|
||||||
final List<List<Widget>> rows;
|
final double width;
|
||||||
final void Function(int columnIndex)? onFilter;
|
final bool showFilter;
|
||||||
|
final VoidCallback? onFilter;
|
||||||
|
final Function(double) onResize;
|
||||||
|
|
||||||
DynamicTableScreen(
|
const _HeaderColumn({
|
||||||
{required this.titles, required this.rows, required this.onFilter});
|
required this.title,
|
||||||
|
required this.width,
|
||||||
@override
|
required this.showFilter,
|
||||||
_DynamicTableScreenState createState() => _DynamicTableScreenState();
|
required this.onResize,
|
||||||
}
|
this.onFilter,
|
||||||
|
Key? key,
|
||||||
class _DynamicTableScreenState extends State<DynamicTableScreen>
|
}) : super(key: key);
|
||||||
with WidgetsBindingObserver {
|
|
||||||
late List<double> columnWidths;
|
|
||||||
late double totalWidth;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
columnWidths = List<double>.filled(widget.titles.length, 150.0);
|
|
||||||
totalWidth = columnWidths.reduce((a, b) => a + b);
|
|
||||||
WidgetsBinding.instance.addObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeMetrics() {
|
|
||||||
super.didChangeMetrics();
|
|
||||||
final newScreenWidth = MediaQuery.of(context).size.width;
|
|
||||||
setState(() {
|
|
||||||
columnWidths = List<double>.generate(widget.titles.length, (index) {
|
|
||||||
if (index == 1) {
|
|
||||||
return newScreenWidth *
|
|
||||||
0.12; // 20% of screen width for the second column
|
|
||||||
} else if (index == 9) {
|
|
||||||
return newScreenWidth *
|
|
||||||
0.1; // 25% of screen width for the tenth column
|
|
||||||
}
|
|
||||||
return newScreenWidth *
|
|
||||||
0.09; // Default to 10% of screen width for other columns
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
return MouseRegion(
|
||||||
if (columnWidths.every((width) => width == screenWidth * 7)) {
|
cursor: SystemMouseCursors.resizeColumn,
|
||||||
columnWidths = List<double>.generate(widget.titles.length, (index) {
|
child: GestureDetector(
|
||||||
if (index == 1) {
|
onHorizontalDragUpdate: (details) => onResize(details.delta.dx),
|
||||||
return screenWidth * 0.11;
|
child: Container(
|
||||||
} else if (index == 9) {
|
width: width,
|
||||||
return screenWidth * 0.1;
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
}
|
decoration: const BoxDecoration(
|
||||||
return screenWidth * 0.09;
|
border: Border(right: BorderSide(color: ColorsManager.boxDivider)),
|
||||||
});
|
),
|
||||||
setState(() {});
|
child: Row(
|
||||||
}
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
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: [
|
children: [
|
||||||
Container(
|
Expanded(
|
||||||
width: totalWidth,
|
child: Text(
|
||||||
decoration: containerDecoration.copyWith(
|
title,
|
||||||
color: ColorsManager.circleRolesBackground,
|
maxLines: 2,
|
||||||
borderRadius: const BorderRadius.only(
|
overflow: TextOverflow.ellipsis,
|
||||||
topLeft: Radius.circular(15),
|
style: const TextStyle(
|
||||||
topRight: Radius.circular(15))),
|
fontWeight: FontWeight.w400,
|
||||||
child: Row(
|
fontSize: 13,
|
||||||
children: List.generate(widget.titles.length, (index) {
|
color: ColorsManager.grayColor,
|
||||||
return Row(
|
),
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
FittedBox(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.only(left: 5, right: 5),
|
|
||||||
width: columnWidths[index],
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
child: Text(
|
|
||||||
widget.titles[index],
|
|
||||||
maxLines: 2,
|
|
||||||
style: const TextStyle(
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
fontSize: 13,
|
|
||||||
color: ColorsManager.grayColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (index != 1 &&
|
|
||||||
index != 9 &&
|
|
||||||
index != 8 &&
|
|
||||||
index != 5)
|
|
||||||
FittedBox(
|
|
||||||
child: IconButton(
|
|
||||||
icon: SvgPicture.asset(
|
|
||||||
Assets.filterTableIcon,
|
|
||||||
fit: BoxFit.none,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
if (widget.onFilter != null) {
|
|
||||||
widget.onFilter!(index);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
GestureDetector(
|
|
||||||
onHorizontalDragUpdate: (details) {
|
|
||||||
setState(() {
|
|
||||||
columnWidths[index] =
|
|
||||||
(columnWidths[index] + details.delta.dx)
|
|
||||||
.clamp(150.0, 300.0);
|
|
||||||
totalWidth = columnWidths.reduce((a, b) => a + b);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: MouseRegion(
|
|
||||||
cursor: SystemMouseCursors.resizeColumn,
|
|
||||||
child: Container(
|
|
||||||
color: Colors.green,
|
|
||||||
child: Container(
|
|
||||||
color: ColorsManager.boxDivider,
|
|
||||||
width: 1,
|
|
||||||
height: 50,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
widget.rows.isEmpty
|
if (showFilter)
|
||||||
? SizedBox(
|
IconButton(
|
||||||
height: MediaQuery.of(context).size.height / 2,
|
icon: SvgPicture.asset(Assets.filterTableIcon),
|
||||||
child: Column(
|
onPressed: onFilter,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
padding: EdgeInsets.zero,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
constraints: const BoxConstraints(),
|
||||||
children: [
|
),
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
SvgPicture.asset(Assets.emptyTable),
|
|
||||||
const SizedBox(
|
|
||||||
height: 15,
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'No Users',
|
|
||||||
style: TextStyle(
|
|
||||||
color: ColorsManager.lightGrayColor,
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w700),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Center(
|
|
||||||
child: Container(
|
|
||||||
width: totalWidth,
|
|
||||||
decoration: containerDecoration.copyWith(
|
|
||||||
color: ColorsManager.whiteColors,
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
bottomLeft: Radius.circular(15),
|
|
||||||
bottomRight: Radius.circular(15))),
|
|
||||||
child: ListView.builder(
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
shrinkWrap: true,
|
|
||||||
itemCount: widget.rows.length,
|
|
||||||
itemBuilder: (context, rowIndex) {
|
|
||||||
if (columnWidths.every((width) => width == 120.0)) {
|
|
||||||
columnWidths = List<double>.generate(
|
|
||||||
widget.titles.length, (index) {
|
|
||||||
if (index == 1) {
|
|
||||||
return screenWidth * 0.11;
|
|
||||||
} else if (index == 9) {
|
|
||||||
return screenWidth * 0.2;
|
|
||||||
}
|
|
||||||
return screenWidth * 0.11;
|
|
||||||
});
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
final row = widget.rows[rowIndex];
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
left: 5, top: 10, right: 5, bottom: 10),
|
|
||||||
child: Row(
|
|
||||||
children:
|
|
||||||
List.generate(row.length, (index) {
|
|
||||||
return SizedBox(
|
|
||||||
width: columnWidths[index],
|
|
||||||
child: SizedBox(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
left: 15, right: 10),
|
|
||||||
child: row[index],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (rowIndex < widget.rows.length - 1)
|
|
||||||
Row(
|
|
||||||
children: List.generate(
|
|
||||||
widget.titles.length, (index) {
|
|
||||||
return SizedBox(
|
|
||||||
width: columnWidths[index],
|
|
||||||
child: const Divider(
|
|
||||||
color: ColorsManager.boxDivider,
|
|
||||||
thickness: 1,
|
|
||||||
height: 1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -258,3 +61,204 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _TableRow extends StatelessWidget {
|
||||||
|
final List<Widget> cells;
|
||||||
|
final List<double> columnWidths;
|
||||||
|
final bool isLast;
|
||||||
|
|
||||||
|
const _TableRow({
|
||||||
|
required this.cells,
|
||||||
|
required this.columnWidths,
|
||||||
|
required this.isLast,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < cells.length; i++)
|
||||||
|
Container(
|
||||||
|
width: columnWidths[i],
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// border: Border(
|
||||||
|
// right: BorderSide(color: ColorsManager.boxDivider),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
child: cells[i],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (!isLast)
|
||||||
|
Divider(
|
||||||
|
height: 1,
|
||||||
|
thickness: 1,
|
||||||
|
color: ColorsManager.boxDivider,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
class DynamicTableScreen extends StatefulWidget {
|
||||||
|
final List<String> titles;
|
||||||
|
final List<List<Widget>> rows;
|
||||||
|
final void Function(int columnIndex)? onFilter;
|
||||||
|
|
||||||
|
const DynamicTableScreen({
|
||||||
|
required this.titles,
|
||||||
|
required this.rows,
|
||||||
|
required this.onFilter,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_DynamicTableScreenState createState() => _DynamicTableScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DynamicTableScreenState extends State<DynamicTableScreen> {
|
||||||
|
late List<double> columnWidths;
|
||||||
|
final double _minColumnWidth = 100.0;
|
||||||
|
final double _maxColumnWidth = 300.0;
|
||||||
|
final double _dividerWidth = 1.0;
|
||||||
|
double _lastAvailableWidth = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
columnWidths = List.filled(widget.titles.length, _minColumnWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleColumnResize(int index, double delta) {
|
||||||
|
setState(() {
|
||||||
|
double newWidth = columnWidths[index] + delta;
|
||||||
|
newWidth = newWidth.clamp(_minColumnWidth, _maxColumnWidth);
|
||||||
|
double actualDelta = newWidth - columnWidths[index];
|
||||||
|
if (actualDelta == 0) return;
|
||||||
|
|
||||||
|
int nextIndex = (index + 1) % columnWidths.length;
|
||||||
|
columnWidths[index] = newWidth;
|
||||||
|
columnWidths[nextIndex] = (columnWidths[nextIndex] - actualDelta)
|
||||||
|
.clamp(_minColumnWidth, _maxColumnWidth);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader() {
|
||||||
|
return Container(
|
||||||
|
decoration: containerDecoration.copyWith(
|
||||||
|
color: ColorsManager.circleRolesBackground,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(15),
|
||||||
|
topRight: Radius.circular(15),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < widget.titles.length; i++)
|
||||||
|
_HeaderColumn(
|
||||||
|
title: widget.titles[i],
|
||||||
|
width: columnWidths[i],
|
||||||
|
showFilter: i != 1 && i != 9 && i != 8 && i != 5,
|
||||||
|
onFilter: () => widget.onFilter?.call(i),
|
||||||
|
onResize: (delta) => _handleColumnResize(i, delta),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBody() {
|
||||||
|
if (widget.rows.isEmpty) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 300,
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset(Assets.emptyTable),
|
||||||
|
const SizedBox(height: 15),
|
||||||
|
const Text(
|
||||||
|
'No Users',
|
||||||
|
style: TextStyle(
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: containerDecoration.copyWith(
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
bottomLeft: Radius.circular(15),
|
||||||
|
bottomRight: Radius.circular(15),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
for (int rowIndex = 0; rowIndex < widget.rows.length; rowIndex++)
|
||||||
|
_TableRow(
|
||||||
|
cells: widget.rows[rowIndex],
|
||||||
|
columnWidths: columnWidths,
|
||||||
|
isLast: rowIndex == widget.rows.length - 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final availableWidth = constraints.maxWidth;
|
||||||
|
final totalDividersWidth = (widget.titles.length - 1) * _dividerWidth;
|
||||||
|
|
||||||
|
if (_lastAvailableWidth != availableWidth) {
|
||||||
|
final equalWidth =
|
||||||
|
(availableWidth - totalDividersWidth) / widget.titles.length;
|
||||||
|
final clampedWidth =
|
||||||
|
equalWidth.clamp(_minColumnWidth, _maxColumnWidth);
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
setState(() {
|
||||||
|
columnWidths = List.filled(widget.titles.length, clampedWidth);
|
||||||
|
_lastAvailableWidth = availableWidth;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final totalTableWidth =
|
||||||
|
columnWidths.fold(0.0, (sum, w) => sum + w) + totalDividersWidth;
|
||||||
|
return SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Container(
|
||||||
|
width: totalTableWidth,
|
||||||
|
decoration: containerDecoration.copyWith(
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildHeader(),
|
||||||
|
_buildBody(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -108,7 +108,6 @@ class UsersPage extends StatelessWidget {
|
|||||||
final screenSize = MediaQuery.of(context).size;
|
final screenSize = MediaQuery.of(context).size;
|
||||||
final _blocRole = BlocProvider.of<UserTableBloc>(context);
|
final _blocRole = BlocProvider.of<UserTableBloc>(context);
|
||||||
if (state is UsersLoadingState) {
|
if (state is UsersLoadingState) {
|
||||||
_blocRole.add(ChangePage(_blocRole.currentPage));
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
} else if (state is UsersLoadedState) {
|
} else if (state is UsersLoadedState) {
|
||||||
return Padding(
|
return Padding(
|
||||||
@ -215,7 +214,7 @@ class UsersPage extends StatelessWidget {
|
|||||||
|
|
||||||
showPopUpFilterMenu(
|
showPopUpFilterMenu(
|
||||||
position: RelativeRect.fromLTRB(
|
position: RelativeRect.fromLTRB(
|
||||||
overlay.size.width / 4,
|
overlay.size.width / 5.3,
|
||||||
240,
|
240,
|
||||||
overlay.size.width / 4,
|
overlay.size.width / 4,
|
||||||
0,
|
0,
|
||||||
@ -225,6 +224,7 @@ class UsersPage extends StatelessWidget {
|
|||||||
checkboxStates: checkboxStates,
|
checkboxStates: checkboxStates,
|
||||||
isSelected: _blocRole.currentSortOrder,
|
isSelected: _blocRole.currentSortOrder,
|
||||||
onOkPressed: () {
|
onOkPressed: () {
|
||||||
|
searchController.clear();
|
||||||
_blocRole.add(FilterClearEvent());
|
_blocRole.add(FilterClearEvent());
|
||||||
final selectedItems = checkboxStates.entries
|
final selectedItems = checkboxStates.entries
|
||||||
.where((entry) => entry.value)
|
.where((entry) => entry.value)
|
||||||
@ -265,6 +265,7 @@ class UsersPage extends StatelessWidget {
|
|||||||
checkboxStates: checkboxStates,
|
checkboxStates: checkboxStates,
|
||||||
isSelected: _blocRole.currentSortOrder,
|
isSelected: _blocRole.currentSortOrder,
|
||||||
onOkPressed: () {
|
onOkPressed: () {
|
||||||
|
searchController.clear();
|
||||||
_blocRole.add(FilterClearEvent());
|
_blocRole.add(FilterClearEvent());
|
||||||
final selectedItems = checkboxStates.entries
|
final selectedItems = checkboxStates.entries
|
||||||
.where((entry) => entry.value)
|
.where((entry) => entry.value)
|
||||||
@ -320,6 +321,7 @@ class UsersPage extends StatelessWidget {
|
|||||||
checkboxStates: checkboxStates,
|
checkboxStates: checkboxStates,
|
||||||
isSelected: _blocRole.currentSortOrder,
|
isSelected: _blocRole.currentSortOrder,
|
||||||
onOkPressed: () {
|
onOkPressed: () {
|
||||||
|
searchController.clear();
|
||||||
_blocRole.add(FilterClearEvent());
|
_blocRole.add(FilterClearEvent());
|
||||||
final selectedItems = checkboxStates.entries
|
final selectedItems = checkboxStates.entries
|
||||||
.where((entry) => entry.value)
|
.where((entry) => entry.value)
|
||||||
@ -343,6 +345,7 @@ class UsersPage extends StatelessWidget {
|
|||||||
for (var item in _blocRole.status)
|
for (var item in _blocRole.status)
|
||||||
item: _blocRole.selectedStatuses.contains(item),
|
item: _blocRole.selectedStatuses.contains(item),
|
||||||
};
|
};
|
||||||
|
|
||||||
final RenderBox overlay = Overlay.of(context)
|
final RenderBox overlay = Overlay.of(context)
|
||||||
.context
|
.context
|
||||||
.findRenderObject() as RenderBox;
|
.findRenderObject() as RenderBox;
|
||||||
@ -350,7 +353,7 @@ class UsersPage extends StatelessWidget {
|
|||||||
position: RelativeRect.fromLTRB(
|
position: RelativeRect.fromLTRB(
|
||||||
overlay.size.width / 0,
|
overlay.size.width / 0,
|
||||||
240,
|
240,
|
||||||
overlay.size.width / 4,
|
overlay.size.width / 5,
|
||||||
0,
|
0,
|
||||||
),
|
),
|
||||||
list: _blocRole.status,
|
list: _blocRole.status,
|
||||||
@ -358,8 +361,8 @@ class UsersPage extends StatelessWidget {
|
|||||||
checkboxStates: checkboxStates,
|
checkboxStates: checkboxStates,
|
||||||
isSelected: _blocRole.currentSortOrder,
|
isSelected: _blocRole.currentSortOrder,
|
||||||
onOkPressed: () {
|
onOkPressed: () {
|
||||||
|
searchController.clear();
|
||||||
_blocRole.add(FilterClearEvent());
|
_blocRole.add(FilterClearEvent());
|
||||||
|
|
||||||
final selectedItems = checkboxStates.entries
|
final selectedItems = checkboxStates.entries
|
||||||
.where((entry) => entry.value)
|
.where((entry) => entry.value)
|
||||||
.map((entry) => entry.key)
|
.map((entry) => entry.key)
|
||||||
@ -410,7 +413,7 @@ class UsersPage extends StatelessWidget {
|
|||||||
return [
|
return [
|
||||||
Text('${user.firstName} ${user.lastName}'),
|
Text('${user.firstName} ${user.lastName}'),
|
||||||
Text(user.email),
|
Text(user.email),
|
||||||
Text(user.jobTitle ?? '-'),
|
Text(user.jobTitle),
|
||||||
Text(user.roleType ?? ''),
|
Text(user.roleType ?? ''),
|
||||||
Text(user.createdDate ?? ''),
|
Text(user.createdDate ?? ''),
|
||||||
Text(user.createdTime ?? ''),
|
Text(user.createdTime ?? ''),
|
||||||
@ -427,11 +430,6 @@ class UsersPage extends StatelessWidget {
|
|||||||
userId: user.uuid,
|
userId: user.uuid,
|
||||||
onTap: user.status != "invited"
|
onTap: user.status != "invited"
|
||||||
? () {
|
? () {
|
||||||
// final newStatus = user.status == 'active'
|
|
||||||
// ? 'disabled'
|
|
||||||
// : user.status == 'disabled'
|
|
||||||
// ? 'invited'
|
|
||||||
// : 'active';
|
|
||||||
context.read<UserTableBloc>().add(
|
context.read<UserTableBloc>().add(
|
||||||
ChangeUserStatus(
|
ChangeUserStatus(
|
||||||
userId: user.uuid,
|
userId: user.uuid,
|
||||||
@ -443,10 +441,6 @@ class UsersPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
// actionButton(
|
|
||||||
// title: "Activity Log",
|
|
||||||
// onTap: () {},
|
|
||||||
// ),
|
|
||||||
actionButton(
|
actionButton(
|
||||||
title: "Edit",
|
title: "Edit",
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -487,9 +481,7 @@ class UsersPage extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
).then((v) {
|
).then((v) {
|
||||||
if (v != null) {
|
if (v != null) {
|
||||||
if (v != null) {
|
_blocRole.add(const GetUsers());
|
||||||
_blocRole.add(const GetUsers());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -33,55 +33,64 @@ class _RoutinesViewState extends State<RoutinesView> {
|
|||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SpaceTreeView(
|
child:
|
||||||
|
// SideSpacesView(
|
||||||
|
// onSelectAction: (String communityId, String spaceId) {
|
||||||
|
// // context.read<RoutineBloc>()
|
||||||
|
// // ..add(LoadScenes(spaceId, communityId))
|
||||||
|
// // ..add(LoadAutomation(spaceId));
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
SpaceTreeView(
|
||||||
onSelect: () {},
|
onSelect: () {},
|
||||||
)),
|
)),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 4,
|
flex: 3,
|
||||||
child: ListView(children: [
|
child: Padding(
|
||||||
Container(
|
padding: const EdgeInsets.all(16),
|
||||||
padding: const EdgeInsets.all(16),
|
child: Row(
|
||||||
height: MediaQuery.sizeOf(context).height,
|
children: [
|
||||||
child: Column(
|
Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Create New Routines",
|
"Create New Routines",
|
||||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
color: ColorsManager.grayColor,
|
color: ColorsManager.grayColor,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 10,
|
height: 10,
|
||||||
),
|
),
|
||||||
RoutineViewCard(
|
RoutineViewCard(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (context.read<SpaceTreeBloc>().selectedCommunityId.isNotEmpty &&
|
if (context.read<SpaceTreeBloc>().selectedCommunityId.isNotEmpty &&
|
||||||
context.read<SpaceTreeBloc>().selectedSpaceId.isNotEmpty) {
|
context.read<SpaceTreeBloc>().selectedSpaceId.isNotEmpty) {
|
||||||
context.read<RoutineBloc>().add(
|
context.read<RoutineBloc>().add(
|
||||||
(ResetRoutineState()),
|
(ResetRoutineState()),
|
||||||
);
|
);
|
||||||
BlocProvider.of<RoutineBloc>(context).add(
|
BlocProvider.of<RoutineBloc>(context).add(
|
||||||
const CreateNewRoutineViewEvent(createRoutineView: true),
|
const CreateNewRoutineViewEvent(createRoutineView: true),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
CustomSnackBar.redSnackBar('Please select a space');
|
CustomSnackBar.redSnackBar('Please select a space');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: Icons.add,
|
icon: Icons.add,
|
||||||
textString: '',
|
textString: '',
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 15,
|
height: 15,
|
||||||
),
|
),
|
||||||
const Expanded(child: FetchRoutineScenesAutomation()),
|
const Expanded(child: FetchRoutineScenesAutomation()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
]),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -10,23 +10,10 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model
|
|||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/style.dart';
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
class SpaceTreeView extends StatefulWidget {
|
class SpaceTreeView extends StatelessWidget {
|
||||||
final Function onSelect;
|
final Function onSelect;
|
||||||
const SpaceTreeView({required this.onSelect, super.key});
|
const SpaceTreeView({required this.onSelect, super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<SpaceTreeView> createState() => _SpaceTreeViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SpaceTreeViewState extends State<SpaceTreeView> {
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_scrollController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
|
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
|
||||||
@ -34,6 +21,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
|||||||
return Container(
|
return Container(
|
||||||
height: MediaQuery.sizeOf(context).height,
|
height: MediaQuery.sizeOf(context).height,
|
||||||
decoration: subSectionContainerDecoration,
|
decoration: subSectionContainerDecoration,
|
||||||
|
// padding: const EdgeInsets.all(16.0),
|
||||||
child: state is SpaceTreeLoadingState
|
child: state is SpaceTreeLoadingState
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: Column(
|
: Column(
|
||||||
@ -45,150 +33,67 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView(
|
child: Padding(
|
||||||
shrinkWrap: true,
|
padding: const EdgeInsets.all(8.0),
|
||||||
scrollDirection: Axis.horizontal,
|
child: list.isEmpty
|
||||||
children: [
|
? Center(
|
||||||
Container(
|
child: Text(
|
||||||
width: MediaQuery.sizeOf(context).width * 0.5,
|
'No results found',
|
||||||
padding: const EdgeInsets.all(8.0),
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
child: list.isEmpty
|
color: ColorsManager.lightGrayColor, // Gray when not selected
|
||||||
? Center(
|
fontWeight: FontWeight.w400,
|
||||||
child: Text(
|
|
||||||
'No results found',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
|
||||||
color: ColorsManager.lightGrayColor,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Scrollbar(
|
|
||||||
scrollbarOrientation: ScrollbarOrientation.left,
|
|
||||||
thumbVisibility: true,
|
|
||||||
controller: _scrollController,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 16),
|
|
||||||
child: ListView(
|
|
||||||
controller: _scrollController,
|
|
||||||
shrinkWrap: true,
|
|
||||||
children: list
|
|
||||||
.map(
|
|
||||||
(community) => CustomExpansionTileSpaceTree(
|
|
||||||
title: community.name,
|
|
||||||
isSelected: state.selectedCommunities
|
|
||||||
.contains(community.uuid),
|
|
||||||
isSoldCheck: state.selectedCommunities
|
|
||||||
.contains(community.uuid),
|
|
||||||
onExpansionChanged: () {
|
|
||||||
context
|
|
||||||
.read<SpaceTreeBloc>()
|
|
||||||
.add(OnCommunityExpanded(community.uuid));
|
|
||||||
},
|
|
||||||
isExpanded: state.expandedCommunities
|
|
||||||
.contains(community.uuid),
|
|
||||||
onItemSelected: () {
|
|
||||||
context.read<SpaceTreeBloc>().add(
|
|
||||||
OnCommunitySelected(
|
|
||||||
community.uuid, community.spaces));
|
|
||||||
widget.onSelect();
|
|
||||||
},
|
|
||||||
children: community.spaces.map((space) {
|
|
||||||
return CustomExpansionTileSpaceTree(
|
|
||||||
title: space.name,
|
|
||||||
isExpanded:
|
|
||||||
state.expandedSpaces.contains(space.uuid),
|
|
||||||
onItemSelected: () {
|
|
||||||
context.read<SpaceTreeBloc>().add(
|
|
||||||
OnSpaceSelected(community.uuid,
|
|
||||||
space.uuid ?? '', space.children));
|
|
||||||
widget.onSelect();
|
|
||||||
},
|
|
||||||
onExpansionChanged: () {
|
|
||||||
context.read<SpaceTreeBloc>().add(
|
|
||||||
OnSpaceExpanded(
|
|
||||||
community.uuid, space.uuid ?? ''));
|
|
||||||
},
|
|
||||||
isSelected:
|
|
||||||
state.selectedSpaces.contains(space.uuid) ||
|
|
||||||
state.soldCheck.contains(space.uuid),
|
|
||||||
isSoldCheck: state.soldCheck.contains(space.uuid),
|
|
||||||
children: _buildNestedSpaces(
|
|
||||||
context, state, space, community.uuid),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
),
|
: ListView(
|
||||||
],
|
shrinkWrap: true,
|
||||||
|
children: list
|
||||||
|
.map(
|
||||||
|
(community) => CustomExpansionTileSpaceTree(
|
||||||
|
title: community.name,
|
||||||
|
isSelected:
|
||||||
|
state.selectedCommunities.contains(community.uuid),
|
||||||
|
isSoldCheck:
|
||||||
|
state.selectedCommunities.contains(community.uuid),
|
||||||
|
onExpansionChanged: () {
|
||||||
|
context
|
||||||
|
.read<SpaceTreeBloc>()
|
||||||
|
.add(OnCommunityExpanded(community.uuid));
|
||||||
|
},
|
||||||
|
isExpanded:
|
||||||
|
state.expandedCommunities.contains(community.uuid),
|
||||||
|
onItemSelected: () {
|
||||||
|
context.read<SpaceTreeBloc>().add(
|
||||||
|
OnCommunitySelected(community.uuid, community.spaces));
|
||||||
|
|
||||||
|
onSelect();
|
||||||
|
},
|
||||||
|
children: community.spaces.map((space) {
|
||||||
|
return CustomExpansionTileSpaceTree(
|
||||||
|
title: space.name,
|
||||||
|
isExpanded: state.expandedSpaces.contains(space.uuid),
|
||||||
|
onItemSelected: () {
|
||||||
|
context.read<SpaceTreeBloc>().add(OnSpaceSelected(
|
||||||
|
community.uuid, space.uuid ?? '', space.children));
|
||||||
|
onSelect();
|
||||||
|
},
|
||||||
|
onExpansionChanged: () {
|
||||||
|
context.read<SpaceTreeBloc>().add(
|
||||||
|
OnSpaceExpanded(community.uuid, space.uuid ?? ''));
|
||||||
|
},
|
||||||
|
isSelected: state.selectedSpaces.contains(space.uuid) ||
|
||||||
|
state.soldCheck.contains(space.uuid),
|
||||||
|
isSoldCheck: state.soldCheck.contains(space.uuid),
|
||||||
|
children: _buildNestedSpaces(
|
||||||
|
context, state, space, community.uuid),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Expanded(
|
|
||||||
// child: Padding(
|
|
||||||
// padding: const EdgeInsets.all(8.0),
|
|
||||||
// child: list.isEmpty
|
|
||||||
// ? Center(
|
|
||||||
// child: Text(
|
|
||||||
// 'No results found',
|
|
||||||
// style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
|
||||||
// color: ColorsManager.lightGrayColor, // Gray when not selected
|
|
||||||
// fontWeight: FontWeight.w400,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// : ListView(
|
|
||||||
// shrinkWrap: true,
|
|
||||||
// children: list
|
|
||||||
// .map(
|
|
||||||
// (community) => CustomExpansionTileSpaceTree(
|
|
||||||
// title: community.name,
|
|
||||||
// isSelected:
|
|
||||||
// state.selectedCommunities.contains(community.uuid),
|
|
||||||
// isSoldCheck:
|
|
||||||
// state.selectedCommunities.contains(community.uuid),
|
|
||||||
// onExpansionChanged: () {
|
|
||||||
// context
|
|
||||||
// .read<SpaceTreeBloc>()
|
|
||||||
// .add(OnCommunityExpanded(community.uuid));
|
|
||||||
// },
|
|
||||||
// isExpanded:
|
|
||||||
// state.expandedCommunities.contains(community.uuid),
|
|
||||||
// onItemSelected: () {
|
|
||||||
// context.read<SpaceTreeBloc>().add(
|
|
||||||
// OnCommunitySelected(community.uuid, community.spaces));
|
|
||||||
|
|
||||||
// onSelect();
|
|
||||||
// },
|
|
||||||
// children: community.spaces.map((space) {
|
|
||||||
// return CustomExpansionTileSpaceTree(
|
|
||||||
// title: space.name,
|
|
||||||
// isExpanded: state.expandedSpaces.contains(space.uuid),
|
|
||||||
// onItemSelected: () {
|
|
||||||
// context.read<SpaceTreeBloc>().add(OnSpaceSelected(
|
|
||||||
// community.uuid, space.uuid ?? '', space.children));
|
|
||||||
// onSelect();
|
|
||||||
// },
|
|
||||||
// onExpansionChanged: () {
|
|
||||||
// context.read<SpaceTreeBloc>().add(
|
|
||||||
// OnSpaceExpanded(community.uuid, space.uuid ?? ''));
|
|
||||||
// },
|
|
||||||
// isSelected: state.selectedSpaces.contains(space.uuid) ||
|
|
||||||
// state.soldCheck.contains(space.uuid),
|
|
||||||
// isSoldCheck: state.soldCheck.contains(space.uuid),
|
|
||||||
// children: _buildNestedSpaces(
|
|
||||||
// context, state, space, community.uuid),
|
|
||||||
// );
|
|
||||||
// }).toList(),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// .toList(),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -208,7 +113,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
|||||||
context
|
context
|
||||||
.read<SpaceTreeBloc>()
|
.read<SpaceTreeBloc>()
|
||||||
.add(OnSpaceSelected(communityId, child.uuid ?? '', child.children));
|
.add(OnSpaceSelected(communityId, child.uuid ?? '', child.children));
|
||||||
widget.onSelect();
|
onSelect();
|
||||||
},
|
},
|
||||||
onExpansionChanged: () {
|
onExpansionChanged: () {
|
||||||
context.read<SpaceTreeBloc>().add(OnSpaceExpanded(communityId, child.uuid ?? ''));
|
context.read<SpaceTreeBloc>().add(OnSpaceExpanded(communityId, child.uuid ?? ''));
|
||||||
|
@ -87,6 +87,7 @@ class SpaceManagementBloc
|
|||||||
prevSpaceModels = List<SpaceTemplateModel>.from(
|
prevSpaceModels = List<SpaceTemplateModel>.from(
|
||||||
(previousState as dynamic).spaceModels ?? [],
|
(previousState as dynamic).spaceModels ?? [],
|
||||||
);
|
);
|
||||||
|
allSpaces.addAll(prevSpaceModels);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevSpaceModels.isEmpty) {
|
if (prevSpaceModels.isEmpty) {
|
||||||
@ -179,6 +180,7 @@ class SpaceManagementBloc
|
|||||||
final updatedCommunities =
|
final updatedCommunities =
|
||||||
await Future.wait(communities.map((community) async {
|
await Future.wait(communities.map((community) async {
|
||||||
final spaces = await _fetchSpacesForCommunity(community.uuid);
|
final spaces = await _fetchSpacesForCommunity(community.uuid);
|
||||||
|
|
||||||
return CommunityModel(
|
return CommunityModel(
|
||||||
uuid: community.uuid,
|
uuid: community.uuid,
|
||||||
createdAt: community.createdAt,
|
createdAt: community.createdAt,
|
||||||
|
@ -95,6 +95,9 @@ class SpaceModel {
|
|||||||
icon: json['icon'] ?? Assets.location,
|
icon: json['icon'] ?? Assets.location,
|
||||||
position: Offset(json['x'] ?? 0, json['y'] ?? 0),
|
position: Offset(json['x'] ?? 0, json['y'] ?? 0),
|
||||||
isHovered: false,
|
isHovered: false,
|
||||||
|
spaceModel: json['spaceModel'] != null
|
||||||
|
? SpaceTemplateModel.fromJson(json['spaceModel'])
|
||||||
|
: null,
|
||||||
tags: (json['tags'] as List<dynamic>?)
|
tags: (json['tags'] as List<dynamic>?)
|
||||||
?.where((item) => item is Map<String, dynamic>) // Validate type
|
?.where((item) => item is Map<String, dynamic>) // Validate type
|
||||||
.map((item) => Tag.fromJson(item as Map<String, dynamic>))
|
.map((item) => Tag.fromJson(item as Map<String, dynamic>))
|
||||||
|
@ -64,11 +64,11 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
|
|||||||
);
|
);
|
||||||
} else if (state is SpaceModelLoaded) {
|
} else if (state is SpaceModelLoaded) {
|
||||||
return LoadedSpaceView(
|
return LoadedSpaceView(
|
||||||
communities: state.communities,
|
communities: state.communities,
|
||||||
products: state.products,
|
products: state.products,
|
||||||
spaceModels: state.spaceModels,
|
spaceModels: state.spaceModels,
|
||||||
shouldNavigateToSpaceModelPage: true,
|
shouldNavigateToSpaceModelPage: true,
|
||||||
);
|
);
|
||||||
} else if (state is SpaceManagementError) {
|
} else if (state is SpaceManagementError) {
|
||||||
return Center(child: Text('Error: ${state.errorMessage}'));
|
return Center(child: Text('Error: ${state.errorMessage}'));
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,9 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/curved_li
|
|||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_card_widget.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_card_widget.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/helper/connection_helper.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/helper/space_helper.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
@ -130,7 +133,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
communities: widget.communities,
|
communities: widget.communities,
|
||||||
communityName: widget.selectedCommunity?.name,
|
communityName: widget.selectedCommunity?.name,
|
||||||
community: widget.selectedCommunity,
|
community: widget.selectedCommunity,
|
||||||
isSave: isSave(spaces),
|
isSave: SpaceHelper.isSave(spaces),
|
||||||
isEditingName: isEditingName,
|
isEditingName: isEditingName,
|
||||||
nameController: _nameController,
|
nameController: _nameController,
|
||||||
onSave: _saveSpaces,
|
onSave: _saveSpaces,
|
||||||
@ -175,7 +178,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
children: [
|
children: [
|
||||||
for (var connection in connections)
|
for (var connection in connections)
|
||||||
Opacity(
|
Opacity(
|
||||||
opacity: _isHighlightedConnection(connection)
|
opacity: ConnectionHelper.isHighlightedConnection(
|
||||||
|
connection, widget.selectedSpace)
|
||||||
? 1.0
|
? 1.0
|
||||||
: 0.3, // Adjust opacity
|
: 0.3, // Adjust opacity
|
||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
@ -195,7 +199,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
screenSize,
|
screenSize,
|
||||||
position:
|
position:
|
||||||
spaces[index].position + newPosition,
|
spaces[index].position + newPosition,
|
||||||
|
|
||||||
parentIndex: index,
|
parentIndex: index,
|
||||||
direction: direction,
|
direction: direction,
|
||||||
);
|
);
|
||||||
@ -209,7 +212,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
},
|
},
|
||||||
buildSpaceContainer: (int index) {
|
buildSpaceContainer: (int index) {
|
||||||
final bool isHighlighted =
|
final bool isHighlighted =
|
||||||
_isHighlightedSpace(spaces[index]);
|
SpaceHelper.isHighlightedSpace(
|
||||||
|
spaces[index], widget.selectedSpace);
|
||||||
|
|
||||||
return Opacity(
|
return Opacity(
|
||||||
opacity: isHighlighted ? 1.0 : 0.3,
|
opacity: isHighlighted ? 1.0 : 0.3,
|
||||||
@ -295,7 +299,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
return CreateSpaceDialog(
|
return CreateSpaceDialog(
|
||||||
products: widget.products,
|
products: widget.products,
|
||||||
spaceModels: widget.spaceModels,
|
spaceModels: widget.spaceModels,
|
||||||
allTags: _getAllTagValues(spaces),
|
allTags:
|
||||||
|
TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
|
||||||
parentSpace: parentIndex != null ? spaces[parentIndex] : null,
|
parentSpace: parentIndex != null ? spaces[parentIndex] : null,
|
||||||
onCreateSpace: (String name,
|
onCreateSpace: (String name,
|
||||||
String icon,
|
String icon,
|
||||||
@ -306,7 +311,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
// Set the first space in the center or use passed position
|
// Set the first space in the center or use passed position
|
||||||
Offset centerPosition =
|
Offset centerPosition =
|
||||||
position ?? _getCenterPosition(screenSize);
|
position ?? ConnectionHelper.getCenterPosition(screenSize);
|
||||||
SpaceModel newSpace = SpaceModel(
|
SpaceModel newSpace = SpaceModel(
|
||||||
name: name,
|
name: name,
|
||||||
icon: icon,
|
icon: icon,
|
||||||
@ -351,11 +356,15 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
spaceModels: widget.spaceModels,
|
spaceModels: widget.spaceModels,
|
||||||
name: widget.selectedSpace!.name,
|
name: widget.selectedSpace!.name,
|
||||||
icon: widget.selectedSpace!.icon,
|
icon: widget.selectedSpace!.icon,
|
||||||
|
parentSpace: SpaceHelper.findSpaceByInternalId(
|
||||||
|
widget.selectedSpace?.parent?.internalId, spaces),
|
||||||
editSpace: widget.selectedSpace,
|
editSpace: widget.selectedSpace,
|
||||||
|
currentSpaceModel: widget.selectedSpace?.spaceModel,
|
||||||
tags: widget.selectedSpace?.tags,
|
tags: widget.selectedSpace?.tags,
|
||||||
subspaces: widget.selectedSpace?.subspaces,
|
subspaces: widget.selectedSpace?.subspaces,
|
||||||
isEdit: true,
|
isEdit: true,
|
||||||
allTags: _getAllTagValues(spaces),
|
allTags: TagHelper.getAllTagValues(
|
||||||
|
widget.communities, widget.spaceModels),
|
||||||
onCreateSpace: (String name,
|
onCreateSpace: (String name,
|
||||||
String icon,
|
String icon,
|
||||||
List<SelectedProduct> selectedProducts,
|
List<SelectedProduct> selectedProducts,
|
||||||
@ -374,6 +383,15 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
widget.selectedSpace!.status =
|
widget.selectedSpace!.status =
|
||||||
SpaceStatus.modified; // Mark as modified
|
SpaceStatus.modified; // Mark as modified
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (var space in spaces) {
|
||||||
|
if (space.internalId == widget.selectedSpace?.internalId) {
|
||||||
|
space.status = SpaceStatus.modified;
|
||||||
|
space.subspaces = subspaces;
|
||||||
|
space.tags = tags;
|
||||||
|
space.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
key: Key(widget.selectedSpace!.name),
|
key: Key(widget.selectedSpace!.name),
|
||||||
@ -452,7 +470,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
if (spacesToSave.isEmpty) {
|
if (spacesToSave.isEmpty) {
|
||||||
debugPrint("No new or modified spaces to save.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -516,17 +533,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isHighlightedSpace(SpaceModel space) {
|
|
||||||
final selectedSpace = widget.selectedSpace;
|
|
||||||
if (selectedSpace == null) return true;
|
|
||||||
|
|
||||||
return space == selectedSpace ||
|
|
||||||
selectedSpace.parent?.internalId == space.internalId ||
|
|
||||||
selectedSpace.children
|
|
||||||
?.any((child) => child.internalId == space.internalId) ==
|
|
||||||
true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _deselectSpace(BuildContext context) {
|
void _deselectSpace(BuildContext context) {
|
||||||
context.read<SpaceManagementBloc>().add(
|
context.read<SpaceManagementBloc>().add(
|
||||||
SelectSpaceEvent(
|
SelectSpaceEvent(
|
||||||
@ -534,28 +540,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isHighlightedConnection(Connection connection) {
|
|
||||||
if (widget.selectedSpace == null) return true;
|
|
||||||
|
|
||||||
return connection.startSpace == widget.selectedSpace ||
|
|
||||||
connection.endSpace == widget.selectedSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
Offset _getCenterPosition(Size screenSize) {
|
|
||||||
return Offset(
|
|
||||||
screenSize.width / 2 - 260,
|
|
||||||
screenSize.height / 2 - 200,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isSave(List<SpaceModel> spaces) {
|
|
||||||
return spaces.isNotEmpty &&
|
|
||||||
spaces.any((space) =>
|
|
||||||
space.status == SpaceStatus.newSpace ||
|
|
||||||
space.status == SpaceStatus.modified ||
|
|
||||||
space.status == SpaceStatus.deleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onDuplicate(BuildContext parentContext) {
|
void _onDuplicate(BuildContext parentContext) {
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
@ -641,17 +625,10 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
const double horizontalGap = 200.0;
|
const double horizontalGap = 200.0;
|
||||||
const double verticalGap = 100.0;
|
const double verticalGap = 100.0;
|
||||||
|
|
||||||
final Map<String, int> nameCounters = {};
|
|
||||||
|
|
||||||
String _generateCopyName(String originalName) {
|
|
||||||
final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim();
|
|
||||||
nameCounters[baseName] = (nameCounters[baseName] ?? 0) + 1;
|
|
||||||
return "$baseName(${nameCounters[baseName]})";
|
|
||||||
}
|
|
||||||
|
|
||||||
SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition,
|
SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition,
|
||||||
SpaceModel? duplicatedParent) {
|
SpaceModel? duplicatedParent) {
|
||||||
Offset newPosition = parentPosition + Offset(horizontalGap, 0);
|
Offset newPosition =
|
||||||
|
Offset(parentPosition.dx + horizontalGap, original.position.dy);
|
||||||
|
|
||||||
while (spaces.any((s) =>
|
while (spaces.any((s) =>
|
||||||
(s.position - newPosition).distance < horizontalGap &&
|
(s.position - newPosition).distance < horizontalGap &&
|
||||||
@ -659,7 +636,18 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
newPosition += Offset(horizontalGap, 0);
|
newPosition += Offset(horizontalGap, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final duplicatedName = _generateCopyName(original.name);
|
final duplicatedName =
|
||||||
|
SpaceHelper.generateUniqueSpaceName(original.name, spaces);
|
||||||
|
|
||||||
|
final List<SubspaceModel>? duplicatedSubspaces;
|
||||||
|
final List<Tag>? duplicatedTags;
|
||||||
|
if (original.spaceModel != null) {
|
||||||
|
duplicatedTags = [];
|
||||||
|
duplicatedSubspaces = [];
|
||||||
|
} else {
|
||||||
|
duplicatedTags = original.tags;
|
||||||
|
duplicatedSubspaces = original.subspaces;
|
||||||
|
}
|
||||||
|
|
||||||
final duplicated = SpaceModel(
|
final duplicated = SpaceModel(
|
||||||
name: duplicatedName,
|
name: duplicatedName,
|
||||||
@ -670,8 +658,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
status: SpaceStatus.newSpace,
|
status: SpaceStatus.newSpace,
|
||||||
parent: duplicatedParent,
|
parent: duplicatedParent,
|
||||||
spaceModel: original.spaceModel,
|
spaceModel: original.spaceModel,
|
||||||
subspaces: original.subspaces,
|
subspaces: duplicatedSubspaces,
|
||||||
tags: original.tags,
|
tags: duplicatedTags,
|
||||||
);
|
);
|
||||||
|
|
||||||
originalToDuplicate[original] = duplicated;
|
originalToDuplicate[original] = duplicated;
|
||||||
@ -684,7 +672,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
final newConnection = Connection(
|
final newConnection = Connection(
|
||||||
startSpace: duplicatedParent,
|
startSpace: duplicatedParent,
|
||||||
endSpace: duplicated,
|
endSpace: duplicated,
|
||||||
direction: "down",
|
direction: original.incomingConnection?.direction ?? 'down',
|
||||||
);
|
);
|
||||||
connections.add(newConnection);
|
connections.add(newConnection);
|
||||||
duplicated.incomingConnection = newConnection;
|
duplicated.incomingConnection = newConnection;
|
||||||
@ -723,10 +711,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
child.incomingConnection?.direction == "down" ?? false;
|
child.incomingConnection?.direction == "down" ?? false;
|
||||||
|
|
||||||
if (isDownDirection && childrenWithDownDirection.length == 1) {
|
if (isDownDirection && childrenWithDownDirection.length == 1) {
|
||||||
// Place the only "down" child vertically aligned with the parent
|
|
||||||
childStartPosition = duplicated.position + Offset(0, verticalGap);
|
childStartPosition = duplicated.position + Offset(0, verticalGap);
|
||||||
} else if (!isDownDirection) {
|
} else if (!isDownDirection) {
|
||||||
// Position children with other directions horizontally
|
|
||||||
childStartPosition = duplicated.position + Offset(horizontalGap, 0);
|
childStartPosition = duplicated.position + Offset(horizontalGap, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -747,14 +733,4 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
duplicateRecursive(space, space.position, duplicatedParent);
|
duplicateRecursive(space, space.position, duplicatedParent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> _getAllTagValues(List<SpaceModel> spaces) {
|
|
||||||
final List<String> allTags = [];
|
|
||||||
for (final space in spaces) {
|
|
||||||
if (space.tags != null) {
|
|
||||||
allTags.addAll(space.listAllTagValues());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return allTags;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
|||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart';
|
import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart';
|
import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/helper/space_helper.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
|
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart';
|
import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||||
@ -40,6 +41,7 @@ class CreateSpaceDialog extends StatefulWidget {
|
|||||||
final List<SubspaceModel>? subspaces;
|
final List<SubspaceModel>? subspaces;
|
||||||
final List<Tag>? tags;
|
final List<Tag>? tags;
|
||||||
final List<String>? allTags;
|
final List<String>? allTags;
|
||||||
|
final SpaceTemplateModel? currentSpaceModel;
|
||||||
|
|
||||||
const CreateSpaceDialog(
|
const CreateSpaceDialog(
|
||||||
{super.key,
|
{super.key,
|
||||||
@ -54,7 +56,8 @@ class CreateSpaceDialog extends StatefulWidget {
|
|||||||
this.selectedProducts = const [],
|
this.selectedProducts = const [],
|
||||||
this.spaceModels,
|
this.spaceModels,
|
||||||
this.subspaces,
|
this.subspaces,
|
||||||
this.tags});
|
this.tags,
|
||||||
|
this.currentSpaceModel});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
CreateSpaceDialogState createState() => CreateSpaceDialogState();
|
CreateSpaceDialogState createState() => CreateSpaceDialogState();
|
||||||
@ -81,12 +84,22 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
|
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
|
||||||
isOkButtonEnabled =
|
isOkButtonEnabled =
|
||||||
enteredName.isNotEmpty || nameController.text.isNotEmpty;
|
enteredName.isNotEmpty || nameController.text.isNotEmpty;
|
||||||
tags = widget.tags ?? [];
|
if (widget.currentSpaceModel != null) {
|
||||||
subspaces = widget.subspaces ?? [];
|
subspaces = [];
|
||||||
|
tags = [];
|
||||||
|
} else {
|
||||||
|
tags = widget.tags ?? [];
|
||||||
|
subspaces = widget.subspaces ?? [];
|
||||||
|
}
|
||||||
|
selectedSpaceModel = widget.currentSpaceModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
bool isSpaceModelDisabled = (tags != null && tags!.isNotEmpty ||
|
||||||
|
subspaces != null && subspaces!.isNotEmpty);
|
||||||
|
bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null);
|
||||||
|
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: widget.isEdit
|
title: widget.isEdit
|
||||||
@ -165,7 +178,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
isNameFieldInvalid = value.isEmpty;
|
isNameFieldInvalid = value.isEmpty;
|
||||||
|
|
||||||
if (!isNameFieldInvalid) {
|
if (!isNameFieldInvalid) {
|
||||||
if (_isNameConflict(value)) {
|
if (SpaceHelper.isNameConflict(value, widget.parentSpace, widget.editSpace)) {
|
||||||
isNameFieldExist = true;
|
isNameFieldExist = true;
|
||||||
isOkButtonEnabled = false;
|
isOkButtonEnabled = false;
|
||||||
} else {
|
} else {
|
||||||
@ -232,11 +245,14 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_showLinkSpaceModelDialog(context);
|
isSpaceModelDisabled
|
||||||
|
? null
|
||||||
|
: _showLinkSpaceModelDialog(context);
|
||||||
},
|
},
|
||||||
child: const ButtonContentWidget(
|
child: ButtonContentWidget(
|
||||||
svgAssets: Assets.link,
|
svgAssets: Assets.link,
|
||||||
label: 'Link a space model',
|
label: 'Link a space model',
|
||||||
|
disabled: isSpaceModelDisabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Container(
|
: Container(
|
||||||
@ -325,12 +341,15 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
overlayColor: ColorsManager.transparentColor,
|
overlayColor: ColorsManager.transparentColor,
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
_showSubSpaceDialog(context, enteredName, [],
|
isTagsAndSubspaceModelDisabled
|
||||||
false, widget.products, subspaces);
|
? null
|
||||||
|
: _showSubSpaceDialog(context, enteredName,
|
||||||
|
[], false, widget.products, subspaces);
|
||||||
},
|
},
|
||||||
child: const ButtonContentWidget(
|
child: ButtonContentWidget(
|
||||||
icon: Icons.add,
|
icon: Icons.add,
|
||||||
label: 'Create Sub Space',
|
label: 'Create Sub Space',
|
||||||
|
disabled: isTagsAndSubspaceModelDisabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: SizedBox(
|
: SizedBox(
|
||||||
@ -457,7 +476,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AssignTagDialog(
|
builder: (context) => AssignTagDialog(
|
||||||
products: widget.products,
|
products: widget.products,
|
||||||
subspaces: widget.subspaces,
|
subspaces: subspaces,
|
||||||
|
allTags: widget.allTags,
|
||||||
addedProducts: TagHelper
|
addedProducts: TagHelper
|
||||||
.createInitialSelectedProductsForTags(
|
.createInitialSelectedProductsForTags(
|
||||||
tags ?? [], subspaces),
|
tags ?? [], subspaces),
|
||||||
@ -483,20 +503,22 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
)
|
)
|
||||||
: TextButton(
|
: TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_showTagCreateDialog(
|
isTagsAndSubspaceModelDisabled
|
||||||
context,
|
? null
|
||||||
enteredName,
|
: _showTagCreateDialog(
|
||||||
widget.isEdit,
|
context,
|
||||||
widget.products,
|
enteredName,
|
||||||
subspaces,
|
widget.isEdit,
|
||||||
);
|
widget.products,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
child: const ButtonContentWidget(
|
child: ButtonContentWidget(
|
||||||
icon: Icons.add,
|
icon: Icons.add,
|
||||||
label: 'Add Devices',
|
label: 'Add Devices',
|
||||||
|
disabled: isTagsAndSubspaceModelDisabled,
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -570,15 +592,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isNameConflict(String value) {
|
|
||||||
return (widget.parentSpace?.children.any((child) => child.name == value) ??
|
|
||||||
false) ||
|
|
||||||
(widget.parentSpace?.name == value) ||
|
|
||||||
(widget.editSpace?.parent?.name == value) ||
|
|
||||||
(widget.editSpace?.children.any((child) => child.name == value) ??
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showLinkSpaceModelDialog(BuildContext context) {
|
void _showLinkSpaceModelDialog(BuildContext context) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@ -617,9 +631,26 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
products: products,
|
products: products,
|
||||||
existingSubSpaces: existingSubSpaces,
|
existingSubSpaces: existingSubSpaces,
|
||||||
onSave: (slectedSubspaces) {
|
onSave: (slectedSubspaces) {
|
||||||
|
final List<Tag> tagsToAppendToSpace = [];
|
||||||
|
|
||||||
|
if (slectedSubspaces != null) {
|
||||||
|
final updatedIds =
|
||||||
|
slectedSubspaces.map((s) => s.internalId).toSet();
|
||||||
|
if (existingSubSpaces != null) {
|
||||||
|
final deletedSubspaces = existingSubSpaces
|
||||||
|
.where((s) => !updatedIds.contains(s.internalId))
|
||||||
|
.toList();
|
||||||
|
for (var s in deletedSubspaces) {
|
||||||
|
if (s.tags != null) {
|
||||||
|
tagsToAppendToSpace.addAll(s.tags!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (slectedSubspaces != null) {
|
if (slectedSubspaces != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
subspaces = slectedSubspaces;
|
subspaces = slectedSubspaces;
|
||||||
|
tags?.addAll(tagsToAppendToSpace);
|
||||||
selectedSpaceModel = null;
|
selectedSpaceModel = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -629,7 +660,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showTagCreateDialog(BuildContext context, String name, bool isEdit,
|
void _showTagCreateDialog(BuildContext context, String name, bool isEdit,
|
||||||
List<ProductModel>? products, List<SubspaceModel>? subspaces) {
|
List<ProductModel>? products) {
|
||||||
isEdit
|
isEdit
|
||||||
? showDialog(
|
? showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -11,7 +11,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_tem
|
|||||||
import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart';
|
||||||
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
||||||
|
|
||||||
class LoadedSpaceView extends StatelessWidget {
|
class LoadedSpaceView extends StatefulWidget {
|
||||||
final List<CommunityModel> communities;
|
final List<CommunityModel> communities;
|
||||||
final CommunityModel? selectedCommunity;
|
final CommunityModel? selectedCommunity;
|
||||||
final SpaceModel? selectedSpace;
|
final SpaceModel? selectedSpace;
|
||||||
@ -26,41 +26,73 @@ class LoadedSpaceView extends StatelessWidget {
|
|||||||
this.selectedSpace,
|
this.selectedSpace,
|
||||||
this.products,
|
this.products,
|
||||||
this.spaceModels,
|
this.spaceModels,
|
||||||
required this.shouldNavigateToSpaceModelPage
|
required this.shouldNavigateToSpaceModelPage,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
_LoadedSpaceViewState createState() => _LoadedSpaceViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoadedSpaceViewState extends State<LoadedSpaceView> {
|
||||||
|
late List<SpaceTemplateModel> _spaceModels;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_spaceModels = List.from(widget.spaceModels ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant LoadedSpaceView oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (widget.spaceModels != oldWidget.spaceModels) {
|
||||||
|
setState(() {
|
||||||
|
_spaceModels = List.from(widget.spaceModels ?? []);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSpaceModelsUpdated(List<SpaceTemplateModel> updatedModels) {
|
||||||
|
if (mounted && updatedModels != _spaceModels) {
|
||||||
|
setState(() {
|
||||||
|
_spaceModels = updatedModels;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
return Stack(
|
return Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
SidebarWidget(
|
SidebarWidget(
|
||||||
communities: communities,
|
communities: widget.communities,
|
||||||
selectedSpaceUuid:
|
selectedSpaceUuid: widget.selectedSpace?.uuid ??
|
||||||
selectedSpace?.uuid ?? selectedCommunity?.uuid ?? '',
|
widget.selectedCommunity?.uuid ??
|
||||||
|
'',
|
||||||
),
|
),
|
||||||
shouldNavigateToSpaceModelPage
|
widget.shouldNavigateToSpaceModelPage
|
||||||
? Expanded(
|
? Expanded(
|
||||||
child: BlocProvider(
|
child: BlocProvider(
|
||||||
create: (context) => SpaceModelBloc(
|
create: (context) => SpaceModelBloc(
|
||||||
api: SpaceModelManagementApi(),
|
api: SpaceModelManagementApi(),
|
||||||
initialSpaceModels: spaceModels ?? [],
|
initialSpaceModels: _spaceModels,
|
||||||
),
|
),
|
||||||
child: SpaceModelPage(
|
child: SpaceModelPage(
|
||||||
products: products,
|
products: widget.products,
|
||||||
|
onSpaceModelsUpdated: _onSpaceModelsUpdated,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: CommunityStructureArea(
|
: CommunityStructureArea(
|
||||||
selectedCommunity: selectedCommunity,
|
selectedCommunity: widget.selectedCommunity,
|
||||||
selectedSpace: selectedSpace,
|
selectedSpace: widget.selectedSpace,
|
||||||
spaces: selectedCommunity?.spaces ?? [],
|
spaces: widget.selectedCommunity?.spaces ?? [],
|
||||||
products: products,
|
products: widget.products,
|
||||||
communities: communities,
|
communities: widget.communities,
|
||||||
spaceModels: spaceModels,
|
spaceModels: _spaceModels,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -68,13 +100,4 @@ class LoadedSpaceView extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SpaceModel? findSpaceByUuid(String? uuid, List<CommunityModel> communities) {
|
|
||||||
for (var community in communities) {
|
|
||||||
for (var space in community.spaces) {
|
|
||||||
if (space.uuid == uuid) return space;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
|||||||
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
|
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
|
||||||
final tags = List<Tag>.from(currentState.tags);
|
final tags = List<Tag>.from(currentState.tags);
|
||||||
tags[event.index].tag = event.tag;
|
tags[event.index].tag = event.tag;
|
||||||
|
|
||||||
emit(AssignTagLoaded(
|
emit(AssignTagLoaded(
|
||||||
tags: tags,
|
tags: tags,
|
||||||
isSaveEnabled: _validateTags(tags),
|
isSaveEnabled: _validateTags(tags),
|
||||||
@ -78,6 +79,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
|||||||
emit(AssignTagLoaded(
|
emit(AssignTagLoaded(
|
||||||
tags: tags,
|
tags: tags,
|
||||||
isSaveEnabled: _validateTags(tags),
|
isSaveEnabled: _validateTags(tags),
|
||||||
|
errorMessage: _getValidationError(tags),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -106,12 +108,13 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
|||||||
emit(AssignTagLoaded(
|
emit(AssignTagLoaded(
|
||||||
tags: updatedTags,
|
tags: updatedTags,
|
||||||
isSaveEnabled: _validateTags(updatedTags),
|
isSaveEnabled: _validateTags(updatedTags),
|
||||||
|
errorMessage: _getValidationError(updatedTags),
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
emit(const AssignTagLoaded(
|
emit(const AssignTagLoaded(
|
||||||
tags: [],
|
tags: [],
|
||||||
isSaveEnabled: false,
|
isSaveEnabled: false,
|
||||||
));
|
errorMessage: 'Failed to delete tag'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -125,7 +128,10 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
|||||||
|
|
||||||
String? _getValidationError(List<Tag> tags) {
|
String? _getValidationError(List<Tag> tags) {
|
||||||
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
|
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
|
||||||
if (hasEmptyTag) return 'Tags cannot be empty.';
|
if (hasEmptyTag) {
|
||||||
|
return 'Tags cannot be empty.';
|
||||||
|
}
|
||||||
|
|
||||||
final duplicateTags = tags
|
final duplicateTags = tags
|
||||||
.map((tag) => tag.tag?.trim() ?? '')
|
.map((tag) => tag.tag?.trim() ?? '')
|
||||||
.fold<Map<String, int>>({}, (map, tag) {
|
.fold<Map<String, int>>({}, (map, tag) {
|
||||||
|
@ -21,11 +21,11 @@ class AssignTagLoaded extends AssignTagState {
|
|||||||
const AssignTagLoaded({
|
const AssignTagLoaded({
|
||||||
required this.tags,
|
required this.tags,
|
||||||
required this.isSaveEnabled,
|
required this.isSaveEnabled,
|
||||||
this.errorMessage,
|
required this.errorMessage,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [tags, isSaveEnabled];
|
List<Object> get props => [tags, isSaveEnabled, errorMessage ?? ''];
|
||||||
}
|
}
|
||||||
|
|
||||||
class AssignTagError extends AssignTagState {
|
class AssignTagError extends AssignTagState {
|
||||||
|
@ -71,6 +71,7 @@ class AssignTagDialog extends StatelessWidget {
|
|||||||
child: DataTable(
|
child: DataTable(
|
||||||
headingRowColor: WidgetStateProperty.all(
|
headingRowColor: WidgetStateProperty.all(
|
||||||
ColorsManager.dataHeaderGrey),
|
ColorsManager.dataHeaderGrey),
|
||||||
|
key: ValueKey(state.tags.length),
|
||||||
border: TableBorder.all(
|
border: TableBorder.all(
|
||||||
color: ColorsManager.dataHeaderGrey,
|
color: ColorsManager.dataHeaderGrey,
|
||||||
width: 1,
|
width: 1,
|
||||||
@ -120,6 +121,7 @@ class AssignTagDialog extends StatelessWidget {
|
|||||||
final controller = controllers[index];
|
final controller = controllers[index];
|
||||||
final availableTags = getAvailableTags(
|
final availableTags = getAvailableTags(
|
||||||
allTags ?? [], state.tags, tag);
|
allTags ?? [], state.tags, tag);
|
||||||
|
|
||||||
return DataRow(
|
return DataRow(
|
||||||
cells: [
|
cells: [
|
||||||
DataCell(Text((index + 1).toString())),
|
DataCell(Text((index + 1).toString())),
|
||||||
@ -158,6 +160,8 @@ class AssignTagDialog extends StatelessWidget {
|
|||||||
.add(DeleteTag(
|
.add(DeleteTag(
|
||||||
tagToDelete: tag,
|
tagToDelete: tag,
|
||||||
tags: state.tags));
|
tags: state.tags));
|
||||||
|
|
||||||
|
controllers.removeAt(index);
|
||||||
},
|
},
|
||||||
tooltip: 'Delete Tag',
|
tooltip: 'Delete Tag',
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
@ -233,11 +237,13 @@ class AssignTagDialog extends StatelessWidget {
|
|||||||
label: 'Add New Device',
|
label: 'Add New Device',
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final updatedTags = List<Tag>.from(state.tags);
|
final updatedTags = List<Tag>.from(state.tags);
|
||||||
final result = processTags(updatedTags, subspaces);
|
final result =
|
||||||
|
TagHelper.processTags(updatedTags, subspaces);
|
||||||
|
|
||||||
final processedTags =
|
final processedTags =
|
||||||
result['updatedTags'] as List<Tag>;
|
result['updatedTags'] as List<Tag>;
|
||||||
final processedSubspaces = result['subspaces'];
|
final processedSubspaces = List<SubspaceModel>.from(
|
||||||
|
result['subspaces'] as List<dynamic>);
|
||||||
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
@ -253,6 +259,7 @@ class AssignTagDialog extends StatelessWidget {
|
|||||||
spaceTags: processedTags,
|
spaceTags: processedTags,
|
||||||
isCreate: false,
|
isCreate: false,
|
||||||
onSave: onSave,
|
onSave: onSave,
|
||||||
|
allTags: allTags,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -263,20 +270,21 @@ class AssignTagDialog extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
backgroundColor: state.isSaveEnabled
|
backgroundColor: ColorsManager.secondaryColor,
|
||||||
? ColorsManager.secondaryColor
|
foregroundColor: state.isSaveEnabled
|
||||||
: ColorsManager.grayColor,
|
? ColorsManager.whiteColors
|
||||||
foregroundColor: ColorsManager.whiteColors,
|
: ColorsManager.whiteColorsWithOpacity,
|
||||||
onPressed: state.isSaveEnabled
|
onPressed: state.isSaveEnabled
|
||||||
? () async {
|
? () async {
|
||||||
final updatedTags = List<Tag>.from(state.tags);
|
final updatedTags = List<Tag>.from(state.tags);
|
||||||
final result =
|
final result = TagHelper.processTags(
|
||||||
processTags(updatedTags, subspaces);
|
updatedTags, subspaces);
|
||||||
|
|
||||||
final processedTags =
|
final processedTags =
|
||||||
result['updatedTags'] as List<Tag>;
|
result['updatedTags'] as List<Tag>;
|
||||||
final processedSubspaces =
|
final processedSubspaces =
|
||||||
result['subspaces'] as List<SubspaceModel>;
|
List<SubspaceModel>.from(
|
||||||
|
result['subspaces'] as List<dynamic>);
|
||||||
onSave?.call(processedTags, processedSubspaces);
|
onSave?.call(processedTags, processedSubspaces);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
@ -301,115 +309,12 @@ class AssignTagDialog extends StatelessWidget {
|
|||||||
|
|
||||||
List<String> getAvailableTags(
|
List<String> getAvailableTags(
|
||||||
List<String> allTags, List<Tag> currentTags, Tag currentTag) {
|
List<String> allTags, List<Tag> currentTags, Tag currentTag) {
|
||||||
return allTags
|
List<String> availableTagsForTagModel = TagHelper.getAvailableTags<Tag>(
|
||||||
.where((tagValue) => !currentTags
|
allTags: allTags,
|
||||||
.where((e) => e != currentTag) // Exclude the current row
|
currentTags: currentTags,
|
||||||
.map((e) => e.tag)
|
currentTag: currentTag,
|
||||||
.contains(tagValue))
|
getTag: (tag) => tag.tag ?? '',
|
||||||
.toList();
|
);
|
||||||
}
|
return availableTagsForTagModel;
|
||||||
|
|
||||||
Map<String, dynamic> processTags(
|
|
||||||
List<Tag> updatedTags, List<SubspaceModel>? subspaces) {
|
|
||||||
final modifiedTags = List<Tag>.from(updatedTags);
|
|
||||||
final modifiedSubspaces = List<SubspaceModel>.from(subspaces ?? []);
|
|
||||||
|
|
||||||
if (subspaces != null) {
|
|
||||||
for (var subspace in subspaces) {
|
|
||||||
subspace.tags?.removeWhere(
|
|
||||||
(tag) => !modifiedTags
|
|
||||||
.any((updatedTag) => updatedTag.internalId == tag.internalId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var tag in modifiedTags.toList()) {
|
|
||||||
if (modifiedSubspaces.isEmpty) continue;
|
|
||||||
|
|
||||||
final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces);
|
|
||||||
|
|
||||||
if ((tag.location == 'Main Space' || tag.location == null) &&
|
|
||||||
(prevIndice == null ||
|
|
||||||
modifiedSubspaces[prevIndice].subspaceName == 'Main Space')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((tag.location == 'Main Space' || tag.location == null) &&
|
|
||||||
prevIndice != null) {
|
|
||||||
modifiedSubspaces[prevIndice]
|
|
||||||
.tags
|
|
||||||
?.removeWhere((t) => t.internalId == tag.internalId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((tag.location != 'Main Space' && tag.location != null) &&
|
|
||||||
prevIndice == null) {
|
|
||||||
final newIndex = modifiedSubspaces
|
|
||||||
.indexWhere((subspace) => subspace.subspaceName == tag.location);
|
|
||||||
if (newIndex != -1) {
|
|
||||||
if (modifiedSubspaces[newIndex]
|
|
||||||
.tags
|
|
||||||
?.any((t) => t.internalId == tag.internalId) !=
|
|
||||||
true) {
|
|
||||||
tag.location = modifiedSubspaces[newIndex].subspaceName;
|
|
||||||
modifiedSubspaces[newIndex].tags?.add(tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((tag.location != 'Main Space' && tag.location != null) &&
|
|
||||||
tag.location != modifiedSubspaces[prevIndice!].subspaceName) {
|
|
||||||
modifiedSubspaces[prevIndice]
|
|
||||||
.tags
|
|
||||||
?.removeWhere((t) => t.internalId == tag.internalId);
|
|
||||||
final newIndex = modifiedSubspaces
|
|
||||||
.indexWhere((subspace) => subspace.subspaceName == tag.location);
|
|
||||||
if (newIndex != -1) {
|
|
||||||
if (modifiedSubspaces[newIndex]
|
|
||||||
.tags
|
|
||||||
?.any((t) => t.internalId == tag.internalId) !=
|
|
||||||
true) {
|
|
||||||
tag.location = modifiedSubspaces[newIndex].subspaceName;
|
|
||||||
modifiedSubspaces[newIndex].tags?.add(tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((tag.location != 'Main Space' && tag.location != null) &&
|
|
||||||
tag.location == modifiedSubspaces[prevIndice!].subspaceName) {
|
|
||||||
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((tag.location == 'Main Space' || tag.location == null) &&
|
|
||||||
prevIndice != null) {
|
|
||||||
modifiedSubspaces[prevIndice]
|
|
||||||
.tags
|
|
||||||
?.removeWhere((t) => t.internalId == tag.internalId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'updatedTags': modifiedTags,
|
|
||||||
'subspaces': modifiedSubspaces,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
int? checkTagExistInSubspace(Tag tag, List<SubspaceModel>? subspaces) {
|
|
||||||
if (subspaces == null) return null;
|
|
||||||
for (int i = 0; i < subspaces.length; i++) {
|
|
||||||
final subspace = subspaces[i];
|
|
||||||
if (subspace.tags == null) continue;
|
|
||||||
for (var t in subspace.tags!) {
|
|
||||||
if (tag.internalId == t.internalId) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,6 +82,7 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
child: DataTable(
|
child: DataTable(
|
||||||
headingRowColor: WidgetStateProperty.all(
|
headingRowColor: WidgetStateProperty.all(
|
||||||
ColorsManager.dataHeaderGrey),
|
ColorsManager.dataHeaderGrey),
|
||||||
|
key: ValueKey(state.tags.length),
|
||||||
border: TableBorder.all(
|
border: TableBorder.all(
|
||||||
color: ColorsManager.dataHeaderGrey,
|
color: ColorsManager.dataHeaderGrey,
|
||||||
width: 1,
|
width: 1,
|
||||||
@ -133,8 +134,9 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
: List.generate(state.tags.length, (index) {
|
: List.generate(state.tags.length, (index) {
|
||||||
final tag = state.tags[index];
|
final tag = state.tags[index];
|
||||||
final controller = controllers[index];
|
final controller = controllers[index];
|
||||||
final availableTags = getAvailableTags(
|
final availableTags =
|
||||||
allTags ?? [], state.tags, tag);
|
TagHelper.getAvailableTagModels(
|
||||||
|
allTags ?? [], state.tags, tag);
|
||||||
|
|
||||||
return DataRow(
|
return DataRow(
|
||||||
cells: [
|
cells: [
|
||||||
@ -175,6 +177,7 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
.add(DeleteTagModel(
|
.add(DeleteTagModel(
|
||||||
tagToDelete: tag,
|
tagToDelete: tag,
|
||||||
tags: state.tags));
|
tags: state.tags));
|
||||||
|
controllers.removeAt(index);
|
||||||
},
|
},
|
||||||
tooltip: 'Delete Tag',
|
tooltip: 'Delete Tag',
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
@ -254,11 +257,15 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
final updatedTags =
|
final updatedTags =
|
||||||
List<TagModel>.from(state.tags);
|
List<TagModel>.from(state.tags);
|
||||||
final result =
|
final result =
|
||||||
processTags(updatedTags, subspaces);
|
TagHelper.updateSubspaceTagModels(
|
||||||
|
updatedTags, subspaces);
|
||||||
|
|
||||||
final processedTags =
|
final processedTags =
|
||||||
result['updatedTags'] as List<TagModel>;
|
result['updatedTags'] as List<TagModel>;
|
||||||
final processedSubspaces = result['subspaces'];
|
final processedSubspaces =
|
||||||
|
List<SubspaceTemplateModel>.from(
|
||||||
|
result['subspaces'] as List<dynamic>);
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
@ -297,22 +304,25 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
backgroundColor: state.isSaveEnabled
|
backgroundColor: ColorsManager.secondaryColor,
|
||||||
? ColorsManager.secondaryColor
|
foregroundColor: state.isSaveEnabled
|
||||||
: ColorsManager.grayColor,
|
? ColorsManager.whiteColors
|
||||||
foregroundColor: ColorsManager.whiteColors,
|
: ColorsManager.whiteColorsWithOpacity,
|
||||||
onPressed: state.isSaveEnabled
|
onPressed: state.isSaveEnabled
|
||||||
? () async {
|
? () async {
|
||||||
final updatedTags =
|
final updatedTags =
|
||||||
List<TagModel>.from(state.tags);
|
List<TagModel>.from(state.tags);
|
||||||
|
|
||||||
final result =
|
final result =
|
||||||
processTags(updatedTags, subspaces);
|
TagHelper.updateSubspaceTagModels(
|
||||||
|
updatedTags, subspaces);
|
||||||
|
|
||||||
final processedTags =
|
final processedTags =
|
||||||
result['updatedTags'] as List<TagModel>;
|
result['updatedTags'] as List<TagModel>;
|
||||||
final processedSubspaces =
|
final processedSubspaces =
|
||||||
result['subspaces']
|
List<SubspaceTemplateModel>.from(
|
||||||
as List<SubspaceTemplateModel>;
|
result['subspaces']
|
||||||
|
as List<dynamic>);
|
||||||
|
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
.popUntil((route) => route.isFirst);
|
.popUntil((route) => route.isFirst);
|
||||||
@ -356,120 +366,4 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> getAvailableTags(
|
|
||||||
List<String> allTags, List<TagModel> currentTags, TagModel currentTag) {
|
|
||||||
return allTags
|
|
||||||
.where((tagValue) => !currentTags
|
|
||||||
.where((e) => e != currentTag) // Exclude the current row
|
|
||||||
.map((e) => e.tag)
|
|
||||||
.contains(tagValue))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
int? checkTagExistInSubspace(
|
|
||||||
TagModel tag, List<SubspaceTemplateModel>? subspaces) {
|
|
||||||
if (subspaces == null) return null;
|
|
||||||
for (int i = 0; i < subspaces.length; i++) {
|
|
||||||
final subspace = subspaces[i];
|
|
||||||
if (subspace.tags == null) continue;
|
|
||||||
for (var t in subspace.tags!) {
|
|
||||||
if (tag.internalId == t.internalId) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> processTags(
|
|
||||||
List<TagModel> updatedTags, List<SubspaceTemplateModel>? subspaces) {
|
|
||||||
final modifiedTags = List<TagModel>.from(updatedTags);
|
|
||||||
final modifiedSubspaces = List<SubspaceTemplateModel>.from(subspaces ?? []);
|
|
||||||
|
|
||||||
if (subspaces != null) {
|
|
||||||
for (var subspace in subspaces) {
|
|
||||||
subspace.tags?.removeWhere(
|
|
||||||
(tag) => !modifiedTags
|
|
||||||
.any((updatedTag) => updatedTag.internalId == tag.internalId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var tag in modifiedTags.toList()) {
|
|
||||||
if (modifiedSubspaces.isEmpty) continue;
|
|
||||||
|
|
||||||
final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces);
|
|
||||||
|
|
||||||
if ((tag.location == 'Main Space' || tag.location == null) &&
|
|
||||||
(prevIndice == null ||
|
|
||||||
modifiedSubspaces[prevIndice].subspaceName == 'Main Space')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((tag.location == 'Main Space' || tag.location == null) &&
|
|
||||||
prevIndice != null) {
|
|
||||||
modifiedSubspaces[prevIndice]
|
|
||||||
.tags
|
|
||||||
?.removeWhere((t) => t.internalId == tag.internalId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((tag.location != 'Main Space' && tag.location != null) &&
|
|
||||||
prevIndice == null) {
|
|
||||||
final newIndex = modifiedSubspaces
|
|
||||||
.indexWhere((subspace) => subspace.subspaceName == tag.location);
|
|
||||||
if (newIndex != -1) {
|
|
||||||
if (modifiedSubspaces[newIndex]
|
|
||||||
.tags
|
|
||||||
?.any((t) => t.internalId == tag.internalId) !=
|
|
||||||
true) {
|
|
||||||
tag.location = modifiedSubspaces[newIndex].subspaceName;
|
|
||||||
modifiedSubspaces[newIndex].tags?.add(tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((tag.location != 'Main Space' && tag.location != null) &&
|
|
||||||
tag.location != modifiedSubspaces[prevIndice!].subspaceName) {
|
|
||||||
modifiedSubspaces[prevIndice]
|
|
||||||
.tags
|
|
||||||
?.removeWhere((t) => t.internalId == tag.internalId);
|
|
||||||
final newIndex = modifiedSubspaces
|
|
||||||
.indexWhere((subspace) => subspace.subspaceName == tag.location);
|
|
||||||
if (newIndex != -1) {
|
|
||||||
if (modifiedSubspaces[newIndex]
|
|
||||||
.tags
|
|
||||||
?.any((t) => t.internalId == tag.internalId) !=
|
|
||||||
true) {
|
|
||||||
tag.location = modifiedSubspaces[newIndex].subspaceName;
|
|
||||||
modifiedSubspaces[newIndex].tags?.add(tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((tag.location != 'Main Space' && tag.location != null) &&
|
|
||||||
tag.location == modifiedSubspaces[prevIndice!].subspaceName) {
|
|
||||||
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((tag.location == 'Main Space' || tag.location == null) &&
|
|
||||||
prevIndice != null) {
|
|
||||||
modifiedSubspaces[prevIndice]
|
|
||||||
.tags
|
|
||||||
?.removeWhere((t) => t.internalId == tag.internalId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'updatedTags': modifiedTags,
|
|
||||||
'subspaces': modifiedSubspaces,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
21
lib/pages/spaces_management/helper/connection_helper.dart
Normal file
21
lib/pages/spaces_management/helper/connection_helper.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||||
|
|
||||||
|
class ConnectionHelper {
|
||||||
|
static Offset getCenterPosition(Size screenSize) {
|
||||||
|
return Offset(
|
||||||
|
screenSize.width / 2 - 260,
|
||||||
|
screenSize.height / 2 - 200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isHighlightedConnection(
|
||||||
|
Connection connection, SpaceModel? selectedSpace) {
|
||||||
|
if (selectedSpace == null) return true;
|
||||||
|
|
||||||
|
return connection.startSpace == selectedSpace ||
|
||||||
|
connection.endSpace == selectedSpace;
|
||||||
|
}
|
||||||
|
}
|
94
lib/pages/spaces_management/helper/space_helper.dart
Normal file
94
lib/pages/spaces_management/helper/space_helper.dart
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||||
|
|
||||||
|
class SpaceHelper {
|
||||||
|
static SpaceModel? findSpaceByUuid(
|
||||||
|
String? uuid, List<CommunityModel> communities) {
|
||||||
|
for (var community in communities) {
|
||||||
|
for (var space in community.spaces) {
|
||||||
|
if (space.uuid == uuid) return space;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SpaceModel? findSpaceByInternalId(
|
||||||
|
String? internalId, List<SpaceModel> spaces) {
|
||||||
|
if (internalId != null) {
|
||||||
|
for (var space in spaces) {
|
||||||
|
if (space.internalId == internalId) return space;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String generateUniqueSpaceName(
|
||||||
|
String originalName, List<SpaceModel> spaces) {
|
||||||
|
final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim();
|
||||||
|
int maxNumber = 0;
|
||||||
|
|
||||||
|
for (var space in spaces) {
|
||||||
|
final match = RegExp(r'^(.*?)\((\d+)\)$').firstMatch(space.name);
|
||||||
|
if (match != null && match.group(1)?.trim() == baseName) {
|
||||||
|
int existingNumber = int.parse(match.group(2)!);
|
||||||
|
if (existingNumber > maxNumber) {
|
||||||
|
maxNumber = existingNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "$baseName(${maxNumber + 1})";
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isSave(List<SpaceModel> spaces) {
|
||||||
|
return spaces.isNotEmpty &&
|
||||||
|
spaces.any((space) =>
|
||||||
|
space.status == SpaceStatus.newSpace ||
|
||||||
|
space.status == SpaceStatus.modified ||
|
||||||
|
space.status == SpaceStatus.deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isHighlightedSpace(SpaceModel space, SpaceModel? selectedSpace) {
|
||||||
|
if (selectedSpace == null) return true;
|
||||||
|
|
||||||
|
return space == selectedSpace ||
|
||||||
|
selectedSpace.parent?.internalId == space.internalId ||
|
||||||
|
selectedSpace.children
|
||||||
|
?.any((child) => child.internalId == space.internalId) ==
|
||||||
|
true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isNameConflict(
|
||||||
|
String value, SpaceModel? parentSpace, SpaceModel? editSpace) {
|
||||||
|
final siblings = parentSpace?.children
|
||||||
|
.where((child) => child.internalId != editSpace?.internalId)
|
||||||
|
.toList() ??
|
||||||
|
[];
|
||||||
|
|
||||||
|
final editSiblings = editSpace?.parent?.children
|
||||||
|
.where((child) => child.internalId != editSpace.internalId)
|
||||||
|
.toList() ??
|
||||||
|
[];
|
||||||
|
|
||||||
|
final editSiblingConflict =
|
||||||
|
editSiblings.any((child) => child.name == value);
|
||||||
|
|
||||||
|
final siblingConflict = siblings.any((child) => child.name == value);
|
||||||
|
|
||||||
|
final parentConflict = parentSpace?.name == value &&
|
||||||
|
parentSpace?.internalId != editSpace?.internalId;
|
||||||
|
|
||||||
|
final parentOfEditSpaceConflict = editSpace?.parent?.name == value &&
|
||||||
|
editSpace?.parent?.internalId != editSpace?.internalId;
|
||||||
|
|
||||||
|
final childConflict =
|
||||||
|
editSpace?.children.any((child) => child.name == value) ?? false;
|
||||||
|
|
||||||
|
return siblingConflict ||
|
||||||
|
parentConflict ||
|
||||||
|
editSiblingConflict ||
|
||||||
|
parentOfEditSpaceConflict ||
|
||||||
|
childConflict;
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,147 @@
|
|||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||||
|
|
||||||
class TagHelper {
|
class TagHelper {
|
||||||
|
static Map<String, dynamic> updateTags<T>({
|
||||||
|
required List<T> updatedTags,
|
||||||
|
required List<dynamic>? subspaces,
|
||||||
|
required String Function(T) getInternalId,
|
||||||
|
required String? Function(T) getLocation,
|
||||||
|
required void Function(T, String) setLocation,
|
||||||
|
required String Function(dynamic) getSubspaceName,
|
||||||
|
required List<T>? Function(dynamic) getSubspaceTags,
|
||||||
|
required void Function(dynamic, List<T>?) setSubspaceTags,
|
||||||
|
required int? Function(T, List<dynamic>) checkTagExistInSubspace,
|
||||||
|
}) {
|
||||||
|
final modifiedTags = List<T>.from(updatedTags);
|
||||||
|
final modifiedSubspaces = List<dynamic>.from(subspaces ?? []);
|
||||||
|
|
||||||
|
if (subspaces != null) {
|
||||||
|
for (var subspace in subspaces) {
|
||||||
|
getSubspaceTags(subspace)?.removeWhere(
|
||||||
|
(tag) => !modifiedTags.any(
|
||||||
|
(updatedTag) => getInternalId(updatedTag) == getInternalId(tag)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var tag in modifiedTags.toList()) {
|
||||||
|
if (modifiedSubspaces.isEmpty) continue;
|
||||||
|
|
||||||
|
final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces);
|
||||||
|
final tagLocation = getLocation(tag);
|
||||||
|
|
||||||
|
if ((tagLocation == 'Main Space' || tagLocation == null) &&
|
||||||
|
(prevIndice == null ||
|
||||||
|
getSubspaceName(modifiedSubspaces[prevIndice]) == 'Main Space')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((tagLocation == 'Main Space' || tagLocation == null) &&
|
||||||
|
prevIndice != null) {
|
||||||
|
getSubspaceTags(modifiedSubspaces[prevIndice])
|
||||||
|
?.removeWhere((t) => getInternalId(t) == getInternalId(tag));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((tagLocation != 'Main Space' && tagLocation != null) &&
|
||||||
|
prevIndice == null) {
|
||||||
|
final newIndex = modifiedSubspaces
|
||||||
|
.indexWhere((subspace) => getSubspaceName(subspace) == tagLocation);
|
||||||
|
|
||||||
|
if (newIndex != -1) {
|
||||||
|
if (getSubspaceTags(modifiedSubspaces[newIndex])
|
||||||
|
?.any((t) => getInternalId(t) == getInternalId(tag)) !=
|
||||||
|
true) {
|
||||||
|
setLocation(tag, getSubspaceName(modifiedSubspaces[newIndex]));
|
||||||
|
final subspaceTags =
|
||||||
|
getSubspaceTags(modifiedSubspaces[newIndex]) ?? [];
|
||||||
|
subspaceTags.add(tag);
|
||||||
|
setSubspaceTags(modifiedSubspaces[newIndex], subspaceTags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiedTags.removeWhere((t) => getInternalId(t) == getInternalId(tag));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((tagLocation != 'Main Space' && tagLocation != null) &&
|
||||||
|
tagLocation != getSubspaceName(modifiedSubspaces[prevIndice!])) {
|
||||||
|
getSubspaceTags(modifiedSubspaces[prevIndice])
|
||||||
|
?.removeWhere((t) => getInternalId(t) == getInternalId(tag));
|
||||||
|
|
||||||
|
final newIndex = modifiedSubspaces
|
||||||
|
.indexWhere((subspace) => getSubspaceName(subspace) == tagLocation);
|
||||||
|
|
||||||
|
if (newIndex != -1) {
|
||||||
|
if (getSubspaceTags(modifiedSubspaces[newIndex])
|
||||||
|
?.any((t) => getInternalId(t) == getInternalId(tag)) !=
|
||||||
|
true) {
|
||||||
|
setLocation(tag, getSubspaceName(modifiedSubspaces[newIndex]));
|
||||||
|
final subspaceTags =
|
||||||
|
getSubspaceTags(modifiedSubspaces[newIndex]) ?? [];
|
||||||
|
subspaceTags.add(tag);
|
||||||
|
setSubspaceTags(modifiedSubspaces[newIndex], subspaceTags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiedTags.removeWhere((t) => getInternalId(t) == getInternalId(tag));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((tagLocation != 'Main Space' && tagLocation != null) &&
|
||||||
|
tagLocation == getSubspaceName(modifiedSubspaces[prevIndice!])) {
|
||||||
|
modifiedTags.removeWhere((t) => getInternalId(t) == getInternalId(tag));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((tagLocation == 'Main Space' || tagLocation == null) &&
|
||||||
|
prevIndice != null) {
|
||||||
|
getSubspaceTags(modifiedSubspaces[prevIndice])
|
||||||
|
?.removeWhere((t) => getInternalId(t) == getInternalId(tag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'updatedTags': modifiedTags,
|
||||||
|
'subspaces': modifiedSubspaces,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> getAvailableTags<T>({
|
||||||
|
required List<String> allTags,
|
||||||
|
required List<T> currentTags,
|
||||||
|
required T currentTag,
|
||||||
|
required String? Function(T) getTag, // Allow nullable return type
|
||||||
|
}) {
|
||||||
|
return allTags
|
||||||
|
.where((tagValue) => !currentTags
|
||||||
|
.where((e) => e != currentTag) // Exclude the current row
|
||||||
|
.map((e) => getTag(e) ?? '') // Handle null values gracefully
|
||||||
|
.contains(tagValue))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> getAvailableTagModels(
|
||||||
|
List<String> allTags, List<TagModel> currentTags, TagModel currentTag) {
|
||||||
|
List<String> availableTagsForTagModel =
|
||||||
|
TagHelper.getAvailableTags<TagModel>(
|
||||||
|
allTags: allTags,
|
||||||
|
currentTags: currentTags,
|
||||||
|
currentTag: currentTag,
|
||||||
|
getTag: (tag) => tag.tag ?? '',
|
||||||
|
);
|
||||||
|
return availableTagsForTagModel;
|
||||||
|
}
|
||||||
|
|
||||||
static List<TagModel> generateInitialTags({
|
static List<TagModel> generateInitialTags({
|
||||||
List<TagModel>? spaceTagModels,
|
List<TagModel>? spaceTagModels,
|
||||||
List<SubspaceTemplateModel>? subspaces,
|
List<SubspaceTemplateModel>? subspaces,
|
||||||
@ -36,7 +171,7 @@ class TagHelper {
|
|||||||
return initialTags;
|
return initialTags;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<Tag> generateInitialForTags({
|
static List<Tag> generateInitialForTags({
|
||||||
List<Tag>? spaceTags,
|
List<Tag>? spaceTags,
|
||||||
List<SubspaceModel>? subspaces,
|
List<SubspaceModel>? subspaces,
|
||||||
}) {
|
}) {
|
||||||
@ -145,4 +280,85 @@ class TagHelper {
|
|||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int? checkTagExistInSubspaceModels(
|
||||||
|
TagModel tag, List<dynamic>? subspaces) {
|
||||||
|
if (subspaces == null) return null;
|
||||||
|
|
||||||
|
for (int i = 0; i < subspaces.length; i++) {
|
||||||
|
final subspace = subspaces[i] as SubspaceTemplateModel; // Explicit cast
|
||||||
|
if (subspace.tags == null) continue;
|
||||||
|
for (var t in subspace.tags!) {
|
||||||
|
if (tag.internalId == t.internalId) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic> updateSubspaceTagModels(
|
||||||
|
List<TagModel> updatedTags, List<SubspaceTemplateModel>? subspaces) {
|
||||||
|
return TagHelper.updateTags<TagModel>(
|
||||||
|
updatedTags: updatedTags,
|
||||||
|
subspaces: subspaces,
|
||||||
|
getInternalId: (tag) => tag.internalId,
|
||||||
|
getLocation: (tag) => tag.location,
|
||||||
|
setLocation: (tag, location) => tag.location = location,
|
||||||
|
getSubspaceName: (subspace) => subspace.subspaceName,
|
||||||
|
getSubspaceTags: (subspace) => subspace.tags,
|
||||||
|
setSubspaceTags: (subspace, tags) => subspace.tags = tags,
|
||||||
|
checkTagExistInSubspace: checkTagExistInSubspaceModels,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int? checkTagExistInSubspace(Tag tag, List<dynamic>? subspaces) {
|
||||||
|
if (subspaces == null) return null;
|
||||||
|
for (int i = 0; i < subspaces.length; i++) {
|
||||||
|
final subspace = subspaces[i];
|
||||||
|
if (subspace.tags == null) continue;
|
||||||
|
for (var t in subspace.tags!) {
|
||||||
|
if (tag.internalId == t.internalId) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic> processTags(
|
||||||
|
List<Tag> updatedTags, List<SubspaceModel>? subspaces) {
|
||||||
|
return TagHelper.updateTags<Tag>(
|
||||||
|
updatedTags: updatedTags,
|
||||||
|
subspaces: subspaces,
|
||||||
|
getInternalId: (tag) => tag.internalId,
|
||||||
|
getLocation: (tag) => tag.location,
|
||||||
|
setLocation: (tag, location) => tag.location = location,
|
||||||
|
getSubspaceName: (subspace) => subspace.subspaceName,
|
||||||
|
getSubspaceTags: (subspace) => subspace.tags,
|
||||||
|
setSubspaceTags: (subspace, tags) => subspace.tags = tags,
|
||||||
|
checkTagExistInSubspace: checkTagExistInSubspace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> getAllTagValues(
|
||||||
|
List<CommunityModel> communities, List<SpaceTemplateModel>? spaceModels) {
|
||||||
|
final Set<String> allTags = {};
|
||||||
|
|
||||||
|
if (spaceModels != null) {
|
||||||
|
for (var model in spaceModels) {
|
||||||
|
allTags.addAll(model.listAllTagValues());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final community in communities) {
|
||||||
|
for (final space in community.spaces) {
|
||||||
|
if (space.tags != null) {
|
||||||
|
allTags.addAll(space.listAllTagValues());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allTags.toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
|
||||||
|
@ -11,8 +11,10 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
|||||||
|
|
||||||
class SpaceModelPage extends StatelessWidget {
|
class SpaceModelPage extends StatelessWidget {
|
||||||
final List<ProductModel>? products;
|
final List<ProductModel>? products;
|
||||||
|
final Function(List<SpaceTemplateModel>)? onSpaceModelsUpdated;
|
||||||
|
|
||||||
const SpaceModelPage({Key? key, this.products}) : super(key: key);
|
const SpaceModelPage({Key? key, this.products, this.onSpaceModelsUpdated})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -25,6 +27,10 @@ class SpaceModelPage extends StatelessWidget {
|
|||||||
final allTagValues = _getAllTagValues(spaceModels);
|
final allTagValues = _getAllTagValues(spaceModels);
|
||||||
final allSpaceModelNames = _getAllSpaceModelName(spaceModels);
|
final allSpaceModelNames = _getAllSpaceModelName(spaceModels);
|
||||||
|
|
||||||
|
if (onSpaceModelsUpdated != null) {
|
||||||
|
onSpaceModelsUpdated!(spaceModels);
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: ColorsManager.whiteColors,
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
body: Padding(
|
body: Padding(
|
||||||
|
@ -6,55 +6,64 @@ class ButtonContentWidget extends StatelessWidget {
|
|||||||
final IconData? icon;
|
final IconData? icon;
|
||||||
final String label;
|
final String label;
|
||||||
final String? svgAssets;
|
final String? svgAssets;
|
||||||
|
final bool disabled;
|
||||||
|
|
||||||
const ButtonContentWidget(
|
const ButtonContentWidget({
|
||||||
{Key? key, this.icon, required this.label, this.svgAssets})
|
Key? key,
|
||||||
: super(key: key);
|
this.icon,
|
||||||
|
required this.label,
|
||||||
|
this.svgAssets,
|
||||||
|
this.disabled = false,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
return SizedBox(
|
return Opacity(
|
||||||
width: screenWidth * 0.25,
|
opacity: disabled ? 0.5 : 1.0,
|
||||||
child: Container(
|
child: SizedBox(
|
||||||
decoration: BoxDecoration(
|
width: screenWidth * 0.25,
|
||||||
color: ColorsManager.textFieldGreyColor,
|
child: Container(
|
||||||
border: Border.all(
|
decoration: BoxDecoration(
|
||||||
color: ColorsManager.neutralGray,
|
color: ColorsManager.textFieldGreyColor,
|
||||||
width: 3.0,
|
border: Border.all(
|
||||||
|
color: ColorsManager.neutralGray,
|
||||||
|
width: 3.0,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(20),
|
child: Padding(
|
||||||
),
|
padding:
|
||||||
child: Padding(
|
const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
|
child: Row(
|
||||||
child: Row(
|
children: [
|
||||||
children: [
|
if (icon != null)
|
||||||
if (icon != null)
|
Icon(
|
||||||
Icon(
|
icon,
|
||||||
icon,
|
color: ColorsManager.spaceColor,
|
||||||
color: ColorsManager.spaceColor,
|
),
|
||||||
),
|
if (svgAssets != null)
|
||||||
if (svgAssets != null)
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.only(left: 6.0),
|
||||||
padding: const EdgeInsets.only(left: 6.0),
|
child: SvgPicture.asset(
|
||||||
child: SvgPicture.asset(
|
svgAssets!,
|
||||||
svgAssets!,
|
width: screenWidth * 0.015, // Adjust icon size
|
||||||
width: screenWidth * 0.015, // Adjust icon size
|
height: screenWidth * 0.015,
|
||||||
height: screenWidth * 0.015,
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
],
|
||||||
Expanded(
|
),
|
||||||
child: Text(
|
|
||||||
label,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -129,10 +129,16 @@ class CreateSpaceModelDialog extends StatelessWidget {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
SubspaceModelCreate(
|
SubspaceModelCreate(
|
||||||
subspaces: state.space.subspaceModels ?? [],
|
subspaces: state.space.subspaceModels ?? [],
|
||||||
onSpaceModelUpdate: (updatedSubspaces) {
|
tags: state.space.tags ?? [],
|
||||||
|
onSpaceModelUpdate: (updatedSubspaces,updatedTags) {
|
||||||
context
|
context
|
||||||
.read<CreateSpaceModelBloc>()
|
.read<CreateSpaceModelBloc>()
|
||||||
.add(AddSubspacesToSpaceTemplate(updatedSubspaces));
|
.add(AddSubspacesToSpaceTemplate(updatedSubspaces));
|
||||||
|
if(updatedTags!=null){
|
||||||
|
context
|
||||||
|
.read<CreateSpaceModelBloc>()
|
||||||
|
.add(AddTagsToSpaceTemplate(updatedTags));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/common/edit_chip.dart';
|
import 'package:syncrow_web/common/edit_chip.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart';
|
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart';
|
||||||
@ -8,13 +9,16 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
|||||||
|
|
||||||
class SubspaceModelCreate extends StatefulWidget {
|
class SubspaceModelCreate extends StatefulWidget {
|
||||||
final List<SubspaceTemplateModel> subspaces;
|
final List<SubspaceTemplateModel> subspaces;
|
||||||
final void Function(List<SubspaceTemplateModel> newSubspaces)?
|
final void Function(
|
||||||
|
List<SubspaceTemplateModel> newSubspaces, List<TagModel>? tags)?
|
||||||
onSpaceModelUpdate;
|
onSpaceModelUpdate;
|
||||||
|
final List<TagModel> tags;
|
||||||
|
|
||||||
const SubspaceModelCreate({
|
const SubspaceModelCreate({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.subspaces,
|
required this.subspaces,
|
||||||
this.onSpaceModelUpdate,
|
this.onSpaceModelUpdate,
|
||||||
|
required this.tags,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -24,11 +28,13 @@ class SubspaceModelCreate extends StatefulWidget {
|
|||||||
class _SubspaceModelCreateState extends State<SubspaceModelCreate> {
|
class _SubspaceModelCreateState extends State<SubspaceModelCreate> {
|
||||||
late List<SubspaceTemplateModel> _subspaces;
|
late List<SubspaceTemplateModel> _subspaces;
|
||||||
String? errorSubspaceId;
|
String? errorSubspaceId;
|
||||||
|
late List<TagModel> _tags;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_subspaces = List.from(widget.subspaces);
|
_subspaces = List.from(widget.subspaces);
|
||||||
|
_tags = List.from(widget.tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -105,14 +111,26 @@ class _SubspaceModelCreateState extends State<SubspaceModelCreate> {
|
|||||||
isEdit: true,
|
isEdit: true,
|
||||||
dialogTitle: dialogTitle,
|
dialogTitle: dialogTitle,
|
||||||
existingSubSpaces: _subspaces,
|
existingSubSpaces: _subspaces,
|
||||||
|
|
||||||
onUpdate: (subspaceModels) {
|
onUpdate: (subspaceModels) {
|
||||||
|
final updatedIds = subspaceModels.map((s) => s.internalId).toSet();
|
||||||
|
final deletedSubspaces = _subspaces
|
||||||
|
.where((s) => !updatedIds.contains(s.internalId))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final List<TagModel> tagsToAppendToSpace = [];
|
||||||
|
|
||||||
|
for (var s in deletedSubspaces) {
|
||||||
|
if (s.tags != null) {
|
||||||
|
tagsToAppendToSpace.addAll(s.tags!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_subspaces = subspaceModels;
|
_subspaces = subspaceModels;
|
||||||
errorSubspaceId = null;
|
_tags.addAll(tagsToAppendToSpace);
|
||||||
});
|
});
|
||||||
if (widget.onSpaceModelUpdate != null) {
|
if (widget.onSpaceModelUpdate != null) {
|
||||||
widget.onSpaceModelUpdate!(subspaceModels);
|
widget.onSpaceModelUpdate!(subspaceModels, _tags);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -52,6 +52,13 @@ class _SubspaceNameDisplayWidgetState extends State<SubspaceNameDisplayWidget> {
|
|||||||
|
|
||||||
void _handleValidationAndSave() {
|
void _handleValidationAndSave() {
|
||||||
final updatedName = _controller.text;
|
final updatedName = _controller.text;
|
||||||
|
|
||||||
|
if (updatedName.isEmpty) {
|
||||||
|
setState(() {
|
||||||
|
errorText = 'Subspace name cannot be empty.';
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (widget.validateName(updatedName)) {
|
if (widget.validateName(updatedName)) {
|
||||||
setState(() {
|
setState(() {
|
||||||
errorText = null;
|
errorText = null;
|
||||||
|
@ -18,8 +18,7 @@ class UserPermissionApi {
|
|||||||
showServerMessage: true,
|
showServerMessage: true,
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
debugPrint('fetchUsers Response: $json');
|
debugPrint('fetchUsers Response: $json');
|
||||||
final List<dynamic> data =
|
final List<dynamic> data = json['data'] ?? [];
|
||||||
json['data'] ?? []; // Default to an empty list if no data
|
|
||||||
return data.map((item) => RolesUserModel.fromJson(item)).toList();
|
return data.map((item) => RolesUserModel.fromJson(item)).toList();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -119,7 +118,7 @@ class UserPermissionApi {
|
|||||||
);
|
);
|
||||||
return response ?? 'Unknown error occurred';
|
return response ?? 'Unknown error occurred';
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
final errorMessage = e.response?.data['error'];
|
final errorMessage = e.response?.data['error']['message'];
|
||||||
return errorMessage;
|
return errorMessage;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return e.toString();
|
return e.toString();
|
||||||
@ -205,7 +204,6 @@ class UserPermissionApi {
|
|||||||
.replaceAll("{invitedUserUuid}", userUuid),
|
.replaceAll("{invitedUserUuid}", userUuid),
|
||||||
body: bodya,
|
body: bodya,
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
print('changeUserStatusById==${json['success']}');
|
|
||||||
return json['success'];
|
return json['success'];
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -213,7 +211,6 @@ class UserPermissionApi {
|
|||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
print(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user