Compare commits

..

56 Commits

Author SHA1 Message Date
ae3eb6fca8 Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1722-FE-Implement-Duplicate-Space-Feature 2025-07-24 09:52:06 +03:00
f341dcd482 Remove unnecessary event dispatch in CommunityStructureHeaderActionButtonsComposer to streamline community update logic. This change enhances code clarity by eliminating the selection event for the community, focusing solely on the update action. 2025-07-24 09:47:39 +03:00
e98b091253 Refactor SpaceManagementBody to use a Stack layout for improved UI structure, allowing better positioning of the SpaceManagementCommunitiesTree and the main content. Enhance SpaceManagementCommunitiesTree with a shadow effect for better visual separation. This change promotes a more organized and visually appealing interface. 2025-07-24 09:39:38 +03:00
77d6d822cb Refactor SpaceSubSpacesDialog and SubSpacesInput to integrate a shared TextEditingController for improved state management of subspace names. This change enhances the input handling and ensures proper disposal of the controller, promoting better resource management. 2025-07-24 09:39:33 +03:00
f1aab13263 refactor booking page to use PageController (#366)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Description

<!--- Describe your changes in detail -->
refactor booking page to use PageController 

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-07-23 14:37:43 +03:00
a56e422bf5 refactor booking page to use PageController and remove legacy implementation 2025-07-23 14:34:59 +03:00
97530dd351 Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1722-FE-Implement-Duplicate-Space-Feature 2025-07-23 14:19:55 +03:00
a57b6e0853 Enhance CommunityStructureCanvas by adding a _centerOnTree method to improve the centering logic of the community structure. This method calculates the optimal view based on the positions of spaces and adjusts the transformation controller accordingly, ensuring a smoother user experience during updates and animations. 2025-07-23 14:19:25 +03:00
8f71fcb96a enhance week navigation layout for improved UI (#363)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->


## Description

<!--- Describe your changes in detail -->
enhance week navigation layout for improved UI

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-07-23 14:13:27 +03:00
845397e819 Update CommunityStructureCanvas to improve widget update logic by animating to the selected space based on community UUID changes. This enhances the responsiveness of the UI when the community context changes. 2025-07-23 13:21:49 +03:00
2077ef053f Refactor DuplicateSpaceService to return a list of SpaceModel objects instead of a single instance. Update related components including DuplicateSpaceSuccess state and DuplicateSpaceDialog to handle multiple spaces. Enhance CommunityStructureHeaderActionButtonsComposer to reflect these changes in the success callback. 2025-07-23 12:42:23 +03:00
d21850edc8 Enhance DuplicateSpaceDialog to use Bloc for state management and streamline the duplication process with success and error handling. Update CommunityStructureHeaderActionButtonsComposer to integrate the new dialog for duplicating spaces. 2025-07-23 12:37:27 +03:00
85f53ed1f2 Remove DuplicateSpaceBloc and its associated service from SpaceManagementPage to streamline dependencies and improve code clarity. 2025-07-23 12:37:17 +03:00
5fde74fc7d Add DuplicateSpaceFailureDialog widget to display error messages when duplicating spaces fails. 2025-07-23 12:37:07 +03:00
994efc302b Add AppSnackBarsBuildContextExtension for displaying success and failure snack bars in the app. 2025-07-23 12:36:45 +03:00
c403048da7 Bugfix/assign tags to devices table overflow (#364)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Description
Fixed overflow in assign tags to devices table.

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-07-23 11:01:56 +03:00
04b7a506be Remove newSpaceIcon parameter from DuplicateSpaceParam class since it isnt needed. 2025-07-23 10:52:46 +03:00
19ddf443a9 Refactor RemoteDuplicateSpaceService to improve code readability by aligning method chaining for URL replacements. 2025-07-23 10:52:29 +03:00
3779176978 Add DuplicateSpaceDialog widget for user interaction in duplicate space management. 2025-07-23 10:52:18 +03:00
7c5bca35fc Add DuplicateSpaceTextField widget for user input in duplicate space management. 2025-07-23 10:52:07 +03:00
aed3004a31 Added DuplicateSpaceBloc to SpaceManagementPage for managing duplicate space functionality. 2025-07-23 10:51:51 +03:00
8ae4e561c2 SP-1601-FE-Community-and-Space-Dialog-Redesign-in-the-routine-tab (#346)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1601](https://syncrow.atlassian.net/browse/SP-1601)

## Description

fix reWork notes

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1601]:
https://syncrow.atlassian.net/browse/SP-1601?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-23 10:24:17 +03:00
4241d11cb6 Implemented duplicate_space data layer. 2025-07-23 09:59:13 +03:00
ef8c9efff0 Added duplicate space endpoint to ApiEndpoints. 2025-07-23 09:58:46 +03:00
c59d2b7fd6 Implemented toJson method in DuplicateSpaceParam. 2025-07-23 09:58:15 +03:00
71f0da9299 Created duplicate_space presentation layer. 2025-07-23 09:51:09 +03:00
e6d9000ee2 Implemented duplicate space domain layer. 2025-07-23 09:48:23 +03:00
7dc103f904 Merge branch 'dev' of https://github.com/SyncrowIOT/web into bugfix/assign_togs_to_table_overflow 2025-07-23 09:38:00 +03:00
e4c41bab90 bugfix/assign_tag_to_devices_table_overflow. 2025-07-23 09:37:11 +03:00
7f3dfebf15 Merge branch 'dev' into SP-1601-FE-Community-and-Space-Dialog-Redesign-in-the-routine-tab 2025-07-23 09:34:54 +03:00
0de882d43b [FE] On Uba Gateway device the Icons of the devices inside are not all of them displayed (#359)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1569](https://syncrow.atlassian.net/browse/SP-1569)

## Description

add devices and icons for them

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1569]:
https://syncrow.atlassian.net/browse/SP-1569?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-23 09:34:10 +03:00
6a737e5d43 [FE] Manage Bookable Spaces Tab (#355)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1693](https://syncrow.atlassian.net/browse/SP-1693)
[SP-1694](https://syncrow.atlassian.net/browse/SP-1694)

## Description

all about unbookable spaces is Ready

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [x]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1693]:
https://syncrow.atlassian.net/browse/SP-1693?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
[SP-1694]:
https://syncrow.atlassian.net/browse/SP-1694?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-23 09:33:46 +03:00
c323d88790 Feature/reorder spaces api integration (#362)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Description

Integrated reordering spaces with the API.
Fixed drop target bug, where the canvas wouldn't show the first drop
target in the tree.

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ x ]  New feature (non-breaking change which adds functionality)
- [ x ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [   ] 🧹 Code refactor
- [   ]  Build configuration change
- [   ] 📝 Documentation
- [   ] 🗑️ Chore
2025-07-23 09:31:16 +03:00
68153e41ed remove print statement 2025-07-23 09:02:30 +03:00
59058cf2d2 enhance week navigation layout for improved UI 2025-07-22 14:40:50 +03:00
94f9c1beea Adjust layout in CommunityStructureCanvas by adding horizontal padding to positions and refining target position calculations for improved spacing and alignment. Enhance Stack widget behavior by allowing overflow clipping. 2025-07-22 10:04:38 +03:00
dfd8c5fa31 Replace Container with AnimatedContainer in CommunityStructureCanvas to enhance visual feedback during state changes. Adjust alpha value for improved visibility based on candidate data presence. 2025-07-22 09:46:56 +03:00
60b8ee8b50 Enhance DragTarget logic in CommunityStructureCanvas by refining conditions for rendering and improving readability. Ensure proper handling of dragged data and its parent/community relationships. 2025-07-22 09:37:31 +03:00
9d60f913eb Refactor CommunityStructureCanvas to simplify DragTarget logic by replacing SizedBox with SizedBox.shrink() for better performance and readability. 2025-07-22 09:32:57 +03:00
40251b846b Integrate ReorderSpaces functionality into CommunityStructureCanvas and enhance RemoteReorderSpacesService with dynamic URL generation. Update ReorderSpacesParam to require parentSpaceUuid and spaces for improved validation and serialization. 2025-07-21 16:43:26 +03:00
1323bceca1 Update ReorderSpacesParam to make parentSpaceUuid optional and add toJson method for serialization. 2025-07-21 16:39:31 +03:00
35c8a73156 Refactor SpaceManagementPage's initState to ensure HTTPService is initialized before use in CommunitiesBloc. 2025-07-21 16:24:11 +03:00
ce65b068ff Merge branch 'dev' of https://github.com/SyncrowIOT/web into feature/reorder_spaces_api_integration 2025-07-21 16:22:22 +03:00
96f107f972 Refactor SpaceManagementPage to utilize a shared HTTPService instance for API calls in Communities, SpaceDetails, Products, and ReorderSpaces blocs, and injected ReorderSpacesBloc into it. 2025-07-21 16:15:26 +03:00
a3a7937021 Implemented ReorderSpacesBloc. 2025-07-21 16:14:03 +03:00
9bf715501b Implement ReorderSpacesService. 2025-07-21 15:57:31 +03:00
c65f4a7fab Add ReorderSpacesParam and ReorderSpacesService for managing space reordering functionality. 2025-07-21 15:57:20 +03:00
7af8887d4f Add new API endpoint for reordering spaces in the community module. 2025-07-21 15:57:10 +03:00
b738596b50 no need for colors instead use ColorsManager 2025-07-21 09:14:59 +03:00
0ad562b6ce add icons and types for devices did not added before 2025-07-20 14:03:50 +03:00
995ce480cb requested note 2025-07-17 17:44:30 +03:00
b41733ee40 Merge branch 'SP-1601-FE-Community-and-Space-Dialog-Redesign-in-the-routine-tab' of https://github.com/SyncrowIOT/web into SP-1601-FE-Community-and-Space-Dialog-Redesign-in-the-routine-tab 2025-07-15 11:53:29 +03:00
fe090175e3 fix Typo 2025-07-15 11:52:57 +03:00
85544c69f8 Merge branch 'dev' into SP-1601-FE-Community-and-Space-Dialog-Redesign-in-the-routine-tab 2025-07-15 09:09:18 +03:00
9f71bbff63 fix color uses 2025-07-15 08:26:40 +03:00
deb227034a fix Rework notes 2025-07-11 11:44:16 +03:00
40 changed files with 1036 additions and 272 deletions

View File

@ -0,0 +1,9 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M38.0142 39.2553L35.3682 40H20.9308H19.9999H19.0691H1.24111C0.555643 40 0 39.4444 0 38.7589V1.24111C0 0.555643 0.555643 0 1.24111 0H19.0682H20.1226H20.9543H35.4255L38.2625 1.24111C38.9479 1.24111 39.5036 1.79675 39.5036 2.48221L39.2553 38.0142C39.2553 38.6997 38.6997 39.2553 38.0142 39.2553Z" fill="#E9E9E9"/>
<path d="M38.7585 0H35.0352C35.7206 0 36.2763 0.555643 36.2763 1.24111V38.7589C36.2763 39.4444 35.7206 40 35.0352 40H38.7585C39.4439 40 39.9996 39.4444 39.9996 38.7589V1.24111C39.9996 0.555643 39.4439 0 38.7585 0Z" fill="#D1D1D1"/>
<path opacity="0.6" d="M12.0283 31.8319V33.3212C12.0283 34.0067 11.6086 34.5623 11.0908 34.5623H6.96582C6.44804 34.5623 6.02832 34.0067 6.02832 33.3212V31.8319C6.02832 31.1465 6.44804 30.5908 6.96582 30.5908H11.0908C11.6086 30.5908 12.0283 31.1465 12.0283 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
<path opacity="0.6" d="M12.0283 7.24109V8.73042C12.0283 9.41588 11.6086 9.97153 11.0908 9.97153H6.96582C6.44804 9.97153 6.02832 9.41588 6.02832 8.73042V7.24109C6.02832 6.55563 6.44804 5.99998 6.96582 5.99998H11.0908C11.6086 5.99998 12.0283 6.55563 12.0283 7.24109Z" fill="#023DFE" fill-opacity="0.5"/>
<path opacity="0.6" d="M26.0283 31.8319V33.3212C26.0283 34.0067 26.448 34.5623 26.9658 34.5623H31.0908C31.6086 34.5623 32.0283 34.0067 32.0283 33.3212V31.8319C32.0283 31.1465 31.6086 30.5908 31.0908 30.5908H26.9658C26.448 30.5908 26.0283 31.1465 26.0283 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
<path opacity="0.6" d="M26.0283 7.24109V8.73042C26.0283 9.41588 26.448 9.97153 26.9658 9.97153H31.0908C31.6086 9.97153 32.0283 9.41588 32.0283 8.73042V7.24109C32.0283 6.55563 31.6086 5.99998 31.0908 5.99998H26.9658C26.448 5.99998 26.0283 6.55563 26.0283 7.24109Z" fill="#023DFE" fill-opacity="0.5"/>
<path d="M19.0693 0H20.931V40H19.0693V0Z" fill="#D1D1D1"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,12 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M38.0142 39.2553L35.3682 40H20.9308H19.9999H19.0691H1.24111C0.555643 40 0 39.4444 0 38.7589V1.24111C0 0.555651 0.555643 7.62939e-06 1.24111 7.62939e-06H19.0682H20.1226H20.9543H35.4255L38.2625 1.24111C38.9479 1.24111 39.5036 1.79676 39.5036 2.48222L39.2553 38.0142C39.2553 38.6997 38.6997 39.2553 38.0142 39.2553Z" fill="#E9E9E9"/>
<path d="M38.7585 0H35.0352C35.7206 0 36.2763 0.555643 36.2763 1.24111V38.7589C36.2763 39.4444 35.7206 40 35.0352 40H38.7585C39.4439 40 39.9996 39.4444 39.9996 38.7589V1.24111C39.9996 0.555643 39.4439 0 38.7585 0Z" fill="#D1D1D1"/>
<path opacity="0.6" d="M8.64062 31.8319V33.3212C8.64062 34.0067 8.22091 34.5623 7.70312 34.5623H3.57813C3.06034 34.5623 2.64062 34.0067 2.64062 33.3212V31.8319C2.64062 31.1464 3.06034 30.5908 3.57813 30.5908H7.70312C8.22091 30.5908 8.64062 31.1464 8.64062 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
<path opacity="0.6" d="M8.64062 7.24109V8.73042C8.64062 9.41588 8.22091 9.97152 7.70312 9.97152H3.57813C3.06034 9.97152 2.64062 9.41588 2.64062 8.73042V7.24109C2.64062 6.55563 3.06034 5.99998 3.57813 5.99998H7.70312C8.22091 5.99998 8.64062 6.55563 8.64062 7.24109Z" fill="#023DFE" fill-opacity="0.5"/>
<path opacity="0.6" d="M27.6406 31.8319V33.3212C27.6406 34.0067 28.0603 34.5623 28.5781 34.5623H32.7031C33.2209 34.5623 33.6406 34.0067 33.6406 33.3212V31.8319C33.6406 31.1464 33.2209 30.5908 32.7031 30.5908H28.5781C28.0603 30.5908 27.6406 31.1464 27.6406 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
<path opacity="0.6" d="M27.6406 7.24109V8.73042C27.6406 9.41588 28.0603 9.97152 28.5781 9.97152H32.7031C33.2209 9.97152 33.6406 9.41588 33.6406 8.73042V7.24109C33.6406 6.55563 33.2209 5.99998 32.7031 5.99998H28.5781C28.0603 5.99998 27.6406 6.55563 27.6406 7.24109Z" fill="#023DFE" fill-opacity="0.5"/>
<path opacity="0.6" d="M15.0625 31.8319V33.3212C15.0625 34.0067 15.4822 34.5623 16 34.5623H20.125C20.6428 34.5623 21.0625 34.0067 21.0625 33.3212V31.8319C21.0625 31.1464 20.6428 30.5908 20.125 30.5908H16C15.4822 30.5908 15.0625 31.1464 15.0625 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
<path opacity="0.6" d="M15.0625 7.24109V8.73042C15.0625 9.41588 15.4822 9.97152 16 9.97152H20.125C20.6428 9.97152 21.0625 9.41588 21.0625 8.73042V7.24109C21.0625 6.55563 20.6428 5.99998 20.125 5.99998H16C15.4822 5.99998 15.0625 6.55563 15.0625 7.24109Z" fill="#023DFE" fill-opacity="0.5"/>
<path d="M23.125 0H24.9867V40H23.125V0Z" fill="#D1D1D1"/>
<path d="M11.1719 0H13.0335V40H11.1719V0Z" fill="#D1D1D1"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,4 +1,3 @@
// booking_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:calendar_view/calendar_view.dart';
@ -20,7 +19,8 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class BookingPage extends StatefulWidget {
const BookingPage({super.key});
final PageController? pageController;
const BookingPage({super.key, this.pageController});
@override
State<BookingPage> createState() => _BookingPageState();
@ -42,12 +42,15 @@ class _BookingPageState extends State<BookingPage> {
),
),
],
child: _BookingPageContent(),
child: _BookingPageContent(widget.pageController),
);
}
}
class _BookingPageContent extends StatefulWidget {
final PageController? pageController;
const _BookingPageContent(this.pageController);
@override
State<_BookingPageContent> createState() => _BookingPageContentState();
}
@ -90,8 +93,7 @@ class _BookingPageContentState extends State<_BookingPageContent> {
return BlocListener<SelectedBookableSpaceBloc, SelectedBookableSpaceState>(
listener: (context, state) {
if (state.selectedBookableSpace != null) {
// Reset events and clear cache when room changes
context.read<CalendarEventsBloc>().add(ResetEvents());
context.read<CalendarEventsBloc>().add(const ResetEvents());
_loadEvents(context);
}
},
@ -176,7 +178,9 @@ class _BookingPageContentState extends State<_BookingPageContent> {
SvgTextButton(
svgAsset: Assets.homeIcon,
label: 'Manage Bookable Spaces',
onPressed: () {},
onPressed: () {
widget.pageController!.jumpToPage(2);
},
),
const SizedBox(width: 20),
SvgTextButton(
@ -237,7 +241,6 @@ class _BookingPageContentState extends State<_BookingPageContent> {
.watch<DateSelectionBloc>()
.state
.selectedDateFromSideBarCalender,
// isLoading: eventState is EventsLoading,
);
},
);

View File

@ -19,6 +19,7 @@ class WeekNavigation extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 250,
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: ColorsManager.circleRolesBackground,
@ -32,6 +33,8 @@ class WeekNavigation extends StatelessWidget {
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
iconSize: 15,
@ -40,12 +43,16 @@ class WeekNavigation extends StatelessWidget {
onPressed: onPreviousWeek,
),
const SizedBox(width: 10),
Text(
_getMonthYearText(weekStart, weekEnd),
style: const TextStyle(
color: ColorsManager.lightGrayColor,
fontSize: 14,
fontWeight: FontWeight.w400,
SizedBox(
width: 120,
child: Text(
_getMonthYearText(weekStart, weekEnd),
style: const TextStyle(
color: ColorsManager.lightGrayColor,
fontSize: 14,
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
),
const SizedBox(width: 10),

View File

@ -1,57 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class BookingPage extends StatelessWidget {
final PageController pageController;
const BookingPage({
super.key,
required this.pageController,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Container(
color: Colors.blueGrey[100],
child: const Center(
child: Text(
'Side bar',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
)),
Expanded(
flex: 4,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: SizedBox(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SvgTextButton(
svgAsset: Assets.homeIcon,
label: 'Manage Bookable Spaces',
onPressed: () {
pageController.jumpToPage(2);
}),
const SizedBox(width: 20),
SvgTextButton(
svgAsset: Assets.groupIcon,
label: 'Manage Users',
onPressed: () {})
],
)
],
),
),
))
],
);
}
}

View File

@ -2,9 +2,8 @@ import 'package:flutter/material.dart';
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/booking_system/view/booking_page.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/screens/manage_bookable_spaces_screen.dart';
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/booking_page.dart' hide BookingPage;
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/booking_page.dart';
import 'package:syncrow_web/pages/access_management/view/access_overview_content.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';

View File

@ -100,6 +100,7 @@ class _DeviceItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DeviceControlsContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'space_tree_dropdown_bloc.dart';
class DropdownMenuContent extends StatefulWidget {
@ -75,7 +76,8 @@ class _DropdownMenuContentState extends State<DropdownMenuContent> {
child: TextFormField(
controller: _searchController,
onChanged: _handleSearch,
style: const TextStyle(fontSize: 14, color: Colors.black),
style: const TextStyle(
fontSize: 14, color: ColorsManager.blackColor),
decoration: InputDecoration(
hintText: 'Search for space...',
prefixIcon: const Icon(Icons.search, size: 20),
@ -84,6 +86,12 @@ class _DropdownMenuContentState extends State<DropdownMenuContent> {
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: ColorsManager.dropDownSelectBlue,
),
),
isDense: true,
),
),
@ -117,7 +125,9 @@ class _DropdownMenuContentState extends State<DropdownMenuContent> {
title: Text(
community.name,
style: TextStyle(
color: isSelected ? Colors.blue : Colors.black,
color: isSelected
? ColorsManager.dropDownSelectBlue
: ColorsManager.blackColor,
fontWeight:
isSelected ? FontWeight.bold : FontWeight.normal,
),

View File

@ -52,7 +52,7 @@ class SpaceDropdown extends StatelessWidget {
fontSize: 16,
fontWeight: FontWeight.bold,
color: selectedValue == space.uuid
? ColorsManager.dialogBlueTitle
? ColorsManager.dropDownSelectBlue
: ColorsManager.blackColor,
),
),
@ -61,7 +61,7 @@ class SpaceDropdown extends StatelessWidget {
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
color: selectedValue == space.uuid
? ColorsManager.dialogBlueTitle
? ColorsManager.dropDownSelectBlue
: ColorsManager.blackColor,
),
),

View File

@ -9,6 +9,8 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/products/data/services/remote_products_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/data/services/remote_reorder_spaces_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/presentation/bloc/reorder_spaces_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
@ -25,15 +27,16 @@ class SpaceManagementPage extends StatefulWidget {
class _SpaceManagementPageState extends State<SpaceManagementPage> {
late final CommunitiesBloc communitiesBloc;
late final HTTPService _httpService;
@override
void initState() {
_httpService = HTTPService();
communitiesBloc = CommunitiesBloc(
communitiesService: DebouncedCommunitiesService(
RemoteCommunitiesService(HTTPService()),
RemoteCommunitiesService(_httpService),
),
)..add(const LoadCommunities(LoadCommunitiesParam()));
super.initState();
}
@ -50,13 +53,18 @@ class _SpaceManagementPageState extends State<SpaceManagementPage> {
BlocProvider(
create: (context) => SpaceDetailsBloc(
UniqueSpaceDetailsSpacesDecoratorService(
RemoteSpaceDetailsService(httpService: HTTPService()),
RemoteSpaceDetailsService(httpService: _httpService),
),
),
),
BlocProvider(
create: (context) => ProductsBloc(
RemoteProductsService(HTTPService()),
RemoteProductsService(_httpService),
),
),
BlocProvider(
create: (context) => ReorderSpacesBloc(
RemoteReorderSpacesService(_httpService),
),
),
],

View File

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart';
@ -11,6 +13,8 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/presentation/bloc/reorder_spaces_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -49,13 +53,23 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
duration: const Duration(milliseconds: 150),
);
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_centerOnTree();
}
});
}
@override
void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.selectedSpace == null) return;
if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
if (oldWidget.community.uuid != widget.community.uuid) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_centerOnTree(animate: true);
}
});
} else if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_animateToSpace(widget.selectedSpace);
@ -149,6 +163,60 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
_runAnimation(matrix);
}
void _centerOnTree({bool animate = false}) {
if (_positions.isEmpty) {
if (animate) {
_runAnimation(Matrix4.identity());
} else {
_transformationController.value = Matrix4.identity();
}
return;
}
var minX = double.infinity;
var maxX = double.negativeInfinity;
var minY = double.infinity;
var maxY = double.negativeInfinity;
_positions.forEach((uuid, offset) {
final cardWidth = _cardWidths[uuid] ?? _minCardWidth;
minX = min(minX, offset.dx);
maxX = max(maxX, offset.dx + cardWidth);
minY = min(minY, offset.dy);
maxY = max(maxY, offset.dy + _cardHeight);
});
if (!minX.isFinite || !maxX.isFinite || !minY.isFinite || !maxY.isFinite) {
return;
}
final treeWidth = maxX - minX;
final treeHeight = maxY - minY;
final viewSize = context.size;
if (viewSize == null) return;
final scaleX = viewSize.width / treeWidth;
final scaleY = viewSize.height / treeHeight;
final scale = min(scaleX, scaleY).clamp(0.5, 1.0) * 0.9;
final treeCenterX = minX + treeWidth / 2;
final treeCenterY = minY + treeHeight / 2;
final x = -treeCenterX * scale + viewSize.width / 2;
final y = -treeCenterY * scale + viewSize.height / 2;
final matrix = Matrix4.identity()
..translate(x, y)
..scale(scale);
if (animate) {
_runAnimation(matrix);
} else {
_transformationController.value = matrix;
}
}
void _onReorder(SpaceReorderDataModel data, int newIndex) {
final newCommunity = widget.community.copyWith();
final children = data.parent?.children ?? newCommunity.spaces;
@ -164,6 +232,16 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
context.read<CommunitiesBloc>().add(
CommunitiesUpdateCommunity(newCommunity),
);
context.read<ReorderSpacesBloc>().add(
ReorderSpacesEvent(
ReorderSpacesParam(
communityUuid: widget.community.uuid,
parentSpaceUuid: data.parent?.uuid ?? '',
spaces: children,
),
),
);
}
void _onSpaceTapped(SpaceModel? space) {
@ -245,6 +323,13 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
final levelXOffset = <int, double>{};
_calculateLayout(community.spaces, 0, levelXOffset);
const horizontalCanvasPadding = 100.0;
final originalPositions = Map.of(_positions);
_positions.clear();
for (final entry in originalPositions.entries) {
_positions[entry.key] = entry.value.translate(horizontalCanvasPadding, 0);
}
final selectedSpace = widget.selectedSpace;
final highlightedUuids = <String>{};
if (selectedSpace != null) {
@ -262,7 +347,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
community: widget.community,
);
final createButtonX = levelXOffset[0] ?? 0.0;
final createButtonX = (levelXOffset[0] ?? 0.0) + horizontalCanvasPadding;
const createButtonY = 0.0;
widgets.add(
@ -294,10 +379,12 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
CommunityModel? community,
SpaceModel? parent,
}) {
const targetWidth = 40.0;
final padding = (_horizontalSpacing - targetWidth) / 2;
if (spaces.isNotEmpty) {
final firstChildPos = _positions[spaces.first.uuid]!;
final targetPos = Offset(
firstChildPos.dx - (_horizontalSpacing / 4),
firstChildPos.dx - padding - targetWidth,
firstChildPos.dy,
);
widgets.add(_buildDropTarget(parent, community, 0, targetPos));
@ -379,7 +466,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
);
final targetPos = Offset(
position.dx + cardWidth + (_horizontalSpacing / 4) - 20,
position.dx + cardWidth + padding,
position.dy,
);
widgets.add(_buildDropTarget(parent, community, i + 1, targetPos));
@ -414,24 +501,33 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
child: DragTarget<SpaceReorderDataModel>(
builder: (context, candidateData, rejectedData) {
if (_draggedData == null) {
return const SizedBox();
return const SizedBox.shrink();
}
final isTargetForDragged = (_draggedData?.parent?.uuid == parent?.uuid &&
_draggedData?.community == null) ||
(_draggedData?.community?.uuid == community?.uuid &&
_draggedData?.parent == null);
final children = parent?.children ?? community?.spaces ?? [];
final isSameParent = (_draggedData!.parent?.uuid == parent?.uuid &&
_draggedData!.community == null) ||
(_draggedData!.community?.uuid == community?.uuid &&
_draggedData!.parent == null);
if (!isTargetForDragged) {
return const SizedBox();
if (!isSameParent) {
return const SizedBox.shrink();
}
return Container(
final oldIndex =
children.indexWhere((s) => s.uuid == _draggedData!.space.uuid);
if (oldIndex != -1 && (oldIndex == index || oldIndex == index - 1)) {
return const SizedBox.shrink();
}
return AnimatedContainer(
duration: const Duration(milliseconds: 150),
width: 40,
alignment: Alignment.center,
height: _cardHeight,
decoration: BoxDecoration(
color: context.theme.colorScheme.primary.withValues(
alpha: candidateData.isNotEmpty ? 0.7 : 0.3,
alpha: candidateData.isNotEmpty ? 0.9 : 0.3,
),
borderRadius: BorderRadius.circular(8),
),
@ -454,6 +550,9 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
final oldIndex =
children.indexWhere((s) => s.uuid == data.data.space.uuid);
if (oldIndex == -1) {
return true;
}
if (oldIndex == index || oldIndex == index - 1) {
return false;
}
@ -481,7 +580,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
child: SizedBox(
width: context.screenWidth * 5,
height: context.screenHeight * 5,
child: Stack(children: treeWidgets),
child: Stack(clipBehavior: Clip.none, children: treeWidgets),
),
),
);

View File

@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/views/duplicate_space_dialog.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget {
@ -44,7 +45,22 @@ class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget {
},
),
),
onDuplicate: (space) {},
onDuplicate: (space) => showDialog<void>(
context: context,
builder: (_) => DuplicateSpaceDialog(
initialName: space.spaceName,
selectedSpaceUuid: space.uuid,
selectedCommunityUuid: selectedCommunity.uuid,
onSuccess: (spaces) {
final updatedCommunity = selectedCommunity.copyWith(
spaces: spaces,
);
context.read<CommunitiesBloc>().add(
CommunitiesUpdateCommunity(updatedCommunity),
);
},
),
),
onEdit: (space) => SpaceDetailsDialogHelper.showEdit(
context,
spaceModel: selectedSpace!,

View File

@ -10,21 +10,26 @@ class SpaceManagementBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
return Stack(
children: [
const SpaceManagementCommunitiesTree(),
Expanded(
child: BlocBuilder<CommunitiesTreeSelectionBloc,
CommunitiesTreeSelectionState>(
buildWhen: (previous, current) =>
previous.selectedCommunity != current.selectedCommunity,
builder: (context, state) => Visibility(
visible: state.selectedCommunity == null,
replacement: const SpaceManagementCommunityStructure(),
child: const SpaceManagementTemplatesView(),
Row(
children: [
const SizedBox(width: 320),
Expanded(
child: BlocBuilder<CommunitiesTreeSelectionBloc,
CommunitiesTreeSelectionState>(
buildWhen: (previous, current) =>
previous.selectedCommunity != current.selectedCommunity,
builder: (context, state) => Visibility(
visible: state.selectedCommunity == null,
replacement: const SpaceManagementCommunityStructure(),
child: const SpaceManagementTemplatesView(),
),
),
),
),
],
),
const SpaceManagementCommunitiesTree(),
],
);
}

View File

@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree_community_tile.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_communities_list.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_header.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart';
class SpaceManagementCommunitiesTree extends StatefulWidget {
@ -44,7 +45,15 @@ class _SpaceManagementCommunitiesTreeState
return BlocBuilder<CommunitiesBloc, CommunitiesState>(
builder: (context, state) => Container(
width: 320,
decoration: subSectionContainerDecoration,
decoration: subSectionContainerDecoration.copyWith(
boxShadow: [
BoxShadow(
color: ColorsManager.shadowBlackColor.withValues(alpha: 0.1),
blurRadius: 10,
offset: const Offset(10, 0),
),
],
),
child: Column(
children: [
const SpaceManagementSidebarHeader(),

View File

@ -0,0 +1,60 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
final class RemoteDuplicateSpaceService implements DuplicateSpaceService {
RemoteDuplicateSpaceService(this._httpService);
final HTTPService _httpService;
@override
Future<List<SpaceModel>> duplicateSpace(DuplicateSpaceParam param) async {
try {
final response = await _httpService.post(
path: await _makeUrl(param),
body: param.toJson(),
expectedResponseModel: (json) {
final response = json as Map<String, dynamic>;
final data = response['data'] as List<dynamic>;
return data
.map((e) => SpaceModel.fromJson(e as Map<String, dynamic>))
.toList();
},
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
throw APIException(errorMessage);
} catch (e) {
throw APIException(e.toString());
}
}
Future<String> _makeUrl(DuplicateSpaceParam param) async {
final projectUuid = await ProjectManager.getProjectUUID();
if (projectUuid == null) {
throw APIException('Project UUID is not set');
}
if (param.communityUuid.isEmpty) {
throw APIException('Community UUID is not set');
}
if (param.spaceUuid.isEmpty) {
throw APIException('Space UUID is not set');
}
return ApiEndpoints.duplicateSpace
.replaceAll('{projectUuid}', projectUuid)
.replaceAll('{communityUuid}', param.communityUuid)
.replaceAll('{spaceUuid}', param.spaceUuid);
}
}

View File

@ -0,0 +1,15 @@
class DuplicateSpaceParam {
final String communityUuid;
final String spaceUuid;
final String newSpaceName;
DuplicateSpaceParam({
required this.communityUuid,
required this.spaceUuid,
required this.newSpaceName,
});
Map<String, dynamic> toJson() => {
'spaceName': newSpaceName,
};
}

View File

@ -0,0 +1,6 @@
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
abstract interface class DuplicateSpaceService {
Future<List<SpaceModel>> duplicateSpace(DuplicateSpaceParam param);
}

View File

@ -0,0 +1,36 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'duplicate_space_event.dart';
part 'duplicate_space_state.dart';
class DuplicateSpaceBloc extends Bloc<DuplicateSpaceEvent, DuplicateSpaceState> {
DuplicateSpaceBloc(
this._duplicateSpaceService,
) : super(const DuplicateSpaceInitial()) {
on<DuplicateSpaceEvent>(_onDuplicateSpaceEvent);
}
final DuplicateSpaceService _duplicateSpaceService;
Future<void> _onDuplicateSpaceEvent(
DuplicateSpaceEvent event,
Emitter<DuplicateSpaceState> emit,
) async {
try {
emit(const DuplicateSpaceLoading());
final result = await _duplicateSpaceService.duplicateSpace(event.param);
emit(DuplicateSpaceSuccess(result));
} on APIException catch (e) {
emit(DuplicateSpaceFailure(e.message));
} catch (e) {
emit(DuplicateSpaceFailure(e.toString()));
} finally {
emit(const DuplicateSpaceInitial());
}
}
}

View File

@ -0,0 +1,10 @@
part of 'duplicate_space_bloc.dart';
final class DuplicateSpaceEvent extends Equatable {
const DuplicateSpaceEvent({required this.param});
final DuplicateSpaceParam param;
@override
List<Object> get props => [param];
}

View File

@ -0,0 +1,34 @@
part of 'duplicate_space_bloc.dart';
sealed class DuplicateSpaceState extends Equatable {
const DuplicateSpaceState();
@override
List<Object> get props => [];
}
final class DuplicateSpaceInitial extends DuplicateSpaceState {
const DuplicateSpaceInitial();
}
final class DuplicateSpaceLoading extends DuplicateSpaceState {
const DuplicateSpaceLoading();
}
final class DuplicateSpaceSuccess extends DuplicateSpaceState {
const DuplicateSpaceSuccess(this.spaces);
final List<SpaceModel> spaces;
@override
List<Object> get props => [spaces];
}
final class DuplicateSpaceFailure extends DuplicateSpaceState {
const DuplicateSpaceFailure(this.errorMessage);
final String errorMessage;
@override
List<Object> get props => [errorMessage];
}

View File

@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/widgets/app_loading_indicator.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/extension/app_snack_bar.dart';
class DuplicateSpaceDialog extends StatelessWidget {
const DuplicateSpaceDialog({
required this.initialName,
required this.onSuccess,
required this.selectedSpaceUuid,
required this.selectedCommunityUuid,
super.key,
});
final String initialName;
final void Function(List<SpaceModel> spaces) onSuccess;
final String selectedSpaceUuid;
final String selectedCommunityUuid;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => DuplicateSpaceBloc(
RemoteDuplicateSpaceService(HTTPService()),
),
child: BlocListener<DuplicateSpaceBloc, DuplicateSpaceState>(
listener: _listener,
child: DuplicateSpaceDialogForm(
initialName: initialName,
selectedSpaceUuid: selectedSpaceUuid,
selectedCommunityUuid: selectedCommunityUuid,
),
),
);
}
void _listener(BuildContext context, DuplicateSpaceState state) {
switch (state) {
case DuplicateSpaceLoading():
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (context) => const AppLoadingIndicator(),
);
break;
case DuplicateSpaceFailure(:final errorMessage):
Navigator.pop(context);
Navigator.pop(context);
context.showFailureSnackbar(errorMessage);
break;
case DuplicateSpaceSuccess(:final spaces):
onSuccess.call(spaces);
Navigator.of(context).pop();
Navigator.of(context).pop();
context.showSuccessSnackbar('Space duplicated successfully');
break;
default:
break;
}
}
}

View File

@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart';
class DuplicateSpaceDialogForm extends StatefulWidget {
const DuplicateSpaceDialogForm({
required this.initialName,
required this.selectedSpaceUuid,
required this.selectedCommunityUuid,
super.key,
});
final String initialName;
final String selectedSpaceUuid;
final String selectedCommunityUuid;
@override
State<DuplicateSpaceDialogForm> createState() => _DuplicateSpaceDialogFormState();
}
class _DuplicateSpaceDialogFormState extends State<DuplicateSpaceDialogForm> {
late final TextEditingController _nameController;
bool _isNameValid = true;
@override
void initState() {
super.initState();
_nameController = TextEditingController(text: '${widget.initialName}(1)');
_nameController.addListener(_validateName);
}
void _validateName() => setState(
() => _isNameValid = _nameController.text.trim() != widget.initialName,
);
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const SelectableText('Duplicate Space'),
content: Column(
mainAxisSize: MainAxisSize.min,
spacing: 16,
children: [
const SelectableText('Enter a new name for the duplicated space:'),
DuplicateSpaceTextField(
nameController: _nameController,
isNameValid: _isNameValid,
initialName: widget.initialName,
),
],
),
actions: [
TextButton(
onPressed: Navigator.of(context).pop,
child: const Text('Cancel'),
),
TextButton(
onPressed: _isNameValid ? () => _submit(context) : null,
child: const Text('Duplicate'),
),
],
);
}
void _submit(BuildContext context) {
context.read<DuplicateSpaceBloc>().add(
DuplicateSpaceEvent(
param: DuplicateSpaceParam(
newSpaceName: _nameController.text,
spaceUuid: widget.selectedSpaceUuid,
communityUuid: widget.selectedCommunityUuid,
),
),
);
}
}

View File

@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
class DuplicateSpaceFailureDialog extends StatelessWidget {
const DuplicateSpaceFailureDialog(this.errorMessage, {super.key});
final String errorMessage;
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Failed to duplicate space'),
content: Text(errorMessage),
actions: [
TextButton(
onPressed: Navigator.of(context).pop,
child: const Text('Close'),
),
],
);
}
}

View File

@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class DuplicateSpaceTextField extends StatelessWidget {
const DuplicateSpaceTextField({
required this.nameController,
required this.isNameValid,
required this.initialName,
super.key,
});
final TextEditingController nameController;
final bool isNameValid;
final String initialName;
String get _errorText => 'Name must be different from "$initialName"';
@override
Widget build(BuildContext context) {
return TextField(
controller: nameController,
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.blackColor,
),
decoration: InputDecoration(
label: const Text('Space Name'),
border: _border(),
enabledBorder: _border(),
focusedBorder: _border(ColorsManager.primaryColor),
errorBorder: _border(context.theme.colorScheme.error),
focusedErrorBorder: _border(context.theme.colorScheme.error),
errorStyle: context.textTheme.bodyMedium!.copyWith(
color: context.theme.colorScheme.error,
fontSize: 8,
),
errorText: isNameValid ? null : _errorText,
),
);
}
OutlineInputBorder _border([Color? color]) {
return OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(
color: color ?? ColorsManager.blackColor,
width: 0.5,
),
);
}
}

View File

@ -0,0 +1,58 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/services/reorder_spaces_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
final class RemoteReorderSpacesService implements ReorderSpacesService {
RemoteReorderSpacesService(this._httpClient);
final HTTPService _httpClient;
@override
Future<void> reorderSpaces(ReorderSpacesParam param) async {
try {
await _httpClient.post(
path: await _makeUrl(param),
body: param.toJson(),
expectedResponseModel: (json) => json,
);
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
throw APIException(_getErrorMessageFromBody(message));
} catch (e) {
throw APIException(e.toString());
}
}
String _getErrorMessageFromBody(Map<String, dynamic>? body) {
if (body == null) return 'Failed to delete space';
final error = body['error'] as Map<String, dynamic>?;
final errorMessage = error?['message'] as String? ?? '';
return errorMessage;
}
Future<String> _makeUrl(ReorderSpacesParam param) async {
final projectUuid = await ProjectManager.getProjectUUID();
final communityUuid = param.communityUuid;
if (projectUuid == null || projectUuid.isEmpty) {
throw APIException('Project UUID is not set');
}
if (communityUuid.isEmpty) {
throw APIException('Community UUID is not set');
}
if (param.parentSpaceUuid.isEmpty) {
throw APIException('Parent Space UUID is not set');
}
return ApiEndpoints.reorderSpaces
.replaceAll('{projectUuid}', projectUuid)
.replaceAll('{communityUuid}', communityUuid)
.replaceAll('{parentSpaceUuid}', param.parentSpaceUuid);
}
}

View File

@ -0,0 +1,21 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
class ReorderSpacesParam extends Equatable {
const ReorderSpacesParam({
required this.communityUuid,
required this.parentSpaceUuid,
required this.spaces,
});
final String communityUuid;
final String parentSpaceUuid;
final List<SpaceModel> spaces;
@override
List<Object?> get props => [spaces, communityUuid, parentSpaceUuid];
Map<String, dynamic> toJson() => {
'spacesUuids': spaces.map((space) => space.uuid).toList(),
};
}

View File

@ -0,0 +1,5 @@
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
abstract interface class ReorderSpacesService {
Future<void> reorderSpaces(ReorderSpacesParam param);
}

View File

@ -0,0 +1,35 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/services/reorder_spaces_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'reorder_spaces_event.dart';
part 'reorder_spaces_state.dart';
class ReorderSpacesBloc extends Bloc<ReorderSpacesEvent, ReorderSpacesState> {
ReorderSpacesBloc(
this._reorderSpacesService,
) : super(const ReorderSpacesInitial()) {
on<ReorderSpacesEvent>(_onReorderSpacesEvent);
}
final ReorderSpacesService _reorderSpacesService;
Future<void> _onReorderSpacesEvent(
ReorderSpacesEvent event,
Emitter<ReorderSpacesState> emit,
) async {
emit(const ReorderSpacesLoading());
try {
await _reorderSpacesService.reorderSpaces(event.param);
emit(const ReorderSpacesSuccess());
} on APIException catch (e) {
emit(ReorderSpacesFailure(e.message));
} catch (e) {
emit(ReorderSpacesFailure(e.toString()));
} finally {
emit(const ReorderSpacesInitial());
}
}
}

View File

@ -0,0 +1,10 @@
part of 'reorder_spaces_bloc.dart';
final class ReorderSpacesEvent extends Equatable {
const ReorderSpacesEvent(this.param);
final ReorderSpacesParam param;
@override
List<Object> get props => [param];
}

View File

@ -0,0 +1,29 @@
part of 'reorder_spaces_bloc.dart';
sealed class ReorderSpacesState extends Equatable {
const ReorderSpacesState();
@override
List<Object> get props => [];
}
final class ReorderSpacesInitial extends ReorderSpacesState {
const ReorderSpacesInitial();
}
final class ReorderSpacesLoading extends ReorderSpacesState {
const ReorderSpacesLoading();
}
final class ReorderSpacesSuccess extends ReorderSpacesState {
const ReorderSpacesSuccess();
}
final class ReorderSpacesFailure extends ReorderSpacesState {
const ReorderSpacesFailure(this.errorMessage);
final String errorMessage;
@override
List<Object> get props => [errorMessage];
}

View File

@ -103,7 +103,9 @@ class SpaceDetailsDevicesBox extends StatelessWidget {
).then((resultSpace) {
if (resultSpace != null) {
if (context.mounted) {
context.read<SpaceDetailsModelBloc>().add(UpdateSpaceDetails(resultSpace));
context
.read<SpaceDetailsModelBloc>()
.add(UpdateSpaceDetails(resultSpace));
}
}
});
@ -133,6 +135,9 @@ class SpaceDetailsDevicesBox extends StatelessWidget {
DeviceType.ThreeTouch => Assets.gangSwitch,
DeviceType.NCPS => Assets.sensors,
DeviceType.PC => Assets.powerClamp,
DeviceType.fourSceen => Assets.fourSceenSwitch,
DeviceType.sixSceen => Assets.sixSceenSwitch,
DeviceType.SOS => Assets.sos,
DeviceType.Other => Assets.blackLogo,
null => Assets.blackLogo,
};

View File

@ -19,6 +19,7 @@ class SpaceSubSpacesDialog extends StatefulWidget {
}
class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
late final TextEditingController _subspaceNameController;
late List<Subspace> _subspaces;
bool get _hasDuplicateNames =>
@ -29,6 +30,13 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
void initState() {
super.initState();
_subspaces = List.from(widget.subspaces);
_subspaceNameController = TextEditingController();
}
@override
void dispose() {
_subspaceNameController.dispose();
super.dispose();
}
void _handleSubspaceAdded(String name) {
@ -49,6 +57,10 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
);
void _handleSave() {
final name = _subspaceNameController.text.trim();
if (name.isNotEmpty) {
_handleSubspaceAdded(name);
}
widget.onSave(_subspaces);
Navigator.of(context).pop();
}
@ -65,6 +77,7 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
subSpaces: _subspaces,
onSubspaceAdded: _handleSubspaceAdded,
onSubspaceDeleted: _handleSubspaceDeleted,
controller: _subspaceNameController,
),
AnimatedSwitcher(
duration: const Duration(milliseconds: 100),

View File

@ -10,29 +10,28 @@ class SubSpacesInput extends StatefulWidget {
required this.subSpaces,
required this.onSubspaceAdded,
required this.onSubspaceDeleted,
required this.controller,
});
final List<Subspace> subSpaces;
final void Function(String name) onSubspaceAdded;
final void Function(String uuid) onSubspaceDeleted;
final TextEditingController controller;
@override
State<SubSpacesInput> createState() => _SubSpacesInputState();
}
class _SubSpacesInputState extends State<SubSpacesInput> {
late final TextEditingController _subspaceNameController;
late final FocusNode _focusNode;
@override
void initState() {
super.initState();
_subspaceNameController = TextEditingController();
_focusNode = FocusNode();
}
@override
void dispose() {
_subspaceNameController.dispose();
_focusNode.dispose();
super.dispose();
}
@ -81,7 +80,7 @@ class _SubSpacesInputState extends State<SubSpacesInput> {
width: 200,
child: TextField(
focusNode: _focusNode,
controller: _subspaceNameController,
controller: widget.controller,
decoration: InputDecoration(
border: InputBorder.none,
hintText: widget.subSpaces.isEmpty ? 'Please enter the name' : null,
@ -93,7 +92,7 @@ class _SubSpacesInputState extends State<SubSpacesInput> {
final trimmedValue = value.trim();
if (trimmedValue.isNotEmpty) {
widget.onSubspaceAdded(trimmedValue);
_subspaceNameController.clear();
widget.controller.clear();
_focusNode.requestFocus();
}
},

View File

@ -31,173 +31,178 @@ class AssignTagsTable extends StatelessWidget {
DataColumn _buildDataColumn(BuildContext context, String label) {
return DataColumn(
label: SelectableText(label, style: context.textTheme.bodyMedium),
label: Expanded(
child: FittedBox(
alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown,
child: SelectableText(label, style: context.textTheme.bodyMedium),
),
),
);
}
@override
Widget build(BuildContext context) {
return BlocProvider<TagsBloc>(
create: (BuildContext context) => TagsBloc(
create: (context) => TagsBloc(
RemoteTagsService(HTTPService()),
)..add(const LoadTags()),
child: BlocBuilder<TagsBloc, TagsState>(
builder: (context, state) {
return switch (state) {
TagsLoading() || TagsInitial() => const Center(
child: CircularProgressIndicator(),
),
TagsFailure(:final message) => Center(
child: Text(message),
),
TagsLoaded(:final tags) => ClipRRect(
borderRadius: BorderRadius.circular(20),
child: DataTable(
headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey,
),
key: ValueKey(productAllocations.length),
border: TableBorder.all(
color: ColorsManager.dataHeaderGrey,
width: 1,
borderRadius: BorderRadius.circular(20),
),
columns: [
_buildDataColumn(context, '#'),
_buildDataColumn(context, 'Device'),
_buildDataColumn(context, 'Tag'),
_buildDataColumn(context, 'Location'),
],
rows: productAllocations.isEmpty
? [
DataRow(
cells: [
DataCell(
Center(
child: SelectableText(
'No Devices Available',
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.lightGrayColor,
),
),
),
),
DataCell.empty,
DataCell.empty,
DataCell.empty,
],
),
]
: List.generate(productAllocations.length, (index) {
final productAllocation = productAllocations[index];
final allocationUuid = productAllocation.uuid;
final availableTags = tags
.where(
(tag) =>
!productAllocations
.where((p) =>
p.product.productType ==
productAllocation.product.productType)
.map((p) => p.tag.name.toLowerCase())
.contains(tag.name.toLowerCase()) ||
tag.uuid == productAllocation.tag.uuid,
)
.toList();
final currentLocationUuid =
productLocations[allocationUuid];
final currentLocationName = currentLocationUuid == null
? 'Main Space'
: subspaces
.firstWhere((s) => s.uuid == currentLocationUuid)
.name;
return DataRow(
key: ValueKey(allocationUuid),
cells: [
DataCell(Text((index + 1).toString())),
DataCell(
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
productAllocation.product.name,
overflow: TextOverflow.ellipsis,
)),
const SizedBox(width: 10),
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.lightGrayColor,
width: 1,
),
),
child: IconButton(
icon: const Icon(
Icons.close,
color: ColorsManager.lightGreyColor,
size: 16,
),
onPressed: () {
onProductDeleted(allocationUuid);
},
tooltip: 'Delete Tag',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
),
],
),
),
DataCell(
Container(
alignment: Alignment.centerLeft,
width: double.infinity,
child: ProductTagField(
key: ValueKey('dropdown_$allocationUuid'),
productName: productAllocation.product.uuid,
initialValue: productAllocation.tag,
onSelected: (newTag) {
onTagSelected(allocationUuid, newTag);
},
items: availableTags,
),
),
),
DataCell(
SizedBox(
width: double.infinity,
child: DialogDropdown(
items: [
'Main Space',
...subspaces.map((s) => s.name)
],
selectedValue: currentLocationName,
onSelected: (newLocationName) {
final newSubspaceUuid = newLocationName ==
'Main Space'
? null
: subspaces
.firstWhere(
(s) => s.name == newLocationName)
.uuid;
onLocationSelected(
allocationUuid, newSubspaceUuid);
},
)),
),
],
);
}),
builder: (context, state) => switch (state) {
TagsLoading() || TagsInitial() => const Center(
child: CircularProgressIndicator(),
),
TagsFailure(:final message) => Center(
child: Text(message),
),
TagsLoaded(:final tags) => ClipRRect(
borderRadius: BorderRadius.circular(20),
child: DataTable(
headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey,
),
key: ValueKey(productAllocations.length),
border: TableBorder.all(
color: ColorsManager.dataHeaderGrey,
width: 1,
borderRadius: BorderRadius.circular(20),
),
columns: [
_buildDataColumn(context, '#'),
_buildDataColumn(context, 'Device'),
_buildDataColumn(context, 'Tag'),
_buildDataColumn(context, 'Location'),
],
rows: productAllocations.isEmpty
? [
DataRow(
cells: [
DataCell(
FittedBox(
alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown,
child: SelectableText(
'No Devices Available',
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.lightGrayColor,
),
),
),
),
DataCell.empty,
DataCell.empty,
DataCell.empty,
],
),
]
: List.generate(productAllocations.length, (index) {
final productAllocation = productAllocations[index];
final allocationUuid = productAllocation.uuid;
final availableTags = tags
.where(
(tag) =>
!productAllocations
.where((p) =>
p.product.productType ==
productAllocation.product.productType)
.map((p) => p.tag.name.toLowerCase())
.contains(tag.name.toLowerCase()) ||
tag.uuid == productAllocation.tag.uuid,
)
.toList();
final currentLocationUuid = productLocations[allocationUuid];
final currentLocationName = currentLocationUuid == null
? 'Main Space'
: subspaces
.firstWhere((s) => s.uuid == currentLocationUuid)
.name;
return DataRow(
key: ValueKey(allocationUuid),
cells: [
DataCell(Text((index + 1).toString())),
DataCell(
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
productAllocation.product.name,
overflow: TextOverflow.ellipsis,
)),
const SizedBox(width: 10),
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.lightGrayColor,
width: 1,
),
),
child: IconButton(
icon: const Icon(
Icons.close,
color: ColorsManager.lightGreyColor,
size: 16,
),
onPressed: () {
onProductDeleted(allocationUuid);
},
tooltip: 'Delete Tag',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
),
],
),
),
DataCell(
Container(
alignment: Alignment.centerLeft,
width: double.infinity,
child: ProductTagField(
key: ValueKey('dropdown_$allocationUuid'),
productName: productAllocation.product.uuid,
initialValue: productAllocation.tag,
onSelected: (newTag) {
onTagSelected(allocationUuid, newTag);
},
items: availableTags,
),
),
),
DataCell(
SizedBox(
width: double.infinity,
child: DialogDropdown(
items: [
'Main Space',
...subspaces.map((s) => s.name)
],
selectedValue: currentLocationName,
onSelected: (newLocationName) {
final newSubspaceUuid = newLocationName ==
'Main Space'
? null
: subspaces
.firstWhere(
(s) => s.name == newLocationName)
.uuid;
onLocationSelected(
allocationUuid, newSubspaceUuid);
},
)),
),
],
);
}),
),
_ => const SizedBox.shrink(),
};
),
_ => const SizedBox.shrink(),
},
),
);

View File

@ -84,6 +84,14 @@ class DeviceModel {
tempIcon = Assets.curtainIcon;
} else if (type == DeviceType.Curtain) {
tempIcon = Assets.curtainIcon;
} else if (type == DeviceType.fourSceen) {
tempIcon = Assets.fourSceenSwitch;
} else if (type == DeviceType.sixSceen) {
tempIcon = Assets.sixSceenSwitch;
} else if (type == DeviceType.SOS) {
tempIcon = Assets.sos;
} else if (type == DeviceType.NCPS) {
tempIcon = Assets.presenceSensor;
} else {
tempIcon = Assets.blackLogo;
}

View File

@ -85,6 +85,8 @@ abstract class ColorsManager {
static const Color minBlue = Color(0xFF93AAFD);
static const Color minBlueDot = Color(0xFF023DFE);
static const Color grey25 = Color(0xFFF9F9F9);
static const Color dropDownSelectBlue = Color(0xFF2196F3);
static const Color drpoDownSelectBlue = Color(0xFF2196F3);
static const Color grey50 = Color(0xFF718096);
static const Color red100 = Color(0xFFFE0202);
static const Color grey800 = Color(0xffF8F8F8);
@ -93,4 +95,5 @@ abstract class ColorsManager {
static const Color shadowOfDetailsContainer = Color(0x40000000);
static const Color checkBoxBorderGray = Color(0xffD0D0D0);
static const Color timePickerColor = Color(0xff000000);
}

View File

@ -41,6 +41,8 @@ abstract class ApiEndpoints {
'/projects/{projectId}/communities/{communityId}/spaces/{spaceId}';
static const String getSpaceHierarchy =
'/projects/{projectId}/communities/{communityId}/spaces';
static const String reorderSpaces =
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{parentSpaceUuid}/spaces/order';
// Community Module
static const String createCommunity = '/projects/{projectId}/communities';
@ -139,7 +141,7 @@ abstract class ApiEndpoints {
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces/{subSpaceUuid}/devices/{deviceUuid}';
static const String saveSchedule = '/schedule/{deviceUuid}';
static const String duplicateSpace = '/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/duplicate';
////booking System
static const String bookableSpaces = '/bookable-spaces';

View File

@ -4,6 +4,8 @@ class Assets {
static const String webBackground = 'assets/images/web_Background.svg';
static const String webBackgroundPng = 'assets/images/web_Background.png';
static const String blackLogo = 'assets/images/black-logo.png';
static const String fourSceenSwitch = 'assets/images/4_sceen_switch.svg';
static const String sixSceenSwitch = 'assets/images/6_sceen_switch.svg';
static const String logo = 'assets/images/Logo.svg';
static const String logoHorizontal = 'assets/images/logo_horizontal.png';
static const String vector = 'assets/images/Vector.png';

View File

@ -21,6 +21,9 @@ enum DeviceType {
NCPS,
DoorSensor,
PC,
fourSceen,
sixSceen,
SOS,
Other,
}
/*
@ -63,4 +66,7 @@ Map<String, DeviceType> devicesTypesMap = {
'WL': DeviceType.WaterLeak,
'NCPS': DeviceType.NCPS,
'PC': DeviceType.PC,
'4S': DeviceType.fourSceen,
'6S': DeviceType.sixSceen,
'SOS': DeviceType.SOS,
};

View File

@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
extension AppSnackBarsBuildContextExtension on BuildContext {
void showSuccessSnackbar(String message) {
ScaffoldMessenger.of(this).showSnackBar(
_makeSnackbar(
message: message,
icon: Icons.check_circle,
backgroundColor: Colors.green,
),
);
}
void showFailureSnackbar(String message) {
ScaffoldMessenger.of(this).showSnackBar(
_makeSnackbar(
message: message,
icon: Icons.error,
backgroundColor: Colors.red,
),
);
}
SnackBar _makeSnackbar({
required String message,
required Color backgroundColor,
required IconData icon,
}) {
return SnackBar(
content: Row(
mainAxisSize: MainAxisSize.min,
spacing: 8,
children: [
Icon(icon, color: Colors.white),
Text(
message,
style: textTheme.bodyMedium?.copyWith(
color: ColorsManager.whiteColors,
),
),
],
),
backgroundColor: backgroundColor,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
margin: const EdgeInsetsDirectional.symmetric(
horizontal: 92,
vertical: 32,
),
);
}
}