Compare commits

...

51 Commits

Author SHA1 Message Date
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
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
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
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
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
6a7174b929 no need for selectedbookableSpaces list anymore instead i am using state list 2025-07-22 16:47:50 +03:00
59058cf2d2 enhance week navigation layout for improved UI 2025-07-22 14:40:50 +03:00
b6d4084ca7 fix settings and back button to meet the UI 2025-07-22 10:58:30 +03:00
2b110b7c91 fix pagination UI 2025-07-22 10:50:00 +03:00
066fe4bc95 make variable final 2025-07-22 10:35:06 +03:00
85d65b2d96 Merge branch 'Implement-Spaces-Table-Empty-Filled-Failure-states-bookable-spaces' of https://github.com/SyncrowIOT/web into Implement-Spaces-Table-Empty-Filled-Failure-states-bookable-spaces 2025-07-22 10:12:14 +03:00
a64a9f1d12 fix unactive color 2025-07-22 10:12:02 +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
a7487f5434 Merge branch 'dev' into Implement-Spaces-Table-Empty-Filled-Failure-states-bookable-spaces 2025-07-22 09:58:14 +03:00
c0b74162e9 design fixes 2025-07-22 09:56:32 +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
0eb4652f26 Enhance garage door scheduling functionality and UI improvements (#358)
<!--
  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 garage door scheduling functionality and UI improvements

## 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-21 16:25:53 +03:00
460639b681 Refactor booking system: update API endpoint format, add ResetEvents event, and enhance UI components for improved user experience (#361)
<!--
  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 system: update API endpoint format, add ResetEvents
event, and enhance UI components for improved user experience

## 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-21 16:25:34 +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
2f89c3486c Release incomplete revamped space management (#360)
<!--
  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 User Story
[SP-1686](https://syncrow.atlassian.net/browse/SP-1686)

## Description
Released the new space management rewritten version, which for now
includes, CRUD operations for spaces, and community tree with the new
API.

Rewrote main files, since they had a lot of code duplication, i
extracted duplicated code into one component for all flavors to use.

Enhanced routing solution, to make sure there's only one instance of the
router, as the documentation of `go_router` guides us to do.

## 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)
- [x] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1686]:
https://syncrow.atlassian.net/browse/SP-1686?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-21 15:33:06 +03:00
5589e5b587 Refactor booking system: update API endpoint format, add ResetEvents event, and enhance UI components for improved user experience 2025-07-21 15:22:17 +03:00
d3bd363b70 Fix category check in schedule dialog for water heater 2025-07-21 15:10:15 +03:00
3a4fce966c Merge branch 'dev' of https://github.com/SyncrowIOT/web into release_incomplete_revamped_space_management 2025-07-21 15:06:40 +03:00
6bdd28ec57 Refactor main entry points to utilize SyncrowApp, removing legacy MyApp implementation and associated dependencies, since there was too much duplicated code. 2025-07-21 14:50:29 +03:00
652163fdae Updated Space management route in app_routes.dart to use the new, incomplete, revamped space management. 2025-07-21 14:47:42 +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
076c80fe44 Enhance garage door scheduling functionality and UI improvements 2025-07-17 17:02:23 +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
69 changed files with 1425 additions and 1058 deletions

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.89852 11.08C-0.632759 8.54864 -0.632708 4.42983 1.89852 1.8985C4.42985 -0.632833 8.54861 -0.632833 11.0799 1.8985C13.2274 4.04599 13.5526 7.23986 12.0565 9.73392C12.0565 9.73392 11.949 9.91422 12.0942 10.0593C12.9222 10.8872 15.4065 13.3716 15.4065 13.3716C16.0658 14.0308 16.2227 14.9527 15.638 15.5374L15.5374 15.638C14.9527 16.2227 14.0308 16.0658 13.3716 15.4065C13.3716 15.4065 10.8926 12.9275 10.0662 12.1012C9.91414 11.9491 9.73389 12.0566 9.73389 12.0566C7.23988 13.5526 4.04601 13.2275 1.89852 11.08ZM9.88131 9.88133C11.7517 8.01094 11.7516 4.96763 9.88125 3.09724C8.01086 1.22689 4.96755 1.22684 3.09721 3.09724C1.22681 4.96758 1.22681 8.01094 3.09721 9.88133C4.9676 11.7516 8.01086 11.7516 9.88131 9.88133Z" fill="#999999" fill-opacity="0.7"/>
<path d="M9.46701 6.10386C9.55406 6.10386 9.64256 6.08674 9.72786 6.05072C10.0686 5.9065 10.228 5.51333 10.0838 5.17253C9.17738 3.03045 6.69729 2.0252 4.55526 2.93164C4.21451 3.07586 4.05509 3.46903 4.1993 3.80983C4.34357 4.15063 4.73664 4.30995 5.07755 4.16579C6.539 3.54737 8.23126 4.23326 8.84962 5.69471C8.95781 5.95031 9.20594 6.10386 9.46701 6.10386Z" fill="#999999" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

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,22 +1,9 @@
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/firebase_options.dart'; import 'package:syncrow_web/firebase_options.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.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/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart'; import 'package:syncrow_web/syncrow_app.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 { Future<void> main() async {
try { try {
@ -33,59 +20,5 @@ Future<void> main() async {
); );
initialSetup(); initialSetup();
} catch (_) {} } catch (_) {}
runApp(MyApp()); runApp(const SyncrowApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final GoRouter _router = GoRouter(
initialLocation: RoutesConst.auth,
routes: AppRoutes.getRoutes(),
redirect: (context, state) async {
final checkToken = await AuthBloc.getTokenAndValidate();
final loggedIn = checkToken == 'Success';
final goingToLogin = state.uri.toString() == RoutesConst.auth;
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
if (loggedIn && goingToLogin) return RoutesConst.home;
return null;
},
);
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(),
),
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(),
),
BlocProvider<RoutineBloc>(
create: (context) => RoutineBloc(),
),
BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc(),
),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
},
),
key: NavigationService.navigatorKey,
// scaffoldMessengerKey: NavigationService.snackbarKey,
theme: myTheme,
routerConfig: _router,
));
}
} }

View File

@ -1,22 +1,9 @@
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/firebase_options.dart'; import 'package:syncrow_web/firebase_options.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.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/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart'; import 'package:syncrow_web/syncrow_app.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 { Future<void> main() async {
try { try {
@ -33,59 +20,5 @@ Future<void> main() async {
); );
initialSetup(); initialSetup();
} catch (_) {} } catch (_) {}
runApp(MyApp()); runApp(const SyncrowApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final GoRouter _router = GoRouter(
initialLocation: RoutesConst.auth,
routes: AppRoutes.getRoutes(),
redirect: (context, state) async {
final checkToken = await AuthBloc.getTokenAndValidate();
final loggedIn = checkToken == 'Success';
final goingToLogin = state.uri.toString() == RoutesConst.auth;
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
if (loggedIn && goingToLogin) return RoutesConst.home;
return null;
},
);
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(),
),
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(),
),
BlocProvider<RoutineBloc>(
create: (context) => RoutineBloc(),
),
BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc(),
),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
},
),
key: NavigationService.navigatorKey,
// scaffoldMessengerKey: NavigationService.snackbarKey,
theme: myTheme,
routerConfig: _router,
));
}
} }

View File

@ -1,26 +1,16 @@
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/firebase_options.dart'; import 'package:syncrow_web/firebase_options.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.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/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart'; import 'package:syncrow_web/syncrow_app.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 { Future<void> main() async {
try { try {
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'staging'); const environment = String.fromEnvironment(
'FLAVOR',
defaultValue: 'staging',
);
await dotenv.load(fileName: '.env.$environment'); await dotenv.load(fileName: '.env.$environment');
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp( await Firebase.initializeApp(
@ -30,59 +20,5 @@ Future<void> main() async {
); );
initialSetup(); initialSetup();
} catch (_) {} } catch (_) {}
runApp(MyApp()); runApp(const SyncrowApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final GoRouter _router = GoRouter(
initialLocation: RoutesConst.auth,
routes: AppRoutes.getRoutes(),
redirect: (context, state) async {
final checkToken = await AuthBloc.getTokenAndValidate();
final loggedIn = checkToken == 'Success';
final goingToLogin = state.uri.toString() == RoutesConst.auth;
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
if (loggedIn && goingToLogin) return RoutesConst.home;
return null;
},
);
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(),
),
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(),
),
BlocProvider<RoutineBloc>(
create: (context) => RoutineBloc(),
),
BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc(),
),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
},
),
key: NavigationService.navigatorKey,
// scaffoldMessengerKey: NavigationService.snackbarKey,
theme: myTheme,
routerConfig: _router,
));
}
} }

View File

@ -10,7 +10,7 @@ part 'events_event.dart';
part 'events_state.dart'; part 'events_state.dart';
class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> { class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
final EventController eventController = EventController(); EventController eventController = EventController();
final CalendarSystemService calendarService; final CalendarSystemService calendarService;
CalendarEventsBloc({ CalendarEventsBloc({
@ -20,7 +20,9 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
on<AddEvent>(_onAddEvent); on<AddEvent>(_onAddEvent);
on<DisposeResources>(_onDisposeResources); on<DisposeResources>(_onDisposeResources);
on<GoToWeek>(_onGoToWeek); on<GoToWeek>(_onGoToWeek);
on<ResetEvents>(_onResetEvents);
} }
Future<void> _onLoadEvents( Future<void> _onLoadEvents(
LoadEvents event, LoadEvents event,
Emitter<CalendarEventState> emit, Emitter<CalendarEventState> emit,
@ -126,4 +128,18 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
eventController.dispose(); eventController.dispose();
return super.close(); return super.close();
} }
void _onResetEvents(
ResetEvents event,
Emitter<CalendarEventState> emit,
) {
if (calendarService is MemoryCalendarServiceWithRemoteFallback) {
(calendarService as MemoryCalendarServiceWithRemoteFallback)
.memoryService
.clear();
}
eventController.dispose();
eventController = EventController();
emit(EventsInitial());
}
} }

View File

@ -29,3 +29,10 @@ class CheckWeekHasEvents extends CalendarEventsEvent {
final DateTime weekStart; final DateTime weekStart;
const CheckWeekHasEvents(this.weekStart); const CheckWeekHasEvents(this.weekStart);
} }
class ResetEvents extends CalendarEventsEvent {
const ResetEvents();
@override
List<Object?> get props => [];
}

View File

@ -19,14 +19,44 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
class BookingPage extends StatefulWidget { class BookingPage extends StatefulWidget {
const BookingPage({super.key}); final PageController? pageController;
const BookingPage({super.key, this.pageController});
@override @override
State<BookingPage> createState() => _BookingPageState(); State<BookingPage> createState() => _BookingPageState();
} }
class _BookingPageState extends State<BookingPage> { class _BookingPageState extends State<BookingPage> {
late final EventController _eventController; @override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => SelectedBookableSpaceBloc()),
BlocProvider(create: (_) => DateSelectionBloc()),
BlocProvider(
create: (_) => CalendarEventsBloc(
calendarService: MemoryCalendarServiceWithRemoteFallback(
remoteService: RemoteCalendarService(HTTPService()),
memoryService: MemoryCalendarService(),
),
),
),
],
child: _BookingPageContent(widget.pageController),
);
}
}
class _BookingPageContent extends StatefulWidget {
final PageController? pageController;
const _BookingPageContent(this.pageController);
@override
State<_BookingPageContent> createState() => _BookingPageContentState();
}
class _BookingPageContentState extends State<_BookingPageContent> {
late EventController _eventController;
@override @override
void initState() { void initState() {
@ -40,7 +70,7 @@ class _BookingPageState extends State<BookingPage> {
super.dispose(); super.dispose();
} }
void _dispatchLoadEvents(BuildContext context) { void _loadEvents(BuildContext context) {
final selectedRoom = final selectedRoom =
context.read<SelectedBookableSpaceBloc>().state.selectedBookableSpace; context.read<SelectedBookableSpaceBloc>().state.selectedBookableSpace;
final dateState = context.read<DateSelectionBloc>().state; final dateState = context.read<DateSelectionBloc>().state;
@ -60,186 +90,170 @@ class _BookingPageState extends State<BookingPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return BlocListener<SelectedBookableSpaceBloc, SelectedBookableSpaceState>(
providers: [ listener: (context, state) {
BlocProvider(create: (_) => SelectedBookableSpaceBloc()), if (state.selectedBookableSpace != null) {
BlocProvider(create: (_) => DateSelectionBloc()), context.read<CalendarEventsBloc>().add(const ResetEvents());
BlocProvider( _loadEvents(context);
create: (_) => CalendarEventsBloc( }
calendarService: MemoryCalendarServiceWithRemoteFallback( },
remoteService: RemoteCalendarService( child: BlocListener<DateSelectionBloc, DateSelectionState>(
HTTPService(), listener: (context, state) {
), _loadEvents(context);
memoryService: MemoryCalendarService(), },
), child: BlocListener<CalendarEventsBloc, CalendarEventState>(
)),
],
child: Builder(
builder: (context) =>
BlocListener<CalendarEventsBloc, CalendarEventState>(
listenWhen: (prev, curr) => curr is EventsLoaded,
listener: (context, state) { listener: (context, state) {
if (state is EventsLoaded) { if (state is EventsLoaded) {
_eventController.removeWhere((_) => true); _eventController.removeWhere((_) => true);
_eventController.addAll(state.events); _eventController.addAll(state.events);
} }
}, },
child: BlocListener<SelectedBookableSpaceBloc, child: Row(
SelectedBookableSpaceState>( crossAxisAlignment: CrossAxisAlignment.start,
listener: (context, state) => _dispatchLoadEvents(context), children: [
child: BlocListener<DateSelectionBloc, DateSelectionState>( Expanded(
listener: (context, state) => _dispatchLoadEvents(context), child: Container(
child: Row( decoration: BoxDecoration(
crossAxisAlignment: CrossAxisAlignment.start, color: ColorsManager.whiteColors,
children: [ boxShadow: [
Expanded( BoxShadow(
child: Container( color: ColorsManager.blackColor.withOpacity(0.1),
decoration: BoxDecoration( offset: const Offset(3, 0),
color: ColorsManager.whiteColors, blurRadius: 6,
boxShadow: [ spreadRadius: 0,
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.1),
offset: const Offset(3, 0),
blurRadius: 6,
spreadRadius: 0,
),
],
), ),
child: Column( ],
children: [
Expanded(
flex: 2,
child: BlocBuilder<SelectedBookableSpaceBloc,
SelectedBookableSpaceState>(
builder: (context, state) {
return BookingSidebar(
onRoomSelected: (selectedRoom) {
context
.read<SelectedBookableSpaceBloc>()
.add(SelectBookableSpace(selectedRoom));
},
);
},
),
),
Expanded(
child: BlocBuilder<DateSelectionBloc,
DateSelectionState>(
builder: (context, dateState) {
return CustomCalendarPage(
selectedDate: dateState.selectedDate,
onDateChanged: (day, month, year) {
final newDate = DateTime(year, month, day);
context
.read<DateSelectionBloc>()
.add(SelectDate(newDate));
context.read<DateSelectionBloc>().add(
SelectDateFromSidebarCalendar(newDate));
},
);
},
),
),
],
),
),
), ),
Expanded( child: Column(
flex: 5, children: [
child: Padding( Expanded(
padding: const EdgeInsets.all(20.0), flex: 2,
child: Column( child: BlocBuilder<SelectedBookableSpaceBloc,
crossAxisAlignment: CrossAxisAlignment.start, SelectedBookableSpaceState>(
builder: (context, state) {
return BookingSidebar(
onRoomSelected: (selectedRoom) {
context
.read<SelectedBookableSpaceBloc>()
.add(SelectBookableSpace(selectedRoom));
},
);
},
),
),
Expanded(
child:
BlocBuilder<DateSelectionBloc, DateSelectionState>(
builder: (context, dateState) {
return CustomCalendarPage(
selectedDate: dateState.selectedDate,
onDateChanged: (day, month, year) {
final newDate = DateTime(year, month, day);
context
.read<DateSelectionBloc>()
.add(SelectDate(newDate));
context.read<DateSelectionBloc>().add(
SelectDateFromSidebarCalendar(newDate));
},
);
},
),
),
],
),
),
),
Expanded(
flex: 5,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( SvgTextButton(
children: [ svgAsset: Assets.homeIcon,
SvgTextButton( label: 'Manage Bookable Spaces',
svgAsset: Assets.homeIcon, onPressed: () {
label: 'Manage Bookable Spaces', widget.pageController!.jumpToPage(2);
onPressed: () {},
),
const SizedBox(width: 20),
SvgTextButton(
svgAsset: Assets.groupIcon,
label: 'Manage Users',
onPressed: () {},
),
],
),
BlocBuilder<DateSelectionBloc,
DateSelectionState>(
builder: (context, state) {
final weekStart = state.weekStart;
final weekEnd =
weekStart.add(const Duration(days: 6));
return WeekNavigation(
weekStart: weekStart,
weekEnd: weekEnd,
onPreviousWeek: () {
context
.read<DateSelectionBloc>()
.add(PreviousWeek());
},
onNextWeek: () {
context
.read<DateSelectionBloc>()
.add(NextWeek());
},
);
}, },
), ),
const SizedBox(width: 20),
SvgTextButton(
svgAsset: Assets.groupIcon,
label: 'Manage Users',
onPressed: () {},
),
], ],
), ),
Expanded( BlocBuilder<DateSelectionBloc, DateSelectionState>(
flex: 5, builder: (context, state) {
child: BlocBuilder<SelectedBookableSpaceBloc, final weekStart = state.weekStart;
SelectedBookableSpaceState>( final weekEnd =
builder: (context, roomState) { weekStart.add(const Duration(days: 6));
final selectedRoom = return WeekNavigation(
roomState.selectedBookableSpace; weekStart: weekStart,
return BlocBuilder<DateSelectionBloc, weekEnd: weekEnd,
DateSelectionState>( onPreviousWeek: () {
builder: (context, dateState) { context
return BlocListener<CalendarEventsBloc, .read<DateSelectionBloc>()
CalendarEventState>( .add(PreviousWeek());
listenWhen: (prev, curr) => },
curr is EventsLoaded, onNextWeek: () {
listener: (context, state) { context
if (state is EventsLoaded) { .read<DateSelectionBloc>()
_eventController .add(NextWeek());
.removeWhere((_) => true); },
_eventController.addAll(state.events); );
} },
}, ),
child: WeeklyCalendarPage( ],
startTime: selectedRoom ),
?.bookableConfig.startTime, const SizedBox(height: 20),
endTime: selectedRoom Expanded(
?.bookableConfig.endTime, flex: 5,
weekStart: dateState.weekStart, child: BlocBuilder<SelectedBookableSpaceBloc,
selectedDate: dateState.selectedDate, SelectedBookableSpaceState>(
eventController: _eventController, builder: (context, roomState) {
selectedDateFromSideBarCalender: context final selectedRoom =
.watch<DateSelectionBloc>() roomState.selectedBookableSpace;
.state return BlocBuilder<DateSelectionBloc,
.selectedDateFromSideBarCalender, DateSelectionState>(
), builder: (context, dateState) {
return BlocBuilder<CalendarEventsBloc,
CalendarEventState>(
builder: (context, eventState) {
return WeeklyCalendarPage(
key: ValueKey(
selectedRoom?.uuid ?? 'no-room'),
startTime: selectedRoom
?.bookableConfig.startTime,
endTime:
selectedRoom?.bookableConfig.endTime,
weekStart: dateState.weekStart,
selectedDate: dateState.selectedDate,
eventController: _eventController,
selectedDateFromSideBarCalender: context
.watch<DateSelectionBloc>()
.state
.selectedDateFromSideBarCalender,
); );
}, },
); );
}, },
), );
), },
], ),
), ),
), ],
), ),
], ),
), ),
), ],
), ),
), ),
), ),

View File

@ -76,21 +76,29 @@ class __SidebarContentState extends State<_SidebarContent> {
builder: (context, state) { builder: (context, state) {
return Column( return Column(
children: [ children: [
const _SidebarHeader(title: 'Spaces'), Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
child: Container(
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
boxShadow: [
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.1),
offset: const Offset(0, 4),
blurRadius: 4,
spreadRadius: 0,
),
],
),
child: _SidebarHeader(title: 'Spaces')),
),
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(8.0),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.1), color: ColorsManager.blackColor.withOpacity(0.1),
offset: const Offset(0, -2), offset: const Offset(0, 4),
blurRadius: 4,
spreadRadius: 0,
),
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.1),
offset: const Offset(0, 2),
blurRadius: 4, blurRadius: 4,
spreadRadius: 0, spreadRadius: 0,
), ),
@ -220,7 +228,7 @@ class _SidebarHeader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(10.0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [

View File

@ -66,18 +66,28 @@ class _CustomCalendarPageState extends State<CustomCalendarPage> {
weekdayLabels: const ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'], weekdayLabels: const ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
); );
return CalendarDatePicker2( return Container(
config: config, decoration: const BoxDecoration(
value: [_selectedDate], border: Border(
onValueChanged: (dates) { top: BorderSide(
final picked = dates.first; color: ColorsManager.textGray,
if (picked != null) { width: 1.0,
setState(() { ),
_selectedDate = picked; ),
}); ),
widget.onDateChanged(picked.day, picked.month, picked.year); child: CalendarDatePicker2(
} config: config,
}, value: [_selectedDate],
onValueChanged: (dates) {
final picked = dates.first;
if (picked != null) {
setState(() {
_selectedDate = picked;
});
widget.onDateChanged(picked.day, picked.month, picked.year);
}
},
),
); );
} }
} }

View File

@ -32,15 +32,17 @@ class EventTileWidget extends StatelessWidget {
return Expanded( return Expanded(
child: Container( child: Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.all(5), padding: const EdgeInsets.all(5),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isEventEnded color: isEventEnded
? ColorsManager.grayColor.withOpacity(0.1) ? ColorsManager.grayColor.withOpacity(0.1)
: ColorsManager.blue1.withOpacity(0.1), : ColorsManager.blue1.withOpacity(0.1),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
border: const Border( border: Border(
left: BorderSide( left: BorderSide(
color: ColorsManager.grayColor, color: isEventEnded
? ColorsManager.grayColor
: ColorsManager.secondaryColor,
width: 4, width: 4,
), ),
), ),

View File

@ -21,7 +21,7 @@ class RoomListItem extends StatelessWidget {
groupValue: isSelected ? room.uuid : null, groupValue: isSelected ? room.uuid : null,
visualDensity: const VisualDensity(vertical: -4), visualDensity: const VisualDensity(vertical: -4),
onChanged: (value) => onTap(), onChanged: (value) => onTap(),
activeColor: ColorsManager.primaryColor, activeColor: ColorsManager.secondaryColor,
title: Text( title: Text(
room.spaceName, room.spaceName,
maxLines: 2, maxLines: 2,

View File

@ -14,26 +14,33 @@ class WeekDayHeader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return ColoredBox(
children: [ color: isSelectedDay
Text( ? ColorsManager.secondaryColor.withOpacity(0.1)
DateFormat('EEE').format(date).toUpperCase(), : Colors.transparent,
style: TextStyle( child: Column(
fontWeight: FontWeight.w400, children: [
fontSize: 14, const SizedBox(
color: isSelectedDay ? Colors.blue : Colors.black, height: 10,
), ),
), Text(
Text( DateFormat('EEE').format(date).toUpperCase(),
DateFormat('d').format(date), style: const TextStyle(
style: TextStyle( fontWeight: FontWeight.w400,
fontWeight: FontWeight.w700, fontSize: 14,
fontSize: 20, color: ColorsManager.blackColor,
color: ),
isSelectedDay ? ColorsManager.blue1 : ColorsManager.blackColor,
), ),
), Text(
], DateFormat('d').format(date),
style: const TextStyle(
fontWeight: FontWeight.w700,
fontSize: 30,
color: ColorsManager.blackColor,
),
),
],
),
); );
} }
} }

View File

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

View File

@ -57,13 +57,10 @@ class WeeklyCalendarPage extends StatelessWidget {
); );
} }
const double timeLineWidth = 90;
const double timeLineWidth = 65;
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
bool isInRange(DateTime date, DateTime start, DateTime end) { bool isInRange(DateTime date, DateTime start, DateTime end) {
!date.isBefore(start) && !date.isAfter(end); !date.isBefore(start) && !date.isAfter(end);
// remove this line and Check if the date is within the range // remove this line and Check if the date is within the range
@ -100,7 +97,6 @@ class WeeklyCalendarPage extends StatelessWidget {
width: width, width: width,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.13), color: Colors.orange.withOpacity(0.13),
borderRadius: BorderRadius.circular(8),
), ),
); );
} else if (isSelected) { } else if (isSelected) {
@ -110,7 +106,6 @@ class WeeklyCalendarPage extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: color:
ColorsManager.spaceColor.withOpacity(0.07), ColorsManager.spaceColor.withOpacity(0.07),
borderRadius: BorderRadius.circular(8),
), ),
); );
} }
@ -143,7 +138,7 @@ class WeeklyCalendarPage extends StatelessWidget {
heightPerMinute: 1.7, heightPerMinute: 1.7,
showLiveTimeLineInAllDays: false, showLiveTimeLineInAllDays: false,
showVerticalLines: true, showVerticalLines: true,
emulateVerticalOffsetBy: -80, emulateVerticalOffsetBy: -95,
startDay: WeekDays.monday, startDay: WeekDays.monday,
liveTimeIndicatorSettings: liveTimeIndicatorSettings:
const LiveTimeIndicatorSettings( const LiveTimeIndicatorSettings(
@ -161,7 +156,7 @@ class WeeklyCalendarPage extends StatelessWidget {
}, },
timeLineWidth: timeLineWidth, timeLineWidth: timeLineWidth,
weekPageHeaderBuilder: (start, end) => Container(), weekPageHeaderBuilder: (start, end) => Container(),
weekTitleHeight: 60, weekTitleHeight: 90,
weekNumberBuilder: (firstDayOfWeek) => Padding( weekNumberBuilder: (firstDayOfWeek) => Padding(
padding: const EdgeInsets.only(right: 15, bottom: 10), padding: const EdgeInsets.only(right: 15, bottom: 10),
child: Column( child: Column(
@ -208,8 +203,6 @@ class WeeklyCalendarPage extends StatelessWidget {
}, },
); );
} }
} }
bool isSameDay(DateTime d1, DateTime d2) { bool isSameDay(DateTime d1, DateTime d2) {

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

@ -1,6 +1,6 @@
class NonBookableSpacesParams { class NonBookableSpacesParams {
final int currentPage; final int currentPage;
String? searchedWords; final String? searchedWords;
NonBookableSpacesParams({ NonBookableSpacesParams({
required this.currentPage, required this.currentPage,
this.searchedWords, this.searchedWords,

View File

@ -10,51 +10,123 @@ part 'setup_bookable_spaces_state.dart';
class SetupBookableSpacesBloc class SetupBookableSpacesBloc
extends Bloc<SetupBookableSpacesEvent, SetupBookableSpacesState> { extends Bloc<SetupBookableSpacesEvent, SetupBookableSpacesState> {
NonBookableSpacesService nonBookableSpacesService; NonBookableSpacesService nonBookableSpacesService;
List<BookableSpacemodel> selectedBookableSpaces = [];
SetupBookableSpacesBloc(this.nonBookableSpacesService) SetupBookableSpacesBloc(this.nonBookableSpacesService)
: super(SetupBookableSpacesInitial()) { : super(const SetupBookableSpacesInitial(bookableSpaces: [])) {
on<AddToBookableSpaceEvent>(_onAddToBookableSpaceEvent); on<AddToBookableSpaceEvent>(_onAddToBookableSpaceEvent);
on<RemoveFromBookableSpaceEvent>(_onRemoveFromBookableSpaceEvent); on<RemoveFromBookableSpaceEvent>(_onRemoveFromBookableSpaceEvent);
on<AddBookableDaysEvent>(_onAddBookableDays);
on<ChangeStartTimeEvent>(_onChangeStartTimeEvent);
on<ChangeEndTimeEvent>(_onChangeEndTimeEvent);
on<ChangeCostEvent>(_onChangeCostEvent);
on<CheckConfigurValidityEvent>(_onCheckConfigurValidityEvent); on<CheckConfigurValidityEvent>(_onCheckConfigurValidityEvent);
on<EditModeSelected>(_onEditModeSelected); on<EditModeSelected>(_onEditModeSelected);
} }
TimeOfDay? get endTime => List<BookableSpacemodel> get currentBookableSpaces {
selectedBookableSpaces.first.spaceConfig!.bookingEndTime; return switch (state) {
AddNonBookableSpaceIntoBookableState(:final bookableSpaces) =>
TimeOfDay? get startTime => bookableSpaces,
selectedBookableSpaces.first.spaceConfig!.bookingStartTime; RemoveBookableSpaceIntoNonBookableState(:final bookableSpaces) =>
bookableSpaces,
SetupBookableSpacesInitial(:final bookableSpaces) => bookableSpaces,
InProgressState(:final bookableSpaces) => bookableSpaces,
ValidSaveButtonState(:final bookableSpaces) => bookableSpaces,
UnValidSaveButtonState(:final bookableSpaces) => bookableSpaces,
};
}
void _onAddToBookableSpaceEvent( void _onAddToBookableSpaceEvent(
AddToBookableSpaceEvent event, AddToBookableSpaceEvent event,
Emitter<SetupBookableSpacesState> emit, Emitter<SetupBookableSpacesState> emit,
) { ) {
emit(InProgressState()); emit(InProgressState(bookableSpaces: state.bookableSpaces));
selectedBookableSpaces.add(event.nonBookableSpace); final updatedSpaces = List<BookableSpacemodel>.from(state.bookableSpaces);
emit(AddNonBookableSpaceIntoBookableState(
bookableSpaces: selectedBookableSpaces)); updatedSpaces.add(event.nonBookableSpace);
emit(AddNonBookableSpaceIntoBookableState(bookableSpaces: updatedSpaces));
} }
void _onRemoveFromBookableSpaceEvent(RemoveFromBookableSpaceEvent event, void _onRemoveFromBookableSpaceEvent(RemoveFromBookableSpaceEvent event,
Emitter<SetupBookableSpacesState> emit) { Emitter<SetupBookableSpacesState> emit) {
emit(InProgressState()); emit(InProgressState(bookableSpaces: state.bookableSpaces));
selectedBookableSpaces.remove(event.bookableSpace); state.bookableSpaces.remove(event.bookableSpace);
emit(RemoveBookableSpaceIntoNonBookableState( emit(RemoveBookableSpaceIntoNonBookableState(
bookableSpaces: selectedBookableSpaces)); bookableSpaces: state.bookableSpaces));
}
void _onAddBookableDays(
AddBookableDaysEvent event, Emitter<SetupBookableSpacesState> emit) {
final updatedSpaces = state.bookableSpaces.map((space) {
final updatedConfig = space.spaceConfig?.copyWith(
bookableDays: event.bookableDays,
);
return space.copyWith(spaceConfig: updatedConfig);
}).toList();
emit(SetupBookableSpacesInitial(bookableSpaces: updatedSpaces));
}
void _onChangeStartTimeEvent(
ChangeStartTimeEvent event, Emitter<SetupBookableSpacesState> emit) {
final updatedSpaces = state.bookableSpaces.map((space) {
final updatedConfig = space.spaceConfig?.copyWith(
bookingStartTime: event.startTime,
);
return space.copyWith(spaceConfig: updatedConfig);
}).toList();
emit(SetupBookableSpacesInitial(bookableSpaces: updatedSpaces));
}
void _onChangeEndTimeEvent(
ChangeEndTimeEvent event, Emitter<SetupBookableSpacesState> emit) {
final updatedSpaces = state.bookableSpaces.map((space) {
final updatedConfig = space.spaceConfig?.copyWith(
bookingEndTime: event.endTime,
);
return space.copyWith(spaceConfig: updatedConfig);
}).toList();
emit(SetupBookableSpacesInitial(bookableSpaces: updatedSpaces));
}
void _onChangeCostEvent(
ChangeCostEvent event, Emitter<SetupBookableSpacesState> emit) {
final updatedSpaces = state.bookableSpaces.map((space) {
final updatedConfig = space.spaceConfig?.copyWith(
cost: event.cost,
);
return space.copyWith(spaceConfig: updatedConfig);
}).toList();
emit(SetupBookableSpacesInitial(bookableSpaces: updatedSpaces));
} }
void _onCheckConfigurValidityEvent(CheckConfigurValidityEvent event, void _onCheckConfigurValidityEvent(CheckConfigurValidityEvent event,
Emitter<SetupBookableSpacesState> emit) { Emitter<SetupBookableSpacesState> emit) {
if (selectedBookableSpaces.first.spaceConfig!.isValid) { if (state.bookableSpaces.first.spaceConfig!.isValid) {
emit(ValidSaveButtonState()); emit(ValidSaveButtonState(
bookableSpaces: state.bookableSpaces,
));
} else { } else {
emit(UnValidSaveButtonState()); emit(UnValidSaveButtonState(
bookableSpaces: state.bookableSpaces,
));
} }
} }
void _onEditModeSelected( void _onEditModeSelected(
EditModeSelected event, Emitter<SetupBookableSpacesState> emit) { EditModeSelected event,
selectedBookableSpaces.clear(); Emitter<SetupBookableSpacesState> emit,
selectedBookableSpaces.add(event.editingBookableSpace); ) {
final updatedList = [event.editingBookableSpace];
emit(SetupBookableSpacesInitial(bookableSpaces: updatedList));
} }
} }

View File

@ -21,6 +21,34 @@ class RemoveFromBookableSpaceEvent extends SetupBookableSpacesEvent {
}); });
} }
class AddBookableDaysEvent extends SetupBookableSpacesEvent {
final List<String> bookableDays;
const AddBookableDaysEvent({
required this.bookableDays,
});
}
class ChangeCostEvent extends SetupBookableSpacesEvent {
final int cost;
const ChangeCostEvent({
required this.cost,
});
}
class ChangeStartTimeEvent extends SetupBookableSpacesEvent {
final TimeOfDay startTime;
const ChangeStartTimeEvent({
required this.startTime,
});
}
class ChangeEndTimeEvent extends SetupBookableSpacesEvent {
final TimeOfDay endTime;
const ChangeEndTimeEvent({
required this.endTime,
});
}
class CheckConfigurValidityEvent extends SetupBookableSpacesEvent {} class CheckConfigurValidityEvent extends SetupBookableSpacesEvent {}
class EditModeSelected extends SetupBookableSpacesEvent { class EditModeSelected extends SetupBookableSpacesEvent {

View File

@ -1,32 +1,37 @@
part of 'setup_bookable_spaces_bloc.dart'; part of 'setup_bookable_spaces_bloc.dart';
sealed class SetupBookableSpacesState extends Equatable { sealed class SetupBookableSpacesState extends Equatable {
const SetupBookableSpacesState(); final List<BookableSpacemodel> bookableSpaces;
const SetupBookableSpacesState({required this.bookableSpaces});
TimeOfDay? get startTime =>
bookableSpaces.first.spaceConfig!.bookingStartTime;
TimeOfDay? get endTime => bookableSpaces.first.spaceConfig!.bookingEndTime;
@override @override
List<Object> get props => []; List<Object> get props => [];
} }
final class SetupBookableSpacesInitial extends SetupBookableSpacesState {} final class SetupBookableSpacesInitial extends SetupBookableSpacesState {
const SetupBookableSpacesInitial({required super.bookableSpaces});
}
class AddNonBookableSpaceIntoBookableState extends SetupBookableSpacesState { class AddNonBookableSpaceIntoBookableState extends SetupBookableSpacesState {
final List<BookableSpacemodel> bookableSpaces; const AddNonBookableSpaceIntoBookableState({required super.bookableSpaces});
const AddNonBookableSpaceIntoBookableState({
required this.bookableSpaces,
});
} }
class InProgressState extends SetupBookableSpacesState {} class InProgressState extends SetupBookableSpacesState {
const InProgressState({required super.bookableSpaces});
}
class RemoveBookableSpaceIntoNonBookableState extends SetupBookableSpacesState { class RemoveBookableSpaceIntoNonBookableState extends SetupBookableSpacesState {
final List<BookableSpacemodel> bookableSpaces;
const RemoveBookableSpaceIntoNonBookableState({ const RemoveBookableSpaceIntoNonBookableState({
required this.bookableSpaces, required super.bookableSpaces,
}); });
} }
class ValidSaveButtonState extends SetupBookableSpacesState {} class ValidSaveButtonState extends SetupBookableSpacesState {
const ValidSaveButtonState({required super.bookableSpaces});
class UnValidSaveButtonState extends SetupBookableSpacesState {} }
class UnValidSaveButtonState extends SetupBookableSpacesState {
const UnValidSaveButtonState({required super.bookableSpaces});
}

View File

@ -63,9 +63,10 @@ class _SetupBookableSpacesDialogState extends State<SetupBookableSpacesDialog> {
RemoteNonBookableSpaces(HTTPService())) RemoteNonBookableSpaces(HTTPService()))
: (context) => SetupBookableSpacesBloc( : (context) => SetupBookableSpacesBloc(
RemoteNonBookableSpaces(HTTPService())) RemoteNonBookableSpaces(HTTPService()))
..add(EditModeSelected( ..add(
editingBookableSpace: widget.editingBookableSpace!, EditModeSelected(
)), editingBookableSpace: widget.editingBookableSpace!),
),
), ),
BlocProvider( BlocProvider(
create: (context) => SendBookableSpacesBloc( create: (context) => SendBookableSpacesBloc(
@ -115,16 +116,14 @@ class _SetupBookableSpacesDialogState extends State<SetupBookableSpacesDialog> {
), ),
Builder(builder: (context) { Builder(builder: (context) {
final stepsState = context.watch<StepsCubit>().state; final stepsState = context.watch<StepsCubit>().state;
final setupBookableSpacesBloc = final bookableSpaces =
context.watch<SetupBookableSpacesBloc>(); context.watch<SetupBookableSpacesBloc>().state.bookableSpaces;
final selectedSpaces =
setupBookableSpacesBloc.selectedBookableSpaces;
return stepsState is StepOneState return stepsState is StepOneState
? NextFirstStepButton(selectedSpaces: selectedSpaces) ? const NextFirstStepButton()
: SaveSecondStepButton( : SaveSecondStepButton(
selectedSpaces: selectedSpaces,
pointsController: pointsController, pointsController: pointsController,
isEditingMode: widget.editingBookableSpace != null, isEditingMode: widget.editingBookableSpace != null,
bookableSpaces: bookableSpaces,
); );
}), }),
], ],

View File

@ -47,7 +47,7 @@ class BookableSpaceSwitchActivationWidget extends StatelessWidget {
return ColorsManager.whiteColors; return ColorsManager.whiteColors;
}), }),
value: space.spaceConfig!.availability, value: space.spaceConfig!.availability,
activeTrackColor: ColorsManager.blueColor, activeTrackColor: ColorsManager.dialogBlueTitle,
inactiveTrackColor: ColorsManager.grayBorder, inactiveTrackColor: ColorsManager.grayBorder,
thumbColor: WidgetStateProperty.resolveWith<Color>( thumbColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) { (Set<WidgetState> states) {

View File

@ -17,6 +17,8 @@ class BookingPeriodWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final state = context.watch<SetupBookableSpacesBloc>().state;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -32,122 +34,108 @@ class BookingPeriodWidget extends StatelessWidget {
const Text('Booking Period'), const Text('Booking Period'),
], ],
), ),
const SizedBox(height: 5),
Container( Container(
width: 300, width: 230,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(8),
color: ColorsManager.graysColor, color: ColorsManager.circleRolesBackground,
boxShadow: [
BoxShadow(
offset: Offset.zero,
blurRadius: 4,
spreadRadius: 0,
color: ColorsManager.timePickerColor.withValues(alpha: 0.15),
)
],
), ),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
TimePickerWidget( TimePickerWidget(
title: editingBookableSpace == null title: editingBookableSpace?.spaceConfig?.bookingStartTime
? 'Start Time' ?.format(context) ??
: editingBookableSpace!.spaceConfig!.bookingStartTime! 'Start Time',
.format(context), onTimePicked: (pickedStartTime) {
onTimePicked: (timePicked) { if (pickedStartTime == null) return;
if (timePicked == null) {
if (state.endTime != null &&
isEndTimeAfterStartTime(
pickedStartTime, state.endTime!)) {
_showInvalidSnackBar(
context, "You can't choose Start Time after End Time");
return; return;
} }
final setupBookableSpacesBloc =
context.read<SetupBookableSpacesBloc>();
if (setupBookableSpacesBloc.endTime != null && context.read<SetupBookableSpacesBloc>().add(
isEndTimeAfterStartTime( ChangeStartTimeEvent(startTime: pickedStartTime),
timePicked, setupBookableSpacesBloc.endTime!)) { );
ScaffoldMessenger.of(context).clearSnackBars(); context.read<SetupBookableSpacesBloc>().add(
ScaffoldMessenger.of(context).showSnackBar(const SnackBar( CheckConfigurValidityEvent(),
content: );
Text("You can't choose start Time Before End time"),
duration: Duration(seconds: 2),
backgroundColor: ColorsManager.red,
));
throw Exception();
} else {
for (int i = 0;
i <
setupBookableSpacesBloc
.selectedBookableSpaces.length;
i++) {
final space =
setupBookableSpacesBloc.selectedBookableSpaces[i];
final updatedConfig = space.spaceConfig
?.copyWith(bookingStartTime: timePicked);
final updatedSpace =
space.copyWith(spaceConfig: updatedConfig);
setupBookableSpacesBloc.selectedBookableSpaces[i] =
updatedSpace;
}
}
}, },
), ),
const SizedBox(width: 10),
const Icon( const Icon(
Icons.arrow_right_alt, Icons.arrow_right_alt,
color: ColorsManager.grayColor, color: ColorsManager.grayColor,
size: 13,
), ),
TimePickerWidget( TimePickerWidget(
title: editingBookableSpace == null title: editingBookableSpace?.spaceConfig?.bookingEndTime
? 'End Time' ?.format(context) ??
: editingBookableSpace!.spaceConfig!.bookingEndTime! 'End Time',
.format(context), onTimePicked: (pickedEndTime) {
onTimePicked: (timePicked) { if (pickedEndTime == null) return;
if (timePicked == null) {
if (state.startTime != null &&
isEndTimeAfterStartTime(
state.startTime!, pickedEndTime)) {
_showInvalidSnackBar(
context, "You can't choose End Time before Start Time");
return; return;
} }
final setupBookableSpacesBloc =
context.read<SetupBookableSpacesBloc>();
if (setupBookableSpacesBloc.startTime != null &&
isEndTimeAfterStartTime(
setupBookableSpacesBloc.startTime!, timePicked)) {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content:
Text("You can't choose End Time After Start time"),
duration: Duration(seconds: 2),
backgroundColor: ColorsManager.red,
));
throw Exception();
} else {
for (int i = 0;
i <
setupBookableSpacesBloc
.selectedBookableSpaces.length;
i++) {
final space =
setupBookableSpacesBloc.selectedBookableSpaces[i];
final updatedConfig = space.spaceConfig
?.copyWith(bookingEndTime: timePicked);
final updatedSpace =
space.copyWith(spaceConfig: updatedConfig);
setupBookableSpacesBloc.selectedBookableSpaces[i] = context.read<SetupBookableSpacesBloc>().add(
updatedSpace; ChangeEndTimeEvent(endTime: pickedEndTime),
} );
} context.read<SetupBookableSpacesBloc>().add(
CheckConfigurValidityEvent(),
);
}, },
), ),
const SizedBox(width: 15),
Container( Container(
width: 50, width: 30,
height: 32, height: 32,
alignment: Alignment.center,
decoration: const BoxDecoration( decoration: const BoxDecoration(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(10), topLeft: Radius.circular(10),
bottomLeft: Radius.circular(10), bottomLeft: Radius.circular(10),
), ),
), ),
alignment: Alignment.center,
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.clockIcon, Assets.clockIcon,
height: 15, height: 18,
color: ColorsManager.blackColor.withValues(alpha: 0.4), color: ColorsManager.blackColor.withValues(alpha: 0.4),
), ),
) ),
], ],
), ),
), ),
], ],
); );
} }
void _showInvalidSnackBar(BuildContext context, String message) {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
backgroundColor: ColorsManager.red,
),
);
}
} }

View File

@ -2,16 +2,15 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart'; import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/setup_bookable_spaces_bloc/setup_bookable_spaces_bloc.dart'; import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/setup_bookable_spaces_bloc/setup_bookable_spaces_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/custom_checkbox_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class CheckBoxSpaceWidget extends StatelessWidget { class CheckBoxSpaceWidget extends StatelessWidget {
final BookableSpacemodel nonBookableSpace; final BookableSpacemodel nonBookableSpace;
final List<BookableSpacemodel> selectedSpaces;
const CheckBoxSpaceWidget({ const CheckBoxSpaceWidget({
super.key, super.key,
required this.nonBookableSpace, required this.nonBookableSpace,
required this.selectedSpaces,
}); });
@override @override
@ -32,22 +31,25 @@ class CheckBoxSpaceWidget extends StatelessWidget {
_ => false, _ => false,
}; };
return Checkbox( return CustomCheckboxWidget(
value: isChecked, value: isChecked,
onChanged: (value) { onChanged: (value) {
final bloc = context.read<SetupBookableSpacesBloc>(); final bloc = context.read<SetupBookableSpacesBloc>();
if (value ?? false) { if (value ?? false) {
bloc.add(AddToBookableSpaceEvent( bloc.add(AddToBookableSpaceEvent(
nonBookableSpace: nonBookableSpace)); nonBookableSpace: nonBookableSpace,
));
} else { } else {
bloc.add(RemoveFromBookableSpaceEvent( bloc.add(RemoveFromBookableSpaceEvent(
bookableSpace: nonBookableSpace)); bookableSpace: nonBookableSpace,
));
} }
}, },
); );
}, },
), ),
const SizedBox(width: 5), const SizedBox(width: 15),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -55,15 +57,17 @@ class CheckBoxSpaceWidget extends StatelessWidget {
Text( Text(
nonBookableSpace.spaceName, nonBookableSpace.spaceName,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.w700,
color: ColorsManager.textGray, fontSize: 12,
color: ColorsManager.titleGray,
), ),
), ),
Text( Text(
nonBookableSpace.spaceVirtualAddress, nonBookableSpace.spaceVirtualAddress,
style: const TextStyle( style: const TextStyle(
fontSize: 12, fontWeight: FontWeight.w400,
color: ColorsManager.textGray, fontSize: 10,
color: ColorsManager.titleGray,
), ),
), ),
], ],

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CustomCheckboxWidget extends StatelessWidget {
final bool value;
final ValueChanged<bool?> onChanged;
final double? outHeight;
final double? outWidth;
final double? iconSize;
const CustomCheckboxWidget({
super.key,
required this.value,
required this.onChanged,
this.outWidth,
this.outHeight,
this.iconSize,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => onChanged(!value),
child: Container(
width: outWidth ?? 17,
height: outHeight ?? 17,
decoration: BoxDecoration(
color: value ? Colors.white : ColorsManager.checkBoxFillColor,
border: value
? Border.all(color: ColorsManager.secondaryColor, width: 1)
: Border.all(color: ColorsManager.checkBoxBorderGray, width: 1),
borderRadius: BorderRadius.circular(4),
),
child: value
? Center(
child: Container(
width: outWidth != null ? outWidth! - 4 : 13,
height: outHeight != null ? outHeight! - 4 : 13,
decoration: BoxDecoration(
color: ColorsManager.secondaryColor,
borderRadius: BorderRadius.circular(2),
),
child: const Icon(
Icons.check,
size: 12,
color: Colors.white,
),
),
)
: null,
),
);
}
}

View File

@ -20,38 +20,53 @@ class EditBookableSpaceButtonWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Center( return Center(
child: ElevatedButton( child: Container(
onPressed: () { decoration: BoxDecoration(
final bookableBloc = context.read<BookableSpacesBloc>(); borderRadius: BorderRadius.circular(20),
boxShadow: [
showDialog( BoxShadow(
context: context, offset: Offset.zero,
builder: (context) => MultiBlocProvider( blurRadius: 3,
providers: [ spreadRadius: 0,
BlocProvider.value( color: ColorsManager.timePickerColor.withValues(
value: bookableBloc, alpha: 0.3,
),
BlocProvider(
create: (context) => UpdateBookableSpacesBloc(
RemoteUpdateBookableSpaceService(HTTPService()),
),
),
],
child: SetupBookableSpacesDialog(
editingBookableSpace: space,
), ),
), )
); ],
},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
fixedSize: const Size(50, 30),
elevation: 1,
), ),
child: SvgPicture.asset( child: ElevatedButton(
Assets.settings, onPressed: () {
height: 15, final bookableBloc = context.read<BookableSpacesBloc>();
color: ColorsManager.blue1,
showDialog(
context: context,
builder: (context) => MultiBlocProvider(
providers: [
BlocProvider.value(
value: bookableBloc,
),
BlocProvider(
create: (context) => UpdateBookableSpacesBloc(
RemoteUpdateBookableSpaceService(HTTPService()),
),
),
],
child: SetupBookableSpacesDialog(
editingBookableSpace: space,
),
),
);
},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
minimumSize: const Size(45, 30),
elevation: 0,
),
child: SvgPicture.asset(
Assets.settings,
height: 13,
color: ColorsManager.blue1,
),
), ),
), ),
); );

View File

@ -66,11 +66,13 @@ class PaginationButtonsWidget extends StatelessWidget {
height: 30, height: 30,
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: i == currentPage color: i == currentPage
? ColorsManager.dialogBlueTitle ? ColorsManager.dialogBlueTitle
: Colors.grey[300], : ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), border: Border.all(
color: ColorsManager.lightGrayBorderColor,
)),
child: Text( child: Text(
'$i', '$i',
style: TextStyle( style: TextStyle(
@ -134,11 +136,15 @@ class PaginationButtonsWidget extends StatelessWidget {
child: GestureDetector( child: GestureDetector(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), width: 30,
height: 30,
alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[300], color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), border: Border.all(
color: ColorsManager.lightGrayBorderColor,
)),
child: Text( child: Text(
label, label,
style: const TextStyle( style: const TextStyle(

View File

@ -24,20 +24,37 @@ class RowOfButtonsTitleWidget extends StatelessWidget {
children: [ children: [
Row( Row(
children: [ children: [
ElevatedButton( Container(
style: ElevatedButton.styleFrom( decoration: BoxDecoration(
padding: EdgeInsets.zero, borderRadius: BorderRadius.circular(12),
shape: RoundedRectangleBorder( boxShadow: [
borderRadius: BorderRadius.circular(12), BoxShadow(
offset: Offset.zero,
blurRadius: 3,
spreadRadius: 0,
color: ColorsManager.timePickerColor.withValues(
alpha: 0.3,
),
)
],
),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
minimumSize: const Size(50, 40),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
), ),
), child: SvgPicture.asset(
child: SvgPicture.asset( Assets.backButtonIcon,
Assets.backButtonIcon, height: 15,
height: 15, ),
), onPressed: () {
onPressed: () { pageController.jumpToPage(1);
pageController.jumpToPage(1); }),
}), ),
const SizedBox( const SizedBox(
width: 10, width: 10,
), ),

View File

@ -1,32 +1,42 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/setup_bookable_spaces_bloc/setup_bookable_spaces_bloc.dart'; import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/setup_bookable_spaces_bloc/setup_bookable_spaces_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart'; import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart'; import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart';
class NextFirstStepButton extends StatelessWidget { class NextFirstStepButton extends StatelessWidget {
final List<BookableSpacemodel> selectedSpaces;
const NextFirstStepButton({ const NextFirstStepButton({
super.key, super.key,
required this.selectedSpaces,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ButtonsDividerBottomDialogWidget( return BlocBuilder<SetupBookableSpacesBloc, SetupBookableSpacesState>(
title: 'Next', builder: (context, state) {
onNextPressed: selectedSpaces.isEmpty return switch (state) {
? null SetupBookableSpacesInitial() => ButtonsDividerBottomDialogWidget(
: () { title: 'Next',
context.read<StepsCubit>().goToNextStep(); onNextPressed: null,
context onCancelPressed: () => context.pop(),
.read<SetupBookableSpacesBloc>() ),
.add(CheckConfigurValidityEvent()); AddNonBookableSpaceIntoBookableState(:final bookableSpaces) ||
}, RemoveBookableSpaceIntoNonBookableState(:final bookableSpaces) =>
onCancelPressed: () => context.pop(), ButtonsDividerBottomDialogWidget(
title: 'Next',
onNextPressed: bookableSpaces.isEmpty
? null
: () {
context.read<StepsCubit>().goToNextStep();
context.read<SetupBookableSpacesBloc>().add(
CheckConfigurValidityEvent(),
);
},
onCancelPressed: () => context.pop(),
),
_ => const SizedBox(),
};
},
); );
} }
} }

View File

@ -9,14 +9,14 @@ import 'package:syncrow_web/utils/color_manager.dart';
class PointsPartWidget extends StatefulWidget { class PointsPartWidget extends StatefulWidget {
final BookableSpacemodel? editingBookableSpace; final BookableSpacemodel? editingBookableSpace;
final TextEditingController pointsController;
const PointsPartWidget({ const PointsPartWidget({
super.key, super.key,
required this.pointsController, required this.pointsController,
this.editingBookableSpace, this.editingBookableSpace,
}); });
final TextEditingController pointsController;
@override @override
State<PointsPartWidget> createState() => _PointsPartWidgetState(); State<PointsPartWidget> createState() => _PointsPartWidgetState();
} }
@ -24,26 +24,28 @@ class PointsPartWidget extends StatefulWidget {
class _PointsPartWidgetState extends State<PointsPartWidget> { class _PointsPartWidgetState extends State<PointsPartWidget> {
@override @override
void initState() { void initState() {
super.initState();
if (widget.editingBookableSpace != null) { if (widget.editingBookableSpace != null) {
widget.pointsController.text = widget.pointsController.text =
widget.editingBookableSpace!.spaceConfig!.cost.toString(); widget.editingBookableSpace!.spaceConfig!.cost.toString();
} }
super.initState();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<TogglePointsSwitchCubit, TogglePointsSwitchState>( return BlocBuilder<TogglePointsSwitchCubit, TogglePointsSwitchState>(
builder: (context, state) { builder: (context, switchState) {
final isSwitchOn = switchState is ActivatePointsSwitch;
return Column( return Column(
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
if (state is ActivatePointsSwitch) if (isSwitchOn)
Text( Text(
'* ', '* ',
style: Theme.of(context) style: Theme.of(context)
@ -52,87 +54,62 @@ class _PointsPartWidgetState extends State<PointsPartWidget> {
.copyWith(color: Colors.red), .copyWith(color: Colors.red),
) )
else else
const SizedBox( const SizedBox(width: 11),
width: 11,
),
const Text('Points/hrs'), const Text('Points/hrs'),
], ],
), ),
Transform.scale( Transform.scale(
scale: 0.7, scale: 0.7,
child: Switch( child: Switch(
trackOutlineColor: WidgetStateProperty.resolveWith<Color>( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
(Set<WidgetState> states) { trackOutlineColor:
return ColorsManager.whiteColors; WidgetStateProperty.all(ColorsManager.whiteColors),
}), activeTrackColor: ColorsManager.dialogBlueTitle,
activeTrackColor: ColorsManager.blueColor, inactiveTrackColor: ColorsManager.lightGrayBorderColor,
inactiveTrackColor: ColorsManager.grayBorder, thumbColor:
thumbColor: WidgetStateProperty.resolveWith<Color>( WidgetStateProperty.all(ColorsManager.whiteColors),
(Set<WidgetState> states) { value: isSwitchOn,
return ColorsManager.whiteColors;
}),
value: state is ActivatePointsSwitch,
onChanged: (value) { onChanged: (value) {
final toggleCubit =
context.read<TogglePointsSwitchCubit>();
final bloc = context.read<SetupBookableSpacesBloc>(); final bloc = context.read<SetupBookableSpacesBloc>();
final updatedCost = value ? -1 : 0; final updatedCost = value ? -1 : 0;
final switchCubit =
context.read<TogglePointsSwitchCubit>();
if (value) { if (value) {
switchCubit.activateSwitch(); toggleCubit.activateSwitch();
} else { } else {
switchCubit.unActivateSwitch(); toggleCubit.unActivateSwitch();
widget.pointsController.clear(); widget.pointsController.clear();
} }
bloc.add(ChangeCostEvent(cost: updatedCost));
for (int i = 0;
i < bloc.selectedBookableSpaces.length;
i++) {
final space = bloc.selectedBookableSpaces[i];
final updatedConfig =
space.spaceConfig?.copyWith(cost: updatedCost);
final updatedSpace =
space.copyWith(spaceConfig: updatedConfig);
bloc.selectedBookableSpaces[i] = updatedSpace;
}
bloc.add(CheckConfigurValidityEvent()); bloc.add(CheckConfigurValidityEvent());
}, },
), ),
) ),
], ],
), ),
const SizedBox( const SizedBox(height: 5),
height: 5, if (isSwitchOn)
),
if (state is ActivatePointsSwitch)
SearchUnbookableSpacesWidget( SearchUnbookableSpacesWidget(
title: 'Ex: 0', title: 'Ex: 0',
height: 40, topPadding: 0,
onChanged: (p0) { blur: 1,
raduis: 10,
height: 34,
controller: widget.pointsController,
suffix: const SizedBox(),
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onChanged: (_) {
final updatedCost = final updatedCost =
int.tryParse(widget.pointsController.text) ?? 0; int.tryParse(widget.pointsController.text) ?? 0;
final bloc = context.read<SetupBookableSpacesBloc>();
for (var i = 0; i < bloc.selectedBookableSpaces.length; i++) {
final space = bloc.selectedBookableSpaces[i];
final updatedConfig =
space.spaceConfig?.copyWith(cost: updatedCost);
final updatedSpace =
space.copyWith(spaceConfig: updatedConfig);
bloc.selectedBookableSpaces[i] = updatedSpace;
}
context context
.read<SetupBookableSpacesBloc>() .read<SetupBookableSpacesBloc>()
.add(CheckConfigurValidityEvent()); .add(ChangeCostEvent(cost: updatedCost));
context.read<SetupBookableSpacesBloc>().add(
CheckConfigurValidityEvent(),
);
}, },
controller: widget.pointsController,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
suffix: const SizedBox(),
) )
else else
const SizedBox(), const SizedBox(),

View File

@ -10,68 +10,68 @@ import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/prese
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart'; import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart';
class SaveSecondStepButton extends StatelessWidget { class SaveSecondStepButton extends StatelessWidget {
final List<BookableSpacemodel> selectedSpaces;
final TextEditingController pointsController; final TextEditingController pointsController;
final bool isEditingMode; final bool isEditingMode;
final List<BookableSpacemodel> bookableSpaces;
const SaveSecondStepButton({ const SaveSecondStepButton({
super.key, super.key,
required this.selectedSpaces,
required this.pointsController, required this.pointsController,
required this.isEditingMode, required this.isEditingMode,
required this.bookableSpaces,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocConsumer<SendBookableSpacesBloc, SendBookableSpacesState>( return BlocListener<SendBookableSpacesBloc, SendBookableSpacesState>(
listener: (context, state) { listener: (context, state) {
if (state is SendBookableSpacesSuccess) { if (state is SendBookableSpacesSuccess) {
context.read<NonBookableSpacesBloc>().add(CallInitStateEvent()); context.read<NonBookableSpacesBloc>().add(CallInitStateEvent());
} else if (state is SendBookableSpacesError) { } else if (state is SendBookableSpacesError) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(content: Text(state.error)),
content: Text(state.error),
),
); );
} }
}, },
builder: (context, state) { child: BlocBuilder<SetupBookableSpacesBloc, SetupBookableSpacesState>(
return ButtonsDividerBottomDialogWidget( builder: (context, state) {
title: 'Save', return ButtonsDividerBottomDialogWidget(
onNextPressed: state is UnValidSaveButtonState title: 'Save',
? null onNextPressed: state is UnValidSaveButtonState
: () { ? null
if (selectedSpaces.any( : () {
(element) => element.isValid, if (bookableSpaces.any((e) => e.isValid)) {
)) { if (isEditingMode) {
isEditingMode callEditLogic(context);
? callEditLogic(context) } else {
: context.read<SendBookableSpacesBloc>().add( context.read<SendBookableSpacesBloc>().add(
SendBookableSpacesToApi( SendBookableSpacesToApi(
selectedBookableSpaces: context selectedBookableSpaces: bookableSpaces,
.read<SetupBookableSpacesBloc>() ),
.selectedBookableSpaces),
); );
} }
}, }
onCancelPressed: () => context.pop(), },
); onCancelPressed: () => context.pop(),
}, );
},
),
); );
} }
void callEditLogic(BuildContext context) { void callEditLogic(BuildContext context) {
context.read<UpdateBookableSpacesBloc>().add( print(bookableSpaces.first.spaceConfig!.cost);
UpdateBookableSpace( if (bookableSpaces.isNotEmpty) {
onSuccess: () => context.read<UpdateBookableSpacesBloc>().add(
context.read<NonBookableSpacesBloc>().add(CallInitStateEvent()), UpdateBookableSpace(
updatedParam: UpdateBookableSpaceParam.fromBookableModel( onSuccess: () => context
context .read<NonBookableSpacesBloc>()
.read<SetupBookableSpacesBloc>() .add(CallInitStateEvent()),
.selectedBookableSpaces updatedParam: UpdateBookableSpaceParam.fromBookableModel(
.first, bookableSpaces.first,
),
), ),
), );
); }
} }
} }

View File

@ -1,22 +1,30 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class SearchUnbookableSpacesWidget extends StatelessWidget { class SearchUnbookableSpacesWidget extends StatelessWidget {
final String title; final String title;
final Widget? suffix; final Widget? suffix;
final double? height; final double? height;
final double? width; final double? width;
final double? blur;
final double? raduis;
final double? topPadding;
final TextEditingController? controller; final TextEditingController? controller;
final List<TextInputFormatter>? inputFormatters; final List<TextInputFormatter>? inputFormatters;
final void Function(String)? onChanged; final void Function(String)? onChanged;
const SearchUnbookableSpacesWidget({ const SearchUnbookableSpacesWidget({
required this.title, required this.title,
this.controller, this.controller,
this.blur,
this.onChanged, this.onChanged,
this.suffix, this.suffix,
this.height, this.height,
this.width, this.width,
this.topPadding,
this.raduis,
this.inputFormatters, this.inputFormatters,
super.key, super.key,
}); });
@ -25,16 +33,18 @@ class SearchUnbookableSpacesWidget extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
width: width ?? 480, width: width ?? 480,
height: height ?? 30, height: height ?? 40,
padding: const EdgeInsets.only(top: 4), padding: const EdgeInsets.only(top: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(raduis ?? 15),
boxShadow: const [ boxShadow: [
BoxShadow( BoxShadow(
color: ColorsManager.shadowOfSearchTextfield, color:
offset: Offset(0, 4), ColorsManager.shadowOfSearchTextfield.withValues(alpha: 0.15),
blurRadius: 5, offset: Offset.zero,
blurRadius: blur ?? 5,
spreadRadius: 0,
), ),
], ],
), ),
@ -43,14 +53,21 @@ class SearchUnbookableSpacesWidget extends StatelessWidget {
inputFormatters: inputFormatters, inputFormatters: inputFormatters,
onChanged: onChanged, onChanged: onChanged,
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: contentPadding: EdgeInsets.symmetric(
const EdgeInsets.symmetric(vertical: 5, horizontal: 15), vertical: topPadding ?? 5,
horizontal: 15,
),
hintText: title, hintText: title,
hintStyle: const TextStyle(color: ColorsManager.hintTextGrey), hintStyle: const TextStyle(color: ColorsManager.hintTextGrey),
border: InputBorder.none, border: InputBorder.none,
suffixIcon: suffix ?? suffixIcon: Padding(
const Icon(Icons.search, padding: const EdgeInsets.all(10),
size: 20, color: ColorsManager.hintTextGrey), child: suffix ??
SvgPicture.asset(
Assets.searchIcon,
height: 12,
),
),
), ),
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,

View File

@ -26,7 +26,7 @@ class StepTwoDetailsWidget extends StatelessWidget {
editingBookableSpace: editingBookableSpace, editingBookableSpace: editingBookableSpace,
), ),
const SizedBox( const SizedBox(
height: 20, height: 30,
), ),
BookingPeriodWidget( BookingPeriodWidget(
editingBookableSpace: editingBookableSpace, editingBookableSpace: editingBookableSpace,

View File

@ -26,7 +26,7 @@ class StepperPartWidget extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.only(left: 3), padding: const EdgeInsets.only(left: 3),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
height: 50, height: 40,
child: const VerticalDivider( child: const VerticalDivider(
width: 8, width: 8,
)), )),
@ -59,7 +59,7 @@ class StepperPartWidget extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.only(left: 3), padding: const EdgeInsets.only(left: 3),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
height: 50, height: 40,
child: const VerticalDivider( child: const VerticalDivider(
width: 8, width: 8,
)), )),

View File

@ -12,7 +12,7 @@ class TimePickerWidget extends StatefulWidget {
required this.onTimePicked, required this.onTimePicked,
required this.title, required this.title,
}); });
late SetupBookableSpacesBloc setupBookableSpacesBloc; late final SetupBookableSpacesBloc setupBookableSpacesBloc;
final void Function(TimeOfDay? timePicked) onTimePicked; final void Function(TimeOfDay? timePicked) onTimePicked;
@override @override
State<TimePickerWidget> createState() => _TimePickerWidgetState(); State<TimePickerWidget> createState() => _TimePickerWidgetState();
@ -47,13 +47,17 @@ class _TimePickerWidgetState extends State<TimePickerWidget> {
); );
}, },
); );
if (tempTime == null) return;
widget.onTimePicked(tempTime); widget.onTimePicked(tempTime);
timePicked = tempTime; timePicked = tempTime;
widget.setupBookableSpacesBloc.add(CheckConfigurValidityEvent()); widget.setupBookableSpacesBloc.add(CheckConfigurValidityEvent());
setState(() {}); setState(() {});
}, },
child: Container( child: Container(
width: 100,
height: 32, height: 32,
decoration: const BoxDecoration( decoration: const BoxDecoration(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(

View File

@ -36,9 +36,7 @@ class UnbookableListWidget extends StatelessWidget {
if (index < nonBookableSpaces.data.length) { if (index < nonBookableSpaces.data.length) {
return CheckBoxSpaceWidget( return CheckBoxSpaceWidget(
nonBookableSpace: nonBookableSpaces.data[index], nonBookableSpace: nonBookableSpaces.data[index],
selectedSpaces: context
.read<SetupBookableSpacesBloc>()
.selectedBookableSpaces,
); );
} else { } else {
return const Padding( return const Padding(

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart'; import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/setup_bookable_spaces_bloc/setup_bookable_spaces_bloc.dart'; import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/setup_bookable_spaces_bloc/setup_bookable_spaces_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/custom_checkbox_widget.dart';
class WeekDaysCheckboxRow extends StatefulWidget { class WeekDaysCheckboxRow extends StatefulWidget {
final BookableSpacemodel? editingBookableSpace; final BookableSpacemodel? editingBookableSpace;
@ -24,6 +25,7 @@ class _WeekDaysCheckboxRowState extends State<WeekDaysCheckboxRow> {
'Sat': false, 'Sat': false,
'Sun': false, 'Sun': false,
}; };
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -40,52 +42,66 @@ class _WeekDaysCheckboxRowState extends State<WeekDaysCheckboxRow> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _daysChecked.entries.map((entry) { children: [
return Expanded( Row(
child: Row( children: [
children: [ Text(
Expanded( '* ',
child: Checkbox( style: Theme.of(context)
value: entry.value, .textTheme
onChanged: (newValue) { .bodyMedium!
setState(() { .copyWith(color: Colors.red),
_daysChecked[entry.key] = newValue ?? false; ),
}); const Text('Days'),
],
),
const SizedBox(height: 20),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: _daysChecked.entries.map((entry) {
return Expanded(
child: Row(
children: [
CustomCheckboxWidget(
outHeight: 16,
outWidth: 16,
value: entry.value,
onChanged: (newValue) {
setState(() {
_daysChecked[entry.key] = newValue ?? false;
});
final selectedDays = _daysChecked.entries final selectedDays = _daysChecked.entries
.where((e) => e.value) .where((e) => e.value)
.map((e) => e.key) .map((e) => e.key)
.toList(); .toList();
final bloc = context.read<SetupBookableSpacesBloc>(); context.read<SetupBookableSpacesBloc>().add(
AddBookableDaysEvent(bookableDays: selectedDays),
for (int i = 0; );
i < bloc.selectedBookableSpaces.length; context.read<SetupBookableSpacesBloc>().add(
i++) { CheckConfigurValidityEvent(),
final space = bloc.selectedBookableSpaces[i]; );
final updatedConfig = space.spaceConfig },
?.copyWith(bookableDays: selectedDays); ),
final updatedSpace = const SizedBox(width: 8),
space.copyWith(spaceConfig: updatedConfig); Expanded(
child: Text(
bloc.selectedBookableSpaces[i] = updatedSpace; entry.key,
} style: const TextStyle(
fontSize: 13,
bloc.add(CheckConfigurValidityEvent()); fontWeight: FontWeight.w400,
}, ),
), ),
),
],
), ),
Expanded( );
child: Text( }).toList(),
entry.key, ),
style: const TextStyle(fontSize: 10), ],
)),
],
),
);
}).toList(),
); );
} }
} }

View File

@ -2,9 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_event.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/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/access_management/view/access_overview_content.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';

View File

@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_
import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/schedule_view/schedule_garage_view.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/schedule_view/schedule_garage_view.dart';
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart';
import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart'; import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart';
import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart'; import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
@ -94,11 +95,18 @@ class GarageDoorControlView extends StatelessWidget
FetchGarageDoorSchedulesEvent( FetchGarageDoorSchedulesEvent(
deviceId: deviceId, category: 'doorcontact_state'), deviceId: deviceId, category: 'doorcontact_state'),
); );
showDialog(
showDialog<void>(
context: context, context: context,
builder: (ctx) => BlocProvider.value( builder: (ctx) => BlocProvider.value(
value: BlocProvider.of<GarageDoorBloc>(context), value: BlocProvider.of<GarageDoorBloc>(context),
child: BuildGarageDoorScheduleView(status: status), child: BuildScheduleView(
deviceUuid: deviceId,
category: 'Timer',
code: 'doorcontact_state',
countdownCode: 'Timer',
deviceType: 'GD',
),
)); ));
}, },
name: 'Scheduling', name: 'Scheduling',

View File

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

View File

@ -287,7 +287,8 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
try { try {
if (state is ScheduleLoaded) { if (state is ScheduleLoaded) {
Status status = Status(code: '', value: ''); Status status = Status(code: '', value: '');
if (event.deviceType == 'CUR_2') { if (event.deviceType == 'CUR_2' ||
event.deviceType == 'GD' ) {
status = status.copyWith( status = status.copyWith(
code: 'control', code: 'control',
value: event.functionOn == true ? 'open' : 'close'); value: event.functionOn == true ? 'open' : 'close');

View File

@ -69,7 +69,7 @@ class CountdownModeButtons extends StatelessWidget {
countDownCode: countDownCode), countDownCode: countDownCode),
); );
}, },
backgroundColor: ColorsManager.primaryColorWithOpacity, backgroundColor: ColorsManager.secondaryColor,
child: const Text('Save'), child: const Text('Save'),
), ),
), ),

View File

@ -63,7 +63,7 @@ class InchingModeButtons extends StatelessWidget {
), ),
); );
}, },
backgroundColor: ColorsManager.primaryColor, backgroundColor: ColorsManager.secondaryColor,
child: const Text('Save'), child: const Text('Save'),
), ),
), ),

View File

@ -31,11 +31,12 @@ class BuildScheduleView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (_) => ScheduleBloc(deviceId: deviceUuid,) create: (_) => ScheduleBloc(
deviceId: deviceUuid,
)
..add(ScheduleGetEvent(category: category)) ..add(ScheduleGetEvent(category: category))
..add(ScheduleFetchStatusEvent( ..add(ScheduleFetchStatusEvent(
deviceId: deviceUuid, deviceId: deviceUuid, countdownCode: countdownCode ?? '')),
countdownCode: countdownCode ?? '')),
child: Dialog( child: Dialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
insetPadding: const EdgeInsets.all(20), insetPadding: const EdgeInsets.all(20),
@ -56,7 +57,7 @@ class BuildScheduleView extends StatelessWidget {
children: [ children: [
const ScheduleHeader(), const ScheduleHeader(),
const SizedBox(height: 20), const SizedBox(height: 20),
if (deviceType == 'CUR_2') if (deviceType == 'CUR_2' || deviceType == 'GD')
const SizedBox() const SizedBox()
else else
ScheduleModeSelector( ScheduleModeSelector(
@ -76,8 +77,7 @@ class BuildScheduleView extends StatelessWidget {
category: category, category: category,
time: '', time: '',
function: Status( function: Status(
code: code.toString(), code: code.toString(), value: true),
value: true),
days: [], days: [],
), ),
isEdit: false, isEdit: false,
@ -96,7 +96,7 @@ class BuildScheduleView extends StatelessWidget {
} }
}, },
), ),
if (deviceType != 'CUR_2') if (deviceType != 'CUR_2'|| deviceType != 'GD')
if (state.scheduleMode == ScheduleModes.countdown || if (state.scheduleMode == ScheduleModes.countdown ||
state.scheduleMode == ScheduleModes.inching) state.scheduleMode == ScheduleModes.inching)
CountdownInchingView( CountdownInchingView(

View File

@ -24,12 +24,13 @@ class ScheduleManagementUI extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox( SizedBox(
width: 170, width: 177,
height: 40, height: 40,
child: DefaultButton( child: DefaultButton(
borderColor: ColorsManager.grayColor.withOpacity(0.5), borderWidth: 4,
padding: 2, borderColor: ColorsManager.neutralGray,
backgroundColor: ColorsManager.graysColor, padding: 8,
backgroundColor: ColorsManager.textFieldGreyColor,
borderRadius: 15, borderRadius: 15,
onPressed: onAddSchedule, onPressed: onAddSchedule,
child: Row( child: Row(

View File

@ -39,7 +39,7 @@ class ScheduleModeButtons extends StatelessWidget {
borderRadius: 8, borderRadius: 8,
height: 40, height: 40,
onPressed: onSave, onPressed: onSave,
backgroundColor: ColorsManager.primaryColorWithOpacity, backgroundColor: ColorsManager.secondaryColor,
child: const Text('Save'), child: const Text('Save'),
), ),
), ),

View File

@ -194,7 +194,7 @@ class _ScheduleTableView extends StatelessWidget {
child: Text(_getSelectedDays( child: Text(_getSelectedDays(
ScheduleModel.parseSelectedDays(schedule.days)))), ScheduleModel.parseSelectedDays(schedule.days)))),
Center(child: Text(formatIsoStringToTime(schedule.time, context))), Center(child: Text(formatIsoStringToTime(schedule.time, context))),
if (deviceType == 'CUR_2') if (deviceType == 'CUR_2' || deviceType == 'GD')
Center( Center(
child: Text(schedule.function.value == true ? 'open' : 'close')) child: Text(schedule.function.value == true ? 'open' : 'close'))
else else

View File

@ -23,7 +23,7 @@ class ScheduleDialogHelper {
required String deviceType, required String deviceType,
}) { }) {
bool temp; bool temp;
if (deviceType == 'CUR_2') { if (deviceType == 'CUR_2' || deviceType == 'GD') {
temp = schedule!.function.value == 'open' ? true : false; temp = schedule!.function.value == 'open' ? true : false;
} else { } else {
temp = schedule!.function.value; temp = schedule!.function.value;
@ -116,7 +116,7 @@ class ScheduleDialogHelper {
ScheduleModeButtons( ScheduleModeButtons(
onSave: () { onSave: () {
dynamic temp; dynamic temp;
if (deviceType == 'CUR_2') { if (deviceType == 'CUR_2' || deviceType == 'GD') {
temp = functionOn! ? 'open' : 'close'; temp = functionOn! ? 'open' : 'close';
} else { } else {
temp = functionOn; temp = functionOn;
@ -202,18 +202,23 @@ class ScheduleDialogHelper {
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Radio<bool>( Radio<bool>(
activeColor: ColorsManager.secondaryColor,
focusColor: ColorsManager.secondaryColor,
value: true, value: true,
groupValue: isOn, groupValue: isOn,
onChanged: (val) => onChanged(true), onChanged: (val) => onChanged(true),
), ),
Text(categor == 'CUR_2' ? 'open' : 'On'), Text(categor == 'CUR_2' || categor == 'GD' ? 'open' : 'On'),
const SizedBox(width: 10), const SizedBox(width: 10),
Radio<bool>( Radio<bool>(
activeColor: ColorsManager.secondaryColor,
focusColor: ColorsManager.secondaryColor,
value: false, value: false,
groupValue: isOn, groupValue: isOn,
onChanged: (val) => onChanged(false), onChanged: (val) => onChanged(false),
), ),
Text(categor == 'CUR_2' ? 'close' : 'Off'), Text(categor == 'CUR_2' || categor == 'GD' ? 'close' : 'Off'),
], ],
); );
} }

View File

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

View File

@ -52,7 +52,7 @@ class SpaceDropdown extends StatelessWidget {
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: selectedValue == space.uuid color: selectedValue == space.uuid
? ColorsManager.dialogBlueTitle ? ColorsManager.dropDownSelectBlue
: ColorsManager.blackColor, : ColorsManager.blackColor,
), ),
), ),
@ -61,7 +61,7 @@ class SpaceDropdown extends StatelessWidget {
style: Theme.of(context).textTheme.bodyMedium!.copyWith( style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12, fontSize: 12,
color: selectedValue == space.uuid color: selectedValue == space.uuid
? ColorsManager.dialogBlueTitle ? ColorsManager.dropDownSelectBlue
: ColorsManager.blackColor, : 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/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/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/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/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/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'; 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> { class _SpaceManagementPageState extends State<SpaceManagementPage> {
late final CommunitiesBloc communitiesBloc; late final CommunitiesBloc communitiesBloc;
late final HTTPService _httpService;
@override @override
void initState() { void initState() {
_httpService = HTTPService();
communitiesBloc = CommunitiesBloc( communitiesBloc = CommunitiesBloc(
communitiesService: DebouncedCommunitiesService( communitiesService: DebouncedCommunitiesService(
RemoteCommunitiesService(HTTPService()), RemoteCommunitiesService(_httpService),
), ),
)..add(const LoadCommunities(LoadCommunitiesParam())); )..add(const LoadCommunities(LoadCommunitiesParam()));
super.initState(); super.initState();
} }
@ -50,13 +53,18 @@ class _SpaceManagementPageState extends State<SpaceManagementPage> {
BlocProvider( BlocProvider(
create: (context) => SpaceDetailsBloc( create: (context) => SpaceDetailsBloc(
UniqueSpaceDetailsSpacesDecoratorService( UniqueSpaceDetailsSpacesDecoratorService(
RemoteSpaceDetailsService(httpService: HTTPService()), RemoteSpaceDetailsService(httpService: _httpService),
), ),
), ),
), ),
BlocProvider( BlocProvider(
create: (context) => ProductsBloc( create: (context) => ProductsBloc(
RemoteProductsService(HTTPService()), RemoteProductsService(_httpService),
),
),
BlocProvider(
create: (context) => ReorderSpacesBloc(
RemoteReorderSpacesService(_httpService),
), ),
), ),
], ],

View File

@ -11,6 +11,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/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/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/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/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -164,6 +166,16 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
context.read<CommunitiesBloc>().add( context.read<CommunitiesBloc>().add(
CommunitiesUpdateCommunity(newCommunity), CommunitiesUpdateCommunity(newCommunity),
); );
context.read<ReorderSpacesBloc>().add(
ReorderSpacesEvent(
ReorderSpacesParam(
communityUuid: widget.community.uuid,
parentSpaceUuid: data.parent?.uuid ?? '',
spaces: children,
),
),
);
} }
void _onSpaceTapped(SpaceModel? space) { void _onSpaceTapped(SpaceModel? space) {
@ -245,6 +257,13 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
final levelXOffset = <int, double>{}; final levelXOffset = <int, double>{};
_calculateLayout(community.spaces, 0, levelXOffset); _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 selectedSpace = widget.selectedSpace;
final highlightedUuids = <String>{}; final highlightedUuids = <String>{};
if (selectedSpace != null) { if (selectedSpace != null) {
@ -262,7 +281,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
community: widget.community, community: widget.community,
); );
final createButtonX = levelXOffset[0] ?? 0.0; final createButtonX = (levelXOffset[0] ?? 0.0) + horizontalCanvasPadding;
const createButtonY = 0.0; const createButtonY = 0.0;
widgets.add( widgets.add(
@ -294,10 +313,12 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
CommunityModel? community, CommunityModel? community,
SpaceModel? parent, SpaceModel? parent,
}) { }) {
const targetWidth = 40.0;
final padding = (_horizontalSpacing - targetWidth) / 2;
if (spaces.isNotEmpty) { if (spaces.isNotEmpty) {
final firstChildPos = _positions[spaces.first.uuid]!; final firstChildPos = _positions[spaces.first.uuid]!;
final targetPos = Offset( final targetPos = Offset(
firstChildPos.dx - (_horizontalSpacing / 4), firstChildPos.dx - padding - targetWidth,
firstChildPos.dy, firstChildPos.dy,
); );
widgets.add(_buildDropTarget(parent, community, 0, targetPos)); widgets.add(_buildDropTarget(parent, community, 0, targetPos));
@ -379,7 +400,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
); );
final targetPos = Offset( final targetPos = Offset(
position.dx + cardWidth + (_horizontalSpacing / 4) - 20, position.dx + cardWidth + padding,
position.dy, position.dy,
); );
widgets.add(_buildDropTarget(parent, community, i + 1, targetPos)); widgets.add(_buildDropTarget(parent, community, i + 1, targetPos));
@ -414,24 +435,33 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
child: DragTarget<SpaceReorderDataModel>( child: DragTarget<SpaceReorderDataModel>(
builder: (context, candidateData, rejectedData) { builder: (context, candidateData, rejectedData) {
if (_draggedData == null) { if (_draggedData == null) {
return const SizedBox(); return const SizedBox.shrink();
} }
final isTargetForDragged = (_draggedData?.parent?.uuid == parent?.uuid && final children = parent?.children ?? community?.spaces ?? [];
_draggedData?.community == null) || final isSameParent = (_draggedData!.parent?.uuid == parent?.uuid &&
(_draggedData?.community?.uuid == community?.uuid && _draggedData!.community == null) ||
_draggedData?.parent == null); (_draggedData!.community?.uuid == community?.uuid &&
_draggedData!.parent == null);
if (!isTargetForDragged) { if (!isSameParent) {
return const SizedBox(); 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, width: 40,
alignment: Alignment.center,
height: _cardHeight, height: _cardHeight,
decoration: BoxDecoration( decoration: BoxDecoration(
color: context.theme.colorScheme.primary.withValues( color: context.theme.colorScheme.primary.withValues(
alpha: candidateData.isNotEmpty ? 0.7 : 0.3, alpha: candidateData.isNotEmpty ? 0.9 : 0.3,
), ),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
@ -454,6 +484,9 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
final oldIndex = final oldIndex =
children.indexWhere((s) => s.uuid == data.data.space.uuid); children.indexWhere((s) => s.uuid == data.data.space.uuid);
if (oldIndex == -1) {
return true;
}
if (oldIndex == index || oldIndex == index - 1) { if (oldIndex == index || oldIndex == index - 1) {
return false; return false;
} }
@ -481,7 +514,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
child: SizedBox( child: SizedBox(
width: context.screenWidth * 5, width: context.screenWidth * 5,
height: context.screenHeight * 5, height: context.screenHeight * 5,
child: Stack(children: treeWidgets), child: Stack(clipBehavior: Clip.none, children: treeWidgets),
), ),
), ),
); );

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) { ).then((resultSpace) {
if (resultSpace != null) { if (resultSpace != null) {
if (context.mounted) { 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.ThreeTouch => Assets.gangSwitch,
DeviceType.NCPS => Assets.sensors, DeviceType.NCPS => Assets.sensors,
DeviceType.PC => Assets.powerClamp, DeviceType.PC => Assets.powerClamp,
DeviceType.fourSceen => Assets.fourSceenSwitch,
DeviceType.sixSceen => Assets.sixSceenSwitch,
DeviceType.SOS => Assets.sos,
DeviceType.Other => Assets.blackLogo, DeviceType.Other => Assets.blackLogo,
null => Assets.blackLogo, null => Assets.blackLogo,
}; };

View File

@ -31,173 +31,178 @@ class AssignTagsTable extends StatelessWidget {
DataColumn _buildDataColumn(BuildContext context, String label) { DataColumn _buildDataColumn(BuildContext context, String label) {
return DataColumn( 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider<TagsBloc>( return BlocProvider<TagsBloc>(
create: (BuildContext context) => TagsBloc( create: (context) => TagsBloc(
RemoteTagsService(HTTPService()), RemoteTagsService(HTTPService()),
)..add(const LoadTags()), )..add(const LoadTags()),
child: BlocBuilder<TagsBloc, TagsState>( child: BlocBuilder<TagsBloc, TagsState>(
builder: (context, state) { builder: (context, state) => switch (state) {
return switch (state) { TagsLoading() || TagsInitial() => const Center(
TagsLoading() || TagsInitial() => const Center( child: CircularProgressIndicator(),
child: CircularProgressIndicator(), ),
), TagsFailure(:final message) => Center(
TagsFailure(:final message) => Center( child: Text(message),
child: Text(message), ),
), TagsLoaded(:final tags) => ClipRRect(
TagsLoaded(:final tags) => ClipRRect( borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), child: DataTable(
child: DataTable( headingRowColor: WidgetStateProperty.all(
headingRowColor: WidgetStateProperty.all( ColorsManager.dataHeaderGrey,
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);
},
)),
),
],
);
}),
), ),
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; tempIcon = Assets.curtainIcon;
} else if (type == DeviceType.Curtain) { } else if (type == DeviceType.Curtain) {
tempIcon = Assets.curtainIcon; 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 { } else {
tempIcon = Assets.blackLogo; tempIcon = Assets.blackLogo;
} }

View File

@ -4,9 +4,7 @@ import 'package:syncrow_web/services/api/http_interceptor.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
final GetIt serviceLocator = GetIt.instance; final GetIt serviceLocator = GetIt.instance;
//setupLocator() // to search for dependency injection in flutter void initialSetup() {
initialSetup() {
serviceLocator.registerSingleton<HTTPInterceptor>(HTTPInterceptor()); serviceLocator.registerSingleton<HTTPInterceptor>(HTTPInterceptor());
//Base classes
serviceLocator.registerSingleton<Dio>(HTTPService.setupDioClient()); serviceLocator.registerSingleton<Dio>(HTTPService.setupDioClient());
} }

53
lib/syncrow_app.dart Normal file
View File

@ -0,0 +1,53 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.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/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/utils/app_routes.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
import 'package:syncrow_web/utils/theme/theme.dart';
class SyncrowApp extends StatelessWidget {
const SyncrowApp({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(),
),
BlocProvider(
create: (context) => HomeBloc()..add(const FetchUserInfo()),
),
BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(),
),
BlocProvider<RoutineBloc>(
create: (context) => RoutineBloc(),
),
BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc(),
),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
},
),
key: NavigationService.navigatorKey,
theme: myTheme,
routerConfig: AppRoutes.router,
),
);
}
}

View File

@ -1,17 +1,31 @@
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:syncrow_web/pages/access_management/view/access_management.dart'; import 'package:syncrow_web/pages/access_management/view/access_management.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/views/analytics_page.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/views/analytics_page.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/auth/view/login_page.dart'; import 'package:syncrow_web/pages/auth/view/login_page.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart';
import 'package:syncrow_web/pages/home/view/home_page.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart';
import 'package:syncrow_web/pages/roles_and_permission/view/roles_and_permission_page.dart'; import 'package:syncrow_web/pages/roles_and_permission/view/roles_and_permission_page.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/view/spaces_management_page.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/views/space_management_page.dart';
import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart';
class AppRoutes { abstract final class AppRoutes {
static List<GoRoute> getRoutes() { const AppRoutes._();
return [
static final GoRouter router = GoRouter(
initialLocation: RoutesConst.auth,
redirect: (context, state) async {
final checkToken = await AuthBloc.getTokenAndValidate();
final loggedIn = checkToken == 'Success';
final goingToLogin = state.uri.toString() == RoutesConst.auth;
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
if (loggedIn && goingToLogin) return RoutesConst.home;
return null;
},
routes: [
GoRoute( GoRoute(
path: RoutesConst.auth, path: RoutesConst.auth,
builder: (context, state) => const LoginPage(), builder: (context, state) => const LoginPage(),
@ -43,6 +57,6 @@ class AppRoutes {
name: 'analytics', name: 'analytics',
builder: (context, state) => const AnalyticsPage(), builder: (context, state) => const AnalyticsPage(),
), ),
]; ],
} );
} }

View File

@ -38,6 +38,7 @@ abstract class ColorsManager {
static const Color lightGrayColor = Color(0xB2999999); static const Color lightGrayColor = Color(0xB2999999);
static const Color grayBorder = Color(0xFFCFCFCF); static const Color grayBorder = Color(0xFFCFCFCF);
static const Color textGray = Color(0xffD5D5D5); static const Color textGray = Color(0xffD5D5D5);
static const Color titleGray = Color(0xB2999999);
static const Color btnColor = Color(0xFF00008B); static const Color btnColor = Color(0xFF00008B);
static const Color blueColor = Color(0xFF0036E6); static const Color blueColor = Color(0xFF0036E6);
static const Color boxColor = Color(0xFFF5F6F7); static const Color boxColor = Color(0xFFF5F6F7);
@ -84,10 +85,15 @@ abstract class ColorsManager {
static const Color minBlue = Color(0xFF93AAFD); static const Color minBlue = Color(0xFF93AAFD);
static const Color minBlueDot = Color(0xFF023DFE); static const Color minBlueDot = Color(0xFF023DFE);
static const Color grey25 = Color(0xFFF9F9F9); 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 grey50 = Color(0xFF718096);
static const Color red100 = Color(0xFFFE0202); static const Color red100 = Color(0xFFFE0202);
static const Color grey800 = Color(0xffF8F8F8); static const Color grey800 = Color(0xffF8F8F8);
static const Color shadowOfSearchTextfield = Color(0x26000000); static const Color shadowOfSearchTextfield = Color(0x26000000);
static const Color hintTextGrey = Colors.grey; static const Color hintTextGrey = Colors.grey;
static const Color shadowOfDetailsContainer = Color(0x40000000); 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}'; '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}';
static const String getSpaceHierarchy = static const String getSpaceHierarchy =
'/projects/{projectId}/communities/{communityId}/spaces'; '/projects/{projectId}/communities/{communityId}/spaces';
static const String reorderSpaces =
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{parentSpaceUuid}/spaces/order';
// Community Module // Community Module
static const String createCommunity = '/projects/{projectId}/communities'; static const String createCommunity = '/projects/{projectId}/communities';
@ -140,6 +142,7 @@ abstract class ApiEndpoints {
static const String saveSchedule = '/schedule/{deviceUuid}'; static const String saveSchedule = '/schedule/{deviceUuid}';
////booking System ////booking System
static const String bookableSpaces = '/bookable-spaces'; static const String bookableSpaces = '/bookable-spaces';
static const String getCalendarEvents = '/api'; static const String getCalendarEvents = '/api';

View File

@ -4,6 +4,8 @@ class Assets {
static const String webBackground = 'assets/images/web_Background.svg'; static const String webBackground = 'assets/images/web_Background.svg';
static const String webBackgroundPng = 'assets/images/web_Background.png'; static const String webBackgroundPng = 'assets/images/web_Background.png';
static const String blackLogo = 'assets/images/black-logo.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 logo = 'assets/images/Logo.svg';
static const String logoHorizontal = 'assets/images/logo_horizontal.png'; static const String logoHorizontal = 'assets/images/logo_horizontal.png';
static const String vector = 'assets/images/Vector.png'; static const String vector = 'assets/images/Vector.png';

View File

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