Compare commits

..

75 Commits

Author SHA1 Message Date
72af55ef98 Add booking page and related routes, icons, and button widget 2025-07-02 15:50:46 +03:00
b888f516e2 Fix device status display in Control modal to reflect actual status (#337)
<!--
  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 -->
table enhancement

## 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-02 11:28:50 +03:00
c1e61ee61d [FE] Community and Space Dialog Redesign in the routine tab (#336)
<!--
  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

i made the fixes requested by Yazan

## 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-02 11:21:34 +03:00
7750290be4 Fix device status display in Control modal to reflect actual status 2025-07-02 10:14:54 +03:00
7f26c773a7 [FE] Preferences & Calibration (#332)
<!--
  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-1707](https://syncrow.atlassian.net/browse/SP-1707)

## Description

change the color of completed dialog

## 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-1707]:
https://syncrow.atlassian.net/browse/SP-1707?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-02 09:05:44 +03:00
1adbae6735 Clarification on Default Value for Start Date in Door Lock Online Tile Limited Password repeat section (#331)
…t with dialog information showing the error and if the init start date
is null fill it with the needed value

<!--
  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-368](https://syncrow.atlassian.net/browse/SP-368)

## Description

now if user change end time into value before start time it prevent it
and give init start date value to start date

## 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-368]:
https://syncrow.atlassian.net/browse/SP-368?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-02 08:58:47 +03:00
ede2da6632 hot fix thermostat string (#334)
<!--
  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
no ticket

## Description

hot fix thermostat string

## 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-02 08:32:48 +03:00
b06e4bd2ba hot fix thermostat string 2025-07-02 08:27:09 +03:00
0847cb8a41 fix UI 2025-07-02 08:19:56 +03:00
818bdee745 change calibration completed dialog color 2025-07-01 15:04:50 +03:00
0a022d8a8d [FE] On devices management page when we search for a device then select a space that has devices and try to search again it does not work (#327)
<!--
  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-1805](https://syncrow.atlassian.net/browse/SP-1805)

## Description

should reset filters when selecting any community 

## 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-1805]:
https://syncrow.atlassian.net/browse/SP-1805?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-01 11:34:12 +03:00
f33b3e8bd2 now if user change end time into value before start time it prevent it with dialog information showing the error and if the init start date is null fill it with the needed value 2025-07-01 11:19:35 +03:00
8f0eb88567 remove countdownRemaining from ScheduleLoaded state (#330)
<!--
  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
remove countdownRemaining from ScheduleLoaded state
<!--- Describe your changes in detail -->

## 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-01 11:18:17 +03:00
19739c6e4d Add EnergyConsumptionPage to SmartPowerDeviceControl for enhanced ene… (#329)
…rgy data visualization

<!--
  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 -->
The phases and the chart should be synced.

## 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-01 11:18:00 +03:00
9f86b8d638 remove countdownRemaining from ScheduleLoaded state 2025-07-01 11:14:02 +03:00
95907661d2 align bar charts to start. (#328)
<!--
  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

Align AQI Distribution, and Occupancy charts bars to start.

## Type of Change

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

- [ ]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [x]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-07-01 10:44:20 +03:00
9c9b7d99dc enhance sizing of energy management view. 2025-07-01 09:55:38 +03:00
037895844a Add EnergyConsumptionPage to SmartPowerDeviceControl for enhanced energy data visualization 2025-07-01 09:44:59 +03:00
c07bae5cbc align bar charts to start. 2025-07-01 09:32:00 +03:00
e6fe9f35b0 problem fixed should reset filters when select space or community 2025-07-01 08:41:38 +03:00
8cb6c13cd5 Changed timer codes in curtain module to match what the API expects. 2025-06-30 15:53:23 +03:00
949c27938a Analytics empty state (#325)
<!--
  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-0000](https://syncrow.atlassian.net/browse/SP-0000)

## Description

<!--- Describe your changes in detail -->

## Type of Change

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

- [ ]  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
2025-06-30 15:32:12 +03:00
4c582b865d Sp 1611 in user management if email address already exists the error message does not go away until the user clicks next the error message should clear if a good email is entered (#321)
<!--
  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-1611
](https://syncrow.atlassian.net/jira/software/projects/SP/boards/5?assignee=712020%3A71e88a7f-7752-44b3-8177-4ab51a950811&selectedIssue=SP-1611)

## Description

use textfield validator on chaging value not only with next button 

## 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-06-30 15:31:04 +03:00
d7467adeda Add countdown functionality and device type support across device man… (#323)
…agement views

<!--
  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 -->

## 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-06-30 15:22:28 +03:00
5486f0832d Remove unused copyWith method from Status class and simplify status assignment in ScheduleBloc 2025-06-30 15:22:02 +03:00
fd239a3907 Merge branch 'dev' of https://github.com/SyncrowIOT/web into fix-schedule 2025-06-30 15:17:37 +03:00
e2d6f5eea8 Update device type from '1GT' to '2GT' in TwoGangGlassSwitchControlView 2025-06-30 15:11:17 +03:00
289922071a Add countdown functionality and device type support across device management views 2025-06-30 15:05:59 +03:00
8594168548 hardcoded device location to dubai for demo purposes. 2025-06-30 14:22:54 +03:00
bd9a74b380 fix touch gangs realtime. 2025-06-30 13:58:10 +03:00
15ee79688d reComite 2025-06-30 13:36:52 +03:00
e5e88385e9 change autoValidae mode to userInteraction and give some time to check validate when typing on keyboard with debouncer 2025-06-30 13:31:38 +03:00
62d5bbce7e add isValid to basic step (1) and insure that user can go to another step using next button without filling the form 2025-06-30 13:22:04 +03:00
05d784ec11 Merge branch 'dev' of https://github.com/SyncrowIOT/web into analytics-empty-state 2025-06-30 12:53:42 +03:00
9ebf474a60 analytics-empty-state. 2025-06-30 12:52:22 +03:00
af48bbead5 fix-occupancy-devices-bug (#318)
<!--
  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

handle more cases when decoding analytics devices.

## 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-06-30 11:28:38 +03:00
3c80724c1e Sp 1707 fe preferences calibration fix UI (#317)
<!--
  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-1707](https://syncrow.atlassian.net/browse/SP-1707)

## Description

fix the UI as wanted in Figma 

## 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-1707]:
https://syncrow.atlassian.net/browse/SP-1707?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-30 11:21:26 +03:00
db05331e9a update AnalyticsChartEmptyStateWidget to use new icons. 2025-06-30 11:18:08 +03:00
cdc76c2c8e handle more cases when decoding analytics devices. 2025-06-30 11:04:22 +03:00
44c88fb1c4 added empty charts icons. 2025-06-30 10:54:33 +03:00
dfb120e7cf [FE] Smart curtain module device Icon and other devices icons are appearing as a sensor Icon on Add device dialog on space management (#316)
<!--
  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-1804](https://syncrow.atlassian.net/browse/SP-1804)

## Description

add the new product types and map to the icons

## 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-1804]:
https://syncrow.atlassian.net/browse/SP-1804?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-30 10:47:37 +03:00
8c3861e83c fix the button border Raduis when hovering 2025-06-30 10:44:16 +03:00
b90f25f7b0 fix all UI notes make white dialogs with fix calibrating textfield for seconds && fix icon of completed action dialog 2025-06-30 10:39:23 +03:00
4d51321675 add the new devices to mapIconToProduct func 2025-06-30 10:05:15 +03:00
b5e7776ccb Sp 1705 fe create scheduling UI fixes (#315)
<!--
  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-1705](https://syncrow.atlassian.net/browse/SP-1705)

## Description

i fixed edit and insure integrating with backend for schedule and remove
countdown from UI if category cur module

## 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-1705]:
https://syncrow.atlassian.net/browse/SP-1705?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-30 09:34:11 +03:00
32938404dd PR requested changes 2025-06-30 09:28:09 +03:00
0cfd58d820 fix to send fit data to integrate with API (was true and false)now cur module send close and open with control key 2025-06-30 08:56:42 +03:00
d4625a8f04 fix edit to accept string of cur module 2025-06-30 08:45:18 +03:00
9f24606613 Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1705-fe-create-scheduling-ui-fixes 2025-06-30 08:28:40 +03:00
e87dffd76b when it is CUR module there is no countdown and other selector 2025-06-30 08:28:19 +03:00
0c220a1f34 [FE] UI Enhancement: Update Confirmation Dialog on "Create Visitor Password" Flow (#310)
… the requested ticket)

<!--
  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-1660](https://syncrow.atlassian.net/browse/SP-1660)

## Description
enhance UI in create visitor insure dialog as wanted in Ticket (in figma
not updated yet)

## 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-1660]:
https://syncrow.atlassian.net/browse/SP-1660?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-29 16:40:15 +03:00
a526fcbeee Merge branch 'dev' into SP-1660-fe-ui-enhancement-update-confirmation-dialog-on-create-visitor-password-flow 2025-06-29 16:10:43 +03:00
172e1d208a [FE] Preferences & Calibration (#312)
<!--
  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-1707](https://syncrow.atlassian.net/browse/SP-1707)

## Description

fix UI  like in figma

## 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-1707]:
https://syncrow.atlassian.net/browse/SP-1707?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-29 16:10:00 +03:00
2c254c1a91 Sp 1771 fe device name and subspace changes not reflected immediately after update on device management page (#313)
<!--
  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-1771](https://syncrow.atlassian.net/browse/SP-1771)

## Description

Synced state between settings and 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 


[SP-1771]:
https://syncrow.atlassian.net/browse/SP-1771?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-29 16:09:37 +03:00
480e183b91 Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1771-FE-Device-name-and-subspace-changes-not-reflected-immediately-after-update-on-Device-Management-page 2025-06-29 16:02:24 +03:00
d8bb234537 SP-1771 2025-06-29 16:00:15 +03:00
354d61dfa2 UI Enhancement 2025-06-29 15:50:37 +03:00
8916efcebb fixed aqi filter bugs. 2025-06-29 15:39:30 +03:00
175d1e662b Revert "Sp 1589 fe when user navigates to devices page the devices ar… (#311)
…e already listed although no community is selected also when we select
a community the api is being called repeatedly too many times (#305)"

This reverts commit 034a5ef908, reversing
changes made to b97183fb61.

<!--
  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-1589](https://syncrow.atlassian.net/browse/SP-1589)

## Description

revert sp:1589 cuz have problems in routin

## Type of Change

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

- [ ]  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-1589]:
https://syncrow.atlassian.net/browse/SP-1589?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-29 15:26:29 +03:00
57bd4b8527 Revert "Sp 1589 fe when user navigates to devices page the devices are already listed although no community is selected also when we select a community the api is being called repeatedly too many times (#305)"
This reverts commit 034a5ef908, reversing
changes made to b97183fb61.
2025-06-29 14:45:54 +03:00
df308fd12a Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1771-FE-Device-name-and-subspace-changes-not-reflected-immediately-after-update-on-Device-Management-page 2025-06-29 14:14:00 +03:00
e0cfe541dd name changes in table when changed. 2025-06-29 14:13:25 +03:00
814cbf787f edit the UI as wanted in ticket (note: in figma is not updated yet to the requested ticket) 2025-06-29 13:58:57 +03:00
df8eff895e [FE] Create Scheduling UI (#309)
and funtion name in dialog was olways keep close now it is really take
the real value

<!--
  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-1705](https://syncrow.atlassian.net/browse/SP-1705)

## Description
in curtain Module
fix edit issue and insure function name in  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 


[SP-1705]:
https://syncrow.atlassian.net/browse/SP-1705?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-29 13:14:08 +03:00
9514200892 sp:1728 [FE] Build Curtain Dialog Component (#307)
<!--
  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-1728](https://syncrow.atlassian.net/browse/SP-1728)

## Description

change the name  to curtain functions and conditions

## 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-1728]:
https://syncrow.atlassian.net/browse/SP-1728?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-29 13:13:47 +03:00
cf4bfc41f6 [FE] Disable AC Control Button When AC is Off (#308)
<!--
  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-1387](https://syncrow.atlassian.net/browse/SP-1387)

## Description

fix bug to dont stack snackbars

## 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-1387]:
https://syncrow.atlassian.net/browse/SP-1387?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-29 13:11:36 +03:00
01f55c14de Add update events for device and subspace names
implement copyWith methods in models
2025-06-29 13:03:33 +03:00
388391eec4 stop stacking snackbars 2025-06-29 11:29:43 +03:00
23cfee1490 fix curtain name in curtain if then containers dialogs 2025-06-29 11:12:28 +03:00
2f5ad03431 created empty charts widget. 2025-06-29 10:57:18 +03:00
6dd3329288 Sp 1703 fe build device overview page curtain module (#303)
<!--
  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-1703](https://syncrow.atlassian.net/browse/SP-1703)
[SP-1704](https://syncrow.atlassian.net/browse/SP-1704)
[SP-1706](https://syncrow.atlassian.net/browse/SP-1706)
[SP-1705](https://syncrow.atlassian.net/browse/SP-1705)
[SP-1707](https://syncrow.atlassian.net/browse/SP-1707)

## Description

all about curtain module (UI + logic + integrate with API +
control/batch)
all 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-1703]:
https://syncrow.atlassian.net/browse/SP-1703?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
[SP-1704]:
https://syncrow.atlassian.net/browse/SP-1704?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
[SP-1706]:
https://syncrow.atlassian.net/browse/SP-1706?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
[SP-1705]:
https://syncrow.atlassian.net/browse/SP-1705?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
[SP-1707]:
https://syncrow.atlassian.net/browse/SP-1707?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-29 10:54:14 +03:00
034a5ef908 Sp 1589 fe when user navigates to devices page the devices are already listed although no community is selected also when we select a community the api is being called repeatedly too many times (#305)
<!--
  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-1589](https://syncrow.atlassian.net/browse/SP-1589)

## Description

[SP-1589]now only one API made with needed filters

## 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-1589]:
https://syncrow.atlassian.net/browse/SP-1589?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
[SP-1589]:
https://syncrow.atlassian.net/browse/SP-1589?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-29 09:46:17 +03:00
1828ffb87a remove print statment 2025-06-29 09:17:57 +03:00
bd53388438 make one API with new QP to filter on spacesId 2025-06-29 09:17:22 +03:00
df34ded153 Add responsive input fields and radio groups for visitor password setup 2025-06-24 11:35:03 +03:00
85 changed files with 2504 additions and 1385 deletions

View File

@ -0,0 +1,8 @@
<svg width="175" height="134" viewBox="0 0 175 134" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="0.5" y1="2.18557e-08" x2="0.499994" y2="132.759" stroke="#B9C0C5"/>
<line x1="175" y1="133.259" x2="-4.37114e-08" y2="133.259" stroke="#B9C0C5"/>
<rect x="16.0922" y="66.3794" width="28.1609" height="66.3793" fill="#C7CDD1"/>
<rect x="54.3105" y="24.1379" width="28.1609" height="108.621" fill="#ABB4BA"/>
<rect x="92.5288" y="78.4484" width="28.1609" height="54.3103" fill="#C7CDD1"/>
<rect x="130.747" y="48.2759" width="28.1609" height="84.4828" fill="#ABB4BA"/>
</svg>

After

Width:  |  Height:  |  Size: 583 B

View File

@ -0,0 +1,5 @@
<svg width="175" height="134" viewBox="0 0 175 134" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="0.5" y1="3.05394e-05" x2="0.499994" y2="132.759" stroke="#B9C0C5"/>
<line x1="175" y1="133.259" x2="-4.37114e-08" y2="133.259" stroke="#B9C0C5"/>
<path d="M1.5 132.5C13 132.5 6.58852 66.5 29.5 66.5C46.5 66.5 46.1214 24.9349 68.5 24.5C94.2816 23.999 80.7136 78.5065 106.5 78.5C125.715 78.4952 131.5 48.5 145.5 48.5C159.5 48.5 156.5 96 171.5 96" stroke="#ABB4BA" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 520 B

View File

@ -0,0 +1,7 @@
<svg width="175" height="134" viewBox="0 0 175 134" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="0.5" y1="2.18557e-08" x2="0.499994" y2="132.759" stroke="#B9C0C5"/>
<line x1="175" y1="133.259" x2="-4.37114e-08" y2="133.259" stroke="#B9C0C5"/>
<path d="M1.5 132.5C13 132.5 6.58852 66.5 29.5 66.5C46.5 66.5 46.1214 24.9348 68.5 24.5C94.2816 23.999 80.7136 78.5064 106.5 78.5C125.715 78.4951 131.5 48.5 145.5 48.5C159.5 48.5 156.5 95.9999 171.5 95.9999" stroke="#ABB4BA" stroke-width="1.5" stroke-linecap="round" stroke-dasharray="4 4"/>
<path d="M1.5 132.5C13 132.5 6.58852 78.4999 29.5 78.4999C46.5 78.4999 45.6214 44.9349 68 44.5C93.7816 43.999 80.7136 27.0065 106.5 27C125.715 26.9952 131.5 63.5 145.5 63.5C159.5 63.5 156.5 113.5 171.5 113.5" stroke="#C7CDD1" stroke-width="1.5" stroke-linecap="round" stroke-dasharray="4 4"/>
<path d="M1.5 132.5C13 132.5 6.58852 85.9999 29.5 85.9999C46.5 85.9999 45.6214 11.9348 68 11.4999C93.7816 10.9989 80.7136 43.5064 106.5 43.4999C125.715 43.4951 131.5 35.4999 145.5 35.4999C159.5 35.4999 156.5 105.5 171.5 105.5" stroke="#D5D5D5" stroke-width="1.5" stroke-linecap="round" stroke-dasharray="4 4"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,99 @@
<svg width="181" height="121" viewBox="0 0 181 121" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="15.5" y1="-2.52181e-08" x2="15.5" y2="120" stroke="#B9B9B9"/>
<line x1="45.5" y1="-2.52181e-08" x2="45.5" y2="120" stroke="#B9B9B9"/>
<line x1="75.5" y1="-2.52181e-08" x2="75.5" y2="120" stroke="#B9B9B9"/>
<line x1="105.5" y1="-2.52181e-08" x2="105.5" y2="120" stroke="#B9B9B9"/>
<line x1="135.5" y1="-2.52181e-08" x2="135.5" y2="120" stroke="#B9B9B9"/>
<line x1="165.5" y1="-2.52181e-08" x2="165.5" y2="120" stroke="#B9B9B9"/>
<line x1="30.5" y1="-2.52181e-08" x2="30.5" y2="120" stroke="#B9B9B9"/>
<line x1="60.5" y1="-2.52181e-08" x2="60.5" y2="120" stroke="#B9B9B9"/>
<line x1="90.5" y1="-2.52181e-08" x2="90.5" y2="120" stroke="#B9B9B9"/>
<line x1="120.5" y1="-2.52181e-08" x2="120.5" y2="120" stroke="#B9B9B9"/>
<line x1="150.5" y1="-2.52181e-08" x2="150.5" y2="120" stroke="#B9B9B9"/>
<line x1="180.5" y1="-2.52181e-08" x2="180.5" y2="120" stroke="#B9B9B9"/>
<line x1="181" y1="120.5" x2="-4.52101e-08" y2="120.5" stroke="#B9B9B9"/>
<line x1="181" y1="60.5" x2="-4.52101e-08" y2="60.5" stroke="#B9B9B9"/>
<line x1="181" y1="90.5" x2="-4.52101e-08" y2="90.5" stroke="#B9B9B9"/>
<line x1="181" y1="30.5" x2="-4.52101e-08" y2="30.5" stroke="#B9B9B9"/>
<line x1="181" y1="105.5" x2="-4.52101e-08" y2="105.5" stroke="#B9B9B9"/>
<line x1="181" y1="45.5" x2="-4.52101e-08" y2="45.5" stroke="#B9B9B9"/>
<line x1="181" y1="75.5" x2="-4.52101e-08" y2="75.5" stroke="#B9B9B9"/>
<line x1="181" y1="15.5" x2="-4.52101e-08" y2="15.5" stroke="#B9B9B9"/>
<rect x="16" y="16" width="14" height="14" fill="#F2F2F2"/>
<rect x="31" y="16" width="14" height="14" fill="#F2F2F2"/>
<rect x="46" y="16" width="14" height="14" fill="#F2F2F2"/>
<rect x="61" y="16" width="14" height="14" fill="#F2F2F2"/>
<rect x="76" y="16" width="14" height="14" fill="#F2F2F2"/>
<rect x="91" y="16" width="14" height="14" fill="#F2F2F2"/>
<rect x="106" y="16" width="14" height="14" fill="#F2F2F2"/>
<rect x="121" y="16" width="14" height="14" fill="#F2F2F2"/>
<rect x="136" y="16" width="14" height="14" fill="#F2F2F2"/>
<rect x="151" y="16" width="14" height="14" fill="#F2F2F2"/>
<rect x="166" y="16" width="14" height="14" fill="#F2F2F2"/>
<rect x="16" y="31" width="14" height="14" fill="#F2F2F2"/>
<rect x="31" y="31" width="14" height="14" fill="#D5D5D5"/>
<rect x="46" y="31" width="14" height="14" fill="#D5D5D5"/>
<rect x="61" y="31" width="14" height="14" fill="#D5D5D5"/>
<rect x="76" y="31" width="14" height="14" fill="#D5D5D5"/>
<rect x="91" y="31" width="14" height="14" fill="#D5D5D5"/>
<rect x="106" y="31" width="14" height="14" fill="#D5D5D5"/>
<rect x="121" y="31" width="14" height="14" fill="#D5D5D5"/>
<rect x="136" y="31" width="14" height="14" fill="#D5D5D5"/>
<rect x="151" y="31" width="14" height="14" fill="#D5D5D5"/>
<rect x="166" y="31" width="14" height="14" fill="#F2F2F2"/>
<rect x="16" y="46" width="14" height="14" fill="#F2F2F2"/>
<rect x="31" y="46" width="14" height="14" fill="#D5D5D5"/>
<rect x="46" y="46" width="14" height="14" fill="#D5D5D5"/>
<rect x="61" y="46" width="14" height="14" fill="#D5D5D5"/>
<rect x="76" y="46" width="14" height="14" fill="#D5D5D5"/>
<rect x="91" y="46" width="14" height="14" fill="#D5D5D5"/>
<rect x="106" y="46" width="14" height="14" fill="#D5D5D5"/>
<rect x="121" y="46" width="14" height="14" fill="#D5D5D5"/>
<rect x="136" y="46" width="14" height="14" fill="#D5D5D5"/>
<rect x="151" y="46" width="14" height="14" fill="#D5D5D5"/>
<rect x="166" y="46" width="14" height="14" fill="#F2F2F2"/>
<rect x="16" y="61" width="14" height="14" fill="#F2F2F2"/>
<rect x="31" y="61" width="14" height="14" fill="#D5D5D5"/>
<rect x="46" y="61" width="14" height="14" fill="#D5D5D5"/>
<rect x="61" y="61" width="14" height="14" fill="#B1B1B1"/>
<rect x="76" y="61" width="14" height="14" fill="#B1B1B1"/>
<rect x="91" y="61" width="14" height="14" fill="#B1B1B1"/>
<rect x="106" y="61" width="14" height="14" fill="#B1B1B1"/>
<rect x="121" y="61" width="14" height="14" fill="#B1B1B1"/>
<rect x="136" y="61" width="14" height="14" fill="#D5D5D5"/>
<rect x="151" y="61" width="14" height="14" fill="#D5D5D5"/>
<rect x="166" y="61" width="14" height="14" fill="#F2F2F2"/>
<rect x="16" y="76" width="14" height="14" fill="#F2F2F2"/>
<rect x="31" y="76" width="14" height="14" fill="#D5D5D5"/>
<rect x="46" y="76" width="14" height="14" fill="#D5D5D5"/>
<rect x="61" y="76" width="14" height="14" fill="#D5D5D5"/>
<rect x="76" y="76" width="14" height="14" fill="#D5D5D5"/>
<rect x="91" y="76" width="14" height="14" fill="#D5D5D5"/>
<rect x="106" y="76" width="14" height="14" fill="#D5D5D5"/>
<rect x="121" y="76" width="14" height="14" fill="#D5D5D5"/>
<rect x="136" y="76" width="14" height="14" fill="#D5D5D5"/>
<rect x="151" y="76" width="14" height="14" fill="#D5D5D5"/>
<rect x="166" y="76" width="14" height="14" fill="#F2F2F2"/>
<rect x="16" y="91" width="14" height="14" fill="#F2F2F2"/>
<rect x="31" y="91" width="14" height="14" fill="#D5D5D5"/>
<rect x="46" y="91" width="14" height="14" fill="#D5D5D5"/>
<rect x="61" y="91" width="14" height="14" fill="#D5D5D5"/>
<rect x="76" y="91" width="14" height="14" fill="#D5D5D5"/>
<rect x="91" y="91" width="14" height="14" fill="#D5D5D5"/>
<rect x="106" y="91" width="14" height="14" fill="#D5D5D5"/>
<rect x="121" y="91" width="14" height="14" fill="#D5D5D5"/>
<rect x="136" y="91" width="14" height="14" fill="#D5D5D5"/>
<rect x="151" y="91" width="14" height="14" fill="#D5D5D5"/>
<rect x="166" y="91" width="14" height="14" fill="#F2F2F2"/>
<rect x="16" y="106" width="14" height="14" fill="#F2F2F2"/>
<rect x="31" y="106" width="14" height="14" fill="#F2F2F2"/>
<rect x="46" y="106" width="14" height="14" fill="#F2F2F2"/>
<rect x="61" y="106" width="14" height="14" fill="#F2F2F2"/>
<rect x="76" y="106" width="14" height="14" fill="#F2F2F2"/>
<rect x="91" y="106" width="14" height="14" fill="#F2F2F2"/>
<rect x="106" y="106" width="14" height="14" fill="#F2F2F2"/>
<rect x="121" y="106" width="14" height="14" fill="#F2F2F2"/>
<rect x="136" y="106" width="14" height="14" fill="#F2F2F2"/>
<rect x="151" y="106" width="14" height="14" fill="#F2F2F2"/>
<rect x="166" y="106" width="14" height="14" fill="#F2F2F2"/>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -0,0 +1,7 @@
<svg width="175" height="134" viewBox="0 0 175 134" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="0.5" y1="2.18557e-08" x2="0.499994" y2="132.759" stroke="#B9C0C5"/>
<line x1="175" y1="133.259" x2="-4.37114e-08" y2="133.259" stroke="#B9C0C5"/>
<path d="M1.5 95.9999C13 95.9999 6.58853 66.4999 29.5 66.4999C46.5 66.4999 46.1214 34.9348 68.5 34.4999C94.2816 33.9989 80.7136 65.0065 106.5 65C125.715 64.9952 131.5 50.5 145.5 50.5C159.5 50.5 156.5 70.5 171.5 70.5" stroke="#C7CDD1" stroke-width="2" stroke-linecap="round"/>
<path d="M1.5 106C13 106 6.58853 76.4999 29.5 76.4999C46.5 76.4999 46.1214 44.9348 68.5 44.4999C94.2816 43.9989 80.7136 75.0065 106.5 75C125.715 74.9952 131.5 60.5 145.5 60.5C159.5 60.5 156.5 80.5 171.5 80.5" stroke="#F2F2F2" stroke-width="2" stroke-linecap="round"/>
<path d="M1.5 116C13 116 6.58853 86.4999 29.5 86.4999C46.5 86.4999 46.1214 54.9348 68.5 54.4999C94.2816 53.9989 80.7136 85.0065 106.5 85C125.715 84.9952 131.5 70.5 145.5 70.5C159.5 70.5 156.5 90.5 171.5 90.5" stroke="#ABB4BA" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,15 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_9717_7433)">
<path d="M17.1131 10.6766H15.5664C15.7241 11.1083 15.8102 11.5741 15.8102 12.0596V17.9053C15.8102 18.1077 15.775 18.302 15.7109 18.4827H18.2679C19.2231 18.4827 20.0002 17.7056 20.0002 16.7505V13.5637C20.0002 11.9718 18.7051 10.6766 17.1131 10.6766Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M4.19005 12.0596C4.19005 11.5741 4.27618 11.1083 4.43384 10.6766H2.88712C1.29516 10.6766 0 11.9718 0 13.5637V16.7505C0 17.7057 0.777072 18.4828 1.73227 18.4828H4.28938C4.22528 18.302 4.19005 18.1077 4.19005 17.9053V12.0596Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M11.7679 9.17249H8.23184C6.63989 9.17249 5.34473 10.4676 5.34473 12.0596V17.9053C5.34473 18.2242 5.60324 18.4827 5.92215 18.4827H14.0776C14.3965 18.4827 14.655 18.2242 14.655 17.9053V12.0596C14.655 10.4676 13.3598 9.17249 11.7679 9.17249Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M9.99995 1.51721C8.08541 1.51721 6.52783 3.07479 6.52783 4.98937C6.52783 6.288 7.24459 7.42218 8.30311 8.01765C8.80518 8.30008 9.38401 8.46148 9.99995 8.46148C10.6159 8.46148 11.1947 8.30008 11.6968 8.01765C12.7553 7.42218 13.4721 6.28796 13.4721 4.98937C13.4721 3.07483 11.9145 1.51721 9.99995 1.51721Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M3.90284 4.75354C2.471 4.75354 1.30615 5.91839 1.30615 7.35022C1.30615 8.78206 2.471 9.94691 3.90284 9.94691C4.26604 9.94691 4.6119 9.87168 4.92608 9.73644C5.46929 9.50257 5.91718 9.08859 6.19433 8.57003C6.38886 8.20609 6.49952 7.79089 6.49952 7.35022C6.49952 5.91843 5.33468 4.75354 3.90284 4.75354Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M16.0972 4.75354C14.6653 4.75354 13.5005 5.91839 13.5005 7.35022C13.5005 7.79093 13.6112 8.20612 13.8057 8.57003C14.0828 9.08863 14.5307 9.50261 15.0739 9.73644C15.3881 9.87168 15.734 9.94691 16.0972 9.94691C17.529 9.94691 18.6939 8.78206 18.6939 7.35022C18.6939 5.91839 17.529 4.75354 16.0972 4.75354Z" fill="#023DFE" fill-opacity="0.7"/>
</g>
<defs>
<clipPath id="clip0_9717_7433">
<rect width="20" height="20" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0002 5.97498L3.12109 11.2683V18.3601H8.64871V13.163H11.5852V18.3601H16.8794V11.2683L10.0002 5.97498Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M17.1673 7.15356V3.52759H14.2702V4.92485L10 1.63989L0 9.33274L1.38043 11.1271L10 4.49458L18.6196 11.1272L20 9.33278L17.1673 7.15356Z" fill="#023DFE" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 433 B

View File

@ -0,0 +1,4 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M37.8033 16.3567C37.2313 15.7848 36.3039 15.7847 35.7318 16.3568L21.5532 30.5353L14.2681 23.2504C13.6962 22.6784 12.7686 22.6783 12.1966 23.2505C11.6246 23.8226 11.6246 24.75 12.1966 25.3221L20.5174 33.6427C20.8034 33.9287 21.1783 34.0717 21.5531 34.0717C21.928 34.0717 22.3029 33.9287 22.5888 33.6426L37.8033 18.4283C38.3754 17.8563 38.3754 16.9288 37.8033 16.3567Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M42.6776 7.32236C37.9558 2.60049 31.6776 0 25 0C18.3223 0 12.0442 2.60049 7.32236 7.32236C2.60039 12.0443 0 18.3224 0 25C0 31.6778 2.60039 37.9559 7.32236 42.6777C12.0441 47.3996 18.3223 50 25 50C31.6777 50 37.9558 47.3996 42.6776 42.6777C47.3995 37.9559 50 31.6778 50 25C50 18.3224 47.3995 12.0443 42.6776 7.32236ZM25 47.0703C12.8304 47.0703 2.92969 37.1696 2.92969 25C2.92969 12.8304 12.8304 2.92969 25 2.92969C37.1696 2.92969 47.0703 12.8304 47.0703 25C47.0703 37.1696 37.1696 47.0703 25 47.0703Z" fill="#023DFE" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/access_management/booking_system/view/widgets/icon_text_button.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class BookingPage extends StatelessWidget {
const BookingPage({super.key});
@override
Widget build(BuildContext context) {
return Container(
child: 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: () {}),
SizedBox(width: 20),
SvgTextButton(
svgAsset: Assets.groupIcon,
label: 'Manage Users',
onPressed: () {})
],
)
],
),
),
))
],
),
);
}
}

View File

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class SvgTextButton extends StatelessWidget {
final String svgAsset;
final String label;
final VoidCallback onPressed;
final Color backgroundColor;
final Color svgColor;
final Color labelColor;
final double borderRadius;
final List<BoxShadow> boxShadow;
final double svgSize;
const SvgTextButton({
super.key,
required this.svgAsset,
required this.label,
required this.onPressed,
this.backgroundColor = ColorsManager.circleRolesBackground,
this.svgColor = const Color(0xFF496EFF),
this.labelColor = Colors.black87,
this.borderRadius = 10.0,
this.boxShadow = const [
BoxShadow(
color: ColorsManager.textGray,
blurRadius: 12,
offset: Offset(0, 4),
),
],
this.svgSize = 24.0,
});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(borderRadius),
onTap: onPressed,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(borderRadius),
boxShadow: boxShadow,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
svgAsset,
width: svgSize,
height: svgSize,
color: svgColor,
),
const SizedBox(width: 12),
Text(
label,
style: TextStyle(
color: labelColor,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
);
}
}

View File

@ -2,302 +2,86 @@ 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/bloc/access_state.dart'; import 'package:syncrow_web/pages/access_management/booking_system/view/booking_page.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/access_management/view/access_overview_content.dart';
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
import 'package:syncrow_web/pages/common/custom_table.dart';
import 'package:syncrow_web/pages/common/date_time_widget.dart';
import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.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/pages/visitor_password/view/visitor_password_dialog.dart';
// import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { class AccessManagementPage extends StatefulWidget {
const AccessManagementPage({super.key}); const AccessManagementPage({super.key});
@override @override
Widget build(BuildContext context) { State<AccessManagementPage> createState() => _AccessManagementPageState();
final isLargeScreen = isLargeScreenSize(context); }
final isSmallScreen = isSmallScreenSize(context);
final isHalfMediumScreen = isHafMediumScreenSize(context);
final padding =
isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
return WebScaffold( class _AccessManagementPageState extends State<AccessManagementPage>
with HelperResponsiveLayout {
final PageController _pageController = PageController(initialPage: 0);
int _currentPageIndex = 0;
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => AccessBloc()..add(FetchTableData()),
child: WebScaffold(
enableMenuSidebar: false, enableMenuSidebar: false,
appBarTitle: Text( appBarTitle: Text(
'Access Management', 'Access Management',
style: ResponsiveTextTheme.of(context).deviceManagementTitle, style: ResponsiveTextTheme.of(context).deviceManagementTitle,
), ),
rightBody: const NavigateHomeGridView(), centerBody: Row(
scaffoldBody: BlocProvider( mainAxisSize: MainAxisSize.min,
create: (BuildContext context) => children: [
AccessBloc()..add(FetchTableData()), TextButton(
child: BlocConsumer<AccessBloc, AccessState>( onPressed: () => _switchPage(0),
listener: (context, state) {},
builder: (context, state) {
final accessBloc = BlocProvider.of<AccessBloc>(context);
final filteredData = accessBloc.filteredData;
return state is AccessLoaded
? const Center(child: CircularProgressIndicator())
: Container(
padding: padding,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FilterWidget(
size: MediaQuery.of(context).size,
tabs: accessBloc.tabs,
selectedIndex: accessBloc.selectedIndex,
onTabChanged: (index) {
accessBloc.add(TabChangedEvent(index));
},
),
const SizedBox(height: 20),
if (isSmallScreen || isHalfMediumScreen)
_buildSmallSearchFilters(context, accessBloc)
else
_buildNormalSearchWidgets(context, accessBloc),
const SizedBox(height: 20),
_buildVisitorAdminPasswords(context, accessBloc),
const SizedBox(height: 20),
Expanded(
child: DynamicTable(
tableName: 'AccessManagement',
uuidIndex: 1,
withSelectAll: true,
isEmpty: filteredData.isEmpty,
withCheckBox: false,
size: MediaQuery.of(context).size,
cellDecoration: containerDecoration,
headers: const [
'Name',
'Access Type',
'Access Start',
'Access End',
'Accessible Device',
'Authorizer',
'Authorization Date & Time',
'Access Status'
],
data: filteredData.map((item) {
return [
item.passwordName,
item.passwordType.value,
accessBloc
.timestampToDate(item.effectiveTime),
accessBloc
.timestampToDate(item.invalidTime),
item.deviceName.toString(),
item.authorizerEmail.toString(),
accessBloc
.timestampToDate(item.invalidTime),
item.passwordStatus.value,
];
}).toList(),
)),
],
),
);
})));
}
Wrap _buildVisitorAdminPasswords(
BuildContext context, AccessBloc accessBloc) {
return Wrap(
spacing: 10,
runSpacing: 10,
children: [
Container(
width: 205,
height: 42,
decoration: containerDecoration,
child: DefaultButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const VisitorPasswordDialog();
},
).then((v) {
if (v != null) {
accessBloc.add(FetchTableData());
}
});
},
borderRadius: 8,
child: Text( child: Text(
'Create Visitor Password ', 'Access Overview',
style: context.textTheme.titleSmall! style: context.textTheme.titleMedium?.copyWith(
.copyWith(color: Colors.white, fontSize: 12), color: _currentPageIndex == 0 ? Colors.white : Colors.grey,
)), fontWeight: _currentPageIndex == 0
? FontWeight.w700
: FontWeight.w400,
),
),
),
TextButton(
onPressed: () => _switchPage(1),
child: Text(
'Booking System',
style: context.textTheme.titleMedium?.copyWith(
color: _currentPageIndex == 1 ? Colors.white : Colors.grey,
fontWeight: _currentPageIndex == 1
? FontWeight.w700
: FontWeight.w400,
),
),
),
],
), ),
// Container( rightBody: const NavigateHomeGridView(),
// width: 133, scaffoldBody: PageView(
// height: 42, controller: _pageController,
// decoration: containerDecoration, physics: const NeverScrollableScrollPhysics(),
// child: DefaultButton( children: const [
// borderRadius: 8, AccessOverviewContent(),
// backgroundColor: ColorsManager.whiteColors, BookingPage(),
// child: Text( ],
// 'Admin Password', ),
// style: context.textTheme.titleSmall! ),
// .copyWith(color: Colors.black, fontSize: 12),
// )),
// ),
],
); );
} }
Row _buildNormalSearchWidgets(BuildContext context, AccessBloc accessBloc) { void _switchPage(int index) {
// TimeOfDay _selectedTime = TimeOfDay.now(); setState(() => _currentPageIndex = index);
_pageController.jumpToPage(index);
return Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
textBaseline: TextBaseline.ideographic,
children: [
SizedBox(
width: 250,
child: CustomWebTextField(
controller: accessBloc.passwordName,
height: 43,
isRequired: false,
textFieldName: 'Name',
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
),
),
const SizedBox(width: 15),
SizedBox(
width: 250,
child: CustomWebTextField(
controller: accessBloc.emailAuthorizer,
height: 43,
isRequired: false,
textFieldName: 'Authorizer',
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
),
),
const SizedBox(width: 15),
SizedBox(
child: DateTimeWebWidget(
icon: Assets.calendarIcon,
isRequired: false,
title: 'Access Time',
size: MediaQuery.of(context).size,
endTime: () {
accessBloc.add(SelectTime(context: context, isStart: false));
},
startTime: () {
accessBloc.add(SelectTime(context: context, isStart: true));
},
firstString: BlocProvider.of<AccessBloc>(context).startTime,
secondString: BlocProvider.of<AccessBloc>(context).endTime,
),
),
const SizedBox(width: 15),
SearchResetButtons(
onSearch: () {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
onReset: () {
accessBloc.add(ResetSearch());
},
),
],
);
}
Widget _buildSmallSearchFilters(BuildContext context, AccessBloc accessBloc) {
return Wrap(
spacing: 20,
runSpacing: 10,
children: [
SizedBox(
width: 300,
child: CustomWebTextField(
controller: accessBloc.passwordName,
isRequired: true,
height: 40,
textFieldName: 'Name',
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
}),
),
DateTimeWebWidget(
icon: Assets.calendarIcon,
isRequired: false,
title: 'Access Time',
size: MediaQuery.of(context).size,
endTime: () {
accessBloc.add(SelectTime(context: context, isStart: false));
},
startTime: () {
accessBloc.add(SelectTime(context: context, isStart: true));
},
firstString: BlocProvider.of<AccessBloc>(context).startTime,
secondString: BlocProvider.of<AccessBloc>(context).endTime,
),
SearchResetButtons(
onSearch: () {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
onReset: () {
accessBloc.add(ResetSearch());
},
),
],
);
} }
} }

View File

@ -0,0 +1,289 @@
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
import 'package:syncrow_web/pages/common/custom_table.dart';
import 'package:syncrow_web/pages/common/date_time_widget.dart';
import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart';
class AccessOverviewContent extends StatelessWidget
with HelperResponsiveLayout {
const AccessOverviewContent({super.key});
@override
Widget build(BuildContext context) {
final isLargeScreen = isLargeScreenSize(context);
final isSmallScreen = isSmallScreenSize(context);
final isHalfMediumScreen = isHafMediumScreenSize(context);
final padding =
isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
return BlocProvider(
create: (BuildContext context) => AccessBloc()..add(FetchTableData()),
child: BlocConsumer<AccessBloc, AccessState>(
listener: (context, state) {},
builder: (context, state) {
final accessBloc = BlocProvider.of<AccessBloc>(context);
final filteredData = accessBloc.filteredData;
return state is AccessLoaded
? const Center(child: CircularProgressIndicator())
: Container(
padding: padding,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FilterWidget(
size: MediaQuery.of(context).size,
tabs: accessBloc.tabs,
selectedIndex: accessBloc.selectedIndex,
onTabChanged: (index) {
accessBloc.add(TabChangedEvent(index));
},
),
const SizedBox(height: 20),
if (isSmallScreen || isHalfMediumScreen)
_buildSmallSearchFilters(context, accessBloc)
else
_buildNormalSearchWidgets(context, accessBloc),
const SizedBox(height: 20),
_buildVisitorAdminPasswords(context, accessBloc),
const SizedBox(height: 20),
Expanded(
child: DynamicTable(
tableName: 'AccessManagement',
uuidIndex: 1,
withSelectAll: true,
isEmpty: filteredData.isEmpty,
withCheckBox: false,
size: MediaQuery.of(context).size,
cellDecoration: containerDecoration,
headers: const [
'Name',
'Access Type',
'Access Start',
'Access End',
'Accessible Device',
'Authorizer',
'Authorization Date & Time',
'Access Status'
],
data: filteredData.map((item) {
return [
item.passwordName,
item.passwordType.value,
accessBloc.timestampToDate(item.effectiveTime),
accessBloc.timestampToDate(item.invalidTime),
item.deviceName.toString(),
item.authorizerEmail.toString(),
accessBloc.timestampToDate(item.invalidTime),
item.passwordStatus.value,
];
}).toList(),
)),
],
),
);
}));
}
Wrap _buildVisitorAdminPasswords(
BuildContext context, AccessBloc accessBloc) {
return Wrap(
spacing: 10,
runSpacing: 10,
children: [
Container(
width: 205,
height: 42,
decoration: containerDecoration,
child: DefaultButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const VisitorPasswordDialog();
},
).then((v) {
if (v != null) {
accessBloc.add(FetchTableData());
}
});
},
borderRadius: 8,
child: Text(
'Create Visitor Password ',
style: context.textTheme.titleSmall!
.copyWith(color: Colors.white, fontSize: 12),
)),
),
// Container(
// width: 133,
// height: 42,
// decoration: containerDecoration,
// child: DefaultButton(
// borderRadius: 8,
// backgroundColor: ColorsManager.whiteColors,
// child: Text(
// 'Admin Password',
// style: context.textTheme.titleSmall!
// .copyWith(color: Colors.black, fontSize: 12),
// )),
// ),
],
);
}
Row _buildNormalSearchWidgets(BuildContext context, AccessBloc accessBloc) {
// TimeOfDay _selectedTime = TimeOfDay.now();
return Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
textBaseline: TextBaseline.ideographic,
children: [
SizedBox(
width: 250,
child: CustomWebTextField(
controller: accessBloc.passwordName,
height: 43,
isRequired: false,
textFieldName: 'Name',
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
),
),
const SizedBox(width: 15),
SizedBox(
width: 250,
child: CustomWebTextField(
controller: accessBloc.emailAuthorizer,
height: 43,
isRequired: false,
textFieldName: 'Authorizer',
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
),
),
const SizedBox(width: 15),
SizedBox(
child: DateTimeWebWidget(
icon: Assets.calendarIcon,
isRequired: false,
title: 'Access Time',
size: MediaQuery.of(context).size,
endTime: () {
accessBloc.add(SelectTime(context: context, isStart: false));
},
startTime: () {
accessBloc.add(SelectTime(context: context, isStart: true));
},
firstString: BlocProvider.of<AccessBloc>(context).startTime,
secondString: BlocProvider.of<AccessBloc>(context).endTime,
),
),
const SizedBox(width: 15),
SearchResetButtons(
onSearch: () {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
onReset: () {
accessBloc.add(ResetSearch());
},
),
],
);
}
Widget _buildSmallSearchFilters(BuildContext context, AccessBloc accessBloc) {
return Wrap(
spacing: 20,
runSpacing: 10,
children: [
SizedBox(
width: 300,
child: CustomWebTextField(
controller: accessBloc.passwordName,
isRequired: true,
height: 40,
textFieldName: 'Name',
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
}),
),
DateTimeWebWidget(
icon: Assets.calendarIcon,
isRequired: false,
title: 'Access Time',
size: MediaQuery.of(context).size,
endTime: () {
accessBloc.add(SelectTime(context: context, isStart: false));
},
startTime: () {
accessBloc.add(SelectTime(context: context, isStart: true));
},
firstString: BlocProvider.of<AccessBloc>(context).startTime,
secondString: BlocProvider.of<AccessBloc>(context).endTime,
),
SearchResetButtons(
onSearch: () {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
onReset: () {
accessBloc.add(ResetSearch());
},
),
],
);
}
}

View File

@ -39,8 +39,12 @@ class AnalyticsDevice {
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>) ? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
: null, : null,
spaceUuid: json['spaceUuid'] as String?, spaceUuid: json['spaceUuid'] as String?,
latitude: json['lat'] != null ? double.parse(json['lat'] as String? ?? '0.0') : null, latitude: json['lat'] != null && json['lat'] != ''
longitude: json['lon'] != null ? double.parse(json['lon'] as String? ?? '0.0') : null, ? double.tryParse(json['lat']?.toString() ?? '0.0')
: null,
longitude: json['lon'] != null && json['lon'] != ''
? double.tryParse(json['lon']?.toString() ?? '0.0')
: null,
); );
} }
} }

View File

@ -46,11 +46,11 @@ class AirQualityDistributionBloc
} }
} }
Future<void> _onClearAirQualityDistribution( void _onClearAirQualityDistribution(
ClearAirQualityDistribution event, ClearAirQualityDistribution event,
Emitter<AirQualityDistributionState> emit, Emitter<AirQualityDistributionState> emit,
) async { ) {
emit(const AirQualityDistributionState()); emit(AirQualityDistributionState(selectedAqiType: state.selectedAqiType));
} }
void _onUpdateAqiTypeEvent( void _onUpdateAqiTypeEvent(

View File

@ -75,6 +75,6 @@ class RangeOfAqiBloc extends Bloc<RangeOfAqiEvent, RangeOfAqiState> {
ClearRangeOfAqiEvent event, ClearRangeOfAqiEvent event,
Emitter<RangeOfAqiState> emit, Emitter<RangeOfAqiState> emit,
) { ) {
emit(const RangeOfAqiState()); emit(RangeOfAqiState(selectedAqiType: state.selectedAqiType));
} }
} }

View File

@ -20,6 +20,7 @@ class AqiDistributionChart extends StatelessWidget {
return BarChart( return BarChart(
BarChartData( BarChartData(
maxY: 100.1, maxY: 100.1,
alignment: BarChartAlignment.start,
gridData: EnergyManagementChartsHelper.gridData( gridData: EnergyManagementChartsHelper.gridData(
horizontalInterval: 20, horizontalInterval: 20,
), ),

View File

@ -3,7 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_chart_empty_state_widget.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class AqiDistributionChartBox extends StatelessWidget { class AqiDistributionChartBox extends StatelessWidget {
@ -32,8 +34,20 @@ class AqiDistributionChartBox extends StatelessWidget {
const SizedBox(height: 10), const SizedBox(height: 10),
const Divider(), const Divider(),
const SizedBox(height: 20), const SizedBox(height: 20),
Expanded( Visibility(
child: AqiDistributionChart(chartData: state.chartData), visible: state.chartData.isNotEmpty,
replacement: AnalyticsChartEmptyStateWidget(
isLoading: state.status == AirQualityDistributionStatus.loading,
isError: state.status == AirQualityDistributionStatus.failure,
isInitial: state.status == AirQualityDistributionStatus.initial,
errorMessage: state.errorMessage,
iconPath: Assets.emptyBarredChart,
),
child: Expanded(
child: AqiDistributionChart(
chartData: state.chartData,
),
),
), ),
], ],
), ),

View File

@ -34,6 +34,7 @@ class AqiDistributionChartTitle extends StatelessWidget {
alignment: AlignmentDirectional.centerEnd, alignment: AlignmentDirectional.centerEnd,
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: AqiTypeDropdown( child: AqiTypeDropdown(
selectedAqiType: context.watch<AirQualityDistributionBloc>().state.selectedAqiType,
onChanged: (value) { onChanged: (value) {
if (value != null) { if (value != null) {
final bloc = context.read<AirQualityDistributionBloc>(); final bloc = context.read<AirQualityDistributionBloc>();

View File

@ -18,19 +18,20 @@ enum AqiType {
} }
class AqiTypeDropdown extends StatefulWidget { class AqiTypeDropdown extends StatefulWidget {
const AqiTypeDropdown({super.key, required this.onChanged}); const AqiTypeDropdown({
required this.onChanged,
this.selectedAqiType,
super.key,
});
final ValueChanged<AqiType?> onChanged; final ValueChanged<AqiType?> onChanged;
final AqiType? selectedAqiType;
@override @override
State<AqiTypeDropdown> createState() => _AqiTypeDropdownState(); State<AqiTypeDropdown> createState() => _AqiTypeDropdownState();
} }
class _AqiTypeDropdownState extends State<AqiTypeDropdown> { class _AqiTypeDropdownState extends State<AqiTypeDropdown> {
AqiType? _selectedItem = AqiType.aqi;
void _updateSelectedItem(AqiType? item) => setState(() => _selectedItem = item);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
@ -41,8 +42,8 @@ class _AqiTypeDropdownState extends State<AqiTypeDropdown> {
width: 1, width: 1,
), ),
), ),
child: DropdownButton<AqiType?>( child: DropdownButton<AqiType>(
value: _selectedItem, value: widget.selectedAqiType,
isDense: true, isDense: true,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
dropdownColor: ColorsManager.whiteColors, dropdownColor: ColorsManager.whiteColors,
@ -59,10 +60,7 @@ class _AqiTypeDropdownState extends State<AqiTypeDropdown> {
items: AqiType.values items: AqiType.values
.map((e) => DropdownMenuItem(value: e, child: Text(e.value))) .map((e) => DropdownMenuItem(value: e, child: Text(e.value)))
.toList(), .toList(),
onChanged: (value) { onChanged: widget.onChanged,
_updateSelectedItem(value);
widget.onChanged(value);
},
), ),
); );
} }

View File

@ -3,7 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_title.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_title.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_chart_empty_state_widget.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class RangeOfAqiChartBox extends StatelessWidget { class RangeOfAqiChartBox extends StatelessWidget {
@ -32,10 +34,20 @@ class RangeOfAqiChartBox extends StatelessWidget {
const SizedBox(height: 10), const SizedBox(height: 10),
const Divider(), const Divider(),
const SizedBox(height: 20), const SizedBox(height: 20),
Expanded( Visibility(
child: RangeOfAqiChart( visible: state.filteredRangeOfAqi.isNotEmpty,
chartData: state.filteredRangeOfAqi, replacement: AnalyticsChartEmptyStateWidget(
selectedAqiType: state.selectedAqiType, isLoading: state.status == RangeOfAqiStatus.loading,
isError: state.status == RangeOfAqiStatus.failure,
isInitial: state.status == RangeOfAqiStatus.initial,
errorMessage: state.errorMessage,
iconPath: Assets.emptyRangeOfAqi,
),
child: Expanded(
child: RangeOfAqiChart(
chartData: state.filteredRangeOfAqi,
selectedAqiType: state.selectedAqiType,
),
), ),
), ),
], ],

View File

@ -63,15 +63,15 @@ class RangeOfAqiChartTitle extends StatelessWidget {
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerEnd, alignment: AlignmentDirectional.centerEnd,
child: AqiTypeDropdown( child: AqiTypeDropdown(
selectedAqiType: context.watch<RangeOfAqiBloc>().state.selectedAqiType,
onChanged: (value) { onChanged: (value) {
final spaceTreeState = context.read<SpaceTreeBloc>().state; final spaceTreeState = context.read<SpaceTreeBloc>().state;
final spaceUuid = spaceTreeState.selectedSpaces.firstOrNull; final spaceUuid = spaceTreeState.selectedSpaces.firstOrNull;
if (spaceUuid == null) return;
if (value != null) { if (value != null) {
context.read<RangeOfAqiBloc>().add(UpdateAqiTypeEvent(value)); context.read<RangeOfAqiBloc>().add(UpdateAqiTypeEvent(value));
} }
if (spaceUuid == null) return;
}, },
), ),
), ),

View File

@ -38,7 +38,7 @@ class AnalyticsEnergyManagementView extends StatelessWidget {
return SingleChildScrollView( return SingleChildScrollView(
child: Container( child: Container(
padding: _padding, padding: _padding,
height: MediaQuery.sizeOf(context).height * 1, height: MediaQuery.sizeOf(context).height * 1.05,
child: const Column( child: const Column(
children: [ children: [
Expanded( Expanded(

View File

@ -5,8 +5,10 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/ener
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_chart_empty_state_widget.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart'; import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class EnergyConsumptionPerDeviceChartBox extends StatelessWidget { class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
@ -54,8 +56,24 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
const Divider(height: 0), const Divider(height: 0),
const SizedBox(height: 20), const SizedBox(height: 20),
Expanded( Visibility(
child: EnergyConsumptionPerDeviceChart(chartData: state.chartData), visible: state.chartData.isNotEmpty &&
state.chartData
.every((e) => e.energy.every((e) => e.value != 0)),
replacement: AnalyticsChartEmptyStateWidget(
isLoading:
state.status == EnergyConsumptionPerDeviceStatus.loading,
isError: state.status == EnergyConsumptionPerDeviceStatus.failure,
isInitial:
state.status == EnergyConsumptionPerDeviceStatus.initial,
errorMessage: state.errorMessage,
iconPath: Assets.emptyEnergyManagementPerDevice,
),
child: Expanded(
child: EnergyConsumptionPerDeviceChart(
chartData: state.chartData,
),
),
), ),
], ],
), ),

View File

@ -3,8 +3,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_chart_empty_state_widget.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart'; import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class TotalEnergyConsumptionChartBox extends StatelessWidget { class TotalEnergyConsumptionChartBox extends StatelessWidget {
@ -41,7 +43,18 @@ class TotalEnergyConsumptionChartBox extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
const Divider(), const Divider(),
const SizedBox(height: 20), const SizedBox(height: 20),
TotalEnergyConsumptionChart(chartData: state.chartData), Visibility(
visible: state.chartData.isNotEmpty &&
state.chartData.every((e) => e.value != 0),
replacement: AnalyticsChartEmptyStateWidget(
isLoading: state.status == TotalEnergyConsumptionStatus.loading,
isError: state.status == TotalEnergyConsumptionStatus.failure,
isInitial: state.status == TotalEnergyConsumptionStatus.initial,
errorMessage: state.errorMessage,
iconPath: Assets.emptyEnergyManagementChart,
),
child: TotalEnergyConsumptionChart(chartData: state.chartData),
),
], ],
), ),
), ),

View File

@ -18,6 +18,7 @@ class OccupancyChart extends StatelessWidget {
return BarChart( return BarChart(
BarChartData( BarChartData(
maxY: 100.001, maxY: 100.001,
alignment: BarChartAlignment.start,
gridData: EnergyManagementChartsHelper.gridData().copyWith( gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true, checkToShowHorizontalLine: (value) => true,
horizontalInterval: 20, horizontalInterval: 20,

View File

@ -6,8 +6,10 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/ch
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_chart_empty_state_widget.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class OccupancyChartBox extends StatelessWidget { class OccupancyChartBox extends StatelessWidget {
@ -67,7 +69,24 @@ class OccupancyChartBox extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
const Divider(), const Divider(),
const SizedBox(height: 20), const SizedBox(height: 20),
Expanded(child: OccupancyChart(chartData: state.chartData)), Visibility(
visible: state.chartData.isNotEmpty &&
state.chartData.every(
(e) => e.occupancy.isNotEmpty,
),
replacement: AnalyticsChartEmptyStateWidget(
isLoading: state.status == OccupancyStatus.loading,
isError: state.status == OccupancyStatus.failure,
isInitial: state.status == OccupancyStatus.initial,
errorMessage: state.errorMessage,
iconPath: Assets.emptyBarredChart,
),
child: Expanded(
child: OccupancyChart(
chartData: state.chartData,
),
),
),
], ],
), ),
); );

View File

@ -6,8 +6,10 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/ch
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_chart_empty_state_widget.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class OccupancyHeatMapBox extends StatelessWidget { class OccupancyHeatMapBox extends StatelessWidget {
@ -68,16 +70,29 @@ class OccupancyHeatMapBox extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
const Divider(), const Divider(),
const SizedBox(height: 20), const SizedBox(height: 20),
Expanded( Visibility(
child: OccupancyHeatMap( visible: state.heatMapData.isNotEmpty &&
selectedDate: state.heatMapData.every(
context.watch<AnalyticsDatePickerBloc>().state.yearlyDate, (e) => e.countTotalPresenceDetected != 0,
heatMapData: state.heatMapData.asMap().map( ),
(_, value) => MapEntry( replacement: AnalyticsChartEmptyStateWidget(
value.eventDate, isLoading: state.status == OccupancyHeatMapStatus.loading,
value.countTotalPresenceDetected, isError: state.status == OccupancyHeatMapStatus.failure,
isInitial: state.status == OccupancyHeatMapStatus.initial,
errorMessage: state.errorMessage,
iconPath: Assets.emptyHeatmap,
),
child: Expanded(
child: OccupancyHeatMap(
selectedDate:
context.watch<AnalyticsDatePickerBloc>().state.yearlyDate,
heatMapData: state.heatMapData.asMap().map(
(_, value) => MapEntry(
value.eventDate,
value.countTotalPresenceDetected,
),
), ),
), ),
), ),
), ),
], ],

View File

@ -17,8 +17,8 @@ class DeviceLocationDetailsServiceDecorator implements DeviceLocationService {
'reverse', 'reverse',
queryParameters: { queryParameters: {
'format': 'json', 'format': 'json',
'lat': param.latitude, 'lat': 25.1880567,
'lon': param.longitude, 'lon': 55.266608,
}, },
); );

View File

@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/common/widgets/app_loading_indicator.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class AnalyticsChartEmptyStateWidget extends StatelessWidget {
const AnalyticsChartEmptyStateWidget({
required this.iconPath,
this.isLoading = false,
this.isError = false,
this.isInitial = false,
this.errorMessage,
this.noDataMessage = 'No data to display',
this.initialMessage = 'Please select a space to see data',
super.key,
});
final bool isLoading;
final bool isError;
final bool isInitial;
final String? errorMessage;
final String noDataMessage;
final String initialMessage;
final String iconPath;
@override
Widget build(BuildContext context) {
return Expanded(
child: _buildWidgetBasedOnState(context),
);
}
Widget _buildWidgetBasedOnState(BuildContext context) {
final widgetsMap = {
isLoading: const AppLoadingIndicator(),
isInitial: _buildState(context, initialMessage),
isError: _buildState(context, errorMessage ?? 'Something went wrong'),
};
return widgetsMap[true] ?? _buildState(context, noDataMessage);
}
Widget _buildState(BuildContext context, String message) {
return Center(
child: Column(
spacing: 16,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 16),
Expanded(child: SvgPicture.asset(iconPath, fit: BoxFit.contain)),
SelectableText(
message,
style: isError
? context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.red,
fontSize: 16,
fontWeight: FontWeight.w700,
)
: null,
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
],
),
);
}
}

View File

@ -50,6 +50,9 @@ class _DynamicTableState extends State<DynamicTable> {
bool _selectAll = false; bool _selectAll = false;
final ScrollController _verticalScrollController = ScrollController(); final ScrollController _verticalScrollController = ScrollController();
final ScrollController _horizontalScrollController = ScrollController(); final ScrollController _horizontalScrollController = ScrollController();
static const double _fixedRowHeight = 60;
static const double _checkboxColumnWidth = 50;
static const double _settingsColumnWidth = 100;
@override @override
void initState() { void initState() {
@ -67,7 +70,6 @@ class _DynamicTableState extends State<DynamicTable> {
bool _compareListOfLists( bool _compareListOfLists(
List<List<dynamic>> oldList, List<List<dynamic>> newList) { List<List<dynamic>> oldList, List<List<dynamic>> newList) {
// Check if the old and new lists are the same
if (oldList.length != newList.length) return false; if (oldList.length != newList.length) return false;
for (int i = 0; i < oldList.length; i++) { for (int i = 0; i < oldList.length; i++) {
@ -104,73 +106,130 @@ class _DynamicTableState extends State<DynamicTable> {
context.read<DeviceManagementBloc>().add(UpdateSelection(_selectedRows)); context.read<DeviceManagementBloc>().add(UpdateSelection(_selectedRows));
} }
double get _totalTableWidth {
final hasSettings = widget.headers.contains('Settings');
final base = (widget.withCheckBox ? _checkboxColumnWidth : 0) +
(hasSettings ? _settingsColumnWidth : 0);
final regularCount = widget.headers.length - (hasSettings ? 1 : 0);
final regularWidth = (widget.size.width - base) / regularCount;
return base + regularCount * regularWidth;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
width: widget.size.width,
height: widget.size.height,
decoration: widget.cellDecoration, decoration: widget.cellDecoration,
child: Scrollbar( child: ScrollConfiguration(
controller: _verticalScrollController, behavior: const ScrollBehavior().copyWith(scrollbars: false),
thumbVisibility: true,
trackVisibility: true,
child: Scrollbar( child: Scrollbar(
//fixed the horizontal scrollbar issue
controller: _horizontalScrollController, controller: _horizontalScrollController,
thumbVisibility: true, thumbVisibility: true,
trackVisibility: true, trackVisibility: true,
notificationPredicate: (notif) => notif.depth == 1, notificationPredicate: (notif) =>
notif.metrics.axis == Axis.horizontal,
child: SingleChildScrollView( child: SingleChildScrollView(
controller: _verticalScrollController, controller: _horizontalScrollController,
child: SingleChildScrollView( scrollDirection: Axis.horizontal,
controller: _horizontalScrollController, child: SizedBox(
scrollDirection: Axis.horizontal, width: _totalTableWidth,
child: SizedBox( child: Column(
width: widget.size.width, children: [
child: Column( Container(
children: [ height: _fixedRowHeight,
Container( decoration: widget.headerDecoration ??
decoration: widget.headerDecoration ?? const BoxDecoration(color: ColorsManager.boxColor),
const BoxDecoration( child: Row(
color: ColorsManager.boxColor, children: [
if (widget.withCheckBox)
_buildSelectAllCheckbox(_checkboxColumnWidth),
for (var i = 0; i < widget.headers.length; i++)
_buildTableHeaderCell(
widget.headers[i],
widget.headers[i] == 'Settings'
? _settingsColumnWidth
: (_totalTableWidth -
(widget.withCheckBox
? _checkboxColumnWidth
: 0) -
(widget.headers.contains('Settings')
? _settingsColumnWidth
: 0)) /
(widget.headers.length -
(widget.headers.contains('Settings')
? 1
: 0)),
), ),
child: Row( ],
children: [
if (widget.withCheckBox) _buildSelectAllCheckbox(),
...List.generate(widget.headers.length, (index) {
return _buildTableHeaderCell(
widget.headers[index], index);
})
//...widget.headers.map((header) => _buildTableHeaderCell(header)),
],
),
), ),
SizedBox( ),
width: widget.size.width,
child: widget.isEmpty Expanded(
? _buildEmptyState() child: widget.isEmpty
: Column( ? _buildEmptyState()
children: : Scrollbar(
List.generate(widget.data.length, (rowIndex) { controller: _verticalScrollController,
thumbVisibility: true,
trackVisibility: true,
notificationPredicate: (notif) =>
notif.metrics.axis == Axis.vertical,
child: ListView.builder(
controller: _verticalScrollController,
itemCount: widget.data.length,
itemBuilder: (_, rowIndex) {
final row = widget.data[rowIndex]; final row = widget.data[rowIndex];
return Row( return SizedBox(
children: [ height: _fixedRowHeight,
if (widget.withCheckBox) child: Row(
_buildRowCheckbox( children: [
rowIndex, widget.size.height * 0.08), if (widget.withCheckBox)
...row.asMap().entries.map((entry) { _buildRowCheckbox(
return _buildTableCell( rowIndex,
entry.value.toString(), _checkboxColumnWidth,
widget.size.height * 0.08, ),
rowIndex: rowIndex, for (var colIndex = 0;
columnIndex: entry.key, colIndex < row.length;
); colIndex++)
}).toList(), widget.headers[colIndex] == 'Settings'
], ? buildSettingsIcon(
width: _settingsColumnWidth,
onTap: () => widget
.onSettingsPressed
?.call(rowIndex),
)
: _buildTableCell(
row[colIndex].toString(),
width: widget.headers[
colIndex] ==
'Settings'
? _settingsColumnWidth
: (_totalTableWidth -
(widget.withCheckBox
? _checkboxColumnWidth
: 0) -
(widget.headers
.contains(
'Settings')
? _settingsColumnWidth
: 0)) /
(widget.headers.length -
(widget.headers
.contains(
'Settings')
? 1
: 0)),
rowIndex: rowIndex,
columnIndex: colIndex,
),
],
),
); );
}), },
), ),
), ),
], ),
), ],
), ),
), ),
), ),
@ -210,9 +269,10 @@ class _DynamicTableState extends State<DynamicTable> {
], ],
), ),
); );
Widget _buildSelectAllCheckbox() {
Widget _buildSelectAllCheckbox(double width) {
return Container( return Container(
width: 50, width: width,
decoration: const BoxDecoration( decoration: const BoxDecoration(
border: Border.symmetric( border: Border.symmetric(
vertical: BorderSide(color: ColorsManager.boxDivider), vertical: BorderSide(color: ColorsManager.boxDivider),
@ -227,11 +287,11 @@ class _DynamicTableState extends State<DynamicTable> {
); );
} }
Widget _buildRowCheckbox(int index, double size) { Widget _buildRowCheckbox(int index, double width) {
return Container( return Container(
width: 50, width: width,
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
height: size, height: _fixedRowHeight,
decoration: const BoxDecoration( decoration: const BoxDecoration(
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(
@ -253,50 +313,47 @@ class _DynamicTableState extends State<DynamicTable> {
); );
} }
Widget _buildTableHeaderCell(String title, int index) { Widget _buildTableHeaderCell(String title, double width) {
return Expanded( return Container(
child: Container( width: width,
decoration: const BoxDecoration( decoration: const BoxDecoration(
border: Border.symmetric( border: Border.symmetric(
vertical: BorderSide(color: ColorsManager.boxDivider), vertical: BorderSide(color: ColorsManager.boxDivider),
),
), ),
constraints: const BoxConstraints.expand(height: 40), ),
alignment: Alignment.centerLeft, constraints: BoxConstraints(minHeight: 40, maxHeight: _fixedRowHeight),
child: Padding( alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric( child: Padding(
horizontal: index == widget.headers.length - 1 ? 12 : 8.0, padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
vertical: 4), child: Text(
child: Text( title,
title, style: context.textTheme.titleSmall!.copyWith(
style: context.textTheme.titleSmall!.copyWith( color: ColorsManager.grayColor,
color: ColorsManager.grayColor, fontSize: 12,
fontSize: 12, fontWeight: FontWeight.w400,
fontWeight: FontWeight.w400,
),
maxLines: 2,
), ),
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
), ),
); );
} }
Widget _buildTableCell(String content, double size, Widget _buildTableCell(String content,
{required int rowIndex, required int columnIndex}) { {required double width,
required int rowIndex,
required int columnIndex}) {
bool isBatteryLevel = content.endsWith('%'); bool isBatteryLevel = content.endsWith('%');
double? batteryLevel; double? batteryLevel;
if (isBatteryLevel) { if (isBatteryLevel) {
batteryLevel = double.tryParse(content.replaceAll('%', '').trim()); batteryLevel = double.tryParse(content.replaceAll('%', '').trim());
} }
bool isSettingsColumn = widget.headers[columnIndex] == 'Settings'; bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
if (isSettingsColumn) { if (isSettingsColumn) {
return buildSettingsIcon( return buildSettingsIcon(
width: 120, width: width, onTap: () => widget.onSettingsPressed?.call(rowIndex));
height: 60,
iconSize: 40,
onTap: () => widget.onSettingsPressed?.call(rowIndex),
);
} }
Color? statusColor; Color? statusColor;
@ -320,92 +377,82 @@ class _DynamicTableState extends State<DynamicTable> {
statusColor = Colors.black; statusColor = Colors.black;
} }
return Expanded( return Container(
child: Container( width: width,
height: size, height: _fixedRowHeight,
padding: const EdgeInsets.all(5.0), padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
decoration: const BoxDecoration( decoration: const BoxDecoration(
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(
color: ColorsManager.boxDivider, color: ColorsManager.boxDivider,
width: 1.0, width: 1.0,
),
), ),
color: Colors.white,
), ),
alignment: Alignment.centerLeft, color: Colors.white,
child: Text( ),
content, alignment: Alignment.centerLeft,
style: TextStyle( child: Text(
color: (batteryLevel != null && batteryLevel < 20) content,
? ColorsManager.red style: TextStyle(
: (batteryLevel != null && batteryLevel > 20) color: (batteryLevel != null && batteryLevel < 20)
? ColorsManager.green ? ColorsManager.red
: statusColor, : (batteryLevel != null && batteryLevel > 20)
fontSize: 13, ? ColorsManager.green
fontWeight: FontWeight.w400), : statusColor,
maxLines: 2, fontSize: 13,
fontWeight: FontWeight.w400,
), ),
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
); );
} }
Widget buildSettingsIcon( Widget buildSettingsIcon({required double width, VoidCallback? onTap}) {
{double width = 120, return Container(
double height = 60, width: width,
double iconSize = 40, height: _fixedRowHeight,
VoidCallback? onTap}) { padding: const EdgeInsets.only(left: 15, top: 10, bottom: 10),
return Column( decoration: const BoxDecoration(
children: [ color: ColorsManager.whiteColors,
Container( border: Border(
padding: const EdgeInsets.only(top: 10, bottom: 15, left: 10), bottom: BorderSide(
margin: const EdgeInsets.only(right: 15), color: ColorsManager.boxDivider,
decoration: const BoxDecoration( width: 1.0,
color: ColorsManager.whiteColors,
border: Border(
bottom: BorderSide(
color: ColorsManager.boxDivider,
width: 1.0,
),
),
), ),
width: width, ),
child: Padding( ),
padding: const EdgeInsets.only( child: Align(
right: 16.0, alignment: Alignment.centerLeft,
left: 17.0, child: Container(
), width: 50,
child: Container( decoration: BoxDecoration(
width: 50, color: const Color(0xFFF7F8FA),
decoration: BoxDecoration( borderRadius: BorderRadius.circular(20),
color: const Color(0xFFF7F8FA), boxShadow: [
borderRadius: BorderRadius.circular(height / 2), BoxShadow(
boxShadow: [ color: Colors.black.withOpacity(0.17),
BoxShadow( blurRadius: 14,
color: Colors.black.withOpacity(0.17), offset: const Offset(0, 4),
blurRadius: 14,
offset: const Offset(0, 4),
),
],
), ),
child: InkWell( ],
onTap: onTap, ),
child: Padding( child: InkWell(
padding: const EdgeInsets.all(8.0), onTap: onTap,
child: Center( child: Padding(
child: SvgPicture.asset( padding: EdgeInsets.all(8.0),
Assets.settings, child: Center(
width: 40, child: SvgPicture.asset(
height: 22, Assets.settings,
color: ColorsManager.primaryColor, width: 40,
), height: 20,
), color: ColorsManager.primaryColor,
), ),
), ),
), ),
), ),
), ),
], ),
); );
} }
} }

View File

@ -15,7 +15,8 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayout { class AcDeviceBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
const AcDeviceBatchControlView({super.key, required this.devicesIds}); const AcDeviceBatchControlView({super.key, required this.devicesIds});
final List<String> devicesIds; final List<String> devicesIds;
@ -51,7 +52,7 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
deviceId: devicesIds.first, deviceId: devicesIds.first,
code: 'switch', code: 'switch',
value: state.status.acSwitch, value: state.status.acSwitch,
label: 'ThermoState', label: 'Thermostat',
icon: Assets.ac, icon: Assets.ac,
onChange: (value) { onChange: (value) {
context.read<AcBloc>().add(AcBatchControlEvent( context.read<AcBloc>().add(AcBatchControlEvent(
@ -100,8 +101,8 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
), ),
Text( Text(
'h', 'h',
style: style: context.textTheme.bodySmall!
context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), .copyWith(color: ColorsManager.blackColor),
), ),
Text( Text(
'30', '30',
@ -148,7 +149,8 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
callFactoryReset: () { callFactoryReset: () {
context.read<AcBloc>().add(AcFactoryResetEvent( context.read<AcBloc>().add(AcFactoryResetEvent(
deviceId: state.status.uuid, deviceId: state.status.uuid,
factoryResetModel: FactoryResetModel(devicesUuid: devicesIds), factoryResetModel:
FactoryResetModel(devicesUuid: devicesIds),
)); ));
}, },
), ),

View File

@ -16,11 +16,12 @@ class DeviceManagementBloc
int _onlineCount = 0; int _onlineCount = 0;
int _offlineCount = 0; int _offlineCount = 0;
int _lowBatteryCount = 0; int _lowBatteryCount = 0;
List<AllDevicesModel> _selectedDevices = []; final List<AllDevicesModel> _selectedDevices = [];
List<AllDevicesModel> _filteredDevices = []; List<AllDevicesModel> _filteredDevices = [];
String currentProductName = ''; String currentProductName = '';
String? currentCommunity; String? currentCommunity;
String? currentUnitName; String? currentUnitName;
String subSpaceName = '';
DeviceManagementBloc() : super(DeviceManagementInitial()) { DeviceManagementBloc() : super(DeviceManagementInitial()) {
on<FetchDevices>(_onFetchDevices); on<FetchDevices>(_onFetchDevices);
@ -31,25 +32,26 @@ class DeviceManagementBloc
on<ResetFilters>(_onResetFilters); on<ResetFilters>(_onResetFilters);
on<ResetSelectedDevices>(_onResetSelectedDevices); on<ResetSelectedDevices>(_onResetSelectedDevices);
on<UpdateSelection>(_onUpdateSelection); on<UpdateSelection>(_onUpdateSelection);
on<UpdateDeviceName>(_onUpdateDeviceName);
on<UpdateSubSpaceName>(_onUpdateSubSpaceName);
} }
Future<void> _onFetchDevices( Future<void> _onFetchDevices(
FetchDevices event, Emitter<DeviceManagementState> emit) async { FetchDevices event, Emitter<DeviceManagementState> emit) async {
emit(DeviceManagementLoading()); emit(DeviceManagementLoading());
try { try {
List<AllDevicesModel> devices = []; var devices = <AllDevicesModel>[];
_devices.clear(); _devices.clear();
var spaceBloc = event.context.read<SpaceTreeBloc>(); final spaceBloc = event.context.read<SpaceTreeBloc>();
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (spaceBloc.state.selectedCommunities.isEmpty) { if (spaceBloc.state.selectedCommunities.isEmpty) {
devices = devices = await DevicesManagementApi().fetchDevices('', '', projectUuid);
await DevicesManagementApi().fetchDevices('', '', projectUuid);
} else { } else {
for (var community in spaceBloc.state.selectedCommunities) { for (final community in spaceBloc.state.selectedCommunities) {
List<String> spacesList = final spacesList =
spaceBloc.state.selectedCommunityAndSpaces[community] ?? []; spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
for (var space in spacesList) { for (final space in spacesList) {
devices.addAll(await DevicesManagementApi() devices.addAll(await DevicesManagementApi()
.fetchDevices(community, space, projectUuid)); .fetchDevices(community, space, projectUuid));
} }
@ -74,7 +76,7 @@ class DeviceManagementBloc
} }
} }
void _onFilterDevices( Future<void> _onFilterDevices(
FilterDevices event, Emitter<DeviceManagementState> emit) async { FilterDevices event, Emitter<DeviceManagementState> emit) async {
if (_devices.isNotEmpty) { if (_devices.isNotEmpty) {
_filteredDevices = List.from(_devices.where((device) { _filteredDevices = List.from(_devices.where((device) {
@ -156,8 +158,7 @@ class DeviceManagementBloc
add(FilterDevices(_getFilterFromIndex(_selectedIndex))); add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
} }
void _onSelectDevice( void _onSelectDevice(SelectDevice event, Emitter<DeviceManagementState> emit) {
SelectDevice event, Emitter<DeviceManagementState> emit) {
final selectedUuid = event.selectedDevice.uuid; final selectedUuid = event.selectedDevice.uuid;
if (_selectedDevices.any((device) => device.uuid == selectedUuid)) { if (_selectedDevices.any((device) => device.uuid == selectedUuid)) {
@ -166,9 +167,9 @@ class DeviceManagementBloc
_selectedDevices.add(event.selectedDevice); _selectedDevices.add(event.selectedDevice);
} }
List<AllDevicesModel> clonedSelectedDevices = List.from(_selectedDevices); final clonedSelectedDevices = List<AllDevicesModel>.from(_selectedDevices);
bool isControlButtonEnabled = final isControlButtonEnabled =
_checkIfControlButtonEnabled(clonedSelectedDevices); _checkIfControlButtonEnabled(clonedSelectedDevices);
if (state is DeviceManagementLoaded) { if (state is DeviceManagementLoaded) {
@ -198,8 +199,8 @@ class DeviceManagementBloc
void _onUpdateSelection( void _onUpdateSelection(
UpdateSelection event, Emitter<DeviceManagementState> emit) { UpdateSelection event, Emitter<DeviceManagementState> emit) {
List<AllDevicesModel> selectedDevices = []; final selectedDevices = <AllDevicesModel>[];
List<AllDevicesModel> devicesToSelectFrom = []; var devicesToSelectFrom = <AllDevicesModel>[];
if (state is DeviceManagementLoaded) { if (state is DeviceManagementLoaded) {
devicesToSelectFrom = (state as DeviceManagementLoaded).devices; devicesToSelectFrom = (state as DeviceManagementLoaded).devices;
@ -207,7 +208,7 @@ class DeviceManagementBloc
devicesToSelectFrom = (state as DeviceManagementFiltered).filteredDevices; devicesToSelectFrom = (state as DeviceManagementFiltered).filteredDevices;
} }
for (int i = 0; i < event.selectedRows.length; i++) { for (var i = 0; i < event.selectedRows.length; i++) {
if (event.selectedRows[i]) { if (event.selectedRows[i]) {
selectedDevices.add(devicesToSelectFrom[i]); selectedDevices.add(devicesToSelectFrom[i]);
} }
@ -253,8 +254,7 @@ class DeviceManagementBloc
_onlineCount = _devices.where((device) => device.online == true).length; _onlineCount = _devices.where((device) => device.online == true).length;
_offlineCount = _devices.where((device) => device.online == false).length; _offlineCount = _devices.where((device) => device.online == false).length;
_lowBatteryCount = _devices _lowBatteryCount = _devices
.where((device) => .where((device) => device.batteryLevel != null && device.batteryLevel! < 20)
device.batteryLevel != null && device.batteryLevel! < 20)
.length; .length;
} }
@ -270,8 +270,8 @@ class DeviceManagementBloc
return 'All'; return 'All';
} }
} }
void _onSearchDevices(
SearchDevices event, Emitter<DeviceManagementState> emit) { void _onSearchDevices(SearchDevices event, Emitter<DeviceManagementState> emit) {
if ((event.community == null || event.community!.isEmpty) && if ((event.community == null || event.community!.isEmpty) &&
(event.unitName == null || event.unitName!.isEmpty) && (event.unitName == null || event.unitName!.isEmpty) &&
(event.deviceNameOrProductName == null || (event.deviceNameOrProductName == null ||
@ -300,7 +300,7 @@ class DeviceManagementBloc
currentCommunity = event.community; currentCommunity = event.community;
currentUnitName = event.unitName; currentUnitName = event.unitName;
List<AllDevicesModel> devicesToSearch = _devices; final devicesToSearch = _devices;
if (devicesToSearch.isNotEmpty) { if (devicesToSearch.isNotEmpty) {
final searchText = event.deviceNameOrProductName?.toLowerCase() ?? ''; final searchText = event.deviceNameOrProductName?.toLowerCase() ?? '';
@ -343,5 +343,134 @@ class DeviceManagementBloc
} }
} }
void _onUpdateDeviceName(
UpdateDeviceName event, Emitter<DeviceManagementState> emit) {
final devices = _devices.map((device) {
if (device.uuid == event.deviceId) {
final modifiedDevice = device.copyWith(name: event.newName);
_selectedDevices.removeWhere((device) => device.uuid == event.deviceId);
_selectedDevices.add(modifiedDevice);
return modifiedDevice;
}
return device;
}).toList();
final filteredDevices = _filteredDevices.map((device) {
if (device.uuid == event.deviceId) {
final modifiedDevice = device.copyWith(name: event.newName);
_selectedDevices.removeWhere((device) => device.uuid == event.deviceId);
_selectedDevices.add(modifiedDevice);
return modifiedDevice;
}
return device;
}).toList();
_devices = devices;
_filteredDevices = filteredDevices;
if (state is DeviceManagementLoaded) {
final loaded = state as DeviceManagementLoaded;
final selectedDevices01 = _selectedDevices.map((device) {
if (device.uuid == event.deviceId) {
final modifiedDevice = device.copyWith(name: event.newName);
return modifiedDevice;
}
return device;
}).toList();
emit(DeviceManagementLoaded(
devices: devices,
selectedIndex: loaded.selectedIndex,
onlineCount: loaded.onlineCount,
offlineCount: loaded.offlineCount,
lowBatteryCount: loaded.lowBatteryCount,
selectedDevice: selectedDevices01,
isControlButtonEnabled: loaded.isControlButtonEnabled,
));
} else if (state is DeviceManagementFiltered) {
final filtered = state as DeviceManagementFiltered;
final selectedDevices01 = filtered.selectedDevice?.map((device) {
if (device.uuid == event.deviceId) {
final modifiedDevice = device.copyWith(name: event.newName);
return modifiedDevice;
}
return device;
}).toList();
emit(DeviceManagementFiltered(
filteredDevices: filteredDevices,
selectedIndex: filtered.selectedIndex,
onlineCount: filtered.onlineCount,
offlineCount: filtered.offlineCount,
lowBatteryCount: filtered.lowBatteryCount,
selectedDevice: selectedDevices01,
isControlButtonEnabled: filtered.isControlButtonEnabled,
));
}
}
void _onUpdateSubSpaceName(
UpdateSubSpaceName event, Emitter<DeviceManagementState> emit) {
final devices = _devices.map((device) {
if (device.uuid == event.deviceId) {
return device.copyWith(
subspace:
device.subspace?.copyWith(subspaceName: event.newSubSpaceName));
}
return device;
}).toList();
final filteredDevices = _filteredDevices.map((device) {
if (device.uuid == event.deviceId) {
return device.copyWith(
subspace:
device.subspace?.copyWith(subspaceName: event.newSubSpaceName));
}
return device;
}).toList();
_devices = devices;
_filteredDevices = filteredDevices;
if (state is DeviceManagementLoaded) {
final loaded = state as DeviceManagementLoaded;
final selectedDevices = loaded.selectedDevice?.map((device) {
if (device.uuid == event.deviceId) {
return device.copyWith(
subspace:
device.subspace?.copyWith(subspaceName: event.newSubSpaceName));
}
return device;
}).toList();
emit(DeviceManagementLoaded(
devices: _devices,
selectedIndex: loaded.selectedIndex,
onlineCount: loaded.onlineCount,
offlineCount: loaded.offlineCount,
lowBatteryCount: loaded.lowBatteryCount,
selectedDevice: selectedDevices,
isControlButtonEnabled: loaded.isControlButtonEnabled,
));
} else if (state is DeviceManagementFiltered) {
// final filtered = state as DeviceManagementFiltered;
// emit(DeviceManagementFiltered(
// filteredDevices: _filteredDevices,
// selectedIndex: filtered.selectedIndex,
// onlineCount: filtered.onlineCount,
// offlineCount: filtered.offlineCount,
// lowBatteryCount: filtered.lowBatteryCount,
// selectedDevice: filtered.selectedDevice,
// isControlButtonEnabled: filtered.isControlButtonEnabled,
// ));
}
}
void changeSubspaceName(
String deviceId, String newSubSpaceName, String subspaceId) {
add(UpdateSubSpaceName(
deviceId: deviceId,
newSubSpaceName: newSubSpaceName,
subspaceId: subspaceId,
));
}
List<AllDevicesModel> get selectedDevices => _selectedDevices; List<AllDevicesModel> get selectedDevices => _selectedDevices;
} }

View File

@ -70,3 +70,21 @@ class UpdateSelection extends DeviceManagementEvent {
const UpdateSelection(this.selectedRows); const UpdateSelection(this.selectedRows);
} }
class UpdateDeviceName extends DeviceManagementEvent {
final String deviceId;
final String newName;
const UpdateDeviceName({required this.deviceId, required this.newName});
}
class UpdateSubSpaceName extends DeviceManagementEvent {
final String deviceId;
final String newSubSpaceName;
final String subspaceId;
const UpdateSubSpaceName(
{required this.deviceId,
required this.newSubSpaceName,
required this.subspaceId});
}

View File

@ -60,4 +60,13 @@ class Status {
factory Status.fromJson(String source) => Status.fromMap(json.decode(source)); factory Status.fromJson(String source) => Status.fromMap(json.decode(source));
String toJson() => json.encode(toMap()); String toJson() => json.encode(toMap());
Status copyWith({
String? code,
dynamic value,
}) {
return Status(
code: code ?? this.code,
value: value ?? this.value,
);
}
} }

View File

@ -44,4 +44,20 @@ class DeviceSubspace {
static List<Map<String, dynamic>> listToJson(List<DeviceSubspace> subspaces) { static List<Map<String, dynamic>> listToJson(List<DeviceSubspace> subspaces) {
return subspaces.map((subspace) => subspace.toJson()).toList(); return subspaces.map((subspace) => subspace.toJson()).toList();
} }
DeviceSubspace copyWith({
String? uuid,
DateTime? createdAt,
DateTime? updatedAt,
String? subspaceName,
bool? disabled,
}) {
return DeviceSubspace(
uuid: uuid ?? this.uuid,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
subspaceName: subspaceName ?? this.subspaceName,
disabled: disabled ?? this.disabled,
);
}
} }

View File

@ -588,4 +588,72 @@ SOS
"NCPS": DeviceType.NCPS, "NCPS": DeviceType.NCPS,
"PC": DeviceType.PC, "PC": DeviceType.PC,
}; };
AllDevicesModel copyWith({
DevicesModelRoom? room,
DeviceSubspace? subspace,
DevicesModelUnit? unit,
DeviceCommunityModel? community,
String? productUuid,
String? productType,
String? permissionType,
int? activeTime,
String? category,
String? categoryName,
int? createTime,
String? gatewayId,
String? icon,
String? ip,
String? lat,
String? localKey,
String? lon,
String? model,
String? name,
String? nodeId,
bool? online,
String? ownerId,
bool? sub,
String? timeZone,
int? updateTime,
String? uuid,
int? batteryLevel,
String? productName,
List<DeviceSpaceModel>? spaces,
List<DeviceTagModel>? deviceTags,
DeviceSubSpace? deviceSubSpace,
}) {
return AllDevicesModel(
room: room ?? this.room,
subspace: subspace ?? this.subspace,
unit: unit ?? this.unit,
community: community ?? this.community,
productUuid: productUuid ?? this.productUuid,
productType: productType ?? this.productType,
permissionType: permissionType ?? this.permissionType,
activeTime: activeTime ?? this.activeTime,
category: category ?? this.category,
categoryName: categoryName ?? this.categoryName,
createTime: createTime ?? this.createTime,
gatewayId: gatewayId ?? this.gatewayId,
icon: icon ?? this.icon,
ip: ip ?? this.ip,
lat: lat ?? this.lat,
localKey: localKey ?? this.localKey,
lon: lon ?? this.lon,
model: model ?? this.model,
name: name ?? this.name,
nodeId: nodeId ?? this.nodeId,
online: online ?? this.online,
ownerId: ownerId ?? this.ownerId,
sub: sub ?? this.sub,
timeZone: timeZone ?? this.timeZone,
updateTime: updateTime ?? this.updateTime,
uuid: uuid ?? this.uuid,
batteryLevel: batteryLevel ?? this.batteryLevel,
productName: productName ?? this.productName,
spaces: spaces ?? this.spaces,
deviceTags: deviceTags ?? this.deviceTags,
deviceSubSpace: deviceSubSpace ?? this.deviceSubSpace,
);
}
} }

View File

@ -23,6 +23,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<DeviceManagementBloc, DeviceManagementState>( return BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
buildWhen: (previous, current) => previous != current,
builder: (context, state) { builder: (context, state) {
List<AllDevicesModel> devicesToShow = []; List<AllDevicesModel> devicesToShow = [];
int selectedIndex = 0; int selectedIndex = 0;
@ -31,7 +32,6 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
int lowBatteryCount = 0; int lowBatteryCount = 0;
bool isControlButtonEnabled = false; bool isControlButtonEnabled = false;
List<AllDevicesModel> selectedDevices = []; List<AllDevicesModel> selectedDevices = [];
if (state is DeviceManagementLoaded) { if (state is DeviceManagementLoaded) {
devicesToShow = state.devices; devicesToShow = state.devices;
selectedIndex = state.selectedIndex; selectedIndex = state.selectedIndex;
@ -68,6 +68,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
children: [ children: [
Expanded(child: SpaceTreeView( Expanded(child: SpaceTreeView(
onSelect: () { onSelect: () {
context.read<DeviceManagementBloc>().add(ResetFilters());
context.read<DeviceManagementBloc>().add(FetchDevices(context)); context.read<DeviceManagementBloc>().add(FetchDevices(context));
}, },
)), )),
@ -111,6 +112,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
onPressed: isControlButtonEnabled onPressed: isControlButtonEnabled
? () { ? () {
if (isAnyDeviceOffline) { if (isAnyDeviceOffline) {
ScaffoldMessenger.of(context)
.clearSnackBars();
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context)
.showSnackBar( .showSnackBar(
const SnackBar( const SnackBar(
@ -190,7 +193,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
'Product Name', 'Product Name',
'Device ID', 'Device ID',
'Space Name', 'Space Name',
'location', 'Location',
'Battery Level', 'Battery Level',
'Installation Date and Time', 'Installation Date and Time',
'Status', 'Status',
@ -242,7 +245,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
.map((device) => device.uuid!) .map((device) => device.uuid!)
.toList(), .toList(),
isEmpty: devicesToShow.isEmpty, isEmpty: devicesToShow.isEmpty,
onSettingsPressed: (rowIndex) { onSettingsPressed: (rowIndex) async {
final device = devicesToShow[rowIndex]; final device = devicesToShow[rowIndex];
showDeviceSettingsSidebar(context, device); showDeviceSettingsSidebar(context, device);
}, },
@ -264,7 +267,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
barrierDismissible: true, barrierDismissible: true,
barrierLabel: "Device Settings", barrierLabel: "Device Settings",
transitionDuration: const Duration(milliseconds: 300), transitionDuration: const Duration(milliseconds: 300),
pageBuilder: (context, anim1, anim2) { pageBuilder: (_, anim1, anim2) {
return Align( return Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Material( child: Material(
@ -274,6 +277,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
child: DeviceSettingsPanel( child: DeviceSettingsPanel(
device: device, device: device,
onClose: () => Navigator.of(context).pop(), onClose: () => Navigator.of(context).pop(),
deviceManagementBloc: context.read<DeviceManagementBloc>(),
), ),
), ),
), ),

View File

@ -62,9 +62,10 @@ class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout {
BlocProvider.of<CurtainModuleBloc>(context), BlocProvider.of<CurtainModuleBloc>(context),
child: BuildScheduleView( child: BuildScheduleView(
deviceUuid: deviceId, deviceUuid: deviceId,
category: 'CUR_2', category: 'Timer',
code: 'control', code: 'control',
countdownCode: 'Timer',
deviceType: 'CUR_2',
), ),
)); ));
}, },

View File

@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_m
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart';
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart';
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class AccurteCalibratingDialog extends StatelessWidget { class AccurteCalibratingDialog extends StatelessWidget {
final String deviceId; final String deviceId;
@ -17,14 +18,15 @@ class AccurteCalibratingDialog extends StatelessWidget {
@override @override
Widget build(_) { Widget build(_) {
return AlertDialog( return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: AccurateDialogWidget( content: AccurateDialogWidget(
title: 'Calibrating', title: 'Calibrating',
body: const NormalTextBodyForDialog( body: const NormalTextBodyForDialog(
title: '', title: '',
step1: step1:
'1. Click Close Button to make the Curtain run to Full Close and Position.', 'Click Close Button to make the Curtain run to Full Close and Position.',
step2: '2. click Next to complete the Calibration.', step2: 'click Next to complete the Calibration.',
), ),
leftOnTap: () => Navigator.of(parentContext).pop(), leftOnTap: () => Navigator.of(parentContext).pop(),
rightOnTap: () { rightOnTap: () {

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart';
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart';
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class AccurateCalibrationDialog extends StatelessWidget { class AccurateCalibrationDialog extends StatelessWidget {
final String deviceId; final String deviceId;
@ -15,13 +16,14 @@ class AccurateCalibrationDialog extends StatelessWidget {
@override @override
Widget build(_) { Widget build(_) {
return AlertDialog( return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: AccurateDialogWidget( content: AccurateDialogWidget(
title: 'Accurate Calibration', title: 'Accurate Calibration',
body: const NormalTextBodyForDialog( body: const NormalTextBodyForDialog(
title: 'Prepare Calibration:', title: 'Prepare Calibration:',
step1: '1. Run The Curtain to the Fully Open Position,and pause.', step1: 'Run The Curtain to the Fully Open Position,and pause.',
step2: '2. click Next to Start accurate calibration.', step2: 'click Next to Start accurate calibration.',
), ),
leftOnTap: () => Navigator.of(parentContext).pop(), leftOnTap: () => Navigator.of(parentContext).pop(),
rightOnTap: () { rightOnTap: () {

View File

@ -17,78 +17,102 @@ class AccurateDialogWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(
height: 300, height: 250,
width: 400, width: 500,
child: Column( child: Column(
children: [ children: [
Padding( Expanded(
padding: const EdgeInsets.all(10), flex: 3,
child: Text( child: Column(
title, children: [
style: const TextStyle( Padding(
fontSize: 24, padding: const EdgeInsets.all(10),
fontWeight: FontWeight.bold, child: Text(
color: ColorsManager.blueColor, title,
), style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: ColorsManager.dialogBlueTitle,
),
),
),
const Divider(
indent: 60,
endIndent: 60,
),
],
), ),
), ),
const SizedBox(height: 5), Expanded(
const Divider( flex: 5,
indent: 10,
endIndent: 10,
),
Padding(
padding: const EdgeInsets.all(10),
child: body, child: body,
), ),
const SizedBox(height: 20), Expanded(
const Spacer(), flex: 2,
const Divider(), child: Column(
Row( mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Expanded( const Expanded(child: Divider()),
child: InkWell( Row(
onTap: leftOnTap, children: [
child: Container( Expanded(
height: 60, child: InkWell(
alignment: Alignment.center, borderRadius: const BorderRadius.only(
decoration: const BoxDecoration( bottomLeft: Radius.circular(26),
border: Border( ),
right: BorderSide( onTap: leftOnTap,
color: ColorsManager.grayBorder, child: Container(
height: 40,
alignment: Alignment.center,
decoration: const BoxDecoration(
border: Border(
right: BorderSide(
color: ColorsManager.grayBorder,
),
),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(26),
),
),
child: const Text(
'Cancel',
style: TextStyle(color: ColorsManager.grayBorder),
),
), ),
), ),
), ),
child: const Text( Expanded(
'Cancel', child: InkWell(
style: TextStyle(color: ColorsManager.grayBorder), borderRadius: const BorderRadius.only(
), bottomRight: Radius.circular(26),
), ),
), onTap: rightOnTap,
), child: Container(
Expanded( height: 40,
child: InkWell( alignment: Alignment.center,
onTap: rightOnTap, decoration: const BoxDecoration(
child: Container( border: Border(
height: 60, right: BorderSide(
alignment: Alignment.center, color: ColorsManager.grayBorder,
decoration: const BoxDecoration( ),
border: Border( ),
right: BorderSide( borderRadius: BorderRadius.only(
color: ColorsManager.grayBorder, bottomRight: Radius.circular(26),
),
),
child: const Text(
'Next',
style: TextStyle(
color: ColorsManager.blueColor,
),
),
), ),
), ),
), )
child: const Text( ],
'Next', )
style: TextStyle( ],
color: ColorsManager.blueColor, ),
),
),
),
),
)
],
) )
], ],
), ),

View File

@ -1,7 +1,9 @@
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:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.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 CalibrateCompletedDialog extends StatelessWidget { class CalibrateCompletedDialog extends StatelessWidget {
final BuildContext parentContext; final BuildContext parentContext;
@ -15,58 +17,69 @@ class CalibrateCompletedDialog extends StatelessWidget {
@override @override
Widget build(_) { Widget build(_) {
return AlertDialog( return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: SizedBox( content: SizedBox(
height: 250, height: 250,
width: 400, width: 400,
child: Column( child: Column(
children: [ children: [
const Padding( Expanded(
padding: EdgeInsets.all(10), child: Column(
child: Text( children: [
'Calibration Completed', Padding(
style: TextStyle( padding: const EdgeInsets.all(10),
fontSize: 24, child: Text(
fontWeight: FontWeight.bold, 'Calibration Completed',
color: ColorsManager.blueColor, style: TextStyle(
), fontSize: 24,
fontWeight: FontWeight.bold,
color: ColorsManager.dialogBlueTitle,
),
),
),
const SizedBox(height: 5),
const Divider(
indent: 10,
endIndent: 10,
),
],
), ),
), ),
const SizedBox(height: 5), Expanded(
const Divider( child: SvgPicture.asset(Assets.completedDoneIcon),
indent: 10,
endIndent: 10,
), ),
const Icon( Expanded(
Icons.check_circle, child: Column(
size: 100, mainAxisAlignment: MainAxisAlignment.end,
color: ColorsManager.blueColor, children: [
), const Divider(
const Spacer(), indent: 10,
const Divider( endIndent: 10,
indent: 10,
endIndent: 10,
),
InkWell(
onTap: () {
parentContext.read<CurtainModuleBloc>().add(
FetchCurtainModuleStatusEvent(
deviceId: deviceId,
),
);
Navigator.of(parentContext).pop();
Navigator.of(parentContext).pop();
},
child: Container(
height: 40,
width: double.infinity,
alignment: Alignment.center,
child: const Text(
'Close',
style: TextStyle(
color: ColorsManager.grayBorder,
), ),
), InkWell(
onTap: () {
parentContext.read<CurtainModuleBloc>().add(
FetchCurtainModuleStatusEvent(
deviceId: deviceId,
),
);
Navigator.of(parentContext).pop();
Navigator.of(parentContext).pop();
},
child: Container(
height: 40,
width: double.infinity,
alignment: Alignment.center,
child: const Text(
'Close',
style: TextStyle(
color: ColorsManager.grayBorder,
),
),
),
)
],
), ),
) )
], ],

View File

@ -15,28 +15,72 @@ class NormalTextBodyForDialog extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Padding(
crossAxisAlignment: CrossAxisAlignment.start, padding: EdgeInsetsGeometry.only(left: 15),
children: [ child: Column(
Text( crossAxisAlignment: CrossAxisAlignment.start,
title, children: [
style: const TextStyle( if (title.isEmpty)
color: ColorsManager.grayColor, const SizedBox()
else
Expanded(
child: Text(
title,
style: const TextStyle(
color: ColorsManager.grayColor,
fontSize: 15,
),
),
),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
width: 10,
),
const Text('1. ',
style: TextStyle(
color: ColorsManager.grayColor,
fontSize: 15,
)),
SizedBox(
width: 450,
child: Text(
step1,
style: const TextStyle(
color: ColorsManager.grayColor,
fontSize: 15,
),
),
),
],
),
), ),
), Expanded(
Text( child: Row(
step1, crossAxisAlignment: CrossAxisAlignment.start,
style: const TextStyle( children: [
color: ColorsManager.grayColor, const SizedBox(
), width: 10,
), ),
Text( const Text('2. ',
step2, style: TextStyle(
style: const TextStyle( color: ColorsManager.grayColor,
color: ColorsManager.grayColor, fontSize: 15,
), )),
) Text(
], step2,
style: const TextStyle(
color: ColorsManager.grayColor,
fontSize: 15,
),
),
],
),
)
],
),
); );
} }
} }

View File

@ -19,7 +19,7 @@ class NumberInputField extends StatelessWidget {
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
), ),
style: const TextStyle( style: const TextStyle(
fontSize: 20, fontSize: 15,
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
), ),
); );

View File

@ -18,7 +18,7 @@ class PrefReversCardWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DefaultContainer( return DefaultContainer(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(18),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,

View File

@ -23,12 +23,12 @@ class CurtainModulePrefrencesDialog extends StatelessWidget {
Widget build(_) { Widget build(_) {
return AlertDialog( return AlertDialog(
backgroundColor: ColorsManager.CircleImageBackground, backgroundColor: ColorsManager.CircleImageBackground,
contentPadding: const EdgeInsets.all(30), contentPadding: const EdgeInsets.all(20),
title: const Center( title: Center(
child: Text( child: Text(
'Preferences', 'Preferences',
style: TextStyle( style: TextStyle(
color: ColorsManager.blueColor, color: ColorsManager.dialogBlueTitle,
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),

View File

@ -63,55 +63,82 @@ class _QuickCalibratingDialogState extends State<QuickCalibratingDialog> {
@override @override
Widget build(_) { Widget build(_) {
return AlertDialog( return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: AccurateDialogWidget( content: AccurateDialogWidget(
title: 'Calibrating', title: 'Calibrating',
body: Column( body: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( const Expanded(
'1. please Enter the Travel Time:', child: Align(
style: TextStyle(color: ColorsManager.grayBorder), alignment: Alignment.topCenter,
), child: Padding(
const SizedBox(height: 10), padding: EdgeInsets.only(right: 75),
Container( child: Text(
width: 150, '1.please Enter the Travel Time:',
height: 40, style: TextStyle(color: ColorsManager.lightGrayColor),
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(12),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: NumberInputField(controller: _controller),
),
const Expanded(
child: Text(
'seconds',
style: TextStyle(
fontSize: 15,
color: ColorsManager.blueColor,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
if (_errorText != null)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
_errorText!,
style: const TextStyle(
color: ColorsManager.red,
fontSize: 14,
), ),
), ),
), ),
),
Expanded(
child: Align(
alignment: Alignment.center,
child: Container(
width: 130,
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: ColorsManager.neutralGray.withValues(
alpha: 0.5,
),
borderRadius: BorderRadius.circular(12),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Padding(
padding: const EdgeInsetsGeometry.only(left: 5),
child: NumberInputField(controller: _controller)),
),
Expanded(
child: Text(
'seconds',
style: TextStyle(
fontSize: 12,
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
),
),
if (_errorText != null)
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
_errorText!,
style: const TextStyle(
color: ColorsManager.red,
fontSize: 14,
),
),
),
),
const Expanded(
child: Align(
alignment: Alignment.bottomCenter,
child: Text(
'2.click Next to Complete the calibration',
style: TextStyle(color: ColorsManager.lightGrayColor),
),
),
)
], ],
), ),
leftOnTap: () => Navigator.of(widget.parentContext).pop(), leftOnTap: () => Navigator.of(widget.parentContext).pop(),

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart';
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart';
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart'; import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class QuickCalibrationDialog extends StatelessWidget { class QuickCalibrationDialog extends StatelessWidget {
final int timControl; final int timControl;
@ -17,14 +18,15 @@ class QuickCalibrationDialog extends StatelessWidget {
@override @override
Widget build(_) { Widget build(_) {
return AlertDialog( return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: AccurateDialogWidget( content: AccurateDialogWidget(
title: 'Quick Calibration', title: 'Quick Calibration',
body: const NormalTextBodyForDialog( body: const NormalTextBodyForDialog(
title: 'Prepare Calibration:', title: 'Prepare Calibration:',
step1: step1:
'1. Confirm that the curtain is in the fully closed and suspended state.', 'Confirm that the curtain is in the fully closed and suspended state.',
step2: '2. click Next to Start calibration.', step2: 'click Next to Start calibration.',
), ),
leftOnTap: () => Navigator.of(parentContext).pop(), leftOnTap: () => Navigator.of(parentContext).pop(),
rightOnTap: () { rightOnTap: () {

View File

@ -19,11 +19,14 @@ class DeviceManagementContent extends StatelessWidget {
required this.device, required this.device,
required this.subSpaces, required this.subSpaces,
required this.deviceInfo, required this.deviceInfo,
required this.deviceManagementBloc,
}); });
final AllDevicesModel device; final AllDevicesModel device;
final List<SubSpaceModel> subSpaces; final List<SubSpaceModel> subSpaces;
final DeviceInfoModel deviceInfo; final DeviceInfoModel deviceInfo;
final DeviceManagementBloc deviceManagementBloc;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -87,6 +90,11 @@ class DeviceManagementContent extends StatelessWidget {
), ),
); );
}); });
deviceManagementBloc.add(UpdateSubSpaceName(
subspaceId: selectedSubSpace.id!,
deviceId: device.uuid!,
newSubSpaceName: selectedSubSpace.name ?? ''));
} }
}, },
child: infoRow( child: infoRow(

View File

@ -1,13 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/device_icon_type_helper.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/device_icon_type_helper.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/device_management_content.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/device_management_content.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/remove_device_widget.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/remove_device_widget.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
@ -17,7 +19,13 @@ import 'package:syncrow_web/web_layout/default_container.dart';
class DeviceSettingsPanel extends StatelessWidget { class DeviceSettingsPanel extends StatelessWidget {
final VoidCallback? onClose; final VoidCallback? onClose;
final AllDevicesModel device; final AllDevicesModel device;
const DeviceSettingsPanel({super.key, this.onClose, required this.device}); final DeviceManagementBloc deviceManagementBloc;
const DeviceSettingsPanel({
super.key,
this.onClose,
required this.device,
required this.deviceManagementBloc,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -71,10 +79,10 @@ class DeviceSettingsPanel extends StatelessWidget {
'Device Settings', 'Device Settings',
style: context.theme.textTheme.titleLarge! style: context.theme.textTheme.titleLarge!
.copyWith( .copyWith(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: ColorsManager.vividBlue color: ColorsManager.vividBlue
.withOpacity(0.7), .withOpacity(0.7),
fontSize: 24), fontSize: 24),
), ),
], ],
), ),
@ -134,8 +142,14 @@ class DeviceSettingsPanel extends StatelessWidget {
onFieldSubmitted: (value) { onFieldSubmitted: (value) {
_bloc.add(const ChangeNameEvent( _bloc.add(const ChangeNameEvent(
value: false)); value: false));
deviceManagementBloc
..add(UpdateDeviceName(
deviceId: device.uuid!,
newName: _bloc
.nameController
.text))..add(ResetSelectedDevices());
}, },
decoration: InputDecoration( decoration:const InputDecoration(
isDense: true, isDense: true,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
border: InputBorder.none, border: InputBorder.none,
@ -157,7 +171,7 @@ class DeviceSettingsPanel extends StatelessWidget {
onTap: () { onTap: () {
_bloc.add( _bloc.add(
const ChangeNameEvent( const ChangeNameEvent(
value: true)); value: true));
}, },
child: SvgPicture.asset( child: SvgPicture.asset(
Assets Assets
@ -190,6 +204,7 @@ class DeviceSettingsPanel extends StatelessWidget {
device: device, device: device,
subSpaces: subSpaces.cast<SubSpaceModel>(), subSpaces: subSpaces.cast<SubSpaceModel>(),
deviceInfo: deviceInfo, deviceInfo: deviceInfo,
deviceManagementBloc: deviceManagementBloc,
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
RemoveDeviceWidget(bloc: _bloc), RemoveDeviceWidget(bloc: _bloc),

View File

@ -40,7 +40,7 @@ class OneGangGlassSwitchBloc
emit(OneGangGlassSwitchLoading()); emit(OneGangGlassSwitchLoading());
try { try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
_listenToChanges(event.deviceId, emit); _listenToChanges(event.deviceId);
deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status); deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
@ -48,42 +48,28 @@ class OneGangGlassSwitchBloc
} }
} }
void _listenToChanges( StreamSubscription<DatabaseEvent>? _deviceStatusSubscription;
String deviceId,
Emitter<OneGangGlassSwitchState> emit, void _listenToChanges(String deviceId) {
) {
try { try {
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
final stream = ref.onValue; _deviceStatusSubscription = ref.onValue.listen((DatabaseEvent event) async {
if (event.snapshot.value == null) return;
stream.listen((DatabaseEvent event) { final usersMap = event.snapshot.value! as Map<dynamic, dynamic>;
final data = event.snapshot.value as Map<dynamic, dynamic>?;
if (data == null) return;
final statusList = <Status>[]; final statusList = <Status>[];
if (data['status'] != null) {
for (var element in data['status']) { usersMap['status'].forEach((element) {
statusList.add( statusList.add(Status(code: element['code'], value: element['value']));
Status( });
code: element['code'].toString(),
value: element['value'].toString(), deviceStatus =
), OneGangGlassStatusModel.fromJson(usersMap['productUuid'], statusList);
);
} add(StatusUpdated(deviceStatus));
}
if (statusList.isNotEmpty) {
final newStatus = OneGangGlassStatusModel.fromJson(deviceId, statusList);
if (newStatus != deviceStatus) {
deviceStatus = newStatus;
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
}
}
}); });
} catch (e) { } catch (_) {}
emit(OneGangGlassSwitchError('Failed to listen to changes: $e'));
}
} }
void _onStatusUpdated( void _onStatusUpdated(
@ -174,4 +160,10 @@ class OneGangGlassSwitchBloc
deviceStatus = deviceStatus.copyWith(switch1: value); deviceStatus = deviceStatus.copyWith(switch1: value);
} }
} }
@override
Future<void> close() {
_deviceStatusSubscription?.cancel();
return super.close();
}
} }

View File

@ -90,6 +90,8 @@ class OneGangGlassSwitchControlView extends StatelessWidget
child: BuildScheduleView( child: BuildScheduleView(
category: 'switch_1', category: 'switch_1',
deviceUuid: deviceId, deviceUuid: deviceId,
countdownCode: 'countdown_1',
deviceType: '1GT',
), ),
)); ));
}, },

View File

@ -80,6 +80,8 @@ class WallLightDeviceControl extends StatelessWidget
child: BuildScheduleView( child: BuildScheduleView(
category: 'switch_1', category: 'switch_1',
deviceUuid: deviceId, deviceUuid: deviceId,
countdownCode: 'countdown_1',
deviceType: '1G',
), ),
)); ));
}, },

View File

@ -277,6 +277,32 @@ class SmartPowerDeviceControl extends StatelessWidget
totalConsumption: 10000, totalConsumption: 10000,
date: blocProvider.formattedDate, date: blocProvider.formattedDate,
), ),
EnergyConsumptionPage(
formattedDate:
'${blocProvider.dateTime!.day}/${blocProvider.dateTime!.month}/${blocProvider.dateTime!.year} ${blocProvider.endChartDate}',
onTap: () {
blocProvider.add(SelectDateEvent(context: context));
},
widget: blocProvider.dateSwitcher(),
chartData: blocProvider.energyDataList.isNotEmpty
? blocProvider.energyDataList
: [
EnergyData('12:00 AM', 4.0),
EnergyData('01:00 AM', 6.5),
EnergyData('02:00 AM', 3.8),
EnergyData('03:00 AM', 3.2),
EnergyData('04:00 AM', 6.0),
EnergyData('05:00 AM', 3.4),
EnergyData('06:00 AM', 5.2),
EnergyData('07:00 AM', 3.5),
EnergyData('08:00 AM', 6.8),
EnergyData('09:00 AM', 5.6),
EnergyData('10:00 AM', 3.9),
EnergyData('11:00 AM', 4.0),
],
totalConsumption: 10000,
date: blocProvider.formattedDate,
),
], ],
), ),
), ),

View File

@ -47,7 +47,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
final success = await RemoteControlDeviceService().controlDevice( final success = await RemoteControlDeviceService().controlDevice(
deviceUuid: deviceId, deviceUuid: deviceId,
status: Status( status: Status(
code: 'countdown_1', code: event.countdownCode,
value: 0, value: 0,
), ),
); );
@ -80,15 +80,18 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
) { ) {
if (state is ScheduleLoaded) { if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded; final currentState = state as ScheduleLoaded;
emit(currentState.copyWith( emit(currentState.copyWith(
countdownSeconds: currentState.countdownSeconds,
selectedTime: currentState.selectedTime,
deviceId: deviceId,
scheduleMode: event.scheduleMode, scheduleMode: event.scheduleMode,
countdownRemaining: Duration.zero, countdownHours: currentState.countdownHours,
countdownHours: 0, countdownMinutes: currentState.countdownMinutes,
countdownMinutes: 0, inchingHours: currentState.inchingHours,
inchingHours: 0, inchingMinutes: currentState.inchingMinutes,
inchingMinutes: 0,
isCountdownActive: false,
isInchingActive: false, isInchingActive: false,
isCountdownActive: currentState.countdownRemaining > Duration.zero,
)); ));
} }
} }
@ -221,7 +224,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
deviceId, deviceId,
event.category, event.category,
); );
if (state is ScheduleLoaded) { if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded; final currentState = state as ScheduleLoaded;
emit(currentState.copyWith( emit(currentState.copyWith(
@ -230,7 +232,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
selectedDays: List.filled(7, false), selectedDays: List.filled(7, false),
functionOn: false, functionOn: false,
isEditing: false, isEditing: false,
countdownRemaining: Duration.zero,
)); ));
} else { } else {
emit(ScheduleLoaded( emit(ScheduleLoaded(
@ -285,12 +286,22 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
) async { ) async {
try { try {
if (state is ScheduleLoaded) { if (state is ScheduleLoaded) {
Status status = Status(code: '', value: '');
if (event.deviceType == 'CUR_2') {
status = status.copyWith(
code: 'control',
value: event.functionOn == true ? 'open' : 'close');
} else {
status =
status.copyWith(code: event.category, value: event.functionOn);
}
final dateTime = DateTime.parse(event.time); final dateTime = DateTime.parse(event.time);
final updatedSchedule = ScheduleEntry( final updatedSchedule = ScheduleEntry(
scheduleId: event.scheduleId, scheduleId: event.scheduleId,
category: event.category, category: event.category,
time: getTimeStampWithoutSeconds(dateTime).toString(), time: getTimeStampWithoutSeconds(dateTime).toString(),
function: Status(code: event.category, value: event.functionOn), function: status,
days: event.selectedDays, days: event.selectedDays,
); );
final success = await DevicesManagementApi().editScheduleRecord( final success = await DevicesManagementApi().editScheduleRecord(
@ -396,7 +407,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
final totalSeconds = final totalSeconds =
Duration(hours: event.hours, minutes: event.minutes).inSeconds; Duration(hours: event.hours, minutes: event.minutes).inSeconds;
final code = event.mode == ScheduleModes.countdown final code = event.mode == ScheduleModes.countdown
? 'countdown_1' ? event.countDownCode
: 'switch_inching'; : 'switch_inching';
final currentState = state as ScheduleLoaded; final currentState = state as ScheduleLoaded;
final duration = Duration(seconds: totalSeconds); final duration = Duration(seconds: totalSeconds);
@ -423,7 +434,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
); );
if (success) { if (success) {
if (code == 'countdown_1') { if (code == event.countDownCode) {
final countdownDuration = Duration(seconds: totalSeconds); final countdownDuration = Duration(seconds: totalSeconds);
emit( emit(
@ -437,7 +448,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
); );
if (countdownDuration.inSeconds > 0) { if (countdownDuration.inSeconds > 0) {
_startCountdownTimer(emit, countdownDuration); _startCountdownTimer(emit, countdownDuration, event.countDownCode);
} else { } else {
_countdownTimer?.cancel(); _countdownTimer?.cancel();
emit( emit(
@ -467,9 +478,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
} }
void _startCountdownTimer( void _startCountdownTimer(
Emitter<ScheduleState> emit, Emitter<ScheduleState> emit, Duration duration, String countdownCode) {
Duration duration,
) {
_countdownTimer?.cancel(); _countdownTimer?.cancel();
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (_currentCountdown != null && _currentCountdown! > Duration.zero) { if (_currentCountdown != null && _currentCountdown! > Duration.zero) {
@ -479,6 +488,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
} else { } else {
timer.cancel(); timer.cancel();
add(StopScheduleEvent( add(StopScheduleEvent(
countdownCode: countdownCode,
mode: _currentCountdown == null mode: _currentCountdown == null
? ScheduleModes.countdown ? ScheduleModes.countdown
: ScheduleModes.inching, : ScheduleModes.inching,
@ -515,70 +525,75 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
try { try {
final status = final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId); await DevicesManagementApi().getDeviceStatus(event.deviceId);
print(status.status); int totalSeconds = 0;
final countdownItem = status.status.firstWhere(
(item) => item.code == event.countdownCode,
orElse: () => Status(code: '', value: 0),
);
totalSeconds = (countdownItem.value as int?) ?? 0;
final countdownHours = totalSeconds ~/ 3600;
final countdownMinutes = (totalSeconds % 3600) ~/ 60;
final countdownSeconds = totalSeconds % 60;
final deviceStatus = final deviceStatus =
WaterHeaterStatusModel.fromJson(event.deviceId, status.status); WaterHeaterStatusModel.fromJson(event.deviceId, status.status);
final isCountdownActive = totalSeconds > 0;
final isInchingActive = !isCountdownActive &&
(deviceStatus.inchingHours > 0 || deviceStatus.inchingMinutes > 0);
final scheduleMode = final newState = state is ScheduleLoaded
deviceStatus.countdownHours > 0 || deviceStatus.countdownMinutes > 0 ? (state as ScheduleLoaded).copyWith(
? ScheduleModes.countdown scheduleMode: ScheduleModes.schedule,
: deviceStatus.inchingHours > 0 || deviceStatus.inchingMinutes > 0 countdownHours: countdownHours,
? ScheduleModes.inching countdownMinutes: countdownMinutes,
: ScheduleModes.schedule; countdownSeconds: countdownSeconds,
final isCountdown = scheduleMode == ScheduleModes.countdown; inchingHours: deviceStatus.inchingHours,
final isInching = scheduleMode == ScheduleModes.inching; inchingMinutes: deviceStatus.inchingMinutes,
isCountdownActive: isCountdownActive,
isInchingActive: isInchingActive,
countdownRemaining: isCountdownActive
? Duration(seconds: totalSeconds)
: Duration.zero,
)
: ScheduleLoaded(
scheduleMode: ScheduleModes.schedule,
schedules: const [],
selectedTime: null,
selectedDays: List.filled(7, false),
functionOn: false,
isEditing: false,
deviceId: event.deviceId,
countdownHours: countdownHours,
countdownMinutes: countdownMinutes,
countdownSeconds: countdownSeconds,
inchingHours: deviceStatus.inchingHours,
inchingMinutes: deviceStatus.inchingMinutes,
isCountdownActive: isCountdownActive,
isInchingActive: isInchingActive,
countdownRemaining: isCountdownActive
? Duration(seconds: totalSeconds)
: Duration.zero,
);
emit(newState);
Duration? countdownRemaining; if (isCountdownActive) {
var isCountdownActive = false; _countdownTimer?.cancel();
var isInchingActive = false; _currentCountdown = Duration(seconds: totalSeconds);
countdownRemaining = _currentCountdown!;
if (isCountdown) { if (totalSeconds > 0) {
countdownRemaining = Duration( _startCountdownTimer(
hours: deviceStatus.countdownHours, emit, Duration(seconds: totalSeconds), event.countdownCode);
minutes: deviceStatus.countdownMinutes, } else {
); add(StopScheduleEvent(
isCountdownActive = countdownRemaining > Duration.zero; countdownCode: event.countdownCode,
} else if (isInching) { mode: ScheduleModes.countdown,
isInchingActive = Duration( deviceId: event.deviceId,
hours: deviceStatus.inchingHours, ));
minutes: deviceStatus.inchingMinutes, }
) >
Duration.zero;
}
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
emit(currentState.copyWith(
scheduleMode: scheduleMode,
countdownHours: deviceStatus.countdownHours,
countdownMinutes: deviceStatus.countdownMinutes,
inchingHours: deviceStatus.inchingHours,
inchingMinutes: deviceStatus.inchingMinutes,
isCountdownActive: isCountdownActive,
isInchingActive: isInchingActive,
countdownRemaining: countdownRemaining ?? Duration.zero,
));
} else { } else {
emit(ScheduleLoaded( _countdownTimer?.cancel();
schedules: const [],
selectedTime: null,
selectedDays: List.filled(7, false),
functionOn: false,
isEditing: false,
deviceId: deviceId,
scheduleMode: scheduleMode,
countdownHours: deviceStatus.countdownHours,
countdownMinutes: deviceStatus.countdownMinutes,
inchingHours: deviceStatus.inchingHours,
inchingMinutes: deviceStatus.inchingMinutes,
isCountdownActive: isCountdownActive,
isInchingActive: isInchingActive,
countdownRemaining: countdownRemaining ?? Duration.zero,
));
} }
// if (isCountdownActive && countdownRemaining != null) {
// _startCountdownTimer(emit, countdownRemaining);
// }
} catch (e) { } catch (e) {
emit(ScheduleError('Failed to fetch device status: $e')); emit(ScheduleError('Failed to fetch device status: $e'));
} }

View File

@ -91,6 +91,7 @@ class ScheduleEditEvent extends ScheduleEvent {
final String time; final String time;
final List<String> selectedDays; final List<String> selectedDays;
final bool functionOn; final bool functionOn;
final String deviceType;
const ScheduleEditEvent({ const ScheduleEditEvent({
required this.scheduleId, required this.scheduleId,
@ -98,6 +99,7 @@ class ScheduleEditEvent extends ScheduleEvent {
required this.time, required this.time,
required this.selectedDays, required this.selectedDays,
required this.functionOn, required this.functionOn,
required this.deviceType,
}); });
@override @override
@ -107,6 +109,7 @@ class ScheduleEditEvent extends ScheduleEvent {
time, time,
selectedDays, selectedDays,
functionOn, functionOn,
deviceType,
]; ];
} }
@ -138,11 +141,13 @@ class ScheduleUpdateEntryEvent extends ScheduleEvent {
class UpdateScheduleModeEvent extends ScheduleEvent { class UpdateScheduleModeEvent extends ScheduleEvent {
final ScheduleModes scheduleMode; final ScheduleModes scheduleMode;
final String countdownCode;
const UpdateScheduleModeEvent({required this.scheduleMode}); const UpdateScheduleModeEvent(
{required this.scheduleMode, required this.countdownCode});
@override @override
List<Object> get props => [scheduleMode]; List<Object> get props => [scheduleMode, countdownCode!];
} }
class UpdateCountdownTimeEvent extends ScheduleEvent { class UpdateCountdownTimeEvent extends ScheduleEvent {
@ -177,28 +182,32 @@ class StartScheduleEvent extends ScheduleEvent {
final ScheduleModes mode; final ScheduleModes mode;
final int hours; final int hours;
final int minutes; final int minutes;
final String countDownCode;
const StartScheduleEvent({ const StartScheduleEvent({
required this.mode, required this.mode,
required this.hours, required this.hours,
required this.minutes, required this.minutes,
required this.countDownCode,
}); });
@override @override
List<Object?> get props => [mode, hours, minutes]; List<Object?> get props => [mode, hours, minutes, countDownCode];
} }
class StopScheduleEvent extends ScheduleEvent { class StopScheduleEvent extends ScheduleEvent {
final ScheduleModes mode; final ScheduleModes mode;
final String deviceId; final String deviceId;
final String countdownCode;
const StopScheduleEvent({ const StopScheduleEvent({
required this.mode, required this.mode,
required this.deviceId, required this.deviceId,
required this.countdownCode,
}); });
@override @override
List<Object?> get props => [mode, deviceId]; List<Object?> get props => [mode, deviceId, countdownCode];
} }
class ScheduleDecrementCountdownEvent extends ScheduleEvent { class ScheduleDecrementCountdownEvent extends ScheduleEvent {
@ -210,11 +219,13 @@ class ScheduleDecrementCountdownEvent extends ScheduleEvent {
class ScheduleFetchStatusEvent extends ScheduleEvent { class ScheduleFetchStatusEvent extends ScheduleEvent {
final String deviceId; final String deviceId;
final String countdownCode;
const ScheduleFetchStatusEvent(this.deviceId); const ScheduleFetchStatusEvent(
{required this.deviceId, required this.countdownCode});
@override @override
List<Object> get props => [deviceId]; List<Object> get props => [deviceId, countdownCode];
} }
class DeleteScheduleEvent extends ScheduleEvent { class DeleteScheduleEvent extends ScheduleEvent {

View File

@ -29,7 +29,7 @@ class ScheduleLoaded extends ScheduleState {
final int inchingSeconds; final int inchingSeconds;
final bool isInchingActive; final bool isInchingActive;
final ScheduleModes scheduleMode; final ScheduleModes scheduleMode;
final Duration? countdownRemaining; final Duration countdownRemaining;
final int? countdownSeconds; final int? countdownSeconds;
const ScheduleLoaded({ const ScheduleLoaded({
@ -48,7 +48,7 @@ class ScheduleLoaded extends ScheduleState {
this.inchingMinutes = 0, this.inchingMinutes = 0,
this.isInchingActive = false, this.isInchingActive = false,
this.scheduleMode = ScheduleModes.countdown, this.scheduleMode = ScheduleModes.countdown,
this.countdownRemaining, this.countdownRemaining = Duration.zero,
}); });
ScheduleLoaded copyWith({ ScheduleLoaded copyWith({

View File

@ -11,6 +11,7 @@ class CountdownModeButtons extends StatelessWidget {
final String deviceId; final String deviceId;
final int hours; final int hours;
final int minutes; final int minutes;
final String countDownCode;
const CountdownModeButtons({ const CountdownModeButtons({
super.key, super.key,
@ -18,6 +19,7 @@ class CountdownModeButtons extends StatelessWidget {
required this.deviceId, required this.deviceId,
required this.hours, required this.hours,
required this.minutes, required this.minutes,
required this.countDownCode,
}); });
@override @override
@ -43,6 +45,7 @@ class CountdownModeButtons extends StatelessWidget {
StopScheduleEvent( StopScheduleEvent(
mode: ScheduleModes.countdown, mode: ScheduleModes.countdown,
deviceId: deviceId, deviceId: deviceId,
countdownCode: countDownCode,
), ),
); );
}, },
@ -54,10 +57,10 @@ class CountdownModeButtons extends StatelessWidget {
onPressed: () { onPressed: () {
context.read<ScheduleBloc>().add( context.read<ScheduleBloc>().add(
StartScheduleEvent( StartScheduleEvent(
mode: ScheduleModes.countdown, mode: ScheduleModes.countdown,
hours: hours, hours: hours,
minutes: minutes, minutes: minutes,
), countDownCode: countDownCode),
); );
}, },
backgroundColor: ColorsManager.primaryColor, backgroundColor: ColorsManager.primaryColor,

View File

@ -75,23 +75,33 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
final isCountDown = state.scheduleMode == ScheduleModes.countdown; final isCountDown = state.scheduleMode == ScheduleModes.countdown;
final isActive = final isActive =
isCountDown ? state.isCountdownActive : state.isInchingActive; isCountDown ? state.isCountdownActive : state.isInchingActive;
final displayHours = isActive && state.countdownRemaining != null
? state.countdownRemaining!.inHours
: (isCountDown ? state.countdownHours : state.inchingHours);
final displayMinutes = isActive && state.countdownRemaining != null
? state.countdownRemaining!.inMinutes.remainder(60)
: (isCountDown ? state.countdownMinutes : state.inchingMinutes);
final displaySeconds = isActive && state.countdownRemaining != null
? state.countdownRemaining!.inSeconds.remainder(60)
: (isCountDown ? state.countdownSeconds : state.inchingSeconds);
_updateControllers(displayHours, displayMinutes, displaySeconds!); final displayHours =
isActive && state.countdownRemaining != Duration.zero
? state.countdownRemaining.inHours
: (isCountDown ? state.countdownHours : state.inchingHours);
if (displayHours == 0 && displayMinutes == 0 && displaySeconds == 0) { final displayMinutes =
isActive && state.countdownRemaining != Duration.zero
? state.countdownRemaining.inMinutes.remainder(60)
: (isCountDown ? state.countdownMinutes : state.inchingMinutes);
final displaySeconds =
isActive && state.countdownRemaining != Duration.zero
? state.countdownRemaining.inSeconds.remainder(60)
: (isCountDown ? (state.countdownSeconds ?? 0) : 0);
_updateControllers(displayHours, displayMinutes, displaySeconds);
if (isActive &&
displayHours == 0 &&
displayMinutes == 0 &&
displaySeconds == 0) {
context.read<ScheduleBloc>().add( context.read<ScheduleBloc>().add(
StopScheduleEvent( StopScheduleEvent(
mode: ScheduleModes.countdown, mode: ScheduleModes.countdown,
deviceId: widget.deviceId, deviceId: widget.deviceId,
countdownCode: '',
), ),
); );
} }

View File

@ -43,7 +43,9 @@ class InchingModeButtons extends StatelessWidget {
onPressed: () { onPressed: () {
context.read<ScheduleBloc>().add( context.read<ScheduleBloc>().add(
StopScheduleEvent( StopScheduleEvent(
deviceId: deviceId, mode: ScheduleModes.inching), deviceId: deviceId,
mode: ScheduleModes.inching,
countdownCode: ''),
); );
}, },
backgroundColor: Colors.red, backgroundColor: Colors.red,

View File

@ -18,11 +18,15 @@ class BuildScheduleView extends StatelessWidget {
super.key, super.key,
required this.deviceUuid, required this.deviceUuid,
required this.category, required this.category,
required this.countdownCode,
this.code, this.code,
required this.deviceType,
}); });
final String deviceUuid; final String deviceUuid;
final String category; final String category;
final String? code; final String? code;
final String? countdownCode;
final String deviceType;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -31,7 +35,8 @@ class BuildScheduleView extends StatelessWidget {
deviceId: deviceUuid, deviceId: deviceUuid,
) )
..add(ScheduleGetEvent(category: category)) ..add(ScheduleGetEvent(category: category))
..add(ScheduleFetchStatusEvent(deviceUuid)), ..add(ScheduleFetchStatusEvent(
deviceId: deviceUuid, countdownCode: countdownCode ?? '')),
child: Dialog( child: Dialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
insetPadding: const EdgeInsets.all(20), insetPadding: const EdgeInsets.all(20),
@ -52,28 +57,32 @@ class BuildScheduleView extends StatelessWidget {
children: [ children: [
const ScheduleHeader(), const ScheduleHeader(),
const SizedBox(height: 20), const SizedBox(height: 20),
ScheduleModeSelector( if (deviceType == 'CUR_2')
currentMode: state.scheduleMode, const SizedBox()
), else
ScheduleModeSelector(
countdownCode: countdownCode ?? '',
currentMode: state.scheduleMode,
),
const SizedBox(height: 20), const SizedBox(height: 20),
if (state.scheduleMode == ScheduleModes.schedule) if (state.scheduleMode == ScheduleModes.schedule)
ScheduleManagementUI( ScheduleManagementUI(
deviceType: deviceType,
category: category, category: category,
deviceUuid: deviceUuid, deviceUuid: deviceUuid,
onAddSchedule: () async { onAddSchedule: () async {
final entry = await ScheduleDialogHelper final entry = await ScheduleDialogHelper
.showAddScheduleDialog( .showAddScheduleDialog(context,
context, schedule: ScheduleEntry(
schedule: ScheduleEntry( category: category,
category: category, time: '',
time: '', function: Status(
function: Status( code: code.toString(), value: null),
code: code.toString(), value: null), days: [],
days: [], ),
), isEdit: false,
isEdit: false, code: code,
code: code, deviceType: deviceType);
);
if (entry != null) { if (entry != null) {
context.read<ScheduleBloc>().add( context.read<ScheduleBloc>().add(
ScheduleAddEvent( ScheduleAddEvent(
@ -87,14 +96,16 @@ class BuildScheduleView extends StatelessWidget {
} }
}, },
), ),
if (state.scheduleMode == ScheduleModes.countdown || if (deviceType != 'CUR_2')
state.scheduleMode == ScheduleModes.inching) if (state.scheduleMode == ScheduleModes.countdown ||
CountdownInchingView( state.scheduleMode == ScheduleModes.inching)
deviceId: deviceUuid, CountdownInchingView(
), deviceId: deviceUuid,
),
const SizedBox(height: 20), const SizedBox(height: 20),
if (state.scheduleMode == ScheduleModes.countdown) if (state.scheduleMode == ScheduleModes.countdown)
CountdownModeButtons( CountdownModeButtons(
countDownCode: countdownCode ?? '',
isActive: state.isCountdownActive, isActive: state.isCountdownActive,
deviceId: deviceUuid, deviceId: deviceUuid,
hours: state.countdownHours, hours: state.countdownHours,

View File

@ -5,14 +5,16 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
class ScheduleManagementUI extends StatelessWidget { class ScheduleManagementUI extends StatelessWidget {
final String deviceUuid; final String deviceUuid;
final VoidCallback onAddSchedule; final VoidCallback onAddSchedule;
final String category; final String category;
final String deviceType;
const ScheduleManagementUI({ const ScheduleManagementUI({
super.key, super.key,
required this.deviceUuid, required this.deviceUuid,
required this.onAddSchedule, required this.onAddSchedule,
required this.deviceType,
this.category = 'switch_1', this.category = 'switch_1',
}); });
@ -44,7 +46,11 @@ class ScheduleManagementUI extends StatelessWidget {
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
ScheduleTableWidget(deviceUuid: deviceUuid, category: category), ScheduleTableWidget(
deviceUuid: deviceUuid,
category: category,
deviceType: deviceType,
),
], ],
); );
} }

View File

@ -7,10 +7,12 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart';
class ScheduleModeSelector extends StatelessWidget { class ScheduleModeSelector extends StatelessWidget {
final ScheduleModes currentMode; final ScheduleModes currentMode;
final String countdownCode;
const ScheduleModeSelector({ const ScheduleModeSelector({
super.key, super.key,
required this.currentMode, required this.currentMode,
required this.countdownCode,
}); });
@override @override
@ -71,7 +73,8 @@ class ScheduleModeSelector extends StatelessWidget {
onChanged: (ScheduleModes? value) { onChanged: (ScheduleModes? value) {
if (value != null) { if (value != null) {
context.read<ScheduleBloc>().add( context.read<ScheduleBloc>().add(
UpdateScheduleModeEvent(scheduleMode: value), UpdateScheduleModeEvent(
scheduleMode: value, countdownCode: countdownCode),
); );
if (value == ScheduleModes.schedule) { if (value == ScheduleModes.schedule) {
context.read<ScheduleBloc>().add( context.read<ScheduleBloc>().add(

View File

@ -12,11 +12,13 @@ import 'package:syncrow_web/utils/format_date_time.dart';
class ScheduleTableWidget extends StatelessWidget { class ScheduleTableWidget extends StatelessWidget {
final String deviceUuid; final String deviceUuid;
final String category; final String category;
final String deviceType;
const ScheduleTableWidget({ const ScheduleTableWidget({
super.key, super.key,
required this.deviceUuid, required this.deviceUuid,
this.category = 'switch_1', this.category = 'switch_1',
required this.deviceType,
}); });
@override @override
@ -25,13 +27,14 @@ class ScheduleTableWidget extends StatelessWidget {
create: (_) => ScheduleBloc( create: (_) => ScheduleBloc(
deviceId: deviceUuid, deviceId: deviceUuid,
)..add(ScheduleGetEvent(category: category)), )..add(ScheduleGetEvent(category: category)),
child: _ScheduleTableView(), child: _ScheduleTableView(deviceType),
); );
} }
} }
class _ScheduleTableView extends StatelessWidget { class _ScheduleTableView extends StatelessWidget {
const _ScheduleTableView(); final String deviceType;
const _ScheduleTableView(this.deviceType);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -81,7 +84,7 @@ class _ScheduleTableView extends StatelessWidget {
bottomLeft: Radius.circular(20), bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20)), bottomRight: Radius.circular(20)),
), ),
child: _buildTableBody(state.schedules, context)); child: _buildTableBody(state.schedules, context, deviceType));
} }
if (state is ScheduleError) { if (state is ScheduleError) {
return Center(child: Text(state.error)); return Center(child: Text(state.error));
@ -123,7 +126,8 @@ class _ScheduleTableView extends StatelessWidget {
); );
} }
Widget _buildTableBody(List<ScheduleModel> schedules, BuildContext context) { Widget _buildTableBody(
List<ScheduleModel> schedules, BuildContext context, String deviceType) {
return SizedBox( return SizedBox(
height: 200, height: 200,
child: SingleChildScrollView( child: SingleChildScrollView(
@ -132,7 +136,8 @@ class _ScheduleTableView extends StatelessWidget {
defaultVerticalAlignment: TableCellVerticalAlignment.middle, defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: [ children: [
for (int i = 0; i < schedules.length; i++) for (int i = 0; i < schedules.length; i++)
_buildScheduleRow(schedules[i], i, context), _buildScheduleRow(schedules[i], i, context,
deviceType: deviceType),
], ],
), ),
), ),
@ -155,25 +160,19 @@ class _ScheduleTableView extends StatelessWidget {
} }
TableRow _buildScheduleRow( TableRow _buildScheduleRow(
ScheduleModel schedule, int index, BuildContext context) { ScheduleModel schedule, int index, BuildContext context,
{required String deviceType}) {
return TableRow( return TableRow(
children: [ children: [
Center( Center(
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () { onTap: () {
bool temp;
if (schedule.category == 'CUR_2') {
temp = schedule.function.value == 'open' ? true : false;
} else {
temp = schedule.function.value as bool;
}
context.read<ScheduleBloc>().add( context.read<ScheduleBloc>().add(
ScheduleUpdateEntryEvent( ScheduleUpdateEntryEvent(
category: schedule.category, category: schedule.category,
scheduleId: schedule.scheduleId, scheduleId: schedule.scheduleId,
functionOn: temp, functionOn: schedule.function.value,
// schedule.function.value,
enable: !schedule.enable, enable: !schedule.enable,
), ),
); );
@ -195,8 +194,9 @@ 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 (schedule.category == 'CUR_2') if (deviceType == 'CUR_2')
Center(child: Text(schedule.function.value)) Center(
child: Text(schedule.function.value == true ? 'open' : 'close'))
else else
Center(child: Text(schedule.function.value ? 'On' : 'Off')), Center(child: Text(schedule.function.value ? 'On' : 'Off')),
Center( Center(
@ -206,18 +206,27 @@ class _ScheduleTableView extends StatelessWidget {
TextButton( TextButton(
style: TextButton.styleFrom(padding: EdgeInsets.zero), style: TextButton.styleFrom(padding: EdgeInsets.zero),
onPressed: () { onPressed: () {
ScheduleDialogHelper.showAddScheduleDialog( ScheduleDialogHelper.showAddScheduleDialog(context,
context, schedule: ScheduleEntry.fromScheduleModel(schedule),
schedule: ScheduleEntry.fromScheduleModel(schedule), isEdit: true,
isEdit: true, deviceType: deviceType)
).then((updatedSchedule) { .then((updatedSchedule) {
if (updatedSchedule != null) { if (updatedSchedule != null) {
bool temp;
if (deviceType == 'CUR_2') {
updatedSchedule.function.value == 'open'
? temp = true
: temp = false;
} else {
temp = updatedSchedule.function.value;
}
context.read<ScheduleBloc>().add( context.read<ScheduleBloc>().add(
ScheduleEditEvent( ScheduleEditEvent(
deviceType: deviceType,
scheduleId: schedule.scheduleId, scheduleId: schedule.scheduleId,
category: schedule.category, category: schedule.category,
time: updatedSchedule.time, time: updatedSchedule.time,
functionOn: updatedSchedule.function.value, functionOn: temp,
selectedDays: updatedSchedule.days), selectedDays: updatedSchedule.days),
); );
} }

View File

@ -41,7 +41,7 @@ class ThreeGangGlassSwitchBloc
emit(ThreeGangGlassSwitchLoading()); emit(ThreeGangGlassSwitchLoading());
try { try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
_listenToChanges(event.deviceId, emit); _listenToChanges(event.deviceId);
deviceStatus = deviceStatus =
ThreeGangGlassStatusModel.fromJson(event.deviceId, status.status); ThreeGangGlassStatusModel.fromJson(event.deviceId, status.status);
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus)); emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
@ -50,42 +50,28 @@ class ThreeGangGlassSwitchBloc
} }
} }
void _listenToChanges( StreamSubscription<DatabaseEvent>? _deviceStatusSubscription;
String deviceId,
Emitter<ThreeGangGlassSwitchState> emit, void _listenToChanges(String deviceId) {
) {
try { try {
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
final stream = ref.onValue; _deviceStatusSubscription = ref.onValue.listen((DatabaseEvent event) async {
if (event.snapshot.value == null) return;
stream.listen((DatabaseEvent event) { final usersMap = event.snapshot.value! as Map<dynamic, dynamic>;
final data = event.snapshot.value as Map<dynamic, dynamic>?;
if (data == null) return;
final statusList = <Status>[]; final statusList = <Status>[];
if (data['status'] != null) {
for (var element in data['status']) { usersMap['status'].forEach((element) {
statusList.add( statusList.add(Status(code: element['code'], value: element['value']));
Status( });
code: element['code'].toString(),
value: element['value'].toString(), deviceStatus =
), ThreeGangGlassStatusModel.fromJson(usersMap['productUuid'], statusList);
);
} add(StatusUpdated(deviceStatus));
}
if (statusList.isNotEmpty) {
final newStatus = ThreeGangGlassStatusModel.fromJson(deviceId, statusList);
if (newStatus != deviceStatus) {
deviceStatus = newStatus;
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
}
}
}); });
} catch (e) { } catch (_) {}
emit(ThreeGangGlassSwitchError('Failed to listen to changes: $e'));
}
} }
void _onStatusUpdated( void _onStatusUpdated(
@ -184,4 +170,10 @@ class ThreeGangGlassSwitchBloc
break; break;
} }
} }
@override
Future<void> close() {
_deviceStatusSubscription?.cancel();
return super.close();
}
} }

View File

@ -111,6 +111,8 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget
child: BuildScheduleView( child: BuildScheduleView(
category: 'switch_1', category: 'switch_1',
deviceUuid: deviceId, deviceUuid: deviceId,
countdownCode: 'countdown_1',
deviceType: '3GT',
), ),
)); ));
}, },
@ -127,6 +129,8 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget
child: BuildScheduleView( child: BuildScheduleView(
category: 'switch_2', category: 'switch_2',
deviceUuid: deviceId, deviceUuid: deviceId,
countdownCode: 'countdown_2',
deviceType: '3GT',
), ),
)); ));
}, },
@ -143,6 +147,8 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget
child: BuildScheduleView( child: BuildScheduleView(
category: 'switch_3', category: 'switch_3',
deviceUuid: deviceId, deviceUuid: deviceId,
countdownCode: 'countdown_3',
deviceType: '3GT',
), ),
)); ));
}, },

View File

@ -102,6 +102,8 @@ class LivingRoomDeviceControlsView extends StatelessWidget
child: BuildScheduleView( child: BuildScheduleView(
deviceUuid: deviceId, deviceUuid: deviceId,
category: 'switch_1', category: 'switch_1',
countdownCode: 'countdown_1',
deviceType: '3G',
), ),
)); ));
}, },
@ -118,6 +120,8 @@ class LivingRoomDeviceControlsView extends StatelessWidget
child: BuildScheduleView( child: BuildScheduleView(
deviceUuid: deviceId, deviceUuid: deviceId,
category: 'switch_2', category: 'switch_2',
countdownCode: 'countdown_2',
deviceType: '3G',
), ),
)); ));
}, },
@ -134,6 +138,8 @@ class LivingRoomDeviceControlsView extends StatelessWidget
child: BuildScheduleView( child: BuildScheduleView(
deviceUuid: deviceId, deviceUuid: deviceId,
category: 'switch_3', category: 'switch_3',
countdownCode: 'countdown_3',
deviceType: '3G',
), ),
)); ));
}, },

View File

@ -1,173 +1,177 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:firebase_database/firebase_database.dart'; import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart'; import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart';
import 'package:syncrow_web/services/batch_control_devices_service.dart'; import 'package:syncrow_web/services/batch_control_devices_service.dart';
import 'package:syncrow_web/services/control_device_service.dart'; import 'package:syncrow_web/services/control_device_service.dart';
import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/devices_mang_api.dart';
part 'two_gang_glass_switch_event.dart'; part 'two_gang_glass_switch_event.dart';
part 'two_gang_glass_switch_state.dart'; part 'two_gang_glass_switch_state.dart';
class TwoGangGlassSwitchBloc class TwoGangGlassSwitchBloc
extends Bloc<TwoGangGlassSwitchEvent, TwoGangGlassSwitchState> { extends Bloc<TwoGangGlassSwitchEvent, TwoGangGlassSwitchState> {
final String deviceId; final String deviceId;
final ControlDeviceService controlDeviceService; final ControlDeviceService controlDeviceService;
final BatchControlDevicesService batchControlDevicesService; final BatchControlDevicesService batchControlDevicesService;
late TwoGangGlassStatusModel deviceStatus; late TwoGangGlassStatusModel deviceStatus;
TwoGangGlassSwitchBloc({ TwoGangGlassSwitchBloc({
required this.deviceId, required this.deviceId,
required this.controlDeviceService, required this.controlDeviceService,
required this.batchControlDevicesService, required this.batchControlDevicesService,
}) : super(TwoGangGlassSwitchInitial()) { }) : super(TwoGangGlassSwitchInitial()) {
on<TwoGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus); on<TwoGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
on<TwoGangGlassSwitchControl>(_onControl); on<TwoGangGlassSwitchControl>(_onControl);
on<TwoGangGlassSwitchBatchControl>(_onBatchControl); on<TwoGangGlassSwitchBatchControl>(_onBatchControl);
on<TwoGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus); on<TwoGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus);
on<TwoGangGlassFactoryReset>(_onFactoryReset); on<TwoGangGlassFactoryReset>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated); on<StatusUpdated>(_onStatusUpdated);
}
Future<void> _onFetchDeviceStatus(
TwoGangGlassSwitchFetchDeviceEvent event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(TwoGangGlassSwitchError(e.toString()));
} }
}
void _listenToChanges(String deviceId) { Future<void> _onFetchDeviceStatus(
try { TwoGangGlassSwitchFetchDeviceEvent event,
final ref = FirebaseDatabase.instance.ref( Emitter<TwoGangGlassSwitchState> emit,
'device-status/$deviceId', ) async {
); emit(TwoGangGlassSwitchLoading());
try {
ref.onValue.listen((event) { final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>; deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
List<Status> statusList = []; emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
eventsMap['status'].forEach((element) { } catch (e) {
statusList.add(Status(code: element['code'], value: element['value'])); emit(TwoGangGlassSwitchError(e.toString()));
});
deviceStatus = TwoGangGlassStatusModel.fromJson(deviceId, statusList);
add(StatusUpdated(deviceStatus));
});
} catch (_) {
log(
'Error listening to changes',
name: 'TwoGangGlassSwitchBloc._listenToChanges',
);
}
}
Future<void> _onControl(
TwoGangGlassSwitchControl event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
try {
await controlDeviceService.controlDevice(
deviceUuid: event.deviceId,
status: Status(code: event.code, value: event.value),
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _onBatchControl(
TwoGangGlassSwitchBatchControl event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
try {
await batchControlDevicesService.batchControlDevices(
uuids: event.deviceIds,
code: event.code,
value: event.value,
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _onFetchBatchStatus(
TwoGangGlassSwitchFetchBatchStatusEvent event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
try {
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = TwoGangGlassStatusModel.fromJson(
event.deviceIds.first,
status.status,
);
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
} catch (e) {
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _onFactoryReset(
TwoGangGlassFactoryReset event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
try {
final response = await DevicesManagementApi().factoryReset(
event.factoryReset,
event.deviceId,
);
if (!response) {
emit(TwoGangGlassSwitchError('Failed to reset device'));
} else {
add(TwoGangGlassSwitchFetchDeviceEvent(event.deviceId));
} }
} catch (e) {
emit(TwoGangGlassSwitchError(e.toString()));
} }
}
void _onStatusUpdated( StreamSubscription<DatabaseEvent>? _deviceStatusSubscription;
StatusUpdated event,
Emitter<TwoGangGlassSwitchState> emit,
) {
deviceStatus = event.deviceStatus;
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
}
void _updateLocalValue(String code, bool value) { void _listenToChanges(String deviceId) {
switch (code) { try {
case 'switch_1': final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
deviceStatus = deviceStatus.copyWith(switch1: value); _deviceStatusSubscription = ref.onValue.listen((DatabaseEvent event) async {
break; if (event.snapshot.value == null) return;
case 'switch_2':
deviceStatus = deviceStatus.copyWith(switch2: value); final usersMap = event.snapshot.value! as Map<dynamic, dynamic>;
break;
final statusList = <Status>[];
usersMap['status'].forEach((element) {
statusList.add(Status(code: element['code'], value: element['value']));
});
deviceStatus =
TwoGangGlassStatusModel.fromJson(usersMap['productUuid'], statusList);
add(StatusUpdated(deviceStatus));
});
} catch (_) {}
} }
Future<void> _onControl(
TwoGangGlassSwitchControl event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
try {
await controlDeviceService.controlDevice(
deviceUuid: event.deviceId,
status: Status(code: event.code, value: event.value),
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _onBatchControl(
TwoGangGlassSwitchBatchControl event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
try {
await batchControlDevicesService.batchControlDevices(
uuids: event.deviceIds,
code: event.code,
value: event.value,
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _onFetchBatchStatus(
TwoGangGlassSwitchFetchBatchStatusEvent event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
try {
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = TwoGangGlassStatusModel.fromJson(
event.deviceIds.first,
status.status,
);
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
} catch (e) {
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _onFactoryReset(
TwoGangGlassFactoryReset event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
try {
final response = await DevicesManagementApi().factoryReset(
event.factoryReset,
event.deviceId,
);
if (!response) {
emit(TwoGangGlassSwitchError('Failed to reset device'));
} else {
add(TwoGangGlassSwitchFetchDeviceEvent(event.deviceId));
}
} catch (e) {
emit(TwoGangGlassSwitchError(e.toString()));
}
}
void _onStatusUpdated(
StatusUpdated event,
Emitter<TwoGangGlassSwitchState> emit,
) {
deviceStatus = event.deviceStatus;
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
}
void _updateLocalValue(String code, bool value) {
switch (code) {
case 'switch_1':
deviceStatus = deviceStatus.copyWith(switch1: value);
break;
case 'switch_2':
deviceStatus = deviceStatus.copyWith(switch2: value);
break;
}
}
@override
Future<void> close() {
_deviceStatusSubscription?.cancel();
return super.close();
} }
} }

View File

@ -102,6 +102,8 @@ class TwoGangGlassSwitchControlView extends StatelessWidget
builder: (ctx) => BlocProvider.value( builder: (ctx) => BlocProvider.value(
value: BlocProvider.of<TwoGangGlassSwitchBloc>(context), value: BlocProvider.of<TwoGangGlassSwitchBloc>(context),
child: BuildScheduleView( child: BuildScheduleView(
deviceType: '2GT',
countdownCode: 'countdown_1',
deviceUuid: deviceId, deviceUuid: deviceId,
category: 'switch_1', category: 'switch_1',
), ),
@ -118,6 +120,8 @@ class TwoGangGlassSwitchControlView extends StatelessWidget
builder: (ctx) => BlocProvider.value( builder: (ctx) => BlocProvider.value(
value: BlocProvider.of<TwoGangGlassSwitchBloc>(context), value: BlocProvider.of<TwoGangGlassSwitchBloc>(context),
child: BuildScheduleView( child: BuildScheduleView(
deviceType: '2GT',
countdownCode: 'countdown_2',
deviceUuid: deviceId, deviceUuid: deviceId,
category: 'switch_2', category: 'switch_2',
), ),

View File

@ -97,6 +97,8 @@ class TwoGangBatchControlView extends StatelessWidget
child: BuildScheduleView( child: BuildScheduleView(
category: 'switch_1', category: 'switch_1',
deviceUuid: deviceIds.first, deviceUuid: deviceIds.first,
countdownCode: 'countdown_1',
deviceType: '2G',
), ),
)); ));
}, },
@ -114,6 +116,8 @@ class TwoGangBatchControlView extends StatelessWidget
child: BuildScheduleView( child: BuildScheduleView(
category: 'switch_2', category: 'switch_2',
deviceUuid: deviceIds.first, deviceUuid: deviceIds.first,
countdownCode: 'countdown_2',
deviceType: '2G',
), ),
)); ));
}, },
@ -121,10 +125,7 @@ class TwoGangBatchControlView extends StatelessWidget
subtitle: 'Scheduling', subtitle: 'Scheduling',
iconPath: Assets.scheduling, iconPath: Assets.scheduling,
), ),
// FirmwareUpdateWidget(
// deviceId: deviceIds.first,
// version: 12,
// ),
FactoryResetWidget(callFactoryReset: () { FactoryResetWidget(callFactoryReset: () {
context.read<TwoGangSwitchBloc>().add( context.read<TwoGangSwitchBloc>().add(
TwoGangFactoryReset( TwoGangFactoryReset(

View File

@ -103,6 +103,8 @@ class TwoGangDeviceControlView extends StatelessWidget
child: BuildScheduleView( child: BuildScheduleView(
deviceUuid: deviceId, deviceUuid: deviceId,
category: 'switch_1', category: 'switch_1',
countdownCode: 'countdown_1',
deviceType: '2G',
), ),
)); ));
}, },
@ -125,6 +127,8 @@ class TwoGangDeviceControlView extends StatelessWidget
child: BuildScheduleView( child: BuildScheduleView(
deviceUuid: deviceId, deviceUuid: deviceId,
category: 'switch_2', category: 'switch_2',
countdownCode: 'countdown_2',
deviceType: '2G',
), ),
)); ));
}, },

View File

@ -18,9 +18,10 @@ class ScheduleDialogHelper {
ScheduleEntry? schedule, ScheduleEntry? schedule,
bool isEdit = false, bool isEdit = false,
String? code, String? code,
required String deviceType,
}) { }) {
bool temp; bool temp;
if (schedule?.category == 'CUR_2') { if (deviceType == 'CUR_2') {
temp = schedule!.function.value == 'open' ? true : false; temp = schedule!.function.value == 'open' ? true : false;
} else { } else {
temp = schedule!.function.value; temp = schedule!.function.value;
@ -103,8 +104,7 @@ class ScheduleDialogHelper {
setState(() => selectedDays[i] = v); setState(() => selectedDays[i] = v);
}), }),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildFunctionSwitch(schedule!.category, ctx, functionOn!, _buildFunctionSwitch(deviceType, ctx, functionOn!, (v) {
(v) {
setState(() => functionOn = v); setState(() => functionOn = v);
}), }),
], ],
@ -120,32 +120,29 @@ class ScheduleDialogHelper {
), ),
), ),
SizedBox( SizedBox(
width: 100, width: 100,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
dynamic temp; dynamic temp;
if (schedule?.category == 'CUR_2') { if (deviceType == 'CUR_2') {
temp = functionOn! ? 'open' : 'close'; temp = functionOn! ? 'open' : 'close';
} else { } else {
temp = functionOn; temp = functionOn;
} }
print(temp); final entry = ScheduleEntry(
final entry = ScheduleEntry( category: schedule?.category ?? 'switch_1',
category: schedule?.category ?? 'switch_1', time: _formatTimeOfDayToISO(selectedTime),
time: _formatTimeOfDayToISO(selectedTime), function: Status(
function: Status( code: code ?? 'switch_1',
code: code ?? 'switch_1', value: temp,
value: temp, ),
// functionOn, days: _convertSelectedDaysToStrings(selectedDays),
), scheduleId: schedule.scheduleId,
days: _convertSelectedDaysToStrings(selectedDays), );
scheduleId: schedule?.scheduleId, Navigator.pop(ctx, entry);
); },
Navigator.pop(ctx, entry); child: const Text('Save'),
}, )),
child: const Text('Save'),
),
),
], ],
); );
}, },

View File

@ -84,6 +84,8 @@ class WaterHeaterDeviceControlView extends StatelessWidget
child: BuildScheduleView( child: BuildScheduleView(
deviceUuid: device.uuid ?? '', deviceUuid: device.uuid ?? '',
category: 'switch_1', category: 'switch_1',
countdownCode: 'countdown_1',
deviceType: 'WH',
), ),
)); ));
}, },

View File

@ -455,7 +455,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
Future<void> checkEmail( Future<void> checkEmail(
CheckEmailEvent event, Emitter<UsersState> emit) async { CheckEmailEvent event, Emitter<UsersState> emit) async {
emit(UsersLoadingState()); emit(UsersLoadingState());
String? res = await UserPermissionApi().checkEmail( String? res = await UserPermissionApi().checkEmail(
emailController.text, emailController.text,
); );
checkEmailValid = res!; checkEmailValid = res!;

View File

@ -34,7 +34,8 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
return Dialog( return Dialog(
child: Container( child: Container(
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))), color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20))),
width: 900, width: 900,
child: Column( child: Column(
children: [ children: [
@ -63,7 +64,8 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
children: [ children: [
_buildStep1Indicator(1, "Basics", _blocRole), _buildStep1Indicator(1, "Basics", _blocRole),
_buildStep2Indicator(2, "Spaces", _blocRole), _buildStep2Indicator(2, "Spaces", _blocRole),
_buildStep3Indicator(3, "Role & Permissions", _blocRole), _buildStep3Indicator(
3, "Role & Permissions", _blocRole),
], ],
), ),
), ),
@ -105,18 +107,32 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
), ),
InkWell( InkWell(
onTap: () { onTap: () {
final isBasicsStep = currentStep == 1;
if (isBasicsStep) {
// Validate the form first
final isValid = _blocRole.formKey.currentState
?.validate() ??
false;
if (!isValid)
return; // Stop if form is not valid
}
_blocRole.add(const CheckEmailEvent()); _blocRole.add(const CheckEmailEvent());
setState(() { setState(() {
if (currentStep < 3) { if (currentStep < 3) {
currentStep++; currentStep++;
if (currentStep == 2) { if (currentStep == 2) {
_blocRole.add(const CheckStepStatus(isEditUser: false)); _blocRole.add(const CheckStepStatus(
isEditUser: false));
} else if (currentStep == 3) { } else if (currentStep == 3) {
_blocRole.add(const CheckSpacesStepStatus()); _blocRole
.add(const CheckSpacesStepStatus());
} }
} else { } else {
_blocRole.add(SendInviteUsers(context: context)); _blocRole
.add(SendInviteUsers(context: context));
} }
}); });
}, },
@ -124,8 +140,11 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
currentStep < 3 ? "Next" : "Save", currentStep < 3 ? "Next" : "Save",
style: TextStyle( style: TextStyle(
color: (_blocRole.isCompleteSpaces == false || color: (_blocRole.isCompleteSpaces == false ||
_blocRole.isCompleteBasics == false || _blocRole.isCompleteBasics ==
_blocRole.isCompleteRolePermissions == false) && false ||
_blocRole
.isCompleteRolePermissions ==
false) &&
currentStep == 3 currentStep == 3
? ColorsManager.grayColor ? ColorsManager.grayColor
: ColorsManager.secondaryColor), : ColorsManager.secondaryColor),
@ -143,7 +162,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
Widget _getFormContent() { Widget _getFormContent() {
switch (currentStep) { switch (currentStep) {
case 1: case 1:
return const BasicsView( return BasicsView(
userId: '', userId: '',
); );
case 2: case 2:
@ -196,8 +215,12 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
label, label,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor, color: currentStep == step
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal, ? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
), ),
), ),
], ],
@ -260,8 +283,12 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
label, label,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor, color: currentStep == step
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal, ? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
), ),
), ),
], ],
@ -318,8 +345,12 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
label, label,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor, color: currentStep == step
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal, ? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
), ),
), ),
], ],

View File

@ -1,9 +1,12 @@
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:intl_phone_field/countries.dart'; import 'package:intl_phone_field/countries.dart';
import 'package:intl_phone_field/country_picker_dialog.dart'; import 'package:intl_phone_field/country_picker_dialog.dart';
import 'package:intl_phone_field/intl_phone_field.dart'; import 'package:intl_phone_field/intl_phone_field.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -11,7 +14,9 @@ import 'package:syncrow_web/utils/style.dart';
class BasicsView extends StatelessWidget { class BasicsView extends StatelessWidget {
final String? userId; final String? userId;
const BasicsView({super.key, this.userId = ''}); Timer? _debounce;
BasicsView({super.key, this.userId = ''});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<UsersBloc, UsersState>(builder: (context, state) { return BlocBuilder<UsersBloc, UsersState>(builder: (context, state) {
@ -21,6 +26,7 @@ class BasicsView extends StatelessWidget {
} }
return Form( return Form(
key: _blocRole.formKey, key: _blocRole.formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: ListView( child: ListView(
shrinkWrap: true, shrinkWrap: true,
children: [ children: [
@ -208,6 +214,14 @@ class BasicsView extends StatelessWidget {
fontSize: 12, fontSize: 12,
color: ColorsManager.textGray), color: ColorsManager.textGray),
), ),
onChanged: (value) {
if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 800), () {
_blocRole.add(const CheckEmailEvent());
});
},
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Enter Email Address'; return 'Enter Email Address';

View File

@ -32,113 +32,114 @@ class SpaceDropdown extends StatelessWidget {
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
), ),
), ),
SizedBox( DropdownButton2<String>(
child: Container( underline: const SizedBox(),
buttonStyleData: ButtonStyleData(
decoration:
BoxDecoration(borderRadius: BorderRadius.circular(12)),
),
value: selectedValue,
items: spaces.map((space) {
return DropdownMenuItem<String>(
value: space.uuid,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
' ${space.name}',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
color: selectedValue == space.uuid
? ColorsManager.dialogBlueTitle
: ColorsManager.blackColor,
),
),
Text(
' ${space.lastThreeParents}',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
color: selectedValue == space.uuid
? ColorsManager.dialogBlueTitle
: ColorsManager.blackColor,
),
),
],
),
);
}).toList(),
onChanged: onChanged,
style: TextStyle(
color: Colors.black,
fontSize: 13,
),
hint: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
hintMessage,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray,
),
),
),
customButton: Container(
height: 40, height: 40,
decoration: BoxDecoration(
border: Border.all(color: ColorsManager.textGray, width: 1.0),
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 8,
child: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
selectedValue != null
? spaces
.firstWhere((e) => e.uuid == selectedValue)
.name
: hintMessage,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontSize: 13,
color: selectedValue != null
? Colors.black
: ColorsManager.textGray,
),
overflow: TextOverflow.ellipsis,
),
),
),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
height: 45,
child: const Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
),
],
),
),
dropdownStyleData: DropdownStyleData(
maxHeight: MediaQuery.of(context).size.height * 0.4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
child: DropdownButton2<String>( ),
underline: const SizedBox(), menuItemStyleData: const MenuItemStyleData(
value: selectedValue, height: 60,
items: spaces.map((space) {
return DropdownMenuItem<String>(
value: space.uuid,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
' ${space.name}',
style:
Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
color: ColorsManager.blackColor,
),
),
Text(
' ${space.lastThreeParents}',
style:
Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
),
),
],
),
);
}).toList(),
onChanged: onChanged,
style: TextStyle(color: Colors.black),
hint: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
hintMessage,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray,
),
),
),
customButton: Container(
height: 45,
decoration: BoxDecoration(
border:
Border.all(color: ColorsManager.textGray, width: 1.0),
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 8,
child: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
selectedValue != null
? spaces
.firstWhere((e) => e.uuid == selectedValue)
.name
: hintMessage,
style:
Theme.of(context).textTheme.bodySmall!.copyWith(
color: selectedValue != null
? Colors.black
: ColorsManager.textGray,
),
overflow: TextOverflow.ellipsis,
),
),
),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
height: 45,
child: const Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
),
],
),
),
dropdownStyleData: DropdownStyleData(
maxHeight: MediaQuery.of(context).size.height * 0.4,
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(10),
),
),
menuItemStyleData: const MenuItemStyleData(
height: 60,
),
),
), ),
), ),
], ],

View File

@ -121,7 +121,8 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
child: SizedBox( child: SizedBox(
width: 16, width: 16,
height: 16, height: 16,
child: CircularProgressIndicator(strokeWidth: 2), child:
CircularProgressIndicator(strokeWidth: 2),
), ),
), ),
) )
@ -159,8 +160,9 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
height: iconSize, height: iconSize,
width: iconSize, width: iconSize,
fit: BoxFit.contain, fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) => errorBuilder:
Image.asset( (context, error, stackTrace) =>
Image.asset(
Assets.logo, Assets.logo,
height: iconSize, height: iconSize,
width: iconSize, width: iconSize,
@ -203,7 +205,8 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
maxLines: 1, maxLines: 1,
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
fontSize: widget.isSmallScreenSize(context) ? 10 : 12, fontSize:
widget.isSmallScreenSize(context) ? 10 : 12,
), ),
), ),
if (widget.spaceName != '') if (widget.spaceName != '')
@ -222,8 +225,9 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
maxLines: 1, maxLines: 1,
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
fontSize: fontSize: widget.isSmallScreenSize(context)
widget.isSmallScreenSize(context) ? 10 : 12, ? 10
: 12,
), ),
), ),
], ],

View File

@ -58,7 +58,9 @@ class CurtainHelper {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const DialogHeader('AC Functions'), DialogHeader(dialogType == 'THEN'
? 'Curtain Functions'
: 'Curtain Conditions'),
Expanded( Expanded(
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,

View File

@ -58,11 +58,14 @@ class ProductModel {
'3G': Assets.Gang3SwitchIcon, '3G': Assets.Gang3SwitchIcon,
'3GT': Assets.threeTouchSwitch, '3GT': Assets.threeTouchSwitch,
'CUR': Assets.curtain, 'CUR': Assets.curtain,
'CUR_2': Assets.curtain,
'GD': Assets.garageDoor, 'GD': Assets.garageDoor,
'GW': Assets.SmartGatewayIcon, 'GW': Assets.SmartGatewayIcon,
'DL': Assets.DoorLockIcon, 'DL': Assets.DoorLockIcon,
'WL': Assets.waterLeakSensor, 'WL': Assets.waterLeakSensor,
'WH': Assets.waterHeater, 'WH': Assets.waterHeater,
'WM': Assets.waterLeakSensor,
'SOS': Assets.sos,
'AC': Assets.ac, 'AC': Assets.ac,
'CPS': Assets.presenceSensor, 'CPS': Assets.presenceSensor,
'PC': Assets.powerClamp, 'PC': Assets.powerClamp,

View File

@ -21,7 +21,6 @@ import 'package:syncrow_web/utils/snack_bar.dart';
class VisitorPasswordBloc class VisitorPasswordBloc
extends Bloc<VisitorPasswordEvent, VisitorPasswordState> { extends Bloc<VisitorPasswordEvent, VisitorPasswordState> {
VisitorPasswordBloc() : super(VisitorPasswordInitial()) { VisitorPasswordBloc() : super(VisitorPasswordInitial()) {
on<SelectUsageFrequency>(selectUsageFrequency); on<SelectUsageFrequency>(selectUsageFrequency);
on<FetchDevice>(_onFetchDevice); on<FetchDevice>(_onFetchDevice);
@ -87,6 +86,9 @@ class VisitorPasswordBloc
SelectTimeVisitorPassword event, SelectTimeVisitorPassword event,
Emitter<VisitorPasswordState> emit, Emitter<VisitorPasswordState> emit,
) async { ) async {
// Ensure expirationTimeTimeStamp has a value
effectiveTimeTimeStamp ??= DateTime.now().millisecondsSinceEpoch ~/ 1000;
final DateTime? picked = await showDatePicker( final DateTime? picked = await showDatePicker(
context: event.context, context: event.context,
initialDate: DateTime.now(), initialDate: DateTime.now(),
@ -94,86 +96,124 @@ class VisitorPasswordBloc
lastDate: DateTime.now().add(const Duration(days: 5095)), lastDate: DateTime.now().add(const Duration(days: 5095)),
); );
if (picked != null) { if (picked == null) return;
final TimeOfDay? timePicked = await showTimePicker(
context: event.context, final TimeOfDay? timePicked = await showTimePicker(
initialTime: TimeOfDay.now(), context: event.context,
builder: (context, child) { initialTime: TimeOfDay.now(),
return Theme( builder: (context, child) {
data: ThemeData.light().copyWith( return Theme(
colorScheme: const ColorScheme.light( data: ThemeData.light().copyWith(
primary: ColorsManager.primaryColor, colorScheme: const ColorScheme.light(
onSurface: Colors.black, primary: ColorsManager.primaryColor,
), onSurface: Colors.black,
buttonTheme: const ButtonThemeData(
colorScheme: ColorScheme.light(
primary: Colors.green,
),
),
), ),
child: child!, ),
); child: child!,
},
);
if (timePicked != null) {
final selectedDateTime = DateTime(
picked.year,
picked.month,
picked.day,
timePicked.hour,
timePicked.minute,
); );
},
);
final selectedTimestamp = if (timePicked == null) return;
selectedDateTime.millisecondsSinceEpoch ~/ 1000;
if (event.isStart) { final selectedDateTime = DateTime(
if (expirationTimeTimeStamp != null && picked.year,
selectedTimestamp > expirationTimeTimeStamp!) { picked.month,
CustomSnackBar.displaySnackBar( picked.day,
timePicked.hour,
timePicked.minute,
);
final selectedTimestamp = selectedDateTime.millisecondsSinceEpoch ~/ 1000;
final currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
if (event.isStart) {
// START TIME VALIDATION
if (expirationTimeTimeStamp != null &&
selectedTimestamp > expirationTimeTimeStamp!) {
await showDialog<void>(
context: event.context,
builder: (context) => AlertDialog(
title: const Text(
'Effective Time cannot be later than Expiration Time.', 'Effective Time cannot be later than Expiration Time.',
); ),
return; actionsAlignment: MainAxisAlignment.center,
} content: FilledButton(
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) { onPressed: () {
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) { Navigator.of(event.context).pop();
await showDialog<void>( add(SelectTimeVisitorPassword(
context: event.context, context: event.context,
builder: (context) => AlertDialog( isStart: true,
title: const Text('Effective Time cannot be earlier than current time.'), isRepeat: false,
actionsAlignment: MainAxisAlignment.center, ));
content: },
FilledButton( child: const Text('OK'),
onPressed: () { ),
Navigator.of(event.context).pop(); ),
add(SelectTimeVisitorPassword(context: event.context, isStart: true, isRepeat: false)); );
}, return;
child: const Text('OK'),
),
),
);
}
return;
}
effectiveTimeTimeStamp = selectedTimestamp;
startTimeAccess = selectedDateTime.toString().split('.').first;
} else {
if (effectiveTimeTimeStamp != null &&
selectedTimestamp < effectiveTimeTimeStamp!) {
CustomSnackBar.displaySnackBar(
'Expiration Time cannot be earlier than Effective Time.',
);
return;
}
expirationTimeTimeStamp = selectedTimestamp;
endTimeAccess = selectedDateTime.toString().split('.').first;
}
emit(ChangeTimeState());
emit(VisitorPasswordInitial());
} }
if (selectedTimestamp < currentTimestamp) {
await showDialog<void>(
context: event.context,
builder: (context) => AlertDialog(
title: const Text(
'Effective Time cannot be earlier than current time.',
),
actionsAlignment: MainAxisAlignment.center,
content: FilledButton(
onPressed: () {
Navigator.of(event.context).pop();
add(SelectTimeVisitorPassword(
context: event.context,
isStart: true,
isRepeat: false,
));
},
child: const Text('OK'),
),
),
);
return;
}
// Save effective time
effectiveTimeTimeStamp = selectedTimestamp;
startTimeAccess = selectedDateTime.toString().split('.').first;
} else {
// END TIME VALIDATION
if (effectiveTimeTimeStamp != null &&
selectedTimestamp < effectiveTimeTimeStamp!) {
await showDialog<void>(
context: event.context,
builder: (context) => AlertDialog(
title: const Text(
'Expiration Time cannot be earlier than Effective Time.',
),
actionsAlignment: MainAxisAlignment.center,
content: FilledButton(
onPressed: () {
Navigator.of(event.context).pop();
add(SelectTimeVisitorPassword(
context: event.context,
isStart: false,
isRepeat: false,
));
},
child: const Text('OK'),
),
),
);
return;
}
// Save expiration time
expirationTimeTimeStamp = selectedTimestamp;
endTimeAccess = selectedDateTime.toString().split('.').first;
} }
emit(ChangeTimeState());
emit(VisitorPasswordInitial());
} }
bool toggleRepeat( bool toggleRepeat(
@ -213,7 +253,7 @@ class VisitorPasswordBloc
FetchDevice event, Emitter<VisitorPasswordState> emit) async { FetchDevice event, Emitter<VisitorPasswordState> emit) async {
try { try {
emit(DeviceLoaded()); emit(DeviceLoaded());
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
data = await AccessMangApi().fetchDoorLockDeviceList(projectUuid); data = await AccessMangApi().fetchDoorLockDeviceList(projectUuid);
emit(TableLoaded(data)); emit(TableLoaded(data));

View File

@ -2,10 +2,8 @@ import 'package:flutter/cupertino.dart';
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:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/common/date_time_widget.dart'; import 'package:syncrow_web/pages/common/date_time_widget.dart';
import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart';
@ -23,8 +21,8 @@ class VisitorPasswordDialog extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
var text = Theme.of(context) final text = Theme.of(context)
.textTheme .textTheme
.bodySmall! .bodySmall!
.copyWith(color: Colors.black, fontSize: 13); .copyWith(color: Colors.black, fontSize: 13);
@ -41,8 +39,7 @@ class VisitorPasswordDialog extends StatelessWidget {
title: 'Sent Successfully', title: 'Sent Successfully',
widgeta: Column( widgeta: Column(
children: [ children: [
if (visitorBloc if (visitorBloc.passwordStatus!.failedOperations.isNotEmpty)
.passwordStatus!.failedOperations.isNotEmpty)
Column( Column(
children: [ children: [
const Text('Failed Devices'), const Text('Failed Devices'),
@ -56,22 +53,19 @@ class VisitorPasswordDialog extends StatelessWidget {
.passwordStatus!.failedOperations.length, .passwordStatus!.failedOperations.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return Container( return Container(
margin: EdgeInsets.all(5), margin: const EdgeInsets.all(5),
decoration: containerDecoration, decoration: containerDecoration,
height: 45, height: 45,
child: Center( child: Center(
child: Text(visitorBloc child: Text(visitorBloc.passwordStatus!
.passwordStatus! .failedOperations[index].deviceName)),
.failedOperations[index]
.deviceName)),
); );
}, },
), ),
), ),
], ],
), ),
if (visitorBloc if (visitorBloc.passwordStatus!.successOperations.isNotEmpty)
.passwordStatus!.successOperations.isNotEmpty)
Column( Column(
children: [ children: [
const Text('Success Devices'), const Text('Success Devices'),
@ -85,14 +79,12 @@ class VisitorPasswordDialog extends StatelessWidget {
.passwordStatus!.successOperations.length, .passwordStatus!.successOperations.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return Container( return Container(
margin: EdgeInsets.all(5), margin: const EdgeInsets.all(5),
decoration: containerDecoration, decoration: containerDecoration,
height: 45, height: 45,
child: Center( child: Center(
child: Text(visitorBloc child: Text(visitorBloc.passwordStatus!
.passwordStatus! .successOperations[index].deviceName)),
.successOperations[index]
.deviceName)),
); );
}, },
), ),
@ -115,16 +107,14 @@ class VisitorPasswordDialog extends StatelessWidget {
child: BlocBuilder<VisitorPasswordBloc, VisitorPasswordState>( child: BlocBuilder<VisitorPasswordBloc, VisitorPasswordState>(
builder: (BuildContext context, VisitorPasswordState state) { builder: (BuildContext context, VisitorPasswordState state) {
final visitorBloc = BlocProvider.of<VisitorPasswordBloc>(context); final visitorBloc = BlocProvider.of<VisitorPasswordBloc>(context);
bool isRepeat = final isRepeat =
state is IsRepeatState ? state.repeat : visitorBloc.repeat; state is IsRepeatState ? state.repeat : visitorBloc.repeat;
return AlertDialog( return AlertDialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
title: Text( title: Text(
'Create visitor password', 'Create visitor password',
style: Theme.of(context).textTheme.headlineLarge!.copyWith( style: Theme.of(context).textTheme.headlineLarge!.copyWith(
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400, fontSize: 24, color: Colors.black),
fontSize: 24,
color: Colors.black),
), ),
content: state is LoadingInitialState content: state is LoadingInitialState
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
@ -310,14 +300,12 @@ class VisitorPasswordDialog extends StatelessWidget {
visitorBloc.accessTypeSelected == visitorBloc.accessTypeSelected ==
'Offline Password') { 'Offline Password') {
visitorBloc.add(SelectTimeEvent( visitorBloc.add(SelectTimeEvent(
context: context, context: context, isEffective: false));
isEffective: false));
} else { } else {
visitorBloc.add( visitorBloc.add(SelectTimeVisitorPassword(
SelectTimeVisitorPassword( context: context,
context: context, isStart: false,
isStart: false, isRepeat: false));
isRepeat: false));
} }
}, },
startTime: () { startTime: () {
@ -326,31 +314,28 @@ class VisitorPasswordDialog extends StatelessWidget {
visitorBloc.accessTypeSelected == visitorBloc.accessTypeSelected ==
'Offline Password') { 'Offline Password') {
visitorBloc.add(SelectTimeEvent( visitorBloc.add(SelectTimeEvent(
context: context, context: context, isEffective: true));
isEffective: true));
} else { } else {
visitorBloc.add( visitorBloc.add(SelectTimeVisitorPassword(
SelectTimeVisitorPassword( context: context,
context: context, isStart: true,
isStart: true, isRepeat: false));
isRepeat: false));
} }
}, },
firstString: (visitorBloc firstString:
.usageFrequencySelected == (visitorBloc.usageFrequencySelected ==
'Periodic' && 'Periodic' &&
visitorBloc.accessTypeSelected == visitorBloc.accessTypeSelected ==
'Offline Password') 'Offline Password')
? visitorBloc.effectiveTime ? visitorBloc.effectiveTime
: visitorBloc.startTimeAccess : visitorBloc.startTimeAccess,
.toString(),
secondString: (visitorBloc secondString: (visitorBloc
.usageFrequencySelected == .usageFrequencySelected ==
'Periodic' && 'Periodic' &&
visitorBloc.accessTypeSelected == visitorBloc.accessTypeSelected ==
'Offline Password') 'Offline Password')
? visitorBloc.expirationTime ? visitorBloc.expirationTime
: visitorBloc.endTimeAccess.toString(), : visitorBloc.endTimeAccess,
icon: Assets.calendarIcon), icon: Assets.calendarIcon),
const SizedBox( const SizedBox(
height: 10, height: 10,
@ -410,8 +395,7 @@ class VisitorPasswordDialog extends StatelessWidget {
child: CupertinoSwitch( child: CupertinoSwitch(
value: visitorBloc.repeat, value: visitorBloc.repeat,
onChanged: (value) { onChanged: (value) {
visitorBloc visitorBloc.add(ToggleRepeatEvent());
.add(ToggleRepeatEvent());
}, },
applyTheme: true, applyTheme: true,
), ),
@ -442,8 +426,7 @@ class VisitorPasswordDialog extends StatelessWidget {
}, },
).then((listDevice) { ).then((listDevice) {
if (listDevice != null) { if (listDevice != null) {
visitorBloc.selectedDevices = visitorBloc.selectedDevices = listDevice;
listDevice;
} }
}); });
}, },
@ -455,8 +438,7 @@ class VisitorPasswordDialog extends StatelessWidget {
.bodySmall! .bodySmall!
.copyWith( .copyWith(
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: color: ColorsManager.whiteColors,
ColorsManager.whiteColors,
fontSize: 12), fontSize: 12),
), ),
), ),
@ -495,37 +477,30 @@ class VisitorPasswordDialog extends StatelessWidget {
onPressed: () { onPressed: () {
if (visitorBloc.forgetFormKey.currentState!.validate()) { if (visitorBloc.forgetFormKey.currentState!.validate()) {
if (visitorBloc.selectedDevices.isNotEmpty) { if (visitorBloc.selectedDevices.isNotEmpty) {
if (visitorBloc.usageFrequencySelected == if (visitorBloc.usageFrequencySelected == 'One-Time' &&
'One-Time' && visitorBloc.accessTypeSelected == 'Offline Password') {
visitorBloc.accessTypeSelected ==
'Offline Password') {
setPasswordFunction(context, size, visitorBloc); setPasswordFunction(context, size, visitorBloc);
} else if (visitorBloc.usageFrequencySelected == } else if (visitorBloc.usageFrequencySelected ==
'Periodic' && 'Periodic' &&
visitorBloc.accessTypeSelected == visitorBloc.accessTypeSelected == 'Offline Password') {
'Offline Password') {
if (visitorBloc.expirationTime != 'End Time' && if (visitorBloc.expirationTime != 'End Time' &&
visitorBloc.effectiveTime != 'Start Time') { visitorBloc.effectiveTime != 'Start Time') {
setPasswordFunction(context, size, visitorBloc); setPasswordFunction(context, size, visitorBloc);
} else { } else {
visitorBloc.stateDialog( visitorBloc.stateDialog(
context: context, context: context,
message: message: 'Please select Access Period to continue',
'Please select Access Period to continue',
title: 'Access Period'); title: 'Access Period');
} }
} else if (visitorBloc.endTimeAccess.toString() != } else if (visitorBloc.endTimeAccess != 'End Time' &&
'End Time' && visitorBloc.startTimeAccess != 'Start Time') {
visitorBloc.startTimeAccess.toString() !=
'Start Time') {
if (visitorBloc.effectiveTimeTimeStamp != null && if (visitorBloc.effectiveTimeTimeStamp != null &&
visitorBloc.expirationTimeTimeStamp != null) { visitorBloc.expirationTimeTimeStamp != null) {
if (isRepeat == true) { if (isRepeat == true) {
if (visitorBloc.expirationTime != 'End Time' && if (visitorBloc.expirationTime != 'End Time' &&
visitorBloc.effectiveTime != 'Start Time' && visitorBloc.effectiveTime != 'Start Time' &&
visitorBloc.selectedDays.isNotEmpty) { visitorBloc.selectedDays.isNotEmpty) {
setPasswordFunction( setPasswordFunction(context, size, visitorBloc);
context, size, visitorBloc);
} else { } else {
visitorBloc.stateDialog( visitorBloc.stateDialog(
context: context, context: context,
@ -539,15 +514,13 @@ class VisitorPasswordDialog extends StatelessWidget {
} else { } else {
visitorBloc.stateDialog( visitorBloc.stateDialog(
context: context, context: context,
message: message: 'Please select Access Period to continue',
'Please select Access Period to continue',
title: 'Access Period'); title: 'Access Period');
} }
} else { } else {
visitorBloc.stateDialog( visitorBloc.stateDialog(
context: context, context: context,
message: message: 'Please select Access Period to continue',
'Please select Access Period to continue',
title: 'Access Period'); title: 'Access Period');
} }
} else { } else {
@ -593,17 +566,17 @@ class VisitorPasswordDialog extends StatelessWidget {
alignment: Alignment.center, alignment: Alignment.center,
content: SizedBox( content: SizedBox(
height: size.height * 0.25, height: size.height * 0.25,
child: Center( child: const Center(
child: child: CircularProgressIndicator(), // Display a loading spinner
CircularProgressIndicator(), // Display a loading spinner
), ),
), ),
); );
} else { } else {
return AlertDialog( return AlertDialog(
alignment: Alignment.center, alignment: Alignment.center,
backgroundColor: Colors.white,
content: SizedBox( content: SizedBox(
height: size.height * 0.25, height: size.height * 0.13,
child: Column( child: Column(
children: [ children: [
Column( Column(
@ -617,13 +590,16 @@ class VisitorPasswordDialog extends StatelessWidget {
width: 35, width: 35,
), ),
), ),
const SizedBox(
height: 20,
),
Text( Text(
'Set Password', 'Set Password',
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.headlineLarge! .headlineLarge!
.copyWith( .copyWith(
fontSize: 30, fontSize: 24,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Colors.black, color: Colors.black,
), ),
@ -631,15 +607,6 @@ class VisitorPasswordDialog extends StatelessWidget {
], ],
), ),
const SizedBox(width: 15), const SizedBox(width: 15),
Text(
'This action will update all of the selected\n door locks passwords in the property.\n\nAre you sure you want to continue?',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400,
fontSize: 18,
),
),
], ],
), ),
), ),
@ -668,12 +635,12 @@ class VisitorPasswordDialog extends StatelessWidget {
decoration: containerDecoration, decoration: containerDecoration,
width: size.width * 0.1, width: size.width * 0.1,
child: DefaultButton( child: DefaultButton(
backgroundColor: Color(0xff023DFE),
borderRadius: 8, borderRadius: 8,
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
if (visitorBloc.usageFrequencySelected == 'One-Time' && if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == visitorBloc.accessTypeSelected == 'Online Password') {
'Online Password') {
visitorBloc.add(OnlineOneTimePasswordEvent( visitorBloc.add(OnlineOneTimePasswordEvent(
context: context, context: context,
passwordName: visitorBloc.userNameController.text, passwordName: visitorBloc.userNameController.text,
@ -681,8 +648,7 @@ class VisitorPasswordDialog extends StatelessWidget {
)); ));
} else if (visitorBloc.usageFrequencySelected == } else if (visitorBloc.usageFrequencySelected ==
'Periodic' && 'Periodic' &&
visitorBloc.accessTypeSelected == visitorBloc.accessTypeSelected == 'Online Password') {
'Online Password') {
visitorBloc.add(OnlineMultipleTimePasswordEvent( visitorBloc.add(OnlineMultipleTimePasswordEvent(
passwordName: visitorBloc.userNameController.text, passwordName: visitorBloc.userNameController.text,
email: visitorBloc.emailController.text, email: visitorBloc.emailController.text,
@ -693,8 +659,7 @@ class VisitorPasswordDialog extends StatelessWidget {
)); ));
} else if (visitorBloc.usageFrequencySelected == } else if (visitorBloc.usageFrequencySelected ==
'One-Time' && 'One-Time' &&
visitorBloc.accessTypeSelected == visitorBloc.accessTypeSelected == 'Offline Password') {
'Offline Password') {
visitorBloc.add(OfflineOneTimePasswordEvent( visitorBloc.add(OfflineOneTimePasswordEvent(
context: context, context: context,
passwordName: visitorBloc.userNameController.text, passwordName: visitorBloc.userNameController.text,
@ -702,8 +667,7 @@ class VisitorPasswordDialog extends StatelessWidget {
)); ));
} else if (visitorBloc.usageFrequencySelected == } else if (visitorBloc.usageFrequencySelected ==
'Periodic' && 'Periodic' &&
visitorBloc.accessTypeSelected == visitorBloc.accessTypeSelected == 'Offline Password') {
'Offline Password') {
visitorBloc.add(OfflineMultipleTimePasswordEvent( visitorBloc.add(OfflineMultipleTimePasswordEvent(
passwordName: visitorBloc.userNameController.text, passwordName: visitorBloc.userNameController.text,
email: visitorBloc.emailController.text, email: visitorBloc.emailController.text,
@ -715,7 +679,7 @@ class VisitorPasswordDialog extends StatelessWidget {
} }
}, },
child: Text( child: Text(
'Ok', 'Confirm',
style: Theme.of(context).textTheme.bodySmall!.copyWith( style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,

View File

@ -416,4 +416,5 @@ class DevicesManagementApi {
); );
return response; return response;
} }
} }

View File

@ -83,7 +83,5 @@ abstract class ColorsManager {
static const Color maxPurpleDot = Color(0xFF5F00BD); static const Color maxPurpleDot = Color(0xFF5F00BD);
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);
} }

View File

@ -394,6 +394,7 @@ class Assets {
static const String emptyBox = 'assets/icons/empty_box.png'; static const String emptyBox = 'assets/icons/empty_box.png';
static const String completeProcessIcon = static const String completeProcessIcon =
'assets/icons/compleate_process_icon.svg'; 'assets/icons/compleate_process_icon.svg';
static const String completedDoneIcon = 'assets/images/completed_done.svg';
static const String currentProcessIcon = static const String currentProcessIcon =
'assets/icons/current_process_icon.svg'; 'assets/icons/current_process_icon.svg';
static const String uncomplete_ProcessIcon = static const String uncomplete_ProcessIcon =
@ -505,5 +506,15 @@ class Assets {
static const String aqiAirQuality = 'assets/icons/aqi_air_quality.svg'; static const String aqiAirQuality = 'assets/icons/aqi_air_quality.svg';
static const String temperatureAqiSidebar = 'assets/icons/thermometer.svg'; static const String temperatureAqiSidebar = 'assets/icons/thermometer.svg';
static const String humidityAqiSidebar = 'assets/icons/humidity.svg'; static const String humidityAqiSidebar = 'assets/icons/humidity.svg';
static const String autocadOccupancyImage = 'assets/images/autocad_occupancy_image.png'; static const String autocadOccupancyImage =
'assets/images/autocad_occupancy_image.png';
static const String emptyBarredChart = 'assets/icons/empty_barred_chart.svg';
static const String emptyEnergyManagementChart =
'assets/icons/empty_energy_management_chart.svg';
static const String emptyEnergyManagementPerDevice =
'assets/icons/empty_energy_management_per_device.svg';
static const String emptyHeatmap = 'assets/icons/empty_heatmap.svg';
static const String emptyRangeOfAqi = 'assets/icons/empty_range_of_aqi.svg';
static const String homeIcon = 'assets/icons/home_icon.svg';
static const String groupIcon = 'assets/icons/group_icon.svg';
} }