Compare commits

..

47 Commits

Author SHA1 Message Date
41605bef6b Merge pull request #102 from SyncrowIOT/sp_1171
Added space tree to the add user dialog
2025-02-27 12:32:53 +03:00
fd24d6bd27 change community and spaces in user manage 2025-02-27 12:29:12 +03:00
5ea29efaf5 Merge pull request #103 from SyncrowIOT/SP-1151-FE-Implement-Delete-Action-for-the-Space-Model
Sp 1151 fe implement delete action for the space model
2025-02-26 10:14:06 +04:00
70cb12236b Clear cache on edit dialog and fixed a selection issue 2025-02-26 02:59:17 +03:00
9f75ce817e added space model delete to bloc and api 2025-02-25 21:31:40 +04:00
aa23daa0b4 added DeleteSpaceModelDialog into SpaceModelCardWidget for delete 2025-02-25 21:31:25 +04:00
61f0c3ad2b DeleteSpaceModelDialog into a separate widget 2025-02-25 21:30:27 +04:00
553c77d1e3 Added space tree to the add user dialog 2025-02-25 00:55:44 +03:00
27fef7ddaa Pulled latest changes 2025-02-20 12:55:07 +03:00
010176cc25 Added clear data and cached events in space tree 2025-02-20 12:53:59 +03:00
d08a1d1037 Merge pull request #100 from SyncrowIOT/bugfix/fix-get-all-device-endpoint
fixed device endpoint
2025-02-20 13:33:55 +04:00
e634154fb3 fixed device endpoint 2025-02-20 13:31:31 +04:00
c0f59aba61 Merge pull request #98 from SyncrowIOT:SP-1186-FE-Cursor-Resets-After-Each-Character-When-Creating-a-Tag
fixed text theme of tag list
2025-02-20 11:48:36 +04:00
7ee7681e09 fixed theme 2025-02-20 11:47:58 +04:00
57c5f4752c fixed theme 2025-02-20 11:36:08 +04:00
ad4f2ae382 Merge pull request #96 from SyncrowIOT/SP-1186-FE-Cursor-Resets-After-Each-Character-When-Creating-a-Tag
fixed cursor issue
2025-02-20 11:29:03 +04:00
eafb811d2e fixed cursor issue 2025-02-20 11:26:30 +04:00
2d0dcc41df Merge pull request #93 from SyncrowIOT/bugfix/fix-get-user-endpoint
change endpoint
2025-02-19 17:54:19 +04:00
d89e3fac8e change endpoint 2025-02-19 17:53:20 +04:00
25acd67351 Bug fixes in the side tree 2025-02-19 02:13:14 +03:00
0c2a092f4d Added side tree to space model 2025-02-18 12:31:43 +03:00
b968b5a6eb Merge pull request #92 from SyncrowIOT/bugfix/change-project-cubit
Bugfix/change-project-cubit
2025-02-18 13:22:12 +04:00
05edc7641a removed project cubit from all classes 2025-02-18 12:56:51 +04:00
b8204f1015 changed project class to use shared preference 2025-02-18 12:56:25 +04:00
d87fec796b Merge pull request #91 from SyncrowIOT/add_space_name
spaceName
2025-02-17 12:56:47 +03:00
b00b0c82dc spaceName 2025-02-17 12:36:04 +03:00
0aa029a2fc Removed static space id and community id in the routine 2025-02-17 03:32:40 +03:00
dec3a25639 Merge pull request #90 from SyncrowIOT/feat/use-project-uuid-instead-of-hardcode
Feat/use project UUID instead of hardcode
2025-02-17 01:11:53 +03:00
581dcf7016 changed endpoint for get visitor password and access control 2025-02-16 23:32:46 +04:00
e1609309cf Added ProjectCubit as a dependency in blocs for managing project-level state. 2025-02-15 00:36:20 +04:00
da445e11aa Modified _fetchUserInfo to update the ProjectCubit with the retrieved user's project UUID. 2025-02-15 00:35:34 +04:00
55de7fab0f Introduced ProjectCubit to handle project-related state. 2025-02-15 00:34:38 +04:00
a623f1c723 Merge pull request #89 from SyncrowIOT/disable_edit
disable_edit_user
2025-02-06 11:17:14 +03:00
c5f5992c18 disable_edit_user 2025-02-06 11:12:43 +03:00
98ad7090d8 Removed prints and unused code 2025-02-06 01:01:21 +03:00
ea08024b82 Merge pull request #87 from SyncrowIOT/bugfix/fix-tag-repeat
Bugfix/update-subspace-tag-value
2025-02-06 00:59:11 +03:00
f6d66185b3 updating tags inside subspace 2025-02-06 00:18:44 +04:00
ead5297ba1 Merge pull request #86 from SyncrowIOT/roles_permissions_bugs
Roles permissions bugs
2025-02-05 21:53:46 +03:00
9a6bf5cbaf privacy policy fixes 2025-02-05 18:16:09 +03:00
51fbe64209 fixes bugs 2025-02-05 16:53:36 +03:00
49fa80e7d8 Merge pull request #85 from SyncrowIOT/bugfix/fix-tag-repeat
Fixed tag repeat
2025-02-05 17:47:47 +04:00
1aa15e5dd6 fixed loading 2025-02-05 17:01:03 +04:00
962f2d6861 Fixed tgag repeat 2025-02-05 16:00:53 +04:00
bd6219f915 Merge pull request #84 from SyncrowIOT/side_tree
Enhanced the side tree design
2025-02-05 11:56:23 +03:00
132cafcaa2 Enhanced the side tree design 2025-02-05 11:52:44 +03:00
8862ad95f3 Merge pull request #83 from SyncrowIOT/bugfix/fix-issue-in-creating-space-with-duplicate
Bugfix/fix-issue-in-creating-space-with-duplicate
2025-02-05 12:09:38 +04:00
95cee89b4c Merge pull request #82 from SyncrowIOT/SP-951-FE-Link-Space-Model-Pop-Up
added edit space sibling conflict
2025-02-04 15:57:37 +04:00
70 changed files with 2287 additions and 1250 deletions

View File

@ -0,0 +1,6 @@
<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.4 2.10938H11.7333V1.58203C11.7333 0.709699 11.0156 0 10.1333 0H5.86667C4.98443 0 4.26667 0.709699 4.26667 1.58203V2.10938H1.6C0.71776 2.10938 0 2.81907 0 3.69141C0 4.392 0.463111 4.9873 1.1024 5.19472L2.05369 16.5493C2.1222 17.3628 2.82258 18 3.64814 18H12.3519C13.1775 18 13.8778 17.3628 13.9464 16.5491L14.8976 5.19469C15.5369 4.9873 16 4.392 16 3.69141C16 2.81907 15.2822 2.10938 14.4 2.10938ZM5.33333 1.58203C5.33333 1.29125 5.57259 1.05469 5.86667 1.05469H10.1333C10.4274 1.05469 10.6667 1.29125 10.6667 1.58203V2.10938H5.33333V1.58203ZM12.8833 16.4618C12.8605 16.7329 12.6271 16.9453 12.3519 16.9453H3.64814C3.37298 16.9453 3.13952 16.7329 3.11673 16.462L2.17934 5.27344H13.8207L12.8833 16.4618ZM14.4 4.21875H1.6C1.30592 4.21875 1.06667 3.98218 1.06667 3.69141C1.06667 3.40063 1.30592 3.16406 1.6 3.16406H14.4C14.6941 3.16406 14.9333 3.40063 14.9333 3.69141C14.9333 3.98218 14.6941 4.21875 14.4 4.21875Z" fill="#999999"/>
<path d="M5.86561 15.3307L5.33228 6.82286C5.31404 6.53215 5.05957 6.31106 4.76698 6.32916C4.47297 6.3472 4.24943 6.59744 4.26764 6.88811L4.80097 15.396C4.8185 15.6756 5.05331 15.8907 5.33278 15.8907C5.64165 15.8907 5.88456 15.6335 5.86561 15.3307Z" fill="#999999"/>
<path d="M7.99989 6.32812C7.70534 6.32812 7.46655 6.56423 7.46655 6.85547V15.3633C7.46655 15.6545 7.70534 15.8906 7.99989 15.8906C8.29443 15.8906 8.53322 15.6545 8.53322 15.3633V6.85547C8.53322 6.56423 8.29443 6.32812 7.99989 6.32812Z" fill="#999999"/>
<path d="M11.233 6.32915C10.9396 6.31112 10.6859 6.53215 10.6677 6.82285L10.1343 15.3307C10.1162 15.6213 10.3397 15.8716 10.6337 15.8896C10.9278 15.9076 11.1808 15.6865 11.199 15.3959L11.7323 6.8881C11.7505 6.5974 11.527 6.34715 11.233 6.32915Z" fill="#999999"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -20,15 +20,22 @@ class DialogTextfieldDropdown extends StatefulWidget {
class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
bool _isOpen = false;
late OverlayEntry _overlayEntry;
OverlayEntry? _overlayEntry;
final TextEditingController _controller = TextEditingController();
late List<String> _filteredItems; // Filtered items list
final FocusNode _focusNode = FocusNode();
List<String> _filteredItems = [];
@override
void initState() {
super.initState();
_controller.text = widget.initialValue ?? 'Select Tag';
_filteredItems = List.from(widget.items); // Initialize filtered items
_controller.text = widget.initialValue ?? '';
_filteredItems = List.from(widget.items);
_focusNode.addListener(() {
if (!_focusNode.hasFocus) {
_closeDropdown();
}
});
}
void _toggleDropdown() {
@ -41,13 +48,16 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
void _openDropdown() {
_overlayEntry = _createOverlayEntry();
Overlay.of(context).insert(_overlayEntry);
Overlay.of(context).insert(_overlayEntry!);
_isOpen = true;
}
void _closeDropdown() {
_overlayEntry.remove();
_isOpen = false;
if (_isOpen && _overlayEntry != null) {
_overlayEntry!.remove();
_overlayEntry = null;
_isOpen = false;
}
}
OverlayEntry _createOverlayEntry() {
@ -58,9 +68,7 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
return OverlayEntry(
builder: (context) {
return GestureDetector(
onTap: () {
_closeDropdown();
},
onTap: _closeDropdown,
behavior: HitTestBehavior.translucent,
child: Stack(
children: [
@ -72,40 +80,44 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
elevation: 4.0,
child: Container(
color: ColorsManager.whiteColors,
constraints: const BoxConstraints(
maxHeight: 200.0,
),
child: ListView.builder(
shrinkWrap: true,
itemCount: _filteredItems.length,
itemBuilder: (context, index) {
final item = _filteredItems[index];
return Container(
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: ColorsManager.lightGrayBorderColor,
width: 1.0,
constraints: const BoxConstraints(maxHeight: 200.0),
child: StatefulBuilder(
builder: (context, setStateDropdown) {
return ListView.builder(
shrinkWrap: true,
itemCount: _filteredItems.length,
itemBuilder: (context, index) {
final item = _filteredItems[index];
return Container(
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: ColorsManager.lightGrayBorderColor,
width: 1.0,
),
),
),
),
),
child: ListTile(
title: Text(item,
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: ColorsManager.textPrimaryColor)),
onTap: () {
_controller.text = item;
widget.onSelected(item);
setState(() {
_filteredItems
.remove(item); // Remove selected item
});
_closeDropdown();
},
),
child: ListTile(
title: Text(item,
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: ColorsManager
.textPrimaryColor)),
onTap: () {
_controller.text = item;
widget.onSelected(item);
setState(() {
_filteredItems
.remove(item); // Remove selected item
});
_closeDropdown();
},
),
);
},
);
},
),
@ -122,7 +134,8 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _toggleDropdown,
onTap: () => FocusScope.of(context).unfocus(),
behavior: HitTestBehavior.opaque,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
decoration: BoxDecoration(
@ -135,23 +148,26 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
Expanded(
child: TextFormField(
controller: _controller,
onChanged: (value) {
setState(() {
_filteredItems = widget.items
.where((item) =>
item.toLowerCase().contains(value.toLowerCase()))
.toList(); // Filter items dynamically
});
focusNode: _focusNode,
onFieldSubmitted: (value) {
widget.onSelected(value);
_closeDropdown();
},
onTapOutside: (event) {
widget.onSelected(_controller.text);
_closeDropdown();
},
style: Theme.of(context).textTheme.bodyMedium,
decoration: const InputDecoration(
hintText: 'Enter or Select tag',
hintText: 'Enter or Select a tag',
border: InputBorder.none,
),
),
),
const Icon(Icons.arrow_drop_down),
GestureDetector(
onTap: _toggleDropdown,
child: const Icon(Icons.arrow_drop_down),
),
],
),
),

View File

@ -3,18 +3,33 @@ import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class CustomSearchBar extends StatelessWidget {
class CustomSearchBar extends StatefulWidget {
final TextEditingController? controller;
final String hintText;
final String? searchQuery;
final Function(String)? onSearchChanged; // Callback for search input changes
const CustomSearchBar({
super.key,
this.controller,
this.searchQuery = '',
this.hintText = 'Search',
this.onSearchChanged,
});
@override
State<CustomSearchBar> createState() => _CustomSearchBarState();
}
class _CustomSearchBarState extends State<CustomSearchBar> {
@override
void dispose() {
if (widget.controller != null) {
widget.controller!.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
@ -36,16 +51,17 @@ class CustomSearchBar extends StatelessWidget {
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
),
child: TextField(
controller: controller,
child: TextFormField(
controller: widget.controller,
initialValue: widget.searchQuery,
style: const TextStyle(
color: Colors.black,
),
onChanged: onSearchChanged, // Call the callback on text change
onChanged: widget.onSearchChanged, // Call the callback on text change
decoration: InputDecoration(
filled: true,
fillColor: ColorsManager.textFieldGreyColor,
hintText: hintText,
hintText: widget.hintText,
hintStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: ColorsManager.lightGrayColor,
fontSize: 12,

View File

@ -1,25 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
class SpacesSideTree extends StatefulWidget {
final List<CommunityModel> communities;
final String? selectedSpaceUuid;
const SpacesSideTree({
super.key,
required this.communities,
this.selectedSpaceUuid,
});
@override
State<SpacesSideTree> createState() => _SpacesSideTreeState();
}
class _SpacesSideTreeState extends State<SpacesSideTree> {
String _searchQuery = '';
String? _selectedSpaceUuid;
String? _selectedId;
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}

View File

@ -13,6 +13,7 @@ import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.da
import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
import 'package:syncrow_web/utils/theme/theme.dart';
Future<void> main() async {
@ -26,9 +27,8 @@ Future<void> main() async {
}
class MyApp extends StatelessWidget {
MyApp({
super.key,
});
MyApp({super.key});
final GoRouter _router = GoRouter(
initialLocation: RoutesConst.auth,
@ -70,6 +70,8 @@ class MyApp extends StatelessWidget {
PointerDeviceKind.unknown,
},
),
key: NavigationService.navigatorKey,
// scaffoldMessengerKey: NavigationService.snackbarKey,
theme: myTheme,
routerConfig: _router,
));

View File

@ -3,10 +3,14 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
import 'package:syncrow_web/pages/access_management/model/password_model.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/common/hour_picker_dialog.dart';
import 'package:syncrow_web/services/access_mang_api.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
class AccessBloc extends Bloc<AccessEvent, AccessState> {
@ -30,8 +34,9 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
Future<void> _onFetchTableData(
FetchTableData event, Emitter<AccessState> emit) async {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
emit(AccessLoaded());
data = await AccessMangApi().fetchVisitorPassword();
data = await AccessMangApi().fetchVisitorPassword(projectUuid);
filteredData = data;
updateTabsCount();
emit(TableLoaded(data));
@ -88,8 +93,8 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
return Theme(
data: ThemeData.light().copyWith(
colorScheme: ColorScheme.light(
primary: ColorsManager.blackColor,
onPrimary: Colors.white,
primary: ColorsManager.blackColor,
onPrimary: Colors.white,
onSurface: ColorsManager.grayColor,
),
textButtonTheme: TextButtonThemeData(

View File

@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
import 'package:syncrow_web/pages/common/custom_table.dart';
@ -27,7 +28,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
final isLargeScreen = isLargeScreenSize(context);
final isSmallScreen = isSmallScreenSize(context);
final isHalfMediumScreen = isHafMediumScreenSize(context);
final padding = isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
final padding =
isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
return WebScaffold(
enableMenuSidebar: false,
@ -39,7 +41,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
),
rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocProvider(
create: (BuildContext context) => AccessBloc()..add(FetchTableData()),
create: (BuildContext context) =>
AccessBloc()..add(FetchTableData()),
child: BlocConsumer<AccessBloc, AccessState>(
listener: (context, state) {},
builder: (context, state) {
@ -93,11 +96,14 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
return [
item.passwordName,
item.passwordType.value,
accessBloc.timestampToDate(item.effectiveTime),
accessBloc.timestampToDate(item.invalidTime),
accessBloc
.timestampToDate(item.effectiveTime),
accessBloc
.timestampToDate(item.invalidTime),
item.deviceName.toString(),
item.authorizerEmail.toString(),
accessBloc.timestampToDate(item.invalidTime),
accessBloc
.timestampToDate(item.invalidTime),
item.passwordStatus.value,
];
}).toList(),
@ -108,7 +114,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
})));
}
Wrap _buildVisitorAdminPasswords(BuildContext context, AccessBloc accessBloc) {
Wrap _buildVisitorAdminPasswords(
BuildContext context, AccessBloc accessBloc) {
return Wrap(
spacing: 10,
runSpacing: 10,
@ -134,7 +141,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
borderRadius: 8,
child: Text(
'Create Visitor Password ',
style: context.textTheme.titleSmall!.copyWith(color: Colors.white, fontSize: 12),
style: context.textTheme.titleSmall!
.copyWith(color: Colors.white, fontSize: 12),
)),
),
// Container(
@ -172,8 +180,10 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
@ -191,8 +201,10 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
@ -221,7 +233,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
onSearch: () {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
@ -249,8 +262,10 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
@ -274,7 +289,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
onSearch: () {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));

View File

@ -9,9 +9,13 @@ import 'package:syncrow_web/pages/auth/model/login_with_email_model.dart';
import 'package:syncrow_web/pages/auth/model/region_model.dart';
import 'package:syncrow_web/pages/auth/model/token.dart';
import 'package:syncrow_web/pages/auth/model/user_model.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/services/auth_api.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
@ -31,8 +35,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
////////////////////////////// forget password //////////////////////////////////
final TextEditingController forgetEmailController = TextEditingController();
final TextEditingController forgetPasswordController =
TextEditingController();
final TextEditingController forgetPasswordController = TextEditingController();
final TextEditingController forgetOtp = TextEditingController();
final forgetFormKey = GlobalKey<FormState>();
final forgetEmailKey = GlobalKey<FormState>();
@ -49,8 +52,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
return;
}
_remainingTime = 1;
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
try {
forgetEmailValidate = '';
_remainingTime = (await AuthenticationAPI.sendOtp(
@ -87,8 +89,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
_timer?.cancel();
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
} else {
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
}
});
}
@ -98,8 +99,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
}
Future<void> changePassword(
ChangePasswordEvent event, Emitter<AuthState> emit) async {
Future<void> changePassword(ChangePasswordEvent event, Emitter<AuthState> emit) async {
emit(LoadingForgetState());
try {
var response = await AuthenticationAPI.verifyOtp(
@ -115,8 +115,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
}
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage =
errorData['error']['message'] ?? 'something went wrong';
String errorMessage = errorData['error']['message'] ?? 'something went wrong';
validate = errorMessage;
emit(AuthInitialState());
}
@ -130,9 +129,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
}
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
emit(TimerState(
isButtonEnabled: event.isButtonEnabled,
remainingTime: event.remainingTime));
emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime));
}
///////////////////////////////////// login /////////////////////////////////////
@ -180,18 +177,15 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
return;
}
if (token.accessTokenIsNotEmpty) {
FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write(
key: Token.loginAccessTokenKey, value: token.accessToken);
await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken);
const FlutterSecureStorage().write(
key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString());
user = UserModel.fromToken(token);
loginEmailController.clear();
loginPasswordController.clear();
debugPrint("token " + token.accessToken);
emit(LoginSuccess());
} else {
emit(const LoginFailure(error: 'Something went wrong'));
@ -342,14 +336,12 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
static Future<String> getTokenAndValidate() async {
try {
const storage = FlutterSecureStorage();
final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(
StringsManager.firstLaunch) ??
true;
final firstLaunch =
await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
if (firstLaunch) {
storage.deleteAll();
}
await SharedPreferencesHelper.saveBoolToSP(
StringsManager.firstLaunch, false);
await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false);
final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
if (value.isEmpty) {
return 'Token not found';
@ -402,9 +394,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
final String formattedTime = [
if (days > 0) '${days}d', // Append 'd' for days
if (days > 0 || hours > 0)
hours
.toString()
.padLeft(2, '0'), // Show hours if there are days or hours
hours.toString().padLeft(2, '0'), // Show hours if there are days or hours
minutes.toString().padLeft(2, '0'),
seconds.toString().padLeft(2, '0'),
].join(':');
@ -442,8 +432,10 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(LoginInitial());
}
static logout() {
const storage = FlutterSecureStorage();
static Future<void> logout(BuildContext context) async {
final storage = FlutterSecureStorage();
ProjectManager.clearProjectUUID();
context.read<SpaceTreeBloc>().add(ClearAllData());
storage.deleteAll();
}
}

View File

@ -0,0 +1,27 @@
class Project {
final String uuid;
final String name;
final String description;
const Project({
required this.uuid,
required this.name,
required this.description,
});
factory Project.fromJson(Map<String, dynamic> json) {
return Project(
uuid: json['uuid'] as String,
name: json['name'] as String,
description: json['description'] as String,
);
}
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'name': name,
'description': description,
};
}
}

View File

@ -1,3 +1,4 @@
import 'package:syncrow_web/pages/auth/model/project_model.dart';
import 'package:syncrow_web/pages/auth/model/token.dart';
class UserModel {
@ -13,6 +14,7 @@ class UserModel {
final bool? hasAcceptedWebAgreement;
final DateTime? webAgreementAcceptedAt;
final UserRole? role;
final Project? project;
UserModel({
required this.uuid,
@ -26,6 +28,7 @@ class UserModel {
required this.hasAcceptedWebAgreement,
required this.webAgreementAcceptedAt,
required this.role,
required this.project,
});
factory UserModel.fromJson(Map<String, dynamic> json) {
@ -43,6 +46,8 @@ class UserModel {
? DateTime.parse(json['webAgreementAcceptedAt'])
: null,
role: json['role'] != null ? UserRole.fromJson(json['role']) : null,
project:
json['project'] != null ? Project.fromJson(json['project']) : null,
);
}
@ -64,6 +69,7 @@ class UserModel {
phoneNumber: null,
isEmailVerified: null,
isAgreementAccepted: null,
project: null
);
}

View File

@ -0,0 +1,19 @@
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
class ProjectManager {
static Future<String?> getProjectUUID() async {
final projectUuid = await SharedPreferencesHelper.readStringFromSP(
StringsManager.projectKey);
return projectUuid.isNotEmpty ? projectUuid : null;
}
static Future<void> setProjectUUID(String newUUID) async {
await SharedPreferencesHelper.saveStringToSP(
StringsManager.projectKey, newUUID);
}
static Future<void> clearProjectUUID() async {
await SharedPreferencesHelper.removeValueFromSP(StringsManager.projectKey);
}
}

View File

@ -1,14 +1,19 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
part 'device_managment_event.dart';
part 'device_managment_state.dart';
class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementState> {
class DeviceManagementBloc
extends Bloc<DeviceManagementEvent, DeviceManagementState> {
int _selectedIndex = 0;
List<AllDevicesModel> _devices = [];
int _onlineCount = 0;
@ -31,19 +36,25 @@ class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementS
on<UpdateSelection>(_onUpdateSelection);
}
Future<void> _onFetchDevices(FetchDevices event, Emitter<DeviceManagementState> emit) async {
Future<void> _onFetchDevices(
FetchDevices event, Emitter<DeviceManagementState> emit) async {
emit(DeviceManagementLoading());
try {
List<AllDevicesModel> devices = [];
_devices.clear();
var spaceBloc = event.context.read<SpaceTreeBloc>();
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (spaceBloc.state.selectedCommunities.isEmpty) {
devices = await DevicesManagementApi().fetchDevices('', '');
devices = await DevicesManagementApi()
.fetchDevices('', '', projectUuid );
} else {
for (var community in spaceBloc.state.selectedCommunities) {
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
for (var space in spacesList) {
devices.addAll(await DevicesManagementApi().fetchDevices(community, space));
devices.addAll(await DevicesManagementApi().fetchDevices(
community, space, projectUuid));
}
}
}
@ -66,7 +77,8 @@ class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementS
}
}
void _onFilterDevices(FilterDevices event, Emitter<DeviceManagementState> emit) async {
void _onFilterDevices(
FilterDevices event, Emitter<DeviceManagementState> emit) async {
if (_devices.isNotEmpty) {
_filteredDevices = List.from(_devices.where((device) {
switch (event.filter) {
@ -97,7 +109,8 @@ class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementS
}
}
Future<void> _onResetFilters(ResetFilters event, Emitter<DeviceManagementState> emit) async {
Future<void> _onResetFilters(
ResetFilters event, Emitter<DeviceManagementState> emit) async {
currentProductName = '';
_selectedDevices.clear();
_filteredDevices = List.from(_devices);
@ -113,7 +126,8 @@ class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementS
));
}
void _onResetSelectedDevices(ResetSelectedDevices event, Emitter<DeviceManagementState> emit) {
void _onResetSelectedDevices(
ResetSelectedDevices event, Emitter<DeviceManagementState> emit) {
_selectedDevices.clear();
if (state is DeviceManagementLoaded) {
@ -139,12 +153,14 @@ class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementS
}
}
void _onSelectedFilterChanged(SelectedFilterChanged event, Emitter<DeviceManagementState> emit) {
void _onSelectedFilterChanged(
SelectedFilterChanged event, Emitter<DeviceManagementState> emit) {
_selectedIndex = event.selectedIndex;
add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
}
void _onSelectDevice(SelectDevice event, Emitter<DeviceManagementState> emit) {
void _onSelectDevice(
SelectDevice event, Emitter<DeviceManagementState> emit) {
final selectedUuid = event.selectedDevice.uuid;
if (_selectedDevices.any((device) => device.uuid == selectedUuid)) {
@ -155,7 +171,8 @@ class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementS
List<AllDevicesModel> clonedSelectedDevices = List.from(_selectedDevices);
bool isControlButtonEnabled = _checkIfControlButtonEnabled(clonedSelectedDevices);
bool isControlButtonEnabled =
_checkIfControlButtonEnabled(clonedSelectedDevices);
if (state is DeviceManagementLoaded) {
emit(DeviceManagementLoaded(
@ -164,7 +181,8 @@ class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementS
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
selectedDevice:
clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
isControlButtonEnabled: isControlButtonEnabled,
));
} else if (state is DeviceManagementFiltered) {
@ -174,13 +192,15 @@ class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementS
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
selectedDevice:
clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
isControlButtonEnabled: isControlButtonEnabled,
));
}
}
void _onUpdateSelection(UpdateSelection event, Emitter<DeviceManagementState> emit) {
void _onUpdateSelection(
UpdateSelection event, Emitter<DeviceManagementState> emit) {
List<AllDevicesModel> selectedDevices = [];
List<AllDevicesModel> devicesToSelectFrom = [];
@ -223,7 +243,8 @@ class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementS
bool _checkIfControlButtonEnabled(List<AllDevicesModel> selectedDevices) {
if (selectedDevices.length > 1) {
final productTypes = selectedDevices.map((device) => device.productType).toSet();
final productTypes =
selectedDevices.map((device) => device.productType).toSet();
return productTypes.length == 1;
} else if (selectedDevices.length == 1) {
return true;
@ -234,8 +255,10 @@ class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementS
void _calculateDeviceCounts() {
_onlineCount = _devices.where((device) => device.online == true).length;
_offlineCount = _devices.where((device) => device.online == false).length;
_lowBatteryCount =
_devices.where((device) => device.batteryLevel != null && device.batteryLevel! < 20).length;
_lowBatteryCount = _devices
.where((device) =>
device.batteryLevel != null && device.batteryLevel! < 20)
.length;
}
String _getFilterFromIndex(int index) {
@ -251,7 +274,8 @@ class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementS
}
}
void _onSearchDevices(SearchDevices event, Emitter<DeviceManagementState> emit) {
void _onSearchDevices(
SearchDevices event, Emitter<DeviceManagementState> emit) {
if ((event.community == null || event.community!.isEmpty) &&
(event.unitName == null || event.unitName!.isEmpty) &&
(event.productName == null || event.productName!.isEmpty)) {
@ -280,22 +304,33 @@ class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementS
final filteredDevices = devicesToSearch.where((device) {
final matchesCommunity = event.community == null ||
event.community!.isEmpty ||
(device.community?.name?.toLowerCase().contains(event.community!.toLowerCase()) ??
(device.community?.name
?.toLowerCase()
.contains(event.community!.toLowerCase()) ??
false);
final matchesUnit = event.unitName == null ||
event.unitName!.isEmpty ||
(device.spaces != null &&
device.spaces!.isNotEmpty &&
device.spaces![0].spaceName!.toLowerCase().contains(event.unitName!.toLowerCase()));
device.spaces![0].spaceName!
.toLowerCase()
.contains(event.unitName!.toLowerCase()));
final matchesProductName = event.productName == null ||
event.productName!.isEmpty ||
(device.name?.toLowerCase().contains(event.productName!.toLowerCase()) ?? false);
(device.name
?.toLowerCase()
.contains(event.productName!.toLowerCase()) ??
false);
final matchesDeviceName = event.productName == null ||
event.productName!.isEmpty ||
(device.categoryName?.toLowerCase().contains(event.productName!.toLowerCase()) ??
(device.categoryName
?.toLowerCase()
.contains(event.productName!.toLowerCase()) ??
false);
return matchesCommunity && matchesUnit && (matchesProductName || matchesDeviceName);
return matchesCommunity &&
matchesUnit &&
(matchesProductName || matchesDeviceName);
}).toList();
emit(DeviceManagementFiltered(

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
@ -19,7 +20,8 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices(context)),
create: (context) =>
DeviceManagementBloc()..add(FetchDevices(context)),
),
],
child: WebScaffold(
@ -41,6 +43,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
context
.read<RoutineBloc>()
.add(const TriggerSwitchTabsEvent(isRoutineTab: false));
context.read<DeviceManagementBloc>().add(FetchDevices(context));
},
child: Text(
'Devices',

View File

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

View File

@ -1,16 +1,20 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:go_router/go_router.dart';
// import 'package:graphview/GraphView.dart';
import 'package:syncrow_web/pages/auth/model/user_model.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/home/bloc/home_state.dart';
import 'package:syncrow_web/pages/home/home_model/home_item_model.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/services/home_api.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
class HomeBloc extends Bloc<HomeEvent, HomeState> {
// final Graph graph = Graph()..isTree = true;
@ -47,10 +51,16 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async {
try {
var uuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
user = await HomeApi().fetchUserInfo(uuid);
if (user != null && user!.project != null) {
await ProjectManager.setProjectUUID(user!.project!.uuid);
NavigationService.navigatorKey.currentContext!.read<SpaceTreeBloc>().add(InitialEvent());
}
add(FetchTermEvent());
add(FetchPolicyEvent());
emit(HomeInitial());
} catch (e) {
return;
@ -61,8 +71,9 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
try {
emit(LoadingHome());
terms = await HomeApi().fetchTerms();
add(FetchPolicyEvent());
emit(PolicyAgreement());
emit(HomeInitial());
// emit(PolicyAgreement());
} catch (e) {
return;
}
@ -72,18 +83,19 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
try {
emit(LoadingHome());
policy = await HomeApi().fetchPolicy();
emit(PolicyAgreement());
debugPrint("Fetched policy: $policy");
// Emit a state to trigger the UI update
emit(HomeInitial());
} catch (e) {
debugPrint("Error fetching policy: $e");
return;
}
}
Future _confirmUserAgreement(
ConfirmUserAgreementEvent event, Emitter<HomeState> emit) async {
Future _confirmUserAgreement(ConfirmUserAgreementEvent event, Emitter<HomeState> emit) async {
try {
emit(LoadingHome());
var uuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
policy = await HomeApi().confirmUserAgreements(uuid);
emit(PolicyAgreement());
} catch (e) {
@ -107,6 +119,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
icon: Assets.accessIcon,
active: true,
onPress: (context) {
context.read<SpaceTreeBloc>().add(ClearCachedData());
context.go(RoutesConst.accessManagementPage);
},
color: null,
@ -116,6 +129,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
icon: Assets.spaseManagementIcon,
active: true,
onPress: (context) {
context.read<SpaceTreeBloc>().add(ClearCachedData());
context.go(RoutesConst.spacesManagementPage);
},
color: ColorsManager.primaryColor,
@ -125,6 +139,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
icon: Assets.devicesIcon,
active: true,
onPress: (context) {
context.read<SpaceTreeBloc>().add(ClearCachedData());
BlocProvider.of<RoutineBloc>(context)
.add(const TriggerSwitchTabsEvent(isRoutineTab: false));
context.go(RoutesConst.deviceManagementPage);

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:url_launcher/url_launcher.dart';
@ -162,7 +164,7 @@ class _AgreementAndPrivacyDialogState extends State<AgreementAndPrivacyDialog> {
children: [
InkWell(
onTap: () {
AuthBloc.logout();
AuthBloc.logout(context);
context.go(RoutesConst.auth);
},
child: const Text("Cancel"),

View File

@ -9,103 +9,121 @@ import 'package:syncrow_web/pages/home/view/home_card.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class HomeWebPage extends StatelessWidget {
class HomeWebPage extends StatefulWidget {
const HomeWebPage({super.key});
@override
State<HomeWebPage> createState() => _HomeWebPageState();
}
class _HomeWebPageState extends State<HomeWebPage> {
// Flag to track whether the dialog is already shown.
bool _dialogShown = false;
@override
void initState() {
super.initState();
final homeBloc = BlocProvider.of<HomeBloc>(context);
homeBloc.add(FetchUserInfo());
}
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
final homeBloc = BlocProvider.of<HomeBloc>(context);
return PopScope(
canPop: false,
onPopInvoked: (didPop) => false,
child: BlocConsumer<HomeBloc, HomeState>(
listener: (BuildContext context, state) {
if (state is HomeInitial) {
if (homeBloc.user!.hasAcceptedWebAgreement == false) {
Future.delayed(const Duration(seconds: 2), () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AgreementAndPrivacyDialog(
terms: homeBloc.terms,
policy: homeBloc.policy,
);
},
).then((v) {
if (v != null) {
homeBloc.add(ConfirmUserAgreementEvent());
homeBloc.add(const FetchUserInfo());
}
});
canPop: false,
onPopInvoked: (didPop) => false,
child: BlocConsumer<HomeBloc, HomeState>(
listener: (BuildContext context, state) {
if (state is HomeInitial) {
if (homeBloc.user!.hasAcceptedWebAgreement == false && !_dialogShown) {
_dialogShown = true; // Set the flag to true to indicate the dialog is showing.
Future.delayed(const Duration(seconds: 1), () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AgreementAndPrivacyDialog(
terms: homeBloc.terms,
policy: homeBloc.policy,
);
},
).then((v) {
_dialogShown = false;
if (v != null) {
homeBloc.add(ConfirmUserAgreementEvent());
homeBloc.add(const FetchUserInfo());
}
});
}
});
}
},
builder: (context, state) {
return WebScaffold(
enableMenuSidebar: false,
appBarTitle: Row(
}
},
builder: (context, state) {
return WebScaffold(
enableMenuSidebar: false,
appBarTitle: Row(
children: [
SvgPicture.asset(
Assets.loginLogo,
width: 150,
),
],
),
scaffoldBody: SizedBox(
height: size.height,
width: size.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
Assets.loginLogo,
width: 150,
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: size.height * 0.1),
Text(
'ACCESS YOUR APPS',
style: Theme.of(context)
.textTheme
.headlineLarge!
.copyWith(color: Colors.black, fontSize: 40),
),
const SizedBox(height: 30),
Expanded(
flex: 4,
child: SizedBox(
height: size.height * 0.6,
width: size.width * 0.68,
child: GridView.builder(
itemCount: 3, // Change this count if needed.
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // Adjust as needed.
crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0,
childAspectRatio: 1.5,
),
itemBuilder: (context, index) {
return HomeCard(
index: index,
active: homeBloc.homeItems[index].active!,
name: homeBloc.homeItems[index].title!,
img: homeBloc.homeItems[index].icon!,
onTap: () => homeBloc.homeItems[index].onPress(context),
);
},
),
),
),
],
),
],
),
scaffoldBody: SizedBox(
height: size.height,
width: size.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: size.height * 0.1),
Text(
'ACCESS YOUR APPS',
style: Theme.of(context)
.textTheme
.headlineLarge!
.copyWith(color: Colors.black, fontSize: 40),
),
const SizedBox(height: 30),
Expanded(
flex: 4,
child: SizedBox(
height: size.height * 0.6,
width: size.width * 0.68,
child: GridView.builder(
itemCount: 3, //8
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, //4
crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0,
childAspectRatio: 1.5,
),
itemBuilder: (context, index) {
return HomeCard(
index: index,
active: homeBloc.homeItems[index].active!,
name: homeBloc.homeItems[index].title!,
img: homeBloc.homeItems[index].icon!,
onTap: () => homeBloc.homeItems[index].onPress(context),
);
},
),
),
),
],
),
],
),
),
);
},
));
),
);
},
),
);
}
}

View File

@ -219,6 +219,9 @@ class UserSpaceModel {
final double x;
final double y;
final String icon;
final String communityUuid;
//communityUuid
UserSpaceModel({
required this.uuid,
@ -231,22 +234,23 @@ class UserSpaceModel {
required this.x,
required this.y,
required this.icon,
required this.communityUuid,
});
/// Create a [UserSpaceModel] from JSON data
factory UserSpaceModel.fromJson(Map<String, dynamic> json) {
return UserSpaceModel(
uuid: json['uuid'] as String,
createdAt: json['createdAt'] as String,
updatedAt: json['updatedAt'] as String,
spaceTuyaUuid: json['spaceTuyaUuid'] as String?,
spaceName: json['spaceName'] as String,
invitationCode: json['invitationCode'] as String?,
disabled: json['disabled'] as bool,
x: (json['x'] as num).toDouble(),
y: (json['y'] as num).toDouble(),
icon: json['icon'] as String,
);
uuid: json['uuid'] as String,
createdAt: json['createdAt'] as String,
updatedAt: json['updatedAt'] as String,
spaceTuyaUuid: json['spaceTuyaUuid'] as String?,
spaceName: json['spaceName'] as String,
invitationCode: json['invitationCode'] as String?,
disabled: json['disabled'] as bool,
x: (json['x'] as num).toDouble(),
y: (json['y'] as num).toDouble(),
icon: json['icon'] as String,
communityUuid: json['communityUuid'] as String);
}
/// Convert the [UserSpaceModel] to JSON
@ -262,6 +266,7 @@ class UserSpaceModel {
'x': x,
'y': y,
'icon': icon,
'communityUuid': communityUuid
};
}
}

View File

@ -1,5 +1,7 @@
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/common/custom_dialog.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/edit_user_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart';
@ -7,11 +9,14 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialo
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
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';
import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/services/user_permission.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
class UsersBloc extends Bloc<UsersEvent, UsersState> {
UsersBloc() : super(UsersInitial()) {
@ -61,8 +66,10 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
void isCompleteSpacesFun(
CheckSpacesStepStatus event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
List<String> selectedIds = getSelectedIds(updatedCommunities);
isCompleteSpaces = selectedIds.isNotEmpty;
var spaceBloc =
NavigationService.navigatorKey.currentContext!.read<SpaceTreeBloc>();
isCompleteSpaces = spaceBloc.state.selectedCommunities.isNotEmpty;
emit(ChangeStatusSteps());
}
@ -74,18 +81,23 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
Future<List<SpaceModel>> _fetchSpacesForCommunity(
String communityUuid) async {
return await CommunitySpaceManagementApi().getSpaceHierarchy(communityUuid);
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
return await CommunitySpaceManagementApi()
.getSpaceHierarchy(communityUuid, projectUuid);
}
List<TreeNode> updatedCommunities = [];
List<TreeNode> spacesNodes = [];
List<String> communityIds = [];
List<String> communityIds = [];
_onLoadCommunityAndSpaces(
LoadCommunityAndSpacesEvent event, Emitter<UsersState> emit) async {
try {
emit(UsersLoadingState());
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
List<CommunityModel> communities =
await CommunitySpaceManagementApi().fetchCommunities();
await CommunitySpaceManagementApi().fetchCommunities(projectUuid);
communityIds = communities.map((community) => community.uuid).toList();
updatedCommunities = await Future.wait(
communities.map((community) async {
@ -102,12 +114,19 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
);
}).toList(),
);
originalCommunities = updatedCommunities;
emit(const SpacesLoadedState());
} catch (e) {
emit(ErrorState('Error loading communities and spaces: $e'));
}
}
// This variable holds the full original list.
List<TreeNode> originalCommunities = [];
// This variable holds the working list that may be filtered.
// Build tree nodes from your data model.
List<TreeNode> _buildTreeNodes(List<SpaceModel> spaces) {
return spaces.map((space) {
List<TreeNode> childNodes =
@ -123,12 +142,39 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
}).toList();
}
// Optional helper method to deep clone a TreeNode.
TreeNode _cloneNode(TreeNode node) {
return TreeNode(
uuid: node.uuid,
title: node.title,
isChecked: node.isChecked,
isHighlighted: node.isHighlighted,
isExpanded: node.isExpanded,
children: node.children.map(_cloneNode).toList(),
);
}
// Clone an entire list of tree nodes.
List<TreeNode> _cloneNodes(List<TreeNode> nodes) {
return nodes.map(_cloneNode).toList();
}
// Your search event handler.
void searchTreeNode(SearchAnode event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
// If the search term is empty, restore the original list.
if (event.searchTerm!.isEmpty) {
// Clear any highlights on the restored copy.
updatedCommunities = _cloneNodes(originalCommunities);
_clearHighlights(updatedCommunities);
} else {
_searchAndHighlightNodes(updatedCommunities, event.searchTerm!);
// Start with a fresh clone of the original tree.
List<TreeNode> freshClone = _cloneNodes(originalCommunities);
_searchAndHighlightNodes(freshClone, event.searchTerm!);
updatedCommunities = _filterNodes(freshClone, event.searchTerm!);
}
emit(ChangeStatusSteps());
}
@ -155,6 +201,91 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
return anyMatch;
}
List<TreeNode> _filterNodes(List<TreeNode> nodes, String searchTerm) {
List<TreeNode> filteredNodes = [];
for (var node in nodes) {
bool isMatch =
node.title.toLowerCase().contains(searchTerm.toLowerCase());
List<TreeNode> filteredChildren = _filterNodes(node.children, searchTerm);
if (isMatch || filteredChildren.isNotEmpty) {
node.isHighlighted = isMatch;
node.children = filteredChildren;
filteredNodes.add(node);
}
}
return filteredNodes;
}
// List<TreeNode> _buildTreeNodes(List<SpaceModel> spaces) {
// return spaces.map((space) {
// List<TreeNode> childNodes =
// space.children.isNotEmpty ? _buildTreeNodes(space.children) : [];
// return TreeNode(
// uuid: space.uuid!,
// title: space.name,
// isChecked: false,
// isHighlighted: false,
// isExpanded: childNodes.isNotEmpty,
// children: childNodes,
// );
// }).toList();
// }
// void searchTreeNode(SearchAnode event, Emitter<UsersState> emit) {
// emit(UsersLoadingState());
// if (event.searchTerm!.isEmpty) {
// _clearHighlights(updatedCommunities);
// } else {
// _searchAndHighlightNodes(updatedCommunities, event.searchTerm!);
// updatedCommunities = _filterNodes(updatedCommunities, event.searchTerm!);
// }
// emit(ChangeStatusSteps());
// }
// void _clearHighlights(List<TreeNode> nodes) {
// for (var node in nodes) {
// node.isHighlighted = false;
// if (node.children.isNotEmpty) {
// _clearHighlights(node.children);
// }
// }
// }
// bool _searchAndHighlightNodes(List<TreeNode> nodes, String searchTerm) {
// bool anyMatch = false;
// for (var node in nodes) {
// bool isMatch =
// node.title.toLowerCase().contains(searchTerm.toLowerCase());
// bool childMatch = _searchAndHighlightNodes(node.children, searchTerm);
// node.isHighlighted = isMatch || childMatch;
// anyMatch = anyMatch || node.isHighlighted;
// }
// return anyMatch;
// }
// List<TreeNode> _filterNodes(List<TreeNode> nodes, String searchTerm) {
// List<TreeNode> filteredNodes = [];
// for (var node in nodes) {
// // Check if the current node's title contains the search term.
// bool isMatch =
// node.title.toLowerCase().contains(searchTerm.toLowerCase());
// // Recursively filter the children.
// List<TreeNode> filteredChildren = _filterNodes(node.children, searchTerm);
// // If the current node is a match or any of its children are, include it.
// if (isMatch || filteredChildren.isNotEmpty) {
// // Optionally, update any properties (like isHighlighted) if you still need them.
// node.isHighlighted = isMatch;
// // Replace the children with the filtered ones.
// node.children = filteredChildren;
// filteredNodes.add(node);
// }
// }
// return filteredNodes;
// }
List<String> selectedIds = [];
List<String> getSelectedIds(List<TreeNode> nodes) {
@ -209,20 +340,24 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
void _sendInvitUser(SendInviteUsers event, Emitter<UsersState> emit) async {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
emit(UsersLoadingState());
List<String> selectedIds = getSelectedIds(updatedCommunities)
.where((id) => !communityIds.contains(id))
.toList();
// List<String> selectedIds =
// getSelectedIds(updatedCommunities).where((id) => !communityIds.contains(id)).toList();
List<String> selectedSpacesId = getSelectedSpacesIds();
// List<String> selectedIds = getSelectedIds(updatedCommunities);
bool res = await UserPermissionApi().sendInviteUser(
email: emailController.text,
firstName: firstNameController.text,
jobTitle: jobTitleController.text,
lastName: lastNameController.text,
phoneNumber: phoneController.text,
roleUuid: roleSelected,
spaceUuids: selectedIds,
);
email: emailController.text,
firstName: firstNameController.text,
jobTitle: jobTitleController.text,
lastName: lastNameController.text,
phoneNumber: phoneController.text,
roleUuid: roleSelected,
spaceUuids: selectedSpacesId,
projectUuid: projectUuid);
if (res) {
showCustomDialog(
@ -251,23 +386,31 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
}
}
List<String> getSelectedSpacesIds() {
List<String> selectedSpacesId = [];
var spaceBloc =
NavigationService.navigatorKey.currentContext!.read<SpaceTreeBloc>();
for (var community in spaceBloc.state.selectedCommunities) {
selectedSpacesId
.addAll(spaceBloc.state.selectedCommunityAndSpaces[community] ?? []);
}
return selectedSpacesId;
}
_editInviteUser(EditInviteUsers event, Emitter<UsersState> emit) async {
try {
emit(UsersLoadingState());
List<String> selectedIds = getSelectedIds(updatedCommunities)
.where((id) => !communityIds.contains(id))
.toList();
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
bool res = await UserPermissionApi().editInviteUser(
userId: event.userId,
firstName: firstNameController.text,
jobTitle: jobTitleController.text,
lastName: lastNameController.text,
phoneNumber: phoneController.text,
roleUuid: roleSelected,
spaceUuids: selectedIds,
);
userId: event.userId,
firstName: firstNameController.text,
jobTitle: jobTitleController.text,
lastName: lastNameController.text,
phoneNumber: phoneController.text,
roleUuid: roleSelected,
spaceUuids: getSelectedSpacesIds(),
projectUuid: projectUuid);
if (res == true) {
showCustomDialog(
barrierDismissible: false,
@ -372,8 +515,13 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
emit(UsersLoadingState());
try {
var spaceBloc =
NavigationService.navigatorKey.currentContext!.read<SpaceTreeBloc>();
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (event.uuid?.isNotEmpty ?? false) {
final res = await UserPermissionApi().fetchUserById(event.uuid);
final res =
await UserPermissionApi().fetchUserById(event.uuid, projectUuid);
if (res != null) {
// Populate the text controllers
@ -383,13 +531,20 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
phoneController.text = res.phoneNumber ?? '';
jobTitleController.text = res.jobTitle ?? '';
res.roleType;
if (updatedCommunities.isNotEmpty) {
// Create a list of UUIDs to mark
final uuidsToMark = res.spaces.map((space) => space.uuid).toList();
// Print all IDs and mark nodes in updatedCommunities
debugPrint('Printing and marking nodes in updatedCommunities:');
_printAndMarkNodes(updatedCommunities, uuidsToMark);
}
res.spaces.map((space) {
selectedIds.add(space.uuid);
CommunityModel community = spaceBloc.state.communityList
.firstWhere((item) => item.uuid == space.communityUuid);
spaceBloc.add(OnSpaceSelected(community, space.uuid, []));
}).toList();
// if (updatedCommunities.isNotEmpty) {
// // Create a list of UUIDs to mark
// final uuidsToMark = res.spaces.map((space) => space.uuid).toList();
// // Print all IDs and mark nodes in updatedCommunities
// debugPrint('Printing and marking nodes in updatedCommunities:');
// _printAndMarkNodes(updatedCommunities, uuidsToMark);
// }
final roleId = roles
.firstWhere((element) =>
element.type ==
@ -482,4 +637,16 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
}
return null;
}
@override
Future<void> close() {
emailController.dispose();
firstNameController.dispose();
lastNameController.dispose();
emailController.dispose();
phoneController.dispose();
jobTitleController.dispose();
roleSearchController.dispose();
return super.close();
}
}

View File

@ -24,7 +24,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => UsersBloc()
..add(const LoadCommunityAndSpacesEvent())
// ..add(const LoadCommunityAndSpacesEvent())
..add(const RoleEvent()),
child: BlocConsumer<UsersBloc, UsersState>(
listener: (context, state) {},
@ -34,8 +34,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
return Dialog(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20))),
color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))),
width: 900,
child: Column(
children: [
@ -64,8 +63,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
children: [
_buildStep1Indicator(1, "Basics", _blocRole),
_buildStep2Indicator(2, "Spaces", _blocRole),
_buildStep3Indicator(
3, "Role & Permissions", _blocRole),
_buildStep3Indicator(3, "Role & Permissions", _blocRole),
],
),
),
@ -113,15 +111,12 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
if (currentStep < 3) {
currentStep++;
if (currentStep == 2) {
_blocRole.add(
const CheckStepStatus(isEditUser: false));
_blocRole.add(const CheckStepStatus(isEditUser: false));
} else if (currentStep == 3) {
_blocRole
.add(const CheckSpacesStepStatus());
_blocRole.add(const CheckSpacesStepStatus());
}
} else {
_blocRole
.add(SendInviteUsers(context: context));
_blocRole.add(SendInviteUsers(context: context));
}
});
},
@ -129,11 +124,8 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
currentStep < 3 ? "Next" : "Save",
style: TextStyle(
color: (_blocRole.isCompleteSpaces == false ||
_blocRole.isCompleteBasics ==
false ||
_blocRole
.isCompleteRolePermissions ==
false) &&
_blocRole.isCompleteBasics == false ||
_blocRole.isCompleteRolePermissions == false) &&
currentStep == 3
? ColorsManager.grayColor
: ColorsManager.secondaryColor),
@ -204,12 +196,8 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
),
),
],
@ -236,12 +224,16 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
return GestureDetector(
onTap: () {
setState(() {
currentStep = step;
bloc.add(const CheckStepStatus(isEditUser: false));
currentStep = step;
Future.delayed(const Duration(milliseconds: 500), () {
bloc.add(const CheckStepStatus(isEditUser: false));
});
if (step3 == 3) {
bloc.add(const CheckRoleStepStatus());
Future.delayed(const Duration(seconds: 1), () {
bloc.add(const CheckRoleStepStatus());
});
}
});
},
child: Column(
@ -268,12 +260,8 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
),
),
],
@ -330,12 +318,8 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
),
),
],

View File

@ -46,117 +46,120 @@ class BasicsView extends StatelessWidget {
),
Row(
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.18,
height: MediaQuery.of(context).size.width * 0.08,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: Row(
children: [
const Text(
" * ",
style: TextStyle(
color: ColorsManager.red,
fontWeight: FontWeight.w900,
fontSize: 15,
Flexible(
child: SizedBox(
// width: MediaQuery.of(context).size.width * 0.18,
height: MediaQuery.of(context).size.width * 0.08,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: Row(
children: [
const Text(
" * ",
style: TextStyle(
color: ColorsManager.red,
fontWeight: FontWeight.w900,
fontSize: 15,
),
),
),
Text(
'First Name',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
),
),
],
)),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
style:
const TextStyle(color: ColorsManager.blackColor),
// onChanged: (value) {
// Future.delayed(const Duration(milliseconds: 200),
// () {
// _blocRole.add(const ValidateBasicsStep());
// });
// },
controller: _blocRole.firstNameController,
decoration: inputTextFormDeco(
hintText: "Enter first name",
).copyWith(
hintStyle: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter first name';
}
return null;
},
),
),
],
),
),
const SizedBox(width: 10),
SizedBox(
width: MediaQuery.of(context).size.width * 0.18,
height: MediaQuery.of(context).size.width * 0.08,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: Row(
children: [
const Text(
" * ",
style: TextStyle(
color: ColorsManager.red,
fontWeight: FontWeight.w900,
fontSize: 15,
),
),
Text('Last Name',
Text(
'First Name',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
)),
],
)),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
// onChanged: (value) {
// Future.delayed(const Duration(milliseconds: 200),
// () {
// _blocRole.add(ValidateBasicsStep());
// });
// },
controller: _blocRole.lastNameController,
style: const TextStyle(color: Colors.black),
decoration:
inputTextFormDeco(hintText: "Enter last name")
.copyWith(
hintStyle: context
.textTheme.bodyMedium
?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray)),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter last name';
}
return null;
},
),
),
],
)),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
style: const TextStyle(
color: ColorsManager.blackColor),
// onChanged: (value) {
// Future.delayed(const Duration(milliseconds: 200),
// () {
// _blocRole.add(const ValidateBasicsStep());
// });
// },
controller: _blocRole.firstNameController,
decoration: inputTextFormDeco(
hintText: "Enter first name",
).copyWith(
hintStyle: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter first name';
}
return null;
},
),
),
),
],
],
),
),
),
const SizedBox(width: 10),
Flexible(
child: SizedBox(
// width: MediaQuery.of(context).size.width * 0.18,
height: MediaQuery.of(context).size.width * 0.08,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: Row(
children: [
const Text(
" * ",
style: TextStyle(
color: ColorsManager.red,
fontWeight: FontWeight.w900,
fontSize: 15,
),
),
Text('Last Name',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
)),
],
)),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
// onChanged: (value) {
// Future.delayed(const Duration(milliseconds: 200),
// () {
// _blocRole.add(ValidateBasicsStep());
// });
// },
controller: _blocRole.lastNameController,
style: const TextStyle(color: Colors.black),
decoration:
inputTextFormDeco(hintText: "Enter last name")
.copyWith(
hintStyle: context.textTheme.bodyMedium
?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray)),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter last name';
}
return null;
},
),
),
],
),
),
),
],

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
@ -25,13 +26,12 @@ class _EditUserDialogState extends State<EditUserDialog> {
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => UsersBloc()
..add(const LoadCommunityAndSpacesEvent())
// ..add(const LoadCommunityAndSpacesEvent())
..add(const RoleEvent())
..add(GetUserByIdEvent(uuid: widget.userId)),
child: BlocConsumer<UsersBloc, UsersState>(listener: (context, state) {
if (state is SpacesLoadedState) {
BlocProvider.of<UsersBloc>(context)
.add(GetUserByIdEvent(uuid: widget.userId));
BlocProvider.of<UsersBloc>(context).add(GetUserByIdEvent(uuid: widget.userId));
}
}, builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
@ -39,8 +39,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
return Dialog(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20))),
color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))),
width: 900,
child: Column(
children: [
@ -69,8 +68,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
children: [
_buildStep1Indicator(1, "Basics", _blocRole),
_buildStep2Indicator(2, "Spaces", _blocRole),
_buildStep3Indicator(
3, "Role & Permissions", _blocRole),
_buildStep3Indicator(3, "Role & Permissions", _blocRole),
],
),
),
@ -118,15 +116,13 @@ class _EditUserDialogState extends State<EditUserDialog> {
if (currentStep < 3) {
currentStep++;
if (currentStep == 2) {
_blocRole
.add(CheckStepStatus(isEditUser: true));
_blocRole.add(CheckStepStatus(isEditUser: true));
} else if (currentStep == 3) {
_blocRole.add(const CheckSpacesStepStatus());
}
} else {
_blocRole.add(EditInviteUsers(
context: context,
userId: widget.userId!));
_blocRole
.add(EditInviteUsers(context: context, userId: widget.userId!));
}
});
},
@ -135,8 +131,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
style: TextStyle(
color: (_blocRole.isCompleteSpaces == false ||
_blocRole.isCompleteBasics == false ||
_blocRole.isCompleteRolePermissions ==
false) &&
_blocRole.isCompleteRolePermissions == false) &&
currentStep == 3
? ColorsManager.grayColor
: ColorsManager.secondaryColor),
@ -209,12 +204,8 @@ class _EditUserDialogState extends State<EditUserDialog> {
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
),
),
],
@ -272,12 +263,8 @@ class _EditUserDialogState extends State<EditUserDialog> {
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
),
),
],
@ -334,12 +321,8 @@ class _EditUserDialogState extends State<EditUserDialog> {
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
),
),
],

View File

@ -1,14 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class SpacesAccessView extends StatelessWidget {
final String? userId;
@ -27,10 +22,8 @@ class SpacesAccessView extends StatelessWidget {
children: [
Text(
'Spaces access',
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 20,
color: Colors.black),
style: context.textTheme.bodyLarge
?.copyWith(fontWeight: FontWeight.w700, fontSize: 20, color: Colors.black),
),
const SizedBox(
height: 35,
@ -42,77 +35,78 @@ class SpacesAccessView extends StatelessWidget {
const SizedBox(
height: 25,
),
Expanded(
child: SizedBox(
child: Column(
children: [
Expanded(
flex: 2,
child: Container(
decoration: const BoxDecoration(
color: ColorsManager.circleRolesBackground,
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
topLeft: Radius.circular(20)),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(20)),
border: Border.all(
color: ColorsManager.grayBorder)),
child: TextFormField(
style:
const TextStyle(color: Colors.black),
// controller: _blocRole.firstNameController,
onChanged: (value) {
_blocRole.add(SearchAnode(
nodes: _blocRole.updatedCommunities,
searchTerm: value));
},
decoration: textBoxDecoration(radios: 20)!
.copyWith(
fillColor: Colors.white,
suffixIcon: Padding(
padding:
const EdgeInsets.only(right: 16),
child: SvgPicture.asset(
Assets.textFieldSearch,
width: 24,
height: 24,
),
),
hintStyle: context.textTheme.bodyMedium
?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
),
),
),
],
),
),
),
),
Expanded(
flex: 7,
child: Container(
color: ColorsManager.circleRolesBackground,
padding: const EdgeInsets.all(8.0),
child: Container(
color: ColorsManager.whiteColors,
child: TreeView(userId: userId))))
],
),
),
),
Expanded(child: SpaceTreeView(onSelect: () {}))
// Expanded(
// child: SizedBox(
// child: Column(
// children: [
// Expanded(
// flex: 2,
// child: Container(
// decoration: const BoxDecoration(
// color: ColorsManager.circleRolesBackground,
// borderRadius: BorderRadius.only(
// topRight: Radius.circular(20),
// topLeft: Radius.circular(20)),
// ),
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: Row(
// children: [
// Expanded(
// child: Container(
// decoration: BoxDecoration(
// borderRadius: const BorderRadius.all(
// Radius.circular(20)),
// border: Border.all(
// color: ColorsManager.grayBorder)),
// child: TextFormField(
// style:
// const TextStyle(color: Colors.black),
// // controller: _blocRole.firstNameController,
// onChanged: (value) {
// _blocRole.add(SearchAnode(
// nodes: _blocRole.updatedCommunities,
// searchTerm: value));
// },
// decoration: textBoxDecoration(radios: 20)!
// .copyWith(
// fillColor: Colors.white,
// suffixIcon: Padding(
// padding:
// const EdgeInsets.only(right: 16),
// child: SvgPicture.asset(
// Assets.textFieldSearch,
// width: 24,
// height: 24,
// ),
// ),
// hintStyle: context.textTheme.bodyMedium
// ?.copyWith(
// fontWeight: FontWeight.w400,
// fontSize: 12,
// color: ColorsManager.textGray),
// ),
// ),
// ),
// ),
// ],
// ),
// ),
// ),
// ),
// Expanded(
// flex: 7,
// child: Container(
// color: ColorsManager.circleRolesBackground,
// padding: const EdgeInsets.all(8.0),
// child: Container(
// color: ColorsManager.whiteColors,
// child: TreeView(userId: userId))))
// ],
// ),
// ),
// ),
],
),
),

View File

@ -1,10 +1,13 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart';
import 'package:syncrow_web/services/user_permission.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
UserTableBloc() : super(TableInitial()) {
@ -27,7 +30,16 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
int currentPage = 1;
List<RolesUserModel> users = [];
List<RolesUserModel> initialUsers = [];
List<RolesUserModel> totalUsersCount = [];
String currentSortOrder = '';
String currentSortJopTitle = '';
String currentSortRole = '';
String currentSortCreatedDate = '';
String currentSortStatus = '';
String currentSortCreatedBy = '';
String currentSortOrderDate = '';
List<String> roleTypes = [];
List<String> jobTitle = [];
@ -37,10 +49,12 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
Future<void> _getUsers(GetUsers event, Emitter<UserTableState> emit) async {
emit(UsersLoadingState());
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
roleTypes.clear();
jobTitle.clear();
createdBy.clear();
users = await UserPermissionApi().fetchUsers();
users = await UserPermissionApi().fetchUsers(projectUuid);
users.sort((a, b) {
final dateA = _parseDateTime(a.createdDate);
final dateB = _parseDateTime(b.createdDate);
@ -60,6 +74,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
jobTitle = jobTitle.toSet().toList();
createdBy = createdBy.toSet().toList();
_handlePageChange(ChangePage(1), emit);
totalUsersCount = initialUsers;
add(ChangePage(currentPage));
emit(UsersLoadedState(users: users));
} catch (e) {
@ -86,31 +101,13 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
Future<void> _changeUserStatus(
ChangeUserStatus event, Emitter<UserTableState> emit) async {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
emit(UsersLoadingState());
bool res = await UserPermissionApi().changeUserStatusById(
event.userId, event.newStatus == "disabled" ? false : true);
bool res = await UserPermissionApi().changeUserStatusById(event.userId,
event.newStatus == "disabled" ? false : true, projectUuid);
if (res == true) {
add(const GetUsers());
// users = users.map((user) {
// if (user.uuid == event.userId) {
// return RolesUserModel(
// uuid: user.uuid,
// createdAt: user.createdAt,
// email: user.email,
// firstName: user.firstName,
// lastName: user.lastName,
// roleType: user.roleType,
// status: event.newStatus,
// isEnabled: event.newStatus == "disabled" ? false : true,
// invitedBy: user.invitedBy,
// phoneNumber: user.phoneNumber,
// jobTitle: user.jobTitle,
// createdDate: user.createdDate,
// createdTime: user.createdTime,
// );
// }
// return user;
// }).toList();
}
emit(UsersLoadedState(users: users));
} catch (e) {
@ -128,7 +125,6 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
emit(UsersLoadingState());
currentSortOrder = "";
users = List.from(users);
emit(UsersLoadedState(users: users));
} else {
emit(UsersLoadingState());
currentSortOrder = "Asc";
@ -136,8 +132,12 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
.toString()
.toLowerCase()
.compareTo(b.firstName.toString().toLowerCase()));
emit(UsersLoadedState(users: users));
}
currentSortJopTitle = '';
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
emit(UsersLoadedState(users: users));
}
void _toggleSortUsersByNameDesc(
@ -150,13 +150,16 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
emit(UsersLoadingState());
currentSortOrder = "";
users = List.from(initialUsers);
emit(UsersLoadedState(users: users));
} else {
emit(UsersLoadingState());
currentSortOrder = "Desc";
users.sort((a, b) => b.firstName!.compareTo(a.firstName!));
emit(UsersLoadedState(users: users));
}
currentSortJopTitle = '';
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
emit(UsersLoadedState(users: users));
}
void _toggleSortUsersByDateNewestToOldest(
@ -222,6 +225,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
Future<void> _searchUsers(
SearchUsers event, Emitter<UserTableState> emit) async {
try {
emit(TableSearch());
final query = event.query.toLowerCase();
final filteredUsers = initialUsers.where((user) {
final fullName = "${user.firstName} ${user.lastName}".toLowerCase();
@ -250,7 +254,8 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
}
void _handlePageChange(ChangePage event, Emitter<UserTableState> emit) {
const itemsPerPage = 10;
currentPage = event.pageNumber;
const itemsPerPage = 20;
final startIndex = (event.pageNumber - 1) * itemsPerPage;
final endIndex = startIndex + itemsPerPage;
if (startIndex >= users.length) {
@ -287,9 +292,15 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else {
currentSortOrder = "";
}
} else {}
currentSortOrder = "";
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
currentSortJopTitle = '';
currentSortOrderDate = "";
totalUsersCount = filteredUsers;
emit(UsersLoadedState(users: filteredUsers));
}
@ -311,9 +322,16 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else {
currentSortOrder = "";
}
} else {}
currentSortOrder = "";
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
currentSortRole = '';
currentSortOrderDate = "";
totalUsersCount = filteredUsers;
emit(UsersLoadedState(users: filteredUsers));
}
@ -335,9 +353,15 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else {
currentSortOrder = "";
}
} else {}
currentSortOrder = '';
currentSortRole = '';
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortOrderDate = "";
totalUsersCount = filteredUsers;
emit(UsersLoadedState(users: filteredUsers));
}
@ -371,14 +395,17 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
} else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else {
currentSortOrder = "";
}
totalUsersCount = filteredUsers;
} else {}
currentSortOrder = '';
currentSortRole = '';
currentSortCreatedDate = '';
currentSortCreatedBy = '';
currentSortOrderDate = "";
emit(UsersLoadedState(users: filteredUsers));
}
void _resetAllFilters(Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();

View File

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

View File

@ -13,6 +13,8 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/vi
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/name_filter.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/user_table.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -25,7 +27,7 @@ class UsersPage extends StatelessWidget {
Widget build(BuildContext context) {
final TextEditingController searchController = TextEditingController();
Widget actionButton({required String title, required Function()? onTap}) {
Widget actionButton({bool isActive = false, required String title, Function()? onTap}) {
return InkWell(
onTap: onTap,
child: Padding(
@ -33,9 +35,11 @@ class UsersPage extends StatelessWidget {
child: Text(
title,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: title == "Delete"
? ColorsManager.red
: ColorsManager.spaceColor,
color: isActive == false && title != "Delete"
? Colors.grey
: title == "Delete"
? ColorsManager.red
: ColorsManager.spaceColor,
fontWeight: FontWeight.w400,
),
),
@ -56,8 +60,7 @@ class UsersPage extends StatelessWidget {
: ColorsManager.disabledPink.withOpacity(0.5),
),
child: Padding(
padding:
const EdgeInsets.only(left: 10, right: 10, bottom: 5, top: 5),
padding: const EdgeInsets.only(left: 10, right: 10, bottom: 5, top: 5),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
@ -81,15 +84,12 @@ class UsersPage extends StatelessWidget {
}
Widget changeIconStatus(
{required String userId,
required String status,
required Function()? onTap}) {
{required String userId, required String status, required Function()? onTap}) {
return Center(
child: InkWell(
onTap: onTap,
child: Padding(
padding:
const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5),
padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5),
child: SvgPicture.asset(
status == "invited"
? Assets.invitedIcon
@ -129,9 +129,12 @@ class UsersPage extends StatelessWidget {
child: TextFormField(
controller: searchController,
onChanged: (value) {
context
.read<UserTableBloc>()
.add(SearchUsers(value));
final bloc = context.read<UserTableBloc>();
bloc.add(FilterClearEvent());
bloc.add(SearchUsers(value));
if (value == '') {
bloc.add(ChangePage(1));
}
},
style: const TextStyle(color: Colors.black),
decoration: textBoxDecoration(radios: 15)!.copyWith(
@ -154,6 +157,7 @@ class UsersPage extends StatelessWidget {
const SizedBox(width: 20),
InkWell(
onTap: () {
context.read<SpaceTreeBloc>().add(ClearCachedData());
showDialog(
context: context,
barrierDismissible: false,
@ -192,14 +196,10 @@ class UsersPage extends StatelessWidget {
context: context,
isSelected: _blocRole.currentSortOrder,
aToZTap: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameAsc());
context.read<UserTableBloc>().add(const SortUsersByNameAsc());
},
zToaTap: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
context.read<UserTableBloc>().add(const SortUsersByNameDesc());
},
);
}
@ -208,9 +208,8 @@ class UsersPage extends StatelessWidget {
for (var item in _blocRole.jobTitle)
item: _blocRole.selectedJobTitles.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
@ -222,7 +221,7 @@ class UsersPage extends StatelessWidget {
list: _blocRole.jobTitle,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder,
isSelected: _blocRole.currentSortJopTitle,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
@ -233,14 +232,14 @@ class UsersPage extends StatelessWidget {
Navigator.of(context).pop();
_blocRole.add(FilterUsersByJobEvent(
selectedJob: selectedItems,
sortOrder: _blocRole.currentSortOrder,
sortOrder: _blocRole.currentSortJopTitle,
));
},
onSortAtoZ: (v) {
_blocRole.currentSortOrder = v;
_blocRole.currentSortJopTitle = v;
},
onSortZtoA: (v) {
_blocRole.currentSortOrder = v;
_blocRole.currentSortJopTitle = v;
},
);
}
@ -250,9 +249,8 @@ class UsersPage extends StatelessWidget {
for (var item in _blocRole.roleTypes)
item: _blocRole.selectedRoles.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 4,
@ -263,7 +261,7 @@ class UsersPage extends StatelessWidget {
list: _blocRole.roleTypes,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder,
isSelected: _blocRole.currentSortRole,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
@ -272,16 +270,15 @@ class UsersPage extends StatelessWidget {
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
context.read<UserTableBloc>().add(
FilterUsersByRoleEvent(
selectedRoles: selectedItems,
sortOrder: _blocRole.currentSortOrder));
context.read<UserTableBloc>().add(FilterUsersByRoleEvent(
selectedRoles: selectedItems,
sortOrder: _blocRole.currentSortRole));
},
onSortAtoZ: (v) {
_blocRole.currentSortOrder = v;
_blocRole.currentSortRole = v;
},
onSortZtoA: (v) {
_blocRole.currentSortOrder = v;
_blocRole.currentSortRole = v;
},
);
}
@ -290,14 +287,10 @@ class UsersPage extends StatelessWidget {
context: context,
isSelected: _blocRole.currentSortOrder,
aToZTap: () {
context
.read<UserTableBloc>()
.add(const DateNewestToOldestEvent());
context.read<UserTableBloc>().add(const DateNewestToOldestEvent());
},
zToaTap: () {
context
.read<UserTableBloc>()
.add(const DateOldestToNewestEvent());
context.read<UserTableBloc>().add(const DateOldestToNewestEvent());
},
);
}
@ -306,9 +299,8 @@ class UsersPage extends StatelessWidget {
for (var item in _blocRole.createdBy)
item: _blocRole.selectedCreatedBy.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 1,
@ -319,7 +311,7 @@ class UsersPage extends StatelessWidget {
list: _blocRole.createdBy,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder,
isSelected: _blocRole.currentSortCreatedBy,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
@ -330,13 +322,13 @@ class UsersPage extends StatelessWidget {
Navigator.of(context).pop();
_blocRole.add(FilterUsersByCreatedEvent(
selectedCreatedBy: selectedItems,
sortOrder: _blocRole.currentSortOrder));
sortOrder: _blocRole.currentSortCreatedBy));
},
onSortAtoZ: (v) {
_blocRole.currentSortOrder = v;
_blocRole.currentSortCreatedBy = v;
},
onSortZtoA: (v) {
_blocRole.currentSortOrder = v;
_blocRole.currentSortCreatedBy = v;
},
);
}
@ -346,9 +338,8 @@ class UsersPage extends StatelessWidget {
item: _blocRole.selectedStatuses.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 0,
@ -359,7 +350,7 @@ class UsersPage extends StatelessWidget {
list: _blocRole.status,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortOrder,
isSelected: _blocRole.currentSortStatus,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
@ -370,13 +361,13 @@ class UsersPage extends StatelessWidget {
Navigator.of(context).pop();
_blocRole.add(FilterUsersByDeActevateEvent(
selectedActivate: selectedItems,
sortOrder: _blocRole.currentSortOrder));
sortOrder: _blocRole.currentSortStatus));
},
onSortAtoZ: (v) {
_blocRole.currentSortOrder = v;
_blocRole.currentSortStatus = v;
},
onSortZtoA: (v) {
_blocRole.currentSortOrder = v;
_blocRole.currentSortStatus = v;
},
);
}
@ -385,14 +376,10 @@ class UsersPage extends StatelessWidget {
context: context,
isSelected: _blocRole.currentSortOrderDate,
aToZTap: () {
context
.read<UserTableBloc>()
.add(const DateNewestToOldestEvent());
context.read<UserTableBloc>().add(const DateNewestToOldestEvent());
},
zToaTap: () {
context
.read<UserTableBloc>()
.add(const DateOldestToNewestEvent());
context.read<UserTableBloc>().add(const DateOldestToNewestEvent());
},
);
}
@ -419,46 +406,46 @@ class UsersPage extends StatelessWidget {
Text(user.createdTime ?? ''),
Text(user.invitedBy),
status(
status: user.isEnabled == false
? 'disabled'
: user.status,
status: user.isEnabled == false ? 'disabled' : user.status,
),
changeIconStatus(
status: user.isEnabled == false
? 'disabled'
: user.status,
status: user.isEnabled == false ? 'disabled' : user.status,
userId: user.uuid,
onTap: user.status != "invited"
? () {
context.read<UserTableBloc>().add(
ChangeUserStatus(
userId: user.uuid,
newStatus: user.isEnabled == false
? 'disabled'
: user.status));
context.read<UserTableBloc>().add(ChangeUserStatus(
userId: user.uuid,
newStatus:
user.isEnabled == false ? 'disabled' : user.status));
}
: null,
),
Row(
children: [
actionButton(
title: "Edit",
onTap: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return EditUserDialog(userId: user.uuid);
},
).then((v) {
if (v != null) {
if (v != null) {
_blocRole.add(const GetUsers());
}
}
});
},
),
user.isEnabled != false
? actionButton(
isActive: true,
title: "Edit",
onTap: () {
context.read<SpaceTreeBloc>().add(ClearCachedData());
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return EditUserDialog(userId: user.uuid);
},
).then((v) {
if (v != null) {
if (v != null) {
_blocRole.add(const GetUsers());
}
}
});
},
)
: actionButton(
title: "Edit",
),
actionButton(
title: "Delete",
onTap: () {
@ -466,13 +453,10 @@ class UsersPage extends StatelessWidget {
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return DeleteUserDialog(
onTapDelete: () async {
return DeleteUserDialog(onTapDelete: () async {
try {
_blocRole.add(DeleteUserEvent(
user.uuid, context));
await Future.delayed(
const Duration(seconds: 2));
_blocRole.add(DeleteUserEvent(user.uuid, context));
await Future.delayed(const Duration(seconds: 2));
return true;
} catch (e) {
return false;
@ -502,21 +486,14 @@ class UsersPage extends StatelessWidget {
visiblePagesCount: 4,
buttonRadius: 10,
selectedButtonColor: ColorsManager.secondaryColor,
buttonUnSelectedBorderColor:
ColorsManager.grayBorder,
lastPageIcon:
const Icon(Icons.keyboard_double_arrow_right),
firstPageIcon:
const Icon(Icons.keyboard_double_arrow_left),
totalPages: (_blocRole.users.length /
_blocRole.itemsPerPage)
.ceil(),
buttonUnSelectedBorderColor: ColorsManager.grayBorder,
lastPageIcon: const Icon(Icons.keyboard_double_arrow_right),
firstPageIcon: const Icon(Icons.keyboard_double_arrow_left),
totalPages:
(_blocRole.totalUsersCount.length / _blocRole.itemsPerPage).ceil(),
currentPage: _blocRole.currentPage,
onPageChanged: (int pageNumber) {
_blocRole.currentPage = pageNumber;
context
.read<UserTableBloc>()
.add(ChangePage(pageNumber));
context.read<UserTableBloc>().add(ChangePage(pageNumber));
},
),
),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_state.dart';

View File

@ -3,6 +3,8 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart';
@ -10,17 +12,22 @@ import 'package:syncrow_web/pages/routines/models/delay/delay_fucntions.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/routine_details_model.dart';
import 'package:syncrow_web/pages/routines/models/routine_model.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
import 'package:syncrow_web/services/routines_api.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
import 'package:uuid/uuid.dart';
part 'routine_event.dart';
part 'routine_state.dart';
String spaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6';
String communityId = 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9';
// String spaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6';
// String communityId = 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9';
class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
RoutineBloc() : super(const RoutineState()) {
@ -54,11 +61,12 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
TriggerSwitchTabsEvent event,
Emitter<RoutineState> emit,
) {
emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false));
emit(state.copyWith(
routineTab: event.isRoutineTab, createRoutineView: false));
add(ResetRoutineState());
if (event.isRoutineTab) {
add(LoadScenes(spaceId, communityId));
add(LoadAutomation(spaceId));
add(const LoadScenes());
add(const LoadAutomation());
}
}
@ -80,8 +88,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems);
// Find the index of the item in teh current itemsList
int index =
updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
int index = updatedIfItems.indexWhere(
(map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
// Replace the map if the index is valid
if (index != -1) {
updatedIfItems[index] = event.item;
@ -90,18 +98,21 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
if (event.isTabToRun) {
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
emit(state.copyWith(
ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
} else {
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
emit(state.copyWith(
ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
}
}
void _onAddToThenContainer(AddToThenContainer event, Emitter<RoutineState> emit) {
void _onAddToThenContainer(
AddToThenContainer event, Emitter<RoutineState> emit) {
final currentItems = List<Map<String, dynamic>>.from(state.thenItems);
// Find the index of the item in teh current itemsList
int index =
currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
int index = currentItems.indexWhere(
(map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
// Replace the map if the index is valid
if (index != -1) {
currentItems[index] = event.item;
@ -112,22 +123,26 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(thenItems: currentItems));
}
void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter<RoutineState> emit) {
void _onAddFunctionsToRoutine(
AddFunctionToRoutine event, Emitter<RoutineState> emit) {
try {
if (event.functions.isEmpty) return;
List<DeviceFunctionData> selectedFunction = List<DeviceFunctionData>.from(event.functions);
List<DeviceFunctionData> selectedFunction =
List<DeviceFunctionData>.from(event.functions);
Map<String, List<DeviceFunctionData>> currentSelectedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) {
List<DeviceFunctionData> currentFunctions =
List<DeviceFunctionData>.from(currentSelectedFunctions[event.uniqueCustomId] ?? []);
List<DeviceFunctionData>.from(
currentSelectedFunctions[event.uniqueCustomId] ?? []);
List<String> functionCode = [];
for (int i = 0; i < selectedFunction.length; i++) {
for (int j = 0; j < currentFunctions.length; j++) {
if (selectedFunction[i].functionCode == currentFunctions[j].functionCode) {
if (selectedFunction[i].functionCode ==
currentFunctions[j].functionCode) {
currentFunctions[j] = selectedFunction[i];
if (!functionCode.contains(currentFunctions[j].functionCode)) {
functionCode.add(currentFunctions[j].functionCode);
@ -137,13 +152,15 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
for (int i = 0; i < functionCode.length; i++) {
selectedFunction.removeWhere((code) => code.functionCode == functionCode[i]);
selectedFunction
.removeWhere((code) => code.functionCode == functionCode[i]);
}
currentSelectedFunctions[event.uniqueCustomId] = List.from(currentFunctions)
..addAll(selectedFunction);
currentSelectedFunctions[event.uniqueCustomId] =
List.from(currentFunctions)..addAll(selectedFunction);
} else {
currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
currentSelectedFunctions[event.uniqueCustomId] =
List.from(event.functions);
}
emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
@ -152,18 +169,24 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
Future<void> _onLoadScenes(LoadScenes event, Emitter<RoutineState> emit) async {
Future<void> _onLoadScenes(
LoadScenes event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null));
List<ScenesModel> scenes = [];
try {
spaceId = event.spaceId;
communityId = event.communityId;
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
List<ScenesModel> scenes = [];
if (communityId.isNotEmpty && spaceId.isNotEmpty) {
scenes = await SceneApi.getScenes(event.spaceId, event.communityId);
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
scenes.addAll(
await SceneApi.getScenes(spaceId, communityId, projectUuid));
}
}
emit(state.copyWith(
scenes: scenes,
isLoading: false,
@ -174,18 +197,24 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
loadScenesErrorMessage: 'Failed to load scenes',
errorMessage: '',
loadAutomationErrorMessage: '',
scenes: []));
scenes: scenes));
}
}
Future<void> _onLoadAutomation(LoadAutomation event, Emitter<RoutineState> emit) async {
Future<void> _onLoadAutomation(
LoadAutomation event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null));
List<ScenesModel> automations = [];
try {
spaceId = event.spaceId;
List<ScenesModel> automations = [];
if (spaceId.isNotEmpty) {
automations = await SceneApi.getAutomation(event.spaceId);
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
automations.addAll(await SceneApi.getAutomation(spaceId));
}
}
emit(state.copyWith(
automations: automations,
@ -197,18 +226,20 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
loadAutomationErrorMessage: 'Failed to load automations',
errorMessage: '',
loadScenesErrorMessage: '',
automations: []));
automations: automations));
}
}
FutureOr<void> _onSearchRoutines(SearchRoutines event, Emitter<RoutineState> emit) async {
FutureOr<void> _onSearchRoutines(
SearchRoutines event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null));
await Future.delayed(const Duration(seconds: 1));
emit(state.copyWith(isLoading: false, errorMessage: null));
emit(state.copyWith(searchText: event.query));
}
FutureOr<void> _onAddSelectedIcon(AddSelectedIcon event, Emitter<RoutineState> emit) {
FutureOr<void> _onAddSelectedIcon(
AddSelectedIcon event, Emitter<RoutineState> emit) {
emit(state.copyWith(selectedIcon: event.icon));
}
@ -222,7 +253,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
return actions.last['deviceId'] == 'delay';
}
Future<void> _onCreateScene(CreateSceneEvent event, Emitter<RoutineState> emit) async {
Future<void> _onCreateScene(
CreateSceneEvent event, Emitter<RoutineState> emit) async {
try {
// Check if first action is delay
// if (_isFirstActionDelay(state.thenItems)) {
@ -235,7 +267,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith(
errorMessage: 'A delay condition cannot be the only or the last action',
errorMessage:
'A delay condition cannot be the only or the last action',
isLoading: false,
));
return;
@ -280,8 +313,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
});
}).toList();
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>();
final createSceneModel = CreateSceneModel(
spaceUuid: spaceId,
spaceUuid: spaceBloc.state.selectedSpaces[0],
iconId: state.selectedIcon ?? '',
showInDevice: true,
sceneName: state.routineName ?? '',
@ -292,8 +328,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final result = await SceneApi.createScene(createSceneModel);
if (result['success']) {
add(ResetRoutineState());
add(LoadScenes(spaceId, communityId));
add(LoadAutomation(spaceId));
add(const LoadScenes());
add(const LoadAutomation());
} else {
emit(state.copyWith(
isLoading: false,
@ -308,7 +344,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
Future<void> _onCreateAutomation(CreateAutomationEvent event, Emitter<RoutineState> emit) async {
Future<void> _onCreateAutomation(
CreateAutomationEvent event, Emitter<RoutineState> emit) async {
try {
if (state.routineName == null || state.routineName!.isEmpty) {
emit(state.copyWith(
@ -329,7 +366,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith(
errorMessage: 'A delay condition cannot be the only or the last action',
errorMessage:
'A delay condition cannot be the only or the last action',
isLoading: false,
));
CustomSnackBar.redSnackBar('Cannot have delay as the last action');
@ -404,9 +442,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
);
});
}).toList();
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>();
final createAutomationModel = CreateAutomationModel(
spaceUuid: spaceId,
spaceUuid: spaceBloc.state.selectedSpaces[0],
automationName: state.routineName ?? '',
decisionExpr: state.selectedAutomationOperator,
effectiveTime: EffectiveTime(
@ -421,8 +461,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final result = await SceneApi.createAutomation(createAutomationModel);
if (result['success']) {
add(ResetRoutineState());
add(LoadAutomation(spaceId));
add(LoadScenes(spaceId, communityId));
add(const LoadAutomation());
add(const LoadScenes());
} else {
emit(state.copyWith(
isLoading: false,
@ -439,17 +479,21 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onRemoveDragCard(RemoveDragCard event, Emitter<RoutineState> emit) {
FutureOr<void> _onRemoveDragCard(
RemoveDragCard event, Emitter<RoutineState> emit) {
if (event.isFromThen) {
final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
final selectedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
thenItems.removeAt(event.index);
selectedFunctions.remove(event.key);
emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions));
emit(state.copyWith(
thenItems: thenItems, selectedFunctions: selectedFunctions));
} else {
final ifItems = List<Map<String, dynamic>>.from(state.ifItems);
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
final selectedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
ifItems.removeAt(event.index);
selectedFunctions.remove(event.key);
@ -460,7 +504,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
isAutomation: false,
isTabToRun: false));
} else {
emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions));
emit(state.copyWith(
ifItems: ifItems, selectedFunctions: selectedFunctions));
}
}
}
@ -472,18 +517,23 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
));
}
FutureOr<void> _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
FutureOr<void> _onEffectiveTimeEvent(
EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
emit(state.copyWith(effectiveTime: event.effectiveTime));
}
FutureOr<void> _onSetRoutineName(SetRoutineName event, Emitter<RoutineState> emit) {
FutureOr<void> _onSetRoutineName(
SetRoutineName event, Emitter<RoutineState> emit) {
emit(state.copyWith(
routineName: event.name,
));
}
(List<Map<String, dynamic>>, List<Map<String, dynamic>>, Map<String, List<DeviceFunctionData>>)
_createCardData(
(
List<Map<String, dynamic>>,
List<Map<String, dynamic>>,
Map<String, List<DeviceFunctionData>>
) _createCardData(
List<RoutineAction> actions,
List<RoutineCondition>? conditions,
Map<String, List<DeviceFunctionData>> currentFunctions,
@ -516,7 +566,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
'deviceId': condition.entityId,
'title': matchingDevice.name ?? condition.entityId,
'productType': condition.entityType,
'imagePath': matchingDevice.getDefaultIcon(condition.entityType),
'imagePath':
matchingDevice.getDefaultIcon(condition.entityType),
};
final functions = matchingDevice.functions;
@ -552,8 +603,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final cardData = {
'entityId': action.entityId,
'uniqueCustomId': const Uuid().v4(),
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'title': action.actionExecutor == 'delay' ? 'Delay' : (matchingDevice.name ?? 'Device'),
'deviceId':
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'title': action.actionExecutor == 'delay'
? 'Delay'
: (matchingDevice.name ?? 'Device'),
'productType': action.productType,
'imagePath': matchingDevice.getDefaultIcon(action.productType),
};
@ -596,7 +650,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
return (thenItems, ifItems, currentFunctions);
}
Future<void> _onGetSceneDetails(GetSceneDetails event, Emitter<RoutineState> emit) async {
Future<void> _onGetSceneDetails(
GetSceneDetails event, Emitter<RoutineState> emit) async {
try {
emit(state.copyWith(
isLoading: true,
@ -644,10 +699,12 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (!deviceCards.containsKey(deviceId)) {
deviceCards[deviceId] = {
'entityId': action.entityId,
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay'
? const Uuid().v4()
: action.entityId,
'deviceId':
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId':
action.type == 'automation' || action.actionExecutor == 'delay'
? const Uuid().v4()
: action.entityId,
'title': action.actionExecutor == 'delay'
? 'Delay'
: action.type == 'automation'
@ -682,7 +739,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
),
);
// emit(state.copyWith(automationActionExecutor: action.actionExecutor));
} else if (action.executorProperty != null && action.actionExecutor != 'delay') {
} else if (action.executorProperty != null &&
action.actionExecutor != 'delay') {
if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = [];
}
@ -754,7 +812,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onResetRoutineState(ResetRoutineState event, Emitter<RoutineState> emit) {
FutureOr<void> _onResetRoutineState(
ResetRoutineState event, Emitter<RoutineState> emit) {
emit(state.copyWith(
ifItems: [],
thenItems: [],
@ -778,17 +837,24 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
createRoutineView: false));
}
FutureOr<void> _deleteScene(DeleteScene event, Emitter<RoutineState> emit) {
FutureOr<void> _deleteScene(
DeleteScene event, Emitter<RoutineState> emit) async {
try {
emit(state.copyWith(isLoading: true));
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>();
if (state.isTabToRun) {
SceneApi.deleteScene(unitUuid: spaceId, sceneId: state.sceneId ?? '');
await SceneApi.deleteScene(
unitUuid: spaceBloc.state.selectedSpaces[0],
sceneId: state.sceneId ?? '');
} else {
SceneApi.deleteAutomation(unitUuid: spaceId, automationId: state.automationId ?? '');
await SceneApi.deleteAutomation(
unitUuid: spaceBloc.state.selectedSpaces[0],
automationId: state.automationId ?? '');
}
add(LoadScenes(spaceId, communityId));
add(LoadAutomation(spaceId));
add(const LoadScenes());
add(const LoadAutomation());
add(ResetRoutineState());
emit(state.copyWith(isLoading: false, createRoutineView: false));
} catch (e) {
@ -813,10 +879,24 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// }
// }
FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
FutureOr<void> _fetchDevices(
FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true));
try {
final devices = await DevicesManagementApi().fetchDevices('', '');
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
List<AllDevicesModel> devices = [];
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
devices.addAll(await DevicesManagementApi()
.fetchDevices(communityId, spaceId, projectUuid));
}
}
emit(state.copyWith(isLoading: false, devices: devices));
} catch (e) {
@ -824,7 +904,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onUpdateScene(UpdateScene event, Emitter<RoutineState> emit) async {
FutureOr<void> _onUpdateScene(
UpdateScene event, Emitter<RoutineState> emit) async {
try {
// Check if first action is delay
// if (_isFirstActionDelay(state.thenItems)) {
@ -838,7 +919,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith(
errorMessage: 'A delay condition cannot be the only or the last action',
errorMessage:
'A delay condition cannot be the only or the last action',
isLoading: false,
));
return;
@ -891,11 +973,12 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
actions: actions,
);
final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
final result =
await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
if (result['success']) {
add(ResetRoutineState());
add(LoadScenes(spaceId, communityId));
add(LoadAutomation(spaceId));
add(const LoadScenes());
add(const LoadAutomation());
} else {
emit(state.copyWith(
isLoading: false,
@ -910,7 +993,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onUpdateAutomation(UpdateAutomation event, Emitter<RoutineState> emit) async {
FutureOr<void> _onUpdateAutomation(
UpdateAutomation event, Emitter<RoutineState> emit) async {
try {
if (state.routineName == null || state.routineName!.isEmpty) {
emit(state.copyWith(
@ -1005,8 +1089,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
});
}).toList();
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>();
final createAutomationModel = CreateAutomationModel(
spaceUuid: spaceId,
spaceUuid: spaceBloc.state.selectedSpaces[0],
automationName: state.routineName ?? '',
decisionExpr: state.selectedAutomationOperator,
effectiveTime: EffectiveTime(
@ -1018,13 +1105,13 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
actions: actions,
);
final result =
await SceneApi.updateAutomation(createAutomationModel, state.automationId ?? '');
final result = await SceneApi.updateAutomation(
createAutomationModel, state.automationId ?? '');
if (result['success']) {
add(ResetRoutineState());
add(LoadAutomation(spaceId));
add(LoadScenes(spaceId, communityId));
add(LoadAutomation());
add(LoadScenes());
} else {
emit(state.copyWith(
isLoading: false,
@ -1052,7 +1139,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
thenItems: [],
));
final automationDetails = await SceneApi.getAutomationDetails(event.automationId);
final automationDetails =
await SceneApi.getAutomationDetails(event.automationId);
final Map<String, Map<String, dynamic>> deviceIfCards = {};
final Map<String, Map<String, dynamic>> deviceThenCards = {};
@ -1120,13 +1208,15 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
),
);
final deviceId =
action.actionExecutor == 'delay' ? '${action.entityId}_delay' : action.entityId;
final deviceId = action.actionExecutor == 'delay'
? '${action.entityId}_delay'
: action.entityId;
if (!deviceThenCards.containsKey(deviceId)) {
deviceThenCards[deviceId] = {
'entityId': action.entityId,
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'deviceId':
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId': const Uuid().v4(),
'title': action.actionExecutor == 'delay'
? 'Delay'
@ -1157,7 +1247,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
updatedFunctions[uniqueCustomId] = [];
}
if (action.executorProperty != null && action.actionExecutor != 'delay') {
if (action.executorProperty != null &&
action.actionExecutor != 'delay') {
final functions = matchingDevice.functions;
final functionCode = action.executorProperty!.functionCode;
for (var function in functions) {
@ -1199,10 +1290,14 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
final ifItems = deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
final ifItems = deviceIfCards.values
.where((card) => card['type'] == 'condition')
.toList();
final thenItems = deviceThenCards.values
.where((card) =>
card['type'] == 'action' || card['type'] == 'automation' || card['type'] == 'scene')
card['type'] == 'action' ||
card['type'] == 'automation' ||
card['type'] == 'scene')
.toList();
emit(state.copyWith(

View File

@ -27,22 +27,24 @@ class AddToThenContainer extends RoutineEvent {
}
class LoadScenes extends RoutineEvent {
final String spaceId;
final String communityId;
// final String spaceId;
// final String communityId;
// final BuildContext context;
const LoadScenes(this.spaceId, this.communityId);
const LoadScenes();
@override
List<Object> get props => [spaceId, communityId];
List<Object> get props => [];
}
class LoadAutomation extends RoutineEvent {
final String spaceId;
// final String spaceId;
// final BuildContext context;
const LoadAutomation(this.spaceId);
const LoadAutomation();
@override
List<Object> get props => [spaceId];
List<Object> get props => [];
}
class AddFunctionToRoutine extends RoutineEvent {

View File

@ -7,7 +7,6 @@ import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_vie
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
class RoutinesView extends StatefulWidget {
const RoutinesView({super.key});
@ -20,7 +19,7 @@ class _RoutinesViewState extends State<RoutinesView> {
@override
void initState() {
super.initState();
context.read<RoutineBloc>().add(FetchDevicesInRoutine());
// context.read<RoutineBloc>().add(FetchDevicesInRoutine());
}
@override
@ -32,65 +31,70 @@ class _RoutinesViewState extends State<RoutinesView> {
}
return Row(
children: [
Expanded(
child:
// SideSpacesView(
// onSelectAction: (String communityId, String spaceId) {
// // context.read<RoutineBloc>()
// // ..add(LoadScenes(spaceId, communityId))
// // ..add(LoadAutomation(spaceId));
// },
// )
SpaceTreeView(
onSelect: () {},
Expanded(child: SpaceTreeView(
onSelect: () {
context.read<RoutineBloc>()
..add(const LoadScenes())
..add(const LoadAutomation());
},
)),
Expanded(
flex: 3,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Create New Routines",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
flex: 4,
child: ListView(children: [
Container(
padding: const EdgeInsets.all(16),
height: MediaQuery.sizeOf(context).height,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Create New Routines",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(
height: 10,
),
RoutineViewCard(
onTap: () {
if (context.read<SpaceTreeBloc>().state.selectedCommunities.length == 1 &&
context.read<SpaceTreeBloc>().state.selectedSpaces.length == 1) {
context.read<RoutineBloc>().add(
(ResetRoutineState()),
);
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
context.read<SpaceTreeBloc>().state.selectedSpaces.isEmpty
? 'Please select a space'
: 'Please select only one space to proceed'),
),
),
const SizedBox(
height: 10,
),
RoutineViewCard(
onTap: () {
if (context.read<SpaceTreeBloc>().selectedCommunityId.isNotEmpty &&
context.read<SpaceTreeBloc>().selectedSpaceId.isNotEmpty) {
context.read<RoutineBloc>().add(
(ResetRoutineState()),
);
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
} else {
CustomSnackBar.redSnackBar('Please select a space');
}
},
icon: Icons.add,
textString: '',
),
const SizedBox(
height: 15,
),
const Expanded(child: FetchRoutineScenesAutomation()),
],
),
],
);
// CustomSnackBar.redSnackBar(
// context.read<SpaceTreeBloc>().state.selectedSpaces.isEmpty
// ? 'Please select a space'
// : 'Please select only one space to proceed');
}
},
icon: Icons.add,
textString: '',
),
const SizedBox(
height: 15,
),
const Expanded(child: FetchRoutineScenesAutomation()),
],
),
),
),
]),
),
],
);

View File

@ -20,10 +20,6 @@ class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
@override
void initState() {
super.initState();
context.read<RoutineBloc>()
..add(LoadScenes(context.read<SpaceTreeBloc>().selectedSpaceId,
context.read<SpaceTreeBloc>().selectedCommunityId))
..add(LoadAutomation(context.read<SpaceTreeBloc>().selectedSpaceId));
}
@override

View File

@ -4,9 +4,20 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart';
class RoutineDevices extends StatelessWidget {
class RoutineDevices extends StatefulWidget {
const RoutineDevices({super.key});
@override
State<RoutineDevices> createState() => _RoutineDevicesState();
}
class _RoutineDevicesState extends State<RoutineDevices> {
@override
void initState() {
super.initState();
context.read<RoutineBloc>().add(FetchDevicesInRoutine());
}
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(

View File

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class ScenesAndAutomations extends StatefulWidget {
@ -19,9 +18,8 @@ class _ScenesAndAutomationsState extends State<ScenesAndAutomations> {
void initState() {
super.initState();
context.read<RoutineBloc>()
..add(LoadScenes(context.read<SpaceTreeBloc>().selectedSpaceId,
context.read<SpaceTreeBloc>().selectedCommunityId))
..add(LoadAutomation(context.read<SpaceTreeBloc>().selectedSpaceId));
..add(const LoadScenes())
..add(const LoadAutomation());
}
@override

View File

@ -1,4 +1,5 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
@ -6,9 +7,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model
import 'package:syncrow_web/services/space_mana_api.dart';
class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
String selectedCommunityId = 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9';
String selectedSpaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6';
SpaceTreeBloc() : super(const SpaceTreeState()) {
on<InitialEvent>(_fetchSpaces);
on<OnCommunityExpanded>(_onCommunityExpanded);
@ -16,17 +14,22 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
on<OnCommunitySelected>(_onCommunitySelected);
on<OnSpaceSelected>(_onSpaceSelected);
on<SearchQueryEvent>(_onSearch);
on<ClearAllData>(_clearAllData);
on<ClearCachedData>(_clearCachedData);
}
_fetchSpaces(InitialEvent event, Emitter<SpaceTreeState> emit) async {
emit(SpaceTreeLoadingState());
try {
List<CommunityModel> communities = await CommunitySpaceManagementApi().fetchCommunities();
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
List<CommunityModel> communities =
await CommunitySpaceManagementApi().fetchCommunities(projectUuid);
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces =
await CommunitySpaceManagementApi().getSpaceHierarchy(community.uuid);
await CommunitySpaceManagementApi().getSpaceHierarchy(community.uuid, projectUuid);
return CommunityModel(
uuid: community.uuid,
@ -88,6 +91,7 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList());
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
List<String> selectedSpacesInCommunity = communityAndSpaces[event.communityId] ?? [];
List<String> childrenIds = _getAllChildIds(event.children);
@ -95,14 +99,16 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
// Select the community and all its children
updatedSelectedCommunities.add(event.communityId);
updatedSelectedSpaces.addAll(childrenIds);
selectedSpacesInCommunity.addAll(childrenIds);
} else {
// Unselect the community and all its children
updatedSelectedCommunities.remove(event.communityId);
updatedSelectedSpaces.removeWhere(childrenIds.contains);
updatedSoldChecks.removeWhere(childrenIds.contains);
selectedSpacesInCommunity.removeWhere(childrenIds.contains);
}
communityAndSpaces[event.communityId] = updatedSelectedSpaces;
communityAndSpaces[event.communityId] = selectedSpacesInCommunity;
emit(state.copyWith(
selectedCommunities: updatedSelectedCommunities,
@ -122,6 +128,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
List<String> selectedSpacesInCommunity = communityAndSpaces[event.communityModel.uuid] ?? [];
List<String> childrenIds = _getAllChildIds(event.children);
bool isChildSelected = false;
@ -135,12 +143,15 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
!updatedSoldChecks.contains(event.spaceId)) {
// First click: Select the space and all its children
updatedSelectedSpaces.add(event.spaceId);
updatedSelectedCommunities.add(event.communityId);
updatedSelectedCommunities.add(event.communityModel.uuid);
selectedSpacesInCommunity.add(event.spaceId);
if (childrenIds.isNotEmpty) {
updatedSelectedSpaces.addAll(childrenIds);
selectedSpacesInCommunity.addAll(childrenIds);
}
List<String> spaces = _getThePathToChild(event.communityId, event.spaceId);
List<String> spaces = _getThePathToChild(event.communityModel.uuid, event.spaceId);
for (String space in spaces) {
if (!updatedSelectedSpaces.contains(space) && !updatedSoldChecks.contains(space)) {
updatedSoldChecks.add(space);
@ -150,31 +161,45 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
childrenIds.isNotEmpty &&
isChildSelected) {
// Second click: Unselect space but keep children
selectedSpacesInCommunity.remove(event.spaceId);
updatedSelectedSpaces.remove(event.spaceId);
updatedSoldChecks.add(event.spaceId);
} else {
// Third click: Unselect space and all its children
selectedSpacesInCommunity.remove(event.spaceId);
updatedSelectedSpaces.remove(event.spaceId);
if (childrenIds.isNotEmpty) {
updatedSelectedSpaces.removeWhere(childrenIds.contains);
updatedSoldChecks.removeWhere(childrenIds.contains);
selectedSpacesInCommunity.removeWhere(childrenIds.contains);
}
updatedSoldChecks.remove(event.spaceId);
List<String> parents = _getThePathToChild(event.communityId, event.spaceId);
if (!_parentSelected(parents, updatedSelectedSpaces)) {
List<String> parents =
_getThePathToChild(event.communityModel.uuid, event.spaceId).toSet().toList();
if (updatedSelectedSpaces.isEmpty) {
updatedSoldChecks.removeWhere(parents.contains);
}
if (!_anySpacesSelectedInCommunity(
event.communityId, updatedSelectedSpaces, updatedSoldChecks)) {
updatedSelectedCommunities.remove(event.communityId);
updatedSelectedCommunities.remove(event.communityModel.uuid);
} else {
// Check if any parent has selected children
for (String space in parents) {
if (!_noChildrenSelected(event.communityModel, space, updatedSelectedSpaces, parents)) {
updatedSoldChecks.remove(space);
}
}
if (!_anySpacesSelectedInCommunity(
event.communityModel, updatedSelectedSpaces, updatedSoldChecks)) {
updatedSelectedCommunities.remove(event.communityModel.uuid);
}
}
}
communityAndSpaces[event.communityId] = updatedSelectedSpaces;
communityAndSpaces[event.communityModel.uuid] = selectedSpacesInCommunity;
emit(state.copyWith(
selectedCommunities: updatedSelectedCommunities,
selectedCommunities: updatedSelectedCommunities.toSet().toList(),
selectedSpaces: updatedSelectedSpaces,
soldCheck: updatedSoldChecks,
selectedCommunityAndSpaces: communityAndSpaces));
@ -184,12 +209,24 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
}
}
_parentSelected(List<String> parents, List<String> selectedSpaces) {
for (String space in parents) {
if (selectedSpaces.contains(space)) {
return true;
_noChildrenSelected(
CommunityModel community, String spaceId, List<String> selectedSpaces, List<String> parents) {
if (selectedSpaces.contains(spaceId)) {
return true;
}
List<SpaceModel> children = _getAllChildSpaces(community.spaces);
for (var child in children) {
if (spaceId == child.uuid) {
List<String> ids = _getAllChildIds(child.children);
for (var id in ids) {
if (selectedSpaces.contains(id)) {
return true;
}
}
}
}
return false;
}
@ -209,7 +246,45 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
}).toList();
emit(state.copyWith(
filteredCommunity: filteredCommunity, isSearching: event.searchQuery.isNotEmpty));
filteredCommunity: filteredCommunity,
isSearching: event.searchQuery.isNotEmpty,
searchQuery: event.searchQuery));
} catch (e) {
emit(const SpaceTreeErrorState('Something went wrong'));
}
}
_clearAllData(ClearAllData event, Emitter<SpaceTreeState> emit) async {
try {
emit(state.copyWith(
communitiesList: [],
filteredCommunity: [],
isSearching: false,
soldCheck: [],
selectedSpaces: [],
selectedCommunities: [],
selectedCommunityAndSpaces: {},
searchQuery: '',
expandedSpaces: [],
expandedCommunity: []));
} catch (e) {
emit(const SpaceTreeErrorState('Something went wrong'));
}
}
_clearCachedData(ClearCachedData event, Emitter<SpaceTreeState> emit) async {
try {
emit(state.copyWith(
communitiesList: state.communityList,
filteredCommunity: [],
isSearching: false,
soldCheck: [],
selectedSpaces: [],
selectedCommunities: [],
selectedCommunityAndSpaces: {},
searchQuery: '',
expandedSpaces: [],
expandedCommunity: []));
} catch (e) {
emit(const SpaceTreeErrorState('Something went wrong'));
}
@ -230,21 +305,26 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
ids.add(child.uuid!);
ids.addAll(_getAllChildIds(child.children));
}
return ids;
return ids.toSet().toList();
}
List<SpaceModel> _getAllChildSpaces(List<SpaceModel> spaces) {
List<SpaceModel> children = [];
for (var child in spaces) {
children.add(child);
children.addAll(_getAllChildSpaces(child.children));
}
return children;
}
bool _anySpacesSelectedInCommunity(
String communityId, List<String> selectedSpaces, List<String> partialCheckedList) {
CommunityModel community, List<String> selectedSpaces, List<String> partialCheckedList) {
bool result = false;
for (var community in state.communityList) {
if (community.uuid == communityId) {
List<String> ids = _getAllChildIds(community.spaces);
for (var id in ids) {
result = selectedSpaces.contains(id) || partialCheckedList.contains(id);
if (result) {
return result;
}
}
List<String> ids = _getAllChildIds(community.spaces);
for (var id in ids) {
result = selectedSpaces.contains(id) || partialCheckedList.contains(id);
if (result) {
return result;
}
}
return result;
@ -288,4 +368,9 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
ids.removeLast();
return [];
}
@override
Future<void> close() async {
super.close();
}
}

View File

@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
class SpaceTreeEvent extends Equatable {
@ -49,14 +50,14 @@ class OnSpaceExpanded extends SpaceTreeEvent {
}
class OnSpaceSelected extends SpaceTreeEvent {
final String communityId;
final String spaceId;
final List<SpaceModel> children;
final CommunityModel communityModel;
const OnSpaceSelected(this.communityId, this.spaceId, this.children);
const OnSpaceSelected(this.communityModel, this.spaceId, this.children);
@override
List<Object> get props => [communityId, spaceId, children];
List<Object> get props => [communityModel, spaceId, children];
}
class SearchQueryEvent extends SpaceTreeEvent {
@ -67,3 +68,7 @@ class SearchQueryEvent extends SpaceTreeEvent {
@override
List<Object> get props => [searchQuery];
}
class ClearAllData extends SpaceTreeEvent {}
class ClearCachedData extends SpaceTreeEvent {}

View File

@ -11,6 +11,7 @@ class SpaceTreeState extends Equatable {
final List<String> selectedSpaces;
final List<String> soldCheck;
final bool isSearching;
final String searchQuery;
const SpaceTreeState(
{this.communityList = const [],
@ -21,7 +22,8 @@ class SpaceTreeState extends Equatable {
this.selectedSpaces = const [],
this.soldCheck = const [],
this.isSearching = false,
this.selectedCommunityAndSpaces = const {}});
this.selectedCommunityAndSpaces = const {},
this.searchQuery = ''});
SpaceTreeState copyWith(
{List<CommunityModel>? communitiesList,
@ -32,7 +34,8 @@ class SpaceTreeState extends Equatable {
List<String>? selectedSpaces,
List<String>? soldCheck,
bool? isSearching,
Map<String, List<String>>? selectedCommunityAndSpaces}) {
Map<String, List<String>>? selectedCommunityAndSpaces,
String? searchQuery}) {
return SpaceTreeState(
communityList: communitiesList ?? this.communityList,
filteredCommunity: filteredCommunity ?? this.filteredCommunity,
@ -42,7 +45,8 @@ class SpaceTreeState extends Equatable {
selectedSpaces: selectedSpaces ?? this.selectedSpaces,
soldCheck: soldCheck ?? this.soldCheck,
isSearching: isSearching ?? this.isSearching,
selectedCommunityAndSpaces: selectedCommunityAndSpaces ?? this.selectedCommunityAndSpaces);
selectedCommunityAndSpaces: selectedCommunityAndSpaces ?? this.selectedCommunityAndSpaces,
searchQuery: searchQuery ?? this.searchQuery);
}
@override
@ -55,7 +59,8 @@ class SpaceTreeState extends Equatable {
selectedSpaces,
soldCheck,
isSearching,
selectedCommunityAndSpaces
selectedCommunityAndSpaces,
searchQuery
];
}

View File

@ -10,10 +10,23 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart';
class SpaceTreeView extends StatelessWidget {
class SpaceTreeView extends StatefulWidget {
final Function onSelect;
const SpaceTreeView({required this.onSelect, super.key});
@override
State<SpaceTreeView> createState() => _SpaceTreeViewState();
}
class _SpaceTreeViewState extends State<SpaceTreeView> {
final ScrollController _scrollController = ScrollController();
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
@ -21,79 +34,162 @@ class SpaceTreeView extends StatelessWidget {
return Container(
height: MediaQuery.sizeOf(context).height,
decoration: subSectionContainerDecoration,
// padding: const EdgeInsets.all(16.0),
child: state is SpaceTreeLoadingState
? const Center(child: CircularProgressIndicator())
: Column(
children: [
CustomSearchBar(
searchQuery: state.searchQuery,
onSearchChanged: (query) {
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
},
),
const SizedBox(height: 16),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: list.isEmpty
? Center(
child: Text(
'No results found',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.lightGrayColor, // Gray when not selected
fontWeight: FontWeight.w400,
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: [
Container(
width: MediaQuery.sizeOf(context).width * 0.5,
padding: const EdgeInsets.all(8.0),
child: list.isEmpty
? Center(
child: Text(
'No results found',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.lightGrayColor,
fontWeight: FontWeight.w400,
),
),
)
: Scrollbar(
scrollbarOrientation: ScrollbarOrientation.left,
thumbVisibility: true,
controller: _scrollController,
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: ListView(
controller: _scrollController,
shrinkWrap: true,
children: list
.map(
(community) => CustomExpansionTileSpaceTree(
title: community.name,
isSelected: state.selectedCommunities
.contains(community.uuid),
isSoldCheck: state.selectedCommunities
.contains(community.uuid),
onExpansionChanged: () {
context
.read<SpaceTreeBloc>()
.add(OnCommunityExpanded(community.uuid));
},
isExpanded: state.expandedCommunities
.contains(community.uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(
community.uuid, community.spaces));
widget.onSelect();
},
children: community.spaces.map((space) {
return CustomExpansionTileSpaceTree(
title: space.name,
isExpanded:
state.expandedSpaces.contains(space.uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(community, 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),
);
}).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(),
// ),
// ),
// ),
],
),
);
@ -101,7 +197,7 @@ class SpaceTreeView extends StatelessWidget {
}
List<Widget> _buildNestedSpaces(
BuildContext context, SpaceTreeState state, SpaceModel space, String communityId) {
BuildContext context, SpaceTreeState state, SpaceModel space, CommunityModel community) {
return space.children.map((child) {
return CustomExpansionTileSpaceTree(
isSelected:
@ -112,13 +208,13 @@ class SpaceTreeView extends StatelessWidget {
onItemSelected: () {
context
.read<SpaceTreeBloc>()
.add(OnSpaceSelected(communityId, child.uuid ?? '', child.children));
onSelect();
.add(OnSpaceSelected(community, child.uuid ?? '', child.children));
widget.onSelect();
},
onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(OnSpaceExpanded(communityId, child.uuid ?? ''));
context.read<SpaceTreeBloc>().add(OnSpaceExpanded(community.uuid, child.uuid ?? ''));
},
children: _buildNestedSpaces(context, state, child, communityId),
children: _buildNestedSpaces(context, state, child, community),
);
}).toList();
}

View File

@ -1,4 +1,8 @@
import 'dart:developer';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
@ -14,6 +18,9 @@ import 'package:syncrow_web/services/product_api.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
class SpaceManagementBloc
extends Bloc<SpaceManagementEvent, SpaceManagementState> {
@ -39,15 +46,23 @@ class SpaceManagementBloc
on<SpaceModelLoadEvent>(_onLoadSpaceModel);
}
void _logEvent(String eventName) {
log('Event Triggered: $eventName');
}
void _onUpdateCommunity(
UpdateCommunityEvent event,
Emitter<SpaceManagementState> emit,
) async {
_logEvent('UpdateCommunityEvent');
final previousState = state;
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
emit(SpaceManagementLoading());
final success =
await _api.updateCommunity(event.communityUuid, event.name);
final success = await _api.updateCommunity(
event.communityUuid, event.name, projectUuid);
if (success) {
if (previousState is SpaceManagementLoaded) {
final updatedCommunities =
@ -79,6 +94,8 @@ class SpaceManagementBloc
Future<List<SpaceTemplateModel>> fetchSpaceModels(
SpaceManagementState previousState) async {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
List<SpaceTemplateModel> allSpaces = [];
List<SpaceTemplateModel> prevSpaceModels = [];
@ -95,7 +112,8 @@ class SpaceManagementBloc
int page = 1;
while (hasNext) {
final spaces = await _spaceModelApi.listSpaceModels(page: page);
final spaces = await _spaceModelApi.listSpaceModels(
page: page, projectId: projectUuid);
if (spaces.isNotEmpty) {
allSpaces.addAll(spaces);
page++;
@ -103,7 +121,8 @@ class SpaceManagementBloc
hasNext = false;
}
}
prevSpaceModels = await _spaceModelApi.listSpaceModels(page: 1);
prevSpaceModels = await _spaceModelApi.listSpaceModels(
page: 1, projectId: projectUuid);
}
return allSpaces;
@ -132,7 +151,9 @@ class SpaceManagementBloc
Future<List<SpaceModel>> _fetchSpacesForCommunity(
String communityUuid) async {
return await _api.getSpaceHierarchy(communityUuid);
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
return await _api.getSpaceHierarchy(communityUuid, projectUuid);
}
Future<void> _onNewCommunity(
@ -163,6 +184,8 @@ class SpaceManagementBloc
BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
try {
final previousState = state;
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
var prevSpaceModels = await fetchSpaceModels(previousState);
if (previousState is SpaceManagementLoaded ||
@ -176,7 +199,7 @@ class SpaceManagementBloc
return;
}
final communities = await _api.fetchCommunities();
final communities = await _api.fetchCommunities(projectUuid);
final updatedCommunities =
await Future.wait(communities.map((community) async {
final spaces = await _fetchSpacesForCommunity(community.uuid);
@ -206,11 +229,16 @@ class SpaceManagementBloc
LoadCommunityAndSpacesEvent event,
Emitter<SpaceManagementState> emit,
) async {
_logEvent('LoadCommunityAndSpacesEvent');
var prevState = state;
emit(SpaceManagementLoading());
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
_onloadProducts();
List<CommunityModel> communities = await _api.fetchCommunities();
List<CommunityModel> communities =
await _api.fetchCommunities(projectUuid);
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {
@ -244,8 +272,10 @@ class SpaceManagementBloc
) async {
try {
emit(SpaceManagementLoading());
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final success = await _api.deleteCommunity(event.communityUuid);
final success =
await _api.deleteCommunity(event.communityUuid, projectUuid);
if (success) {
add(LoadCommunityAndSpacesEvent());
} else {
@ -270,8 +300,10 @@ class SpaceManagementBloc
emit(SpaceManagementLoading());
try {
CommunityModel? newCommunity =
await _api.createCommunity(event.name, event.description);
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
CommunityModel? newCommunity = await _api.createCommunity(
event.name, event.description, projectUuid);
var prevSpaceModels = await fetchSpaceModels(previousState);
if (newCommunity != null) {
@ -414,6 +446,7 @@ class SpaceManagementBloc
Future<List<SpaceModel>> saveSpacesHierarchically(
List<SpaceModel> spaces, String communityUuid) async {
final orderedSpaces = flattenHierarchy(spaces);
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final parentsToDelete = orderedSpaces.where((space) =>
space.status == SpaceStatus.deleted &&
@ -422,7 +455,7 @@ class SpaceManagementBloc
for (var parent in parentsToDelete) {
try {
if (parent.uuid != null) {
await _api.deleteSpace(communityUuid, parent.uuid!);
await _api.deleteSpace(communityUuid, parent.uuid!, projectUuid);
}
} catch (e) {
rethrow;
@ -435,7 +468,8 @@ class SpaceManagementBloc
if (space.uuid != null && space.uuid!.isNotEmpty) {
List<TagModelUpdate> tagUpdates = [];
final prevSpace = await _api.getSpace(communityUuid, space.uuid!);
final prevSpace =
await _api.getSpace(communityUuid, space.uuid!, projectUuid);
final List<UpdateSubspaceTemplateModel> subspaceUpdates = [];
final List<SubspaceModel>? prevSubspaces = prevSpace?.subspaces;
final List<SubspaceModel>? newSubspaces = space.subspaces;
@ -504,17 +538,17 @@ class SpaceManagementBloc
}
final response = await _api.updateSpace(
communityId: communityUuid,
spaceId: space.uuid!,
name: space.name,
parentId: space.parent?.uuid,
isPrivate: space.isPrivate,
position: space.position,
icon: space.icon,
subspaces: subspaceUpdates,
tags: tagUpdates,
direction: space.incomingConnection?.direction,
);
communityId: communityUuid,
spaceId: space.uuid!,
name: space.name,
parentId: space.parent?.uuid,
isPrivate: space.isPrivate,
position: space.position,
icon: space.icon,
subspaces: subspaceUpdates,
tags: tagUpdates,
direction: space.incomingConnection?.direction,
projectId: projectUuid);
} else {
// Call create if the space does not have a UUID
final List<CreateTagBodyModel> tagBodyModels = space.tags != null
@ -533,17 +567,17 @@ class SpaceManagementBloc
[];
final response = await _api.createSpace(
communityId: communityUuid,
name: space.name,
parentId: space.parent?.uuid,
isPrivate: space.isPrivate,
position: space.position,
icon: space.icon,
direction: space.incomingConnection?.direction,
spaceModelUuid: space.spaceModel?.uuid,
tags: tagBodyModels,
subspaces: createSubspaceBodyModels,
);
communityId: communityUuid,
name: space.name,
parentId: space.parent?.uuid,
isPrivate: space.isPrivate,
position: space.position,
icon: space.icon,
direction: space.incomingConnection?.direction,
spaceModelUuid: space.spaceModel?.uuid,
tags: tagBodyModels,
subspaces: createSubspaceBodyModels,
projectId: projectUuid);
space.uuid = response?.uuid;
}
} catch (e) {
@ -583,8 +617,10 @@ class SpaceManagementBloc
emit(SpaceManagementLoading());
try {
var prevState = state;
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
List<CommunityModel> communities = await _api.fetchCommunities();
List<CommunityModel> communities =
await _api.fetchCommunities(projectUuid);
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {

View File

@ -28,8 +28,11 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => SpaceManagementBloc(_api, _productApi, _spaceModelApi)
..add(LoadCommunityAndSpacesEvent()),
create: (_) => SpaceManagementBloc(
_api,
_productApi,
_spaceModelApi,
)..add(LoadCommunityAndSpacesEvent()),
),
BlocProvider(
create: (_) => CenterBodyBloc(),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
@ -42,20 +43,29 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
_spaceModels = List.from(widget.spaceModels ?? []);
}
@override
@override
void didUpdateWidget(covariant LoadedSpaceView oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.spaceModels != oldWidget.spaceModels) {
setState(() {
_spaceModels = List.from(widget.spaceModels ?? []);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
_spaceModels = List.from(widget.spaceModels ?? []);
});
}
});
}
}
void _onSpaceModelsUpdated(List<SpaceTemplateModel> updatedModels) {
if (mounted && updatedModels != _spaceModels) {
setState(() {
_spaceModels = updatedModels;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
_spaceModels = updatedModels;
});
}
});
}
}
@ -65,16 +75,11 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
return Stack(
clipBehavior: Clip.none,
children: [
Row(
children: [
SidebarWidget(
communities: widget.communities,
selectedSpaceUuid: widget.selectedSpace?.uuid ??
widget.selectedCommunity?.uuid ??
'',
),
widget.shouldNavigateToSpaceModelPage
? Expanded(
widget.shouldNavigateToSpaceModelPage
? Row(
children: [
SizedBox(width: 300, child: SpaceTreeView(onSelect: () {})),
Expanded(
child: BlocProvider(
create: (context) => SpaceModelBloc(
api: SpaceModelManagementApi(),
@ -85,8 +90,17 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
onSpaceModelsUpdated: _onSpaceModelsUpdated,
),
),
)
: CommunityStructureArea(
),
],
)
: Row(
children: [
SidebarWidget(
communities: widget.communities,
selectedSpaceUuid:
widget.selectedSpace?.uuid ?? widget.selectedCommunity?.uuid ?? '',
),
CommunityStructureArea(
selectedCommunity: widget.selectedCommunity,
selectedSpace: widget.selectedSpace,
spaces: widget.selectedCommunity?.spaces ?? [],
@ -94,8 +108,8 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
communities: widget.communities,
spaceModels: _spaceModels,
),
],
),
],
),
const GradientCanvasBorderWidget(),
],
);

View File

@ -4,7 +4,9 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_e
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
AssignTagBloc() : super(AssignTagInitial()) {
final List<String> allTags;
AssignTagBloc(this.allTags) : super(AssignTagInitial()) {
on<InitializeTags>((event, emit) {
final initialTags = event.initialTags ?? [];
@ -16,25 +18,25 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
}
}
final allTags = <Tag>[];
final tags = <Tag>[];
for (var selectedProduct in event.addedProducts) {
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
if (selectedProduct.count == 0 ||
selectedProduct.count <= existingCount) {
allTags.addAll(initialTags
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
continue;
}
final missingCount = selectedProduct.count - existingCount;
allTags.addAll(initialTags
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (missingCount > 0) {
allTags.addAll(List.generate(
tags.addAll(List.generate(
missingCount,
(index) => Tag(
tag: '',
@ -45,10 +47,14 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
}
}
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagLoaded(
tags: allTags,
isSaveEnabled: _validateTags(allTags),
errorMessage: ''));
tags: tags,
updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags),
errorMessage: '',
));
});
on<UpdateTagEvent>((event, emit) {
@ -56,10 +62,13 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags);
tags[event.index].tag = event.tag;
tags[event.index] = tags[event.index].copyWith(tag: event.tag);
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagLoaded(
tags: tags,
updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
@ -72,12 +81,15 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags);
// Use copyWith for immutability
// Update the location
tags[event.index] =
tags[event.index].copyWith(location: event.location);
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagLoaded(
tags: tags,
updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
@ -92,6 +104,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
emit(AssignTagLoaded(
tags: tags,
updatedTags: _calculateAvailableTags(allTags, tags),
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
@ -102,38 +115,37 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final currentState = state;
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final updatedTags = List<Tag>.from(currentState.tags)
final tags = List<Tag>.from(currentState.tags)
..remove(event.tagToDelete);
// Recalculate available tags
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagLoaded(
tags: updatedTags,
isSaveEnabled: _validateTags(updatedTags),
errorMessage: _getValidationError(updatedTags),
tags: tags,
updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
} else {
emit(const AssignTagLoaded(
tags: [],
isSaveEnabled: false,
errorMessage: 'Failed to delete tag'));
}
});
}
// Validate the tags for duplicates or empty values
bool _validateTags(List<Tag> tags) {
final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet();
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
final isValid = uniqueTags.length == tags.length && !hasEmptyTag;
return isValid;
return uniqueTags.length == tags.length && !hasEmptyTag;
}
// Get validation error for duplicate tags
String? _getValidationError(List<Tag> tags) {
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
if (hasEmptyTag) {
return 'Tags cannot be empty.';
}
final duplicateTags = tags
final nonEmptyTags = tags
.map((tag) => tag.tag?.trim() ?? '')
.where((tag) => tag.isNotEmpty)
.toList();
final duplicateTags = nonEmptyTags
.fold<Map<String, int>>({}, (map, tag) {
map[tag] = (map[tag] ?? 0) + 1;
return map;
@ -149,4 +161,15 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
return null;
}
List<String> _calculateAvailableTags(List<String> allTags, List<Tag> tags) {
final selectedTags = tags
.where((tag) => (tag.tag?.trim().isNotEmpty ?? false))
.map((tag) => tag.tag!.trim())
.toSet();
final availableTags =
allTags.where((tag) => !selectedTags.contains(tag.trim())).toList();
return availableTags;
}
}

View File

@ -1,6 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
abstract class AssignTagState extends Equatable {
const AssignTagState();
@ -15,17 +14,21 @@ class AssignTagLoading extends AssignTagState {}
class AssignTagLoaded extends AssignTagState {
final List<Tag> tags;
final List<String> updatedTags;
final bool isSaveEnabled;
final String? errorMessage;
final String? errorMessage;
const AssignTagLoaded({
required this.tags,
required this.isSaveEnabled,
required this.updatedTags,
required this.errorMessage,
});
@override
List<Object> get props => [tags, isSaveEnabled, errorMessage ?? ''];
List<Object> get props =>
[tags, updatedTags, isSaveEnabled, errorMessage ?? ''];
}
class AssignTagError extends AssignTagState {

View File

@ -14,6 +14,7 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_e
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:uuid/uuid.dart';
class AssignTagDialog extends StatelessWidget {
final List<ProductModel>? products;
@ -47,7 +48,7 @@ class AssignTagDialog extends StatelessWidget {
..add('Main Space');
return BlocProvider(
create: (_) => AssignTagBloc()
create: (_) => AssignTagBloc(allTags ?? [])
..add(InitializeTags(
initialTags: initialTags,
addedProducts: addedProducts,
@ -119,8 +120,6 @@ class AssignTagDialog extends StatelessWidget {
: List.generate(state.tags.length, (index) {
final tag = state.tags[index];
final controller = controllers[index];
final availableTags = getAvailableTags(
allTags ?? [], state.tags, tag);
return DataRow(
cells: [
@ -180,7 +179,9 @@ class AssignTagDialog extends StatelessWidget {
width: double
.infinity, // Ensure full width for dropdown
child: DialogTextfieldDropdown(
items: availableTags,
key: ValueKey(
'dropdown_${Uuid().v4()}_${index}'),
items: state.updatedTags,
initialValue: tag.tag,
onSelected: (value) {
controller.text = value;
@ -306,15 +307,4 @@ class AssignTagDialog extends StatelessWidget {
),
);
}
List<String> getAvailableTags(
List<String> allTags, List<Tag> currentTags, Tag currentTag) {
List<String> availableTagsForTagModel = TagHelper.getAvailableTags<Tag>(
allTags: allTags,
currentTags: currentTags,
currentTag: currentTag,
getTag: (tag) => tag.tag ?? '',
);
return availableTagsForTagModel;
}
}

View File

@ -5,7 +5,9 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model
class AssignTagModelBloc
extends Bloc<AssignTagModelEvent, AssignTagModelState> {
AssignTagModelBloc() : super(AssignTagModelInitial()) {
final List<String> allTags;
AssignTagModelBloc(this.allTags) : super(AssignTagModelInitial()) {
on<InitializeTagModels>((event, emit) {
final initialTags = event.initialTags ?? [];
@ -17,25 +19,25 @@ class AssignTagModelBloc
}
}
final allTags = <TagModel>[];
final tags = <TagModel>[];
for (var selectedProduct in event.addedProducts) {
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
if (selectedProduct.count == 0 ||
selectedProduct.count <= existingCount) {
allTags.addAll(initialTags
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
continue;
}
final missingCount = selectedProduct.count - existingCount;
allTags.addAll(initialTags
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (missingCount > 0) {
allTags.addAll(List.generate(
tags.addAll(List.generate(
missingCount,
(index) => TagModel(
tag: '',
@ -46,9 +48,12 @@ class AssignTagModelBloc
}
}
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagModelLoaded(
tags: allTags,
isSaveEnabled: _validateTags(allTags),
tags: tags,
updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags),
errorMessage: ''));
});
@ -57,9 +62,12 @@ class AssignTagModelBloc
if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) {
final tags = List<TagModel>.from(currentState.tags);
tags[event.index].tag = event.tag;
tags[event.index] = tags[event.index].copyWith(tag: event.tag);
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagModelLoaded(
tags: tags,
updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
@ -77,9 +85,13 @@ class AssignTagModelBloc
tags[event.index] =
tags[event.index].copyWith(location: event.location);
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagModelLoaded(
tags: tags,
updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
}
});
@ -93,6 +105,7 @@ class AssignTagModelBloc
emit(AssignTagModelLoaded(
tags: tags,
updatedTags: _calculateAvailableTags(allTags, tags),
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
@ -104,24 +117,22 @@ class AssignTagModelBloc
if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) {
final updatedTags = List<TagModel>.from(currentState.tags)
final tags = List<TagModel>.from(currentState.tags)
..remove(event.tagToDelete);
final updatedTags = _calculateAvailableTags(allTags, tags);
emit(AssignTagModelLoaded(
tags: updatedTags,
isSaveEnabled: _validateTags(updatedTags),
tags: tags,
updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
} else {
emit(const AssignTagModelLoaded(
tags: [],
isSaveEnabled: false,
));
}
}
});
}
bool _validateTags(List<TagModel> tags) {
final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet();
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
final isValid = uniqueTags.length == tags.length && !hasEmptyTag;
@ -129,14 +140,14 @@ class AssignTagModelBloc
}
String? _getValidationError(List<TagModel> tags) {
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
if (hasEmptyTag) {
return 'Tags cannot be empty.';
}
// Check for duplicate tags
final duplicateTags = tags
final nonEmptyTags = tags
.map((tag) => tag.tag?.trim() ?? '')
.where((tag) => tag.isNotEmpty)
.toList();
final duplicateTags = nonEmptyTags
.fold<Map<String, int>>({}, (map, tag) {
map[tag] = (map[tag] ?? 0) + 1;
return map;
@ -152,4 +163,16 @@ class AssignTagModelBloc
return null;
}
List<String> _calculateAvailableTags(
List<String> allTags, List<TagModel> tags) {
final selectedTags = tags
.where((tag) => (tag.tag?.trim().isNotEmpty ?? false))
.map((tag) => tag.tag!.trim())
.toSet();
final availableTags =
allTags.where((tag) => !selectedTags.contains(tag.trim())).toList();
return availableTags;
}
}

View File

@ -17,14 +17,17 @@ class AssignTagModelLoaded extends AssignTagModelState {
final bool isSaveEnabled;
final String? errorMessage;
final List<String> updatedTags;
const AssignTagModelLoaded({
required this.tags,
required this.isSaveEnabled,
required this.updatedTags,
this.errorMessage,
});
@override
List<Object?> get props => [tags, isSaveEnabled, errorMessage];
List<Object?> get props => [tags, updatedTags, isSaveEnabled, errorMessage];
}
class AssignTagModelError extends AssignTagModelState {

View File

@ -16,6 +16,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/c
import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:uuid/uuid.dart';
class AssignTagModelsDialog extends StatelessWidget {
final List<ProductModel>? products;
@ -56,7 +57,7 @@ class AssignTagModelsDialog extends StatelessWidget {
..add('Main Space');
return BlocProvider(
create: (_) => AssignTagModelBloc()
create: (_) => AssignTagModelBloc(allTags ?? [])
..add(InitializeTagModels(
initialTags: initialTags,
addedProducts: addedProducts,
@ -134,9 +135,6 @@ class AssignTagModelsDialog extends StatelessWidget {
: List.generate(state.tags.length, (index) {
final tag = state.tags[index];
final controller = controllers[index];
final availableTags =
TagHelper.getAvailableTagModels(
allTags ?? [], state.tags, tag);
return DataRow(
cells: [
@ -196,7 +194,9 @@ class AssignTagModelsDialog extends StatelessWidget {
width: double
.infinity, // Ensure full width for dropdown
child: DialogTextfieldDropdown(
items: availableTags,
key: ValueKey(
'dropdown_${Uuid().v4()}_${index}'),
items: state.updatedTags,
initialValue: tag.tag,
onSelected: (value) {
controller.text = value;

View File

@ -299,7 +299,7 @@ class TagHelper {
static Map<String, dynamic> updateSubspaceTagModels(
List<TagModel> updatedTags, List<SubspaceTemplateModel>? subspaces) {
return TagHelper.updateTags<TagModel>(
final result = TagHelper.updateTags<TagModel>(
updatedTags: updatedTags,
subspaces: subspaces,
getInternalId: (tag) => tag.internalId,
@ -310,6 +310,34 @@ class TagHelper {
setSubspaceTags: (subspace, tags) => subspace.tags = tags,
checkTagExistInSubspace: checkTagExistInSubspaceModels,
);
final processedTags = result['updatedTags'] as List<TagModel>;
final processedSubspaces =
List<SubspaceTemplateModel>.from(result['subspaces'] as List<dynamic>);
for (var subspace in processedSubspaces) {
final subspaceTags = subspace.tags;
if (subspaceTags != null) {
for (int i = 0; i < subspaceTags.length; i++) {
final tag = subspaceTags[i];
// Find the updated tag inside processedTags
final changedTag = updatedTags.firstWhere(
(t) => t.internalId == tag.internalId,
orElse: () => tag,
);
if (changedTag.tag != tag.tag) {
subspaceTags[i] = changedTag.copyWith(tag: changedTag.tag);
}
}
}
subspace.tags = subspaceTags;
}
return {'updatedTags': processedTags, 'subspaces': processedSubspaces};
}
static int? checkTagExistInSubspace(Tag tag, List<dynamic>? subspaces) {
@ -328,7 +356,7 @@ class TagHelper {
static Map<String, dynamic> processTags(
List<Tag> updatedTags, List<SubspaceModel>? subspaces) {
return TagHelper.updateTags<Tag>(
final result = TagHelper.updateTags<Tag>(
updatedTags: updatedTags,
subspaces: subspaces,
getInternalId: (tag) => tag.internalId,
@ -339,6 +367,33 @@ class TagHelper {
setSubspaceTags: (subspace, tags) => subspace.tags = tags,
checkTagExistInSubspace: checkTagExistInSubspace,
);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
for (var subspace in processedSubspaces) {
final subspaceTags = subspace.tags;
if (subspaceTags != null) {
for (int i = 0; i < subspaceTags.length; i++) {
final tag = subspaceTags[i];
final changedTag = updatedTags.firstWhere(
(t) => t.internalId == tag.internalId,
orElse: () => tag,
);
if (changedTag.tag != tag.tag) {
subspaceTags[i] = changedTag.copyWith(tag: changedTag.tag);
}
}
}
subspace.tags = subspaceTags;
}
return {'updatedTags': processedTags, 'subspaces': processedSubspaces};
}
static List<String> getAllTagValues(

View File

@ -1,4 +1,5 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
@ -8,6 +9,9 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
class CreateSpaceModelBloc
extends Bloc<CreateSpaceModelEvent, CreateSpaceModelState> {
@ -18,6 +22,8 @@ class CreateSpaceModelBloc
CreateSpaceModelBloc(this._api) : super(CreateSpaceModelInitial()) {
on<CreateSpaceTemplate>((event, emit) async {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
late SpaceTemplateModel spaceTemplate = event.spaceTemplate;
final tagBodyModels =
@ -40,7 +46,8 @@ class CreateSpaceModelBloc
tags: tagBodyModels,
subspaceModels: subspaceTemplateBodyModels);
final newSpaceTemplate = await _api.createSpaceModel(spaceModelBody);
final newSpaceTemplate =
await _api.createSpaceModel(spaceModelBody, projectUuid);
spaceTemplate.uuid = newSpaceTemplate?.uuid ?? '';
if (newSpaceTemplate != null) {
@ -201,9 +208,11 @@ class CreateSpaceModelBloc
on<ModifySpaceTemplate>((event, emit) async {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (event.spaceTemplate.uuid != null) {
final prevSpaceModel =
await _api.getSpaceModel(event.spaceTemplate.uuid ?? '');
final prevSpaceModel = await _api.getSpaceModel(
event.spaceTemplate.uuid ?? '', projectUuid);
final newSpaceModel = event.updatedSpaceTemplate;
String? spaceModelName;
@ -287,7 +296,7 @@ class CreateSpaceModelBloc
subspaceModels: subspaceUpdates);
final res = await _api.updateSpaceModel(
spaceModelBody, prevSpaceModel?.uuid ?? '');
spaceModelBody, prevSpaceModel?.uuid ?? '', projectUuid);
if (res != null) {
emit(CreateSpaceModelLoaded(newSpaceModel));

View File

@ -1,4 +1,5 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
@ -13,15 +14,19 @@ class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
}) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) {
on<CreateSpaceModel>(_onCreateSpaceModel);
on<UpdateSpaceModel>(_onUpdateSpaceModel);
on<DeleteSpaceModel>(_onDeleteSpaceModel);
}
Future<void> _onCreateSpaceModel(
CreateSpaceModel event, Emitter<SpaceModelState> emit) async {
final currentState = state;
if (currentState is SpaceModelLoaded) {
try {
final newSpaceModel =
await api.getSpaceModel(event.newSpaceModel.uuid ?? '');
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final newSpaceModel = await api.getSpaceModel(
event.newSpaceModel.uuid ?? '', projectUuid);
if (newSpaceModel != null) {
final updatedSpaceModels =
@ -40,8 +45,10 @@ class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
final currentState = state;
if (currentState is SpaceModelLoaded) {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final newSpaceModel =
await api.getSpaceModel(event.spaceModelUuid ?? '');
await api.getSpaceModel(event.spaceModelUuid, projectUuid);
if (newSpaceModel != null) {
final updatedSpaceModels = currentState.spaceModels.map((model) {
return model.uuid == event.spaceModelUuid ? newSpaceModel : model;
@ -53,4 +60,28 @@ class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
}
}
}
Future<void> _onDeleteSpaceModel(
DeleteSpaceModel event, Emitter<SpaceModelState> emit) async {
final currentState = state;
if (currentState is SpaceModelLoaded) {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final deletedSuccessfully =
await api.deleteSpaceModel(event.spaceModelUuid, projectUuid);
if (deletedSuccessfully) {
final updatedSpaceModels = currentState.spaceModels
.where((model) => model.uuid != event.spaceModelUuid)
.toList();
emit(SpaceModelLoaded(spaceModels: updatedSpaceModels));
}
} catch (e) {
emit(SpaceModelError(message: e.toString()));
}
}
}
}

View File

@ -34,3 +34,12 @@ class UpdateSpaceModel extends SpaceModelEvent {
@override
List<Object?> get props => [spaceModelUuid];
}
class DeleteSpaceModel extends SpaceModelEvent {
final String spaceModelUuid;
DeleteSpaceModel({required this.spaceModelUuid});
@override
List<Object?> get props => [spaceModelUuid];
}

View File

@ -90,7 +90,11 @@ class SpaceModelPage extends StatelessWidget {
},
child: Container(
margin: const EdgeInsets.all(8.0),
child: SpaceModelCardWidget(model: model),
child: SpaceModelCardWidget(
model: model,
pageContext: context,
topActionsDisabled: false,
),
));
},
),

View File

@ -50,7 +50,9 @@ class CreateSpaceModelDialog extends StatelessWidget {
width: screenWidth * 0.3,
child: BlocProvider(
create: (_) {
final bloc = CreateSpaceModelBloc(_spaceModelApi);
final bloc = CreateSpaceModelBloc(
_spaceModelApi,
);
if (spaceModel != null) {
bloc.add(UpdateSpaceTemplate(spaceModel!, otherSpaceModels));
} else {
@ -130,14 +132,14 @@ class CreateSpaceModelDialog extends StatelessWidget {
SubspaceModelCreate(
subspaces: state.space.subspaceModels ?? [],
tags: state.space.tags ?? [],
onSpaceModelUpdate: (updatedSubspaces,updatedTags) {
onSpaceModelUpdate: (updatedSubspaces, updatedTags) {
context
.read<CreateSpaceModelBloc>()
.add(AddSubspacesToSpaceTemplate(updatedSubspaces));
if(updatedTags!=null){
if (updatedTags != null) {
context
.read<CreateSpaceModelBloc>()
.add(AddTagsToSpaceTemplate(updatedTags));
.read<CreateSpaceModelBloc>()
.add(AddTagsToSpaceTemplate(updatedTags));
}
},
),

View File

@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DeleteSpaceModelDialog extends StatelessWidget {
final VoidCallback onConfirmDelete;
const DeleteSpaceModelDialog({Key? key, required this.onConfirmDelete})
: super(key: key);
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
backgroundColor: ColorsManager.whiteColors,
title: Center(
child: Text(
"Delete Space Model",
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.headlineLarge
?.copyWith(color: ColorsManager.blackColor),
),
),
content: SizedBox(
width: screenWidth * 0.4,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Are you sure you want to delete?",
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 15),
Text(
"The existing sub-spaces, devices, and routines will remain associated with the spaces, but the connection will be removed.",
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(color: ColorsManager.lightGrayColor),
),
],
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 200,
child: CancelButton(
onPressed: () {
Navigator.of(context).pop(); // Close dialog
},
label: "Cancel",
),
),
const SizedBox(width: 10),
SizedBox(
width: 200,
child: DefaultButton(
onPressed: () {
Navigator.of(context).pop(); // Close dialog
onConfirmDelete(); // Execute delete action
},
backgroundColor: ColorsManager.semiTransparentRed,
borderRadius: 10,
foregroundColor: ColorsManager.whiteColors,
child: const Text('Delete'),
),
),
],
),
],
);
}
}

View File

@ -1,13 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/delete_space_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class SpaceModelCardWidget extends StatelessWidget {
final SpaceTemplateModel model;
final BuildContext? pageContext;
final bool topActionsDisabled;
const SpaceModelCardWidget({Key? key, required this.model}) : super(key: key);
const SpaceModelCardWidget({
Key? key,
required this.model,
this.pageContext,
this.topActionsDisabled = true,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -34,7 +51,7 @@ class SpaceModelCardWidget extends StatelessWidget {
return LayoutBuilder(
builder: (context, constraints) {
bool showOnlyName = constraints.maxWidth < 250;
return Container(
return Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: Colors.white,
@ -51,14 +68,50 @@ class SpaceModelCardWidget extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
model.modelName,
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
color: Colors.black,
fontWeight: FontWeight.bold,
Row(
children: [
Expanded(
child: Text(
model.modelName,
style:
Theme.of(context).textTheme.headlineMedium?.copyWith(
color: Colors.black,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (!topActionsDisabled)
GestureDetector(
onTap: () => _showDeleteDialog(context),
child: Container(
width: 36, // Adjust size as needed
height: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
spreadRadius: 2,
blurRadius: 5,
offset: const Offset(0, 2),
),
],
),
child: Center(
child: SvgPicture.asset(
Assets.deleteSpaceModel, // Your actual SVG path
width: 20,
height: 20,
colorFilter: const ColorFilter.mode(
Colors.grey, BlendMode.srcIn),
),
),
),
),
],
),
if (!showOnlyName) ...[
const SizedBox(height: 10),
@ -117,4 +170,22 @@ class SpaceModelCardWidget extends StatelessWidget {
},
);
}
void _showDeleteDialog(BuildContext context) {
showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext dialogContext) {
return DeleteSpaceModelDialog(
onConfirmDelete: () {
if (pageContext != null) {
pageContext!.read<SpaceModelBloc>().add(
DeleteSpaceModel(spaceModelUuid: model.uuid ?? ''),
);
}
},
);
},
);
}
}

View File

@ -3,6 +3,7 @@ import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/common/custom_dialog.dart';
import 'package:syncrow_web/pages/common/hour_picker_dialog.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart';
@ -13,9 +14,14 @@ import 'package:syncrow_web/pages/visitor_password/model/schedule_model.dart';
import 'package:syncrow_web/services/access_mang_api.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordState> {
class VisitorPasswordBloc
extends Bloc<VisitorPasswordEvent, VisitorPasswordState> {
VisitorPasswordBloc() : super(VisitorPasswordInitial()) {
on<SelectUsageFrequency>(selectUsageFrequency);
on<FetchDevice>(_onFetchDevice);
@ -38,7 +44,8 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
final TextEditingController deviceNameController = TextEditingController();
final TextEditingController deviceIdController = TextEditingController();
final TextEditingController unitNameController = TextEditingController();
final TextEditingController virtualAddressController = TextEditingController();
final TextEditingController virtualAddressController =
TextEditingController();
List<String> selectedDevices = [];
List<DeviceModel> data = [];
@ -64,12 +71,14 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
String startTimeAccess = 'Start Time';
String endTimeAccess = 'End Time';
PasswordStatus? passwordStatus;
selectAccessType(SelectPasswordType event, Emitter<VisitorPasswordState> emit) {
selectAccessType(
SelectPasswordType event, Emitter<VisitorPasswordState> emit) {
accessTypeSelected = event.type;
emit(PasswordTypeSelected(event.type));
}
selectUsageFrequency(SelectUsageFrequency event, Emitter<VisitorPasswordState> emit) {
selectUsageFrequency(
SelectUsageFrequency event, Emitter<VisitorPasswordState> emit) {
usageFrequencySelected = event.usageType;
emit(UsageFrequencySelected(event.usageType));
}
@ -116,10 +125,12 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
timePicked.minute,
);
final selectedTimestamp = selectedDateTime.millisecondsSinceEpoch ~/ 1000;
final selectedTimestamp =
selectedDateTime.millisecondsSinceEpoch ~/ 1000;
if (event.isStart) {
if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) {
if (expirationTimeTimeStamp != null &&
selectedTimestamp > expirationTimeTimeStamp!) {
CustomSnackBar.displaySnackBar(
'Effective Time cannot be later than Expiration Time.',
);
@ -128,7 +139,8 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
effectiveTimeTimeStamp = selectedTimestamp;
startTimeAccess = selectedDateTime.toString().split('.').first;
} else {
if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) {
if (effectiveTimeTimeStamp != null &&
selectedTimestamp < effectiveTimeTimeStamp!) {
CustomSnackBar.displaySnackBar(
'Expiration Time cannot be earlier than Effective Time.',
);
@ -143,7 +155,8 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
}
}
bool toggleRepeat(ToggleRepeatEvent event, Emitter<VisitorPasswordState> emit) {
bool toggleRepeat(
ToggleRepeatEvent event, Emitter<VisitorPasswordState> emit) {
emit(LoadingInitialState());
repeat = !repeat;
emit(IsRepeatState(repeat: repeat));
@ -175,10 +188,13 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
emit(ChangeTimeState());
}
Future<void> _onFetchDevice(FetchDevice event, Emitter<VisitorPasswordState> emit) async {
Future<void> _onFetchDevice(
FetchDevice event, Emitter<VisitorPasswordState> emit) async {
try {
emit(DeviceLoaded());
data = await AccessMangApi().fetchDevices();
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
data = await AccessMangApi().fetchDevices(projectUuid);
emit(TableLoaded(data));
} catch (e) {
emit(FailedState(e.toString()));
@ -186,8 +202,8 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
}
//online password
Future<void> postOnlineOneTimePassword(
OnlineOneTimePasswordEvent event, Emitter<VisitorPasswordState> emit) async {
Future<void> postOnlineOneTimePassword(OnlineOneTimePasswordEvent event,
Emitter<VisitorPasswordState> emit) async {
try {
emit(LoadingInitialState());
generate7DigitNumber();
@ -211,7 +227,8 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
}
Future<void> postOnlineMultipleTimePassword(
OnlineMultipleTimePasswordEvent event, Emitter<VisitorPasswordState> emit) async {
OnlineMultipleTimePasswordEvent event,
Emitter<VisitorPasswordState> emit) async {
try {
emit(LoadingInitialState());
@ -221,7 +238,8 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
if (repeat)
Schedule(
effectiveTime: getTimeFromDateTimeString(effectiveTime),
invalidTime: getTimeFromDateTimeString(expirationTime).toString(),
invalidTime:
getTimeFromDateTimeString(expirationTime).toString(),
workingDay: selectedDays,
),
],
@ -244,13 +262,15 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
}
//offline password
Future<void> postOfflineOneTimePassword(
OfflineOneTimePasswordEvent event, Emitter<VisitorPasswordState> emit) async {
Future<void> postOfflineOneTimePassword(OfflineOneTimePasswordEvent event,
Emitter<VisitorPasswordState> emit) async {
try {
emit(LoadingInitialState());
await generate7DigitNumber();
var res = await AccessMangApi().postOffLineOneTime(
email: event.email, devicesUuid: selectedDevices, passwordName: event.passwordName);
email: event.email,
devicesUuid: selectedDevices,
passwordName: event.passwordName);
if (res['statusCode'] == 201) {
passwordStatus = PasswordStatus.fromJson(res['data']);
emit(SuccessState());
@ -264,7 +284,8 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
}
Future<void> postOfflineMultipleTimePassword(
OfflineMultipleTimePasswordEvent event, Emitter<VisitorPasswordState> emit) async {
OfflineMultipleTimePasswordEvent event,
Emitter<VisitorPasswordState> emit) async {
try {
emit(LoadingInitialState());
await generate7DigitNumber();
@ -287,7 +308,8 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
}
}
void selectDevice(SelectDeviceEvent event, Emitter<VisitorPasswordState> emit) {
void selectDevice(
SelectDeviceEvent event, Emitter<VisitorPasswordState> emit) {
if (selectedDeviceIds.contains(event.deviceId)) {
selectedDeviceIds.remove(event.deviceId);
} else {
@ -329,7 +351,8 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
add(UpdateFilteredDevicesEvent(filteredData));
}
Stream<VisitorPasswordState> mapEventToState(VisitorPasswordEvent event) async* {
Stream<VisitorPasswordState> mapEventToState(
VisitorPasswordEvent event) async* {
if (event is FetchDevice) {
} else if (event is UpdateFilteredDevicesEvent) {
yield TableLoaded(event.filteredData);
@ -378,16 +401,20 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
).millisecondsSinceEpoch ~/
1000; // Divide by 1000 to remove milliseconds
if (event.isEffective) {
if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) {
accessPeriodValidate = "Effective Time cannot be later than Expiration Time.";
if (expirationTimeTimeStamp != null &&
selectedTimestamp > expirationTimeTimeStamp!) {
accessPeriodValidate =
"Effective Time cannot be later than Expiration Time.";
} else {
accessPeriodValidate = '';
effectiveTime = selectedDateTime.toString().split('.').first;
effectiveTimeTimeStamp = selectedTimestamp;
}
} else {
if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) {
accessPeriodValidate = 'Expiration Time cannot be earlier than Effective Time.';
if (effectiveTimeTimeStamp != null &&
selectedTimestamp < effectiveTimeTimeStamp!) {
accessPeriodValidate =
'Expiration Time cannot be earlier than Effective Time.';
} else {
accessPeriodValidate = '';
expirationTime = selectedDateTime.toString().split('.').first;

View File

@ -23,6 +23,7 @@ class DeviceModel {
dynamic timeZone;
dynamic updateTime;
dynamic uuid;
dynamic spaceName;
DeviceModel({
required this.productUuid,
@ -45,6 +46,7 @@ class DeviceModel {
required this.timeZone,
required this.updateTime,
required this.uuid,
required this.spaceName,
});
// Deserialize from JSON
@ -53,7 +55,8 @@ class DeviceModel {
DeviceType type = devicesTypesMap[json['productType']] ?? DeviceType.Other;
if (type == DeviceType.LightBulb) {
tempIcon = Assets.lightBulb;
} else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) {
} else if (type == DeviceType.CeilingSensor ||
type == DeviceType.WallSensor) {
tempIcon = Assets.sensors;
} else if (type == DeviceType.AC) {
tempIcon = Assets.ac;
@ -102,6 +105,7 @@ class DeviceModel {
timeZone: json['timeZone'],
updateTime: json['updateTime'],
uuid: json['uuid'],
spaceName: json['spaceName'],
);
}
@ -128,6 +132,7 @@ class DeviceModel {
'timeZone': timeZone,
'updateTime': updateTime,
'uuid': uuid,
'spaceName': spaceName
};
}
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/common/access_device_table.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
@ -19,7 +20,8 @@ class AddDeviceDialog extends StatelessWidget {
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return BlocProvider(
create: (context) => VisitorPasswordBloc()..add(FetchDevice()),
create: (context) =>
VisitorPasswordBloc()..add(FetchDevice()),
child: BlocBuilder<VisitorPasswordBloc, VisitorPasswordState>(
builder: (BuildContext context, VisitorPasswordState state) {
final visitorBloc = BlocProvider.of<VisitorPasswordBloc>(context);
@ -35,10 +37,10 @@ class AddDeviceDialog extends StatelessWidget {
backgroundColor: Colors.white,
title: Text(
'Add Accessible Device',
style: Theme.of(context)
.textTheme
.headlineLarge!
.copyWith(fontWeight: FontWeight.w400, fontSize: 24, color: Colors.black),
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
fontWeight: FontWeight.w400,
fontSize: 24,
color: Colors.black),
),
content: SizedBox(
height: MediaQuery.of(context).size.height / 1.7,
@ -68,10 +70,13 @@ class AddDeviceDialog extends StatelessWidget {
),
Text(
'Only online accessible devices can be added',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.grayColor),
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.grayColor),
),
],
)),
@ -152,7 +157,8 @@ class AddDeviceDialog extends StatelessWidget {
visitorBloc.deviceNameController.clear();
visitorBloc.deviceIdController.clear();
visitorBloc.unitNameController.clear();
visitorBloc.add(FetchDevice()); // Reset to original list
visitorBloc.add(
FetchDevice()); // Reset to original list
},
),
),
@ -172,7 +178,8 @@ class AddDeviceDialog extends StatelessWidget {
selectAll: (p0) {
visitorBloc.selectedDeviceIds.clear();
for (var item in state.data) {
visitorBloc.add(SelectDeviceEvent(item.uuid));
visitorBloc
.add(SelectDeviceEvent(item.uuid));
}
},
onRowSelected: (index, isSelected, row) {
@ -193,7 +200,7 @@ class AddDeviceDialog extends StatelessWidget {
item.name.toString(),
item.uuid.toString(),
item.productType.toString(),
'',
item.spaceName.toString(),
item.online.value.toString(),
];
}).toList(),

View File

@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/common/date_time_widget.dart';
import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart';

View File

@ -6,10 +6,10 @@ import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
class AccessMangApi {
Future<List<PasswordModel>> fetchVisitorPassword() async {
Future<List<PasswordModel>> fetchVisitorPassword(String projectId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.visitorPassword,
path: ApiEndpoints.visitorPassword.replaceAll('{projectId}', projectId),
showServerMessage: true,
expectedResponseModel: (json) {
List<dynamic> jsonData = json;
@ -25,10 +25,10 @@ class AccessMangApi {
}
}
Future fetchDevices() async {
Future fetchDevices(String projectId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.getDevices,
path: ApiEndpoints.getDevices.replaceAll('{projectId}', projectId),
showServerMessage: true,
expectedResponseModel: (json) {
List<dynamic> jsonData = json;
@ -86,7 +86,8 @@ class AccessMangApi {
"invalidTime": invalidTime,
};
if (scheduleList != null) {
body["scheduleList"] = scheduleList.map((schedule) => schedule.toJson()).toList();
body["scheduleList"] =
scheduleList.map((schedule) => schedule.toJson()).toList();
}
final response = await HTTPService().post(
path: ApiEndpoints.sendOnlineMultipleTime,
@ -105,7 +106,11 @@ class AccessMangApi {
{String? email, String? passwordName, List<String>? devicesUuid}) async {
final response = await HTTPService().post(
path: ApiEndpoints.sendOffLineOneTime,
body: jsonEncode({"email": email, "passwordName": passwordName, "devicesUuid": devicesUuid}),
body: jsonEncode({
"email": email,
"passwordName": passwordName,
"devicesUuid": devicesUuid
}),
showServerMessage: true,
expectedResponseModel: (json) {
return json;

View File

@ -12,15 +12,15 @@ import 'package:syncrow_web/utils/constants/api_const.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
class DevicesManagementApi {
Future<List<AllDevicesModel>> fetchDevices(String communityId, String spaceId) async {
Future<List<AllDevicesModel>> fetchDevices(String communityId, String spaceId, String projectId) async {
try {
final response = await HTTPService().get(
path: communityId.isNotEmpty && spaceId.isNotEmpty
? ApiEndpoints.getSpaceDevices
.replaceAll('{spaceUuid}', spaceId)
.replaceAll('{communityUuid}', communityId)
.replaceAll('{projectId}', TempConst.projectId)
: ApiEndpoints.getAllDevices,
.replaceAll('{projectId}', projectId)
: ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId),
showServerMessage: true,
expectedResponseModel: (json) {
List<dynamic> jsonData =

View File

@ -12,7 +12,8 @@ class SceneApi {
static final HTTPService _httpService = HTTPService();
// //create scene
static Future<Map<String, dynamic>> createScene(CreateSceneModel createSceneModel) async {
static Future<Map<String, dynamic>> createScene(
CreateSceneModel createSceneModel) async {
try {
debugPrint('create scene model: ${createSceneModel.toMap()}');
final response = await _httpService.post(
@ -69,14 +70,15 @@ class SceneApi {
//get scenes by community id and space id
static Future<List<ScenesModel>> getScenes(String spaceId, String communityId,
static Future<List<ScenesModel>> getScenes(
String spaceId, String communityId, String projectId,
{showInDevice = false}) async {
try {
final response = await _httpService.get(
path: ApiEndpoints.getUnitScenes
.replaceAll('{spaceUuid}', spaceId)
.replaceAll('{communityUuid}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
.replaceAll('{projectId}', projectId),
queryParameters: {'showInHomePage': showInDevice},
showServerMessage: false,
expectedResponseModel: (json) {
@ -100,7 +102,8 @@ class SceneApi {
static Future<List<ScenesModel>> getAutomation(String spaceId) async {
try {
final response = await _httpService.get(
path: ApiEndpoints.getSpaceAutomation.replaceAll('{spaceUuid}', spaceId),
path:
ApiEndpoints.getSpaceAutomation.replaceAll('{spaceUuid}', spaceId),
showServerMessage: false,
expectedResponseModel: (json) {
List<ScenesModel> scenes = [];
@ -130,10 +133,12 @@ class SceneApi {
// }
//automation details
static Future<RoutineDetailsModel> getAutomationDetails(String automationId) async {
static Future<RoutineDetailsModel> getAutomationDetails(
String automationId) async {
try {
final response = await _httpService.get(
path: ApiEndpoints.getAutomationDetails.replaceAll('{automationId}', automationId),
path: ApiEndpoints.getAutomationDetails
.replaceAll('{automationId}', automationId),
showServerMessage: false,
expectedResponseModel: (json) => RoutineDetailsModel.fromMap(json),
);
@ -148,7 +153,8 @@ class SceneApi {
try {
final response = await _httpService.put(
path: ApiEndpoints.updateScene.replaceAll('{sceneId}', sceneId),
body: createSceneModel.toJson(sceneId.isNotEmpty == true ? sceneId : null),
body: createSceneModel
.toJson(sceneId.isNotEmpty == true ? sceneId : null),
expectedResponseModel: (json) {
return json;
},
@ -160,11 +166,14 @@ class SceneApi {
}
//update automation
static updateAutomation(CreateAutomationModel createAutomationModel, String automationId) async {
static updateAutomation(
CreateAutomationModel createAutomationModel, String automationId) async {
try {
final response = await _httpService.put(
path: ApiEndpoints.updateAutomation.replaceAll('{automationId}', automationId),
body: createAutomationModel.toJson(automationId.isNotEmpty == true ? automationId : null),
path: ApiEndpoints.updateAutomation
.replaceAll('{automationId}', automationId),
body: createAutomationModel
.toJson(automationId.isNotEmpty == true ? automationId : null),
expectedResponseModel: (json) {
return json;
},
@ -181,7 +190,8 @@ class SceneApi {
final response = await _httpService.get(
path: ApiEndpoints.getScene.replaceAll('{sceneId}', sceneId),
showServerMessage: false,
expectedResponseModel: (json) => RoutineDetailsModel.fromMap(json['data']),
expectedResponseModel: (json) =>
RoutineDetailsModel.fromMap(json['data']),
);
return response;
} catch (e) {
@ -190,7 +200,8 @@ class SceneApi {
}
//delete Scene
static Future<bool> deleteScene({required String unitUuid, required String sceneId}) async {
static Future<bool> deleteScene(
{required String unitUuid, required String sceneId}) async {
try {
final response = await _httpService.delete(
path: ApiEndpoints.deleteScene

View File

@ -13,7 +13,8 @@ import 'package:syncrow_web/utils/constants/temp_const.dart';
class CommunitySpaceManagementApi {
// Community Management APIs
Future<List<CommunityModel>> fetchCommunities({int page = 1}) async {
Future<List<CommunityModel>> fetchCommunities(String projectId,
{int page = 1}) async {
try {
List<CommunityModel> allCommunities = [];
bool hasNext = true;
@ -21,7 +22,7 @@ class CommunitySpaceManagementApi {
while (hasNext) {
await HTTPService().get(
path: ApiEndpoints.getCommunityList
.replaceAll('{projectId}', TempConst.projectId),
.replaceAll('{projectId}', projectId),
queryParameters: {'page': page},
expectedResponseModel: (json) {
try {
@ -65,11 +66,10 @@ class CommunitySpaceManagementApi {
}
Future<CommunityModel?> createCommunity(
String name, String description) async {
String name, String description, String projectId) async {
try {
final response = await HTTPService().post(
path: ApiEndpoints.createCommunity
.replaceAll('{projectId}', TempConst.projectId),
path: ApiEndpoints.createCommunity.replaceAll('{projectId}', projectId),
body: {
'name': name,
'description': description,
@ -85,12 +85,13 @@ class CommunitySpaceManagementApi {
}
}
Future<bool> updateCommunity(String communityId, String name) async {
Future<bool> updateCommunity(
String communityId, String name, String projectId) async {
try {
final response = await HTTPService().put(
path: ApiEndpoints.updateCommunity
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
.replaceAll('{projectId}', projectId),
body: {
'name': name,
},
@ -105,12 +106,12 @@ class CommunitySpaceManagementApi {
}
}
Future<bool> deleteCommunity(String communityId) async {
Future<bool> deleteCommunity(String communityId, String projectId) async {
try {
final response = await HTTPService().delete(
path: ApiEndpoints.deleteCommunity
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
.replaceAll('{projectId}', projectId),
expectedResponseModel: (json) {
return json['success'] ?? false;
},
@ -122,12 +123,13 @@ class CommunitySpaceManagementApi {
}
}
Future<SpacesResponse> fetchSpaces(String communityId) async {
Future<SpacesResponse> fetchSpaces(
String communityId, String projectId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.listSpaces
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
.replaceAll('{projectId}', projectId),
expectedResponseModel: (json) {
return SpacesResponse.fromJson(json);
},
@ -148,13 +150,14 @@ class CommunitySpaceManagementApi {
}
}
Future<SpaceModel?> getSpace(String communityId, String spaceId) async {
Future<SpaceModel?> getSpace(
String communityId, String spaceId, String projectId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.getSpace
.replaceAll('{communityId}', communityId)
.replaceAll('{spaceId}', spaceId)
.replaceAll('{projectId}', TempConst.projectId),
.replaceAll('{projectId}', projectId),
expectedResponseModel: (json) {
return SpaceModel.fromJson(json['data']);
},
@ -176,7 +179,8 @@ class CommunitySpaceManagementApi {
String? spaceModelUuid,
String? icon,
List<CreateTagBodyModel>? tags,
List<CreateSubspaceModel>? subspaces}) async {
List<CreateSubspaceModel>? subspaces,
required String projectId}) async {
try {
final body = {
'spaceName': name,
@ -199,7 +203,7 @@ class CommunitySpaceManagementApi {
final response = await HTTPService().post(
path: ApiEndpoints.createSpace
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
.replaceAll('{projectId}', projectId),
body: body,
expectedResponseModel: (json) {
return SpaceModel.fromJson(json['data']);
@ -212,18 +216,18 @@ class CommunitySpaceManagementApi {
}
}
Future<bool> updateSpace({
required String communityId,
required spaceId,
required String name,
String? parentId,
String? icon,
String? direction,
bool isPrivate = false,
required Offset position,
List<TagModelUpdate>? tags,
List<UpdateSubspaceTemplateModel>? subspaces,
}) async {
Future<bool> updateSpace(
{required String communityId,
required spaceId,
required String name,
String? parentId,
String? icon,
String? direction,
bool isPrivate = false,
required Offset position,
List<TagModelUpdate>? tags,
List<UpdateSubspaceTemplateModel>? subspaces,
required String projectId}) async {
try {
final body = {
'spaceName': name,
@ -243,7 +247,7 @@ class CommunitySpaceManagementApi {
path: ApiEndpoints.updateSpace
.replaceAll('{communityId}', communityId)
.replaceAll('{spaceId}', spaceId)
.replaceAll('{projectId}', TempConst.projectId),
.replaceAll('{projectId}', projectId),
body: body,
expectedResponseModel: (json) {
return json['success'] ?? false;
@ -256,13 +260,14 @@ class CommunitySpaceManagementApi {
}
}
Future<bool> deleteSpace(String communityId, String spaceId) async {
Future<bool> deleteSpace(
String communityId, String spaceId, String projectId) async {
try {
final response = await HTTPService().delete(
path: ApiEndpoints.deleteSpace
.replaceAll('{communityId}', communityId)
.replaceAll('{spaceId}', spaceId)
.replaceAll('{projectId}', TempConst.projectId),
.replaceAll('{projectId}', projectId),
expectedResponseModel: (json) {
return json['success'] ?? false;
},
@ -274,12 +279,13 @@ class CommunitySpaceManagementApi {
}
}
Future<List<SpaceModel>> getSpaceHierarchy(String communityId) async {
Future<List<SpaceModel>> getSpaceHierarchy(
String communityId, String projectId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.getSpaceHierarchy
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
.replaceAll('{projectId}', projectId),
expectedResponseModel: (json) {
final spaceModels = (json['data'] as List)
.map((spaceJson) => SpaceModel.fromJson(spaceJson))

View File

@ -5,10 +5,10 @@ import 'package:syncrow_web/utils/constants/api_const.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
class SpaceModelManagementApi {
Future<List<SpaceTemplateModel>> listSpaceModels({int page = 1}) async {
Future<List<SpaceTemplateModel>> listSpaceModels(
{required String projectId, int page = 1}) async {
final response = await HTTPService().get(
path: ApiEndpoints.listSpaceModels
.replaceAll('{projectId}', TempConst.projectId),
path: ApiEndpoints.listSpaceModels.replaceAll('{projectId}', projectId),
queryParameters: {'page': page},
expectedResponseModel: (json) {
List<dynamic> jsonData = json['data'];
@ -21,10 +21,9 @@ class SpaceModelManagementApi {
}
Future<SpaceTemplateModel?> createSpaceModel(
CreateSpaceTemplateBodyModel spaceModel) async {
CreateSpaceTemplateBodyModel spaceModel, String projectId) async {
final response = await HTTPService().post(
path: ApiEndpoints.createSpaceModel
.replaceAll('{projectId}', TempConst.projectId),
path: ApiEndpoints.createSpaceModel.replaceAll('{projectId}', projectId),
showServerMessage: true,
body: spaceModel.toJson(),
expectedResponseModel: (json) {
@ -34,12 +33,12 @@ class SpaceModelManagementApi {
return response;
}
Future<String?> updateSpaceModel(
CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid) async {
Future<String?> updateSpaceModel(CreateSpaceTemplateBodyModel spaceModel,
String spaceModelUuid, String projectId) async {
final response = await HTTPService().put(
path: ApiEndpoints.updateSpaceModel
.replaceAll('{projectId}', TempConst.projectId).replaceAll('{spaceModelUuid}', spaceModelUuid),
.replaceAll('{projectId}', projectId)
.replaceAll('{spaceModelUuid}', spaceModelUuid),
body: spaceModel.toJson(),
expectedResponseModel: (json) {
return json['message'];
@ -48,10 +47,11 @@ class SpaceModelManagementApi {
return response;
}
Future<SpaceTemplateModel?> getSpaceModel(String spaceModelUuid) async {
Future<SpaceTemplateModel?> getSpaceModel(
String spaceModelUuid, String projectId) async {
final response = await HTTPService().get(
path: ApiEndpoints.getSpaceModel
.replaceAll('{projectId}', TempConst.projectId)
.replaceAll('{projectId}', projectId)
.replaceAll('{spaceModelUuid}', spaceModelUuid),
showServerMessage: true,
expectedResponseModel: (json) {
@ -60,4 +60,17 @@ class SpaceModelManagementApi {
);
return response;
}
Future<bool> deleteSpaceModel(String spaceModelUuid, String projectId) async {
final response = await HTTPService().delete(
path: ApiEndpoints.getSpaceModel
.replaceAll('{projectId}', projectId)
.replaceAll('{spaceModelUuid}', spaceModelUuid),
showServerMessage: true,
expectedResponseModel: (json) {
return json['success'] ?? false;
},
);
return response;
}
}

View File

@ -11,10 +11,10 @@ import 'package:syncrow_web/utils/constants/api_const.dart';
class UserPermissionApi {
static final HTTPService _httpService = HTTPService();
Future<List<RolesUserModel>> fetchUsers() async {
Future<List<RolesUserModel>> fetchUsers(String projectId) async {
try {
final response = await _httpService.get(
path: ApiEndpoints.getUsers,
path: ApiEndpoints.getUsers.replaceAll('{projectId}', projectId),
showServerMessage: true,
expectedResponseModel: (json) {
debugPrint('fetchUsers Response: $json');
@ -64,6 +64,7 @@ class UserPermissionApi {
String? phoneNumber,
String? roleUuid,
List<String>? spaceUuids,
required String projectUuid,
}) async {
try {
final body = <String, dynamic>{
@ -73,7 +74,7 @@ class UserPermissionApi {
"jobTitle": jobTitle != '' ? jobTitle : null,
"phoneNumber": phoneNumber != '' ? phoneNumber : null,
"roleUuid": roleUuid,
"projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c",
"projectUuid": projectUuid,
"spaceUuids": spaceUuids,
};
final response = await _httpService.post(
@ -88,7 +89,6 @@ class UserPermissionApi {
}
},
);
print('sendInviteUser=$body');
return response ?? [];
} on DioException catch (e) {
@ -125,9 +125,11 @@ class UserPermissionApi {
}
}
Future<EditUserModel?> fetchUserById(userUuid) async {
Future<EditUserModel?> fetchUserById(userUuid, String projectId) async {
final response = await _httpService.get(
path: ApiEndpoints.getUserById.replaceAll("{userUuid}", userUuid),
path: ApiEndpoints.getUserById
.replaceAll("{userUuid}", userUuid)
.replaceAll("{projectId}", projectId),
showServerMessage: true,
expectedResponseModel: (json) {
EditUserModel res = EditUserModel.fromJson(json['data']);
@ -145,6 +147,7 @@ class UserPermissionApi {
String? phoneNumber,
String? roleUuid,
List<String>? spaceUuids,
required String projectUuid,
}) async {
try {
final body = <String, dynamic>{
@ -153,7 +156,7 @@ class UserPermissionApi {
"jobTitle": jobTitle != '' ? jobTitle : " ",
"phoneNumber": phoneNumber != '' ? phoneNumber : " ",
"roleUuid": roleUuid,
"projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c",
"projectUuid": projectUuid,
"spaceUuids": spaceUuids,
};
final response = await _httpService.put(
@ -190,14 +193,12 @@ class UserPermissionApi {
}
}
Future<bool> changeUserStatusById(userUuid, status) async {
Future<bool> changeUserStatusById(userUuid, status, String projectUuid) async {
try {
Map<String, dynamic> bodya = {
"disable": status,
"projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c"
"projectUuid": projectUuid
};
print('changeUserStatusById==$bodya');
print('changeUserStatusById==$userUuid');
final response = await _httpService.put(
path: ApiEndpoints.changeUserStatus

View File

@ -71,4 +71,5 @@ abstract class ColorsManager {
static const Color lightGrayBorderColor = Color(0xB2D5D5D5);
//background: #F8F8F8;
static const Color vividBlue = Color(0xFF023DFE);
static const Color semiTransparentRed = Color(0x99FF0000);
}

View File

@ -9,8 +9,8 @@ abstract class ApiEndpoints {
static const String sendOtp = '/authentication/user/send-otp';
static const String verifyOtp = '/authentication/user/verify-otp';
static const String getRegion = '/region';
static const String visitorPassword = '/visitor-password';
static const String getDevices = '/visitor-password/devices';
static const String visitorPassword = '/projects/{projectId}/visitor-password';
static const String getDevices = '/projects/{projectId}/visitor-password/devices';
static const String sendOnlineOneTime = '/visitor-password/temporary-password/online/one-time';
static const String sendOnlineMultipleTime =
@ -25,7 +25,7 @@ abstract class ApiEndpoints {
////// Devices Management ////////////////
static const String getAllDevices = '/device';
static const String getAllDevices = '/projects/{projectId}/devices';
static const String getSpaceDevices =
'/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/devices';
static const String getDeviceStatus = '/device/{uuid}/functions/status';
@ -95,8 +95,8 @@ abstract class ApiEndpoints {
static const String inviteUser = '/invite-user';
static const String checkEmail = '/invite-user/check-email';
static const String getUsers = '/projects/${projectUuid}/user';
static const String getUserById = '/projects/${projectUuid}/user/{userUuid}';
static const String getUsers = '/projects/{projectId}/user';
static const String getUserById = '/projects/{projectId}/user/{userUuid}';
static const String editUser = '/invite-user/{inviteUserUuid}';
static const String deleteUser = '/invite-user/{inviteUserUuid}';
static const String changeUserStatus = '/invite-user/{invitedUserUuid}/disable';

View File

@ -258,6 +258,7 @@ class Assets {
static const String doorSensor = 'assets/icons/door_sensor.svg';
static const String delete = 'assets/icons/delete.svg';
static const String deleteSpaceModel = 'assets/icons/delete_space_model.svg';
static const String edit = 'assets/icons/edit.svg';
static const String editSpace = 'assets/icons/edit_space.svg';
//assets/icons/routine/tab_to_run.svg

View File

@ -40,4 +40,5 @@ class StringsManager {
static const String firstLaunch = "firstLaunch";
static const String deleteScene = 'Delete Scene';
static const String deleteAutomation = 'Delete Automation';
static const String projectKey = 'selected_project_uuid';
}

View File

@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/auth/model/user_model.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
@ -218,7 +220,7 @@ class _UserDropdownMenuState extends State<UserDropdownMenu> {
),
GestureDetector(
onTap: () {
AuthBloc.logout();
AuthBloc.logout(context);
context.go(RoutesConst.auth);
},
child: SizedBox(