Compare commits

..

51 Commits

Author SHA1 Message Date
3fc6964e15 Update TotalEnergyConsumptionChart to adjust Y-axis limits and intervals for better data representation (#302)
<!--
  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-27 17:16:49 +03:00
4744009cb6 Update TotalEnergyConsumptionChart to adjust Y-axis limits and intervals for better data representation 2025-06-27 17:11:17 +03:00
5beae81596 fixed datetimes in occupancy heatmap. (#301)
<!--
  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)
- [ ] 🛠️ 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-27 16:40:20 +03:00
f1144762b0 Merge branch 'dev' of https://github.com/SyncrowIOT/web into add_loading_indicator_to_analytics_device_dropdown 2025-06-27 16:37:17 +03:00
ca41aa6224 all dates in heatmap are utc. 2025-06-27 16:37:09 +03:00
2d0019200e manually parse event date for heatmap date object. (#300)
<!--
  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)
- [ ] 🛠️ 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-27 15:29:39 +03:00
475462301f manually parse event date for heatmap date object. 2025-06-27 15:29:11 +03:00
731ba7a768 Add loading indicator to analytics device dropdown (#299)
<!--
  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

Added a loading indicator to analytics device dropdown, for a better UX,
and since the occupancy devices api takes longer than the other tabs, it
makes more sense to add this feature.

## 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
2025-06-27 14:08:13 +03:00
7fda564ee4 hotfixes. 2025-06-27 14:07:24 +03:00
11e2853403 Enhance AnalyticsDeviceDropdown to show loading indicator during loading state. 2025-06-27 11:56:43 +03:00
9c02bed4c0 Sp 1669 fe user edit form does not pre fill existing data on user management page (#284)
<!--
  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-1669](https://syncrow.atlassian.net/browse/SP-1669)

## Description

when opening the dialog for use info pass the user to fill the data 

## 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-1669]:
https://syncrow.atlassian.net/browse/SP-1669?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-27 11:20:09 +03:00
4f932b8c35 [FE] Disable AC Control Button When AC is Off SP-1387 (#283)
<!--
  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

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


[SP-1387]:
https://syncrow.atlassian.net/browse/SP-1387?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-27 11:16:38 +03:00
44ae8386df sp:1740-[FE] On login page when a timeout/connection error occurs the exception is getting out of the container and the message should be handled (#286)
<!--
  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-1740](https://syncrow.atlassian.net/browse/SP-1740)

## Description

handling error from backend on login Error for time out connections or
network issues

## 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-1740]:
https://syncrow.atlassian.net/browse/SP-1740?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-27 11:14:25 +03:00
9d4a665547 SP-368-Clarification-on-Default-Value-for-Start-Date-in-Door-Lock-Online-Tile-Limited-Password-repeat-section (#296)
<!--
  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

1. Shows an error dialog when the users sets the start date in create
visitor password that is before current time.
2. Initially sets the Start Date time as now.

## 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-368]:
https://syncrow.atlassian.net/browse/SP-368?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-27 11:13:37 +03:00
8593055923 Add deviceName field to FailedOperation and SuccessOperation models (#295)
<!--
  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 -->
Add deviceName field to FailedOperation and SuccessOperation models

## 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-06-25 16:12:42 +03:00
18ab9fd24c Add bloc closure handling and improve device status updates in AcBloc (#298)
<!--
  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 -->
Add bloc closure handling and improve device status updates in AcBloc
## 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-25 15:59:41 +03:00
3c9494963d Add generated configuration files for Flutter integration across platforms 2025-06-25 15:58:58 +03:00
07dd260593 Occupancy analytics devices bug (#297)
<!--
  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

1. Supported new device type `NCPS` in analytics occupancy tab.
2. Fixed a bug where the devices weren't being shown.
3. removed old comment.
4. Increased size of `OccupancyEndSideBar` in medium sized screens.
5. Fixed TVOC code to read data.
6. Changed TVOC unit string to match the device. 
7. Fixed data loading bug in AQI.

## 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)
- [x]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-06-25 15:58:41 +03:00
f38ac58442 Add bloc closure handling and improve device status updates in AcBloc 2025-06-25 14:45:10 +03:00
30e940fdfc Reads the correct date to load aqi data. 2025-06-25 14:34:23 +03:00
520b73717a Doesnt load devices when date changes. 2025-06-25 14:34:07 +03:00
e1bb67d7bd reads correct value for TVOC. 2025-06-25 14:26:05 +03:00
5e0df09cb6 Changed tvoc unit to match the device. 2025-06-25 14:25:53 +03:00
22070ca04a removed unused comment. 2025-06-25 13:26:07 +03:00
6d667af7dc increased size of OccupancyEndSideBar in medium sized screens. 2025-06-25 13:25:46 +03:00
3b4952db0a fixed thrown exceptions inAnalyticsDevice. 2025-06-25 13:25:30 +03:00
5f59583696 Supported NCPS device type in occupancy devices dropdown. 2025-06-25 13:25:12 +03:00
7397486e7a SP-368-Clarification-on-Default-Value-for-Start-Date-in-Door-Lock-Online-Tile-Limited-Password-repeat-section 2025-06-25 13:11:49 +03:00
487c5a894b Sp 1796 fe set the max on range of aqi chart based on selected pollutant s current highest value (#290)
<!--
  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-1796](https://syncrow.atlassian.net/browse/SP-1796)

## Description

Added day of month labels to all analytics charts
Implemented ranges for each pollutant based on the highest value of each
pollutant in the range of aqi chart.

## 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)
- [x]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1796]:
https://syncrow.atlassian.net/browse/SP-1796?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-25 12:42:55 +03:00
7e0200aad8 SP-1770-FE-Parent-nodes-in-community-tree-not-partially-selected-when… (#294)
<!--
  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-1770](https://syncrow.atlassian.net/browse/SP-1770)

## Description

Space Tree Selection state reads from the correct list, based on if the
user was filtering or not.

## 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-1770]:
https://syncrow.atlassian.net/browse/SP-1770?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-25 12:42:34 +03:00
562c67a958 Add deviceName field to FailedOperation and SuccessOperation models 2025-06-25 12:24:09 +03:00
52b843d514 SP-1770-FE-Parent-nodes-in-community-tree-not-partially-selected-when-selecting-space-from-sidebar. 2025-06-25 09:53:09 +03:00
423ad6e687 Enhance navigation buttons in SmartPowerDeviceControl for better user… (#293)
… experience

<!--
  Thanks for contributing!

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

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



## Description

<!--- Describe your changes in detail -->
Enhance navigation buttons in SmartPowerDeviceControl 

## 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-25 09:13:33 +03:00
932e50f518 sp:1677 [FE] Device status in Control modal always shows "Online" regardless of actual status (#287)
<!--
  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-1677](https://syncrow.atlassian.net/browse/SP-1677)

## Description

status depend on the real status of the device afterit was static 

## 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-1677]:
https://syncrow.atlassian.net/browse/SP-1677?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-25 08:18:10 +03:00
c649044a1f Enhance navigation buttons in SmartPowerDeviceControl for better user experience 2025-06-24 16:40:42 +03:00
c46cfb04a7 Add countdown seconds to schedule management (#291)
<!--
  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 -->
Add countdown seconds to schedule management
## 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-24 16:21:43 +03:00
8754960713 Cancel-button-on-"Create-Visitor-Password"-modal-unnecessarily-triggers-visitor-passwords-API (#292)
… on pop

<!--
  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-24 16:21:03 +03:00
c6e98fa245 Refactor visitor password dialog navigation to return specific values on pop 2025-06-24 16:06:53 +03:00
f901983aa5 Implemented ranges for the values in the AQI chart based on the selected pollutant. 2025-06-24 15:01:25 +03:00
010403f1fa Added day of month axis name to all charts. 2025-06-24 14:50:22 +03:00
ee1ebeae2e Changed energy management charts titles for a more clear name. 2025-06-24 14:45:15 +03:00
6e6ef79ed0 enhanced heat map tooltip's message. 2025-06-24 14:44:56 +03:00
7e5825de45 Fixed typo in occupancy sidebar. 2025-06-24 14:44:45 +03:00
95d6e1ecda if online go green with online status else red with offline status 2025-06-23 16:33:45 +03:00
d14cc785a8 no need for two condtions inside themselves 2025-06-23 16:16:34 +03:00
379ecec789 use isCurrentStep instead of checking with multi variables everyTime 2025-06-23 16:15:05 +03:00
ad00cf35ba added the PR notes 2025-06-23 16:05:16 +03:00
5276f4186c emit error state when catch error and send the real API exception 2025-06-23 15:55:56 +03:00
e6957d566d use RoleUserModel instead of only id and all is good 2025-06-23 15:29:35 +03:00
71cf0a636e send all user instead of only uuid 2025-06-23 15:22:24 +03:00
1200a809c2 now cant use offline device to controll 2025-06-23 14:33:56 +03:00
33 changed files with 335 additions and 160 deletions

View File

@ -25,8 +25,8 @@ class AnalyticsDevice {
factory AnalyticsDevice.fromJson(Map<String, dynamic> json) {
return AnalyticsDevice(
uuid: json['uuid'] as String,
name: json['name'] as String,
uuid: json['uuid'] as String? ?? '',
name: json['name'] as String? ?? '',
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'] as String)
: null,
@ -39,8 +39,8 @@ class AnalyticsDevice {
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
: null,
spaceUuid: json['spaceUuid'] as String?,
latitude: json['lat'] != null ? double.parse(json['lat'] as String) : null,
longitude: json['lon'] != null ? double.parse(json['lon'] as String) : null,
latitude: json['lat'] != null ? double.parse(json['lat'] as String? ?? '0.0') : null,
longitude: json['lon'] != null ? double.parse(json['lon'] as String? ?? '0.0') : null,
);
}
}

View File

@ -14,12 +14,21 @@ class OccupancyHeatMapModel extends Equatable {
});
factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) {
final eventDate = json['event_date'] as String?;
final year = eventDate?.split('-')[0];
final month = eventDate?.split('-')[1];
final day = eventDate?.split('-')[2];
return OccupancyHeatMapModel(
uuid: json['uuid'] as String? ?? '',
eventDate: DateTime.parse(
json['event_date'] as String? ?? '${DateTime.now()}',
),
countTotalPresenceDetected: json['count_total_presence_detected'] as int? ?? 0,
eventDate: DateTime(
int.parse(year ?? '2025'),
int.parse(month ?? '1'),
int.parse(day ?? '1'),
).toUtc(),
countTotalPresenceDetected: num.parse(
json['count_total_presence_detected']?.toString() ?? '0',
).toInt(),
);
}

View File

@ -4,7 +4,6 @@ import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_qualit
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/device_location/device_location_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/aqi_type_dropdown.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
@ -22,7 +21,6 @@ abstract final class FetchAirQualityDataHelper {
required String spaceUuid,
bool shouldFetchAnalyticsDevices = true,
}) {
final date = context.read<AnalyticsDatePickerBloc>().state.monthlyDate;
final aqiType = context.read<AirQualityDistributionBloc>().state.selectedAqiType;
if (shouldFetchAnalyticsDevices) {
loadAnalyticsDevices(

View File

@ -18,7 +18,11 @@ abstract final class RangeOfAqiChartsHelper {
(ColorsManager.hazardousPurple, 'Hazardous'),
];
static FlTitlesData titlesData(BuildContext context, List<RangeOfAqi> data) {
static FlTitlesData titlesData(
BuildContext context,
List<RangeOfAqi> data, {
double leftSideInterval = 50,
}) {
final titlesData = EnergyManagementChartsHelper.titlesData(context);
return titlesData.copyWith(
bottomTitles: titlesData.bottomTitles.copyWith(
@ -39,11 +43,11 @@ abstract final class RangeOfAqiChartsHelper {
leftTitles: titlesData.leftTitles.copyWith(
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
reservedSize: 70,
interval: 50,
interval: leftSideInterval,
maxIncluded: false,
minIncluded: true,
getTitlesWidget: (value, meta) {
final text = value >= 300 ? '301+' : value.toInt().toString();
final text = value.toInt().toString();
return Padding(
padding: const EdgeInsetsDirectional.only(end: 12),
child: FittedBox(

View File

@ -65,7 +65,7 @@ class AqiDeviceInfo extends StatelessWidget {
);
final tvocValue = _getValueForStatus(
status,
'tvoc_value',
'voc_value',
formatter: (value) => (value / 100).toStringAsFixed(2),
);

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
import 'package:syncrow_web/pages/analytics/widgets/charts_x_axis_title.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -149,6 +150,7 @@ class AqiDistributionChart extends StatelessWidget {
);
final bottomTitles = AxisTitles(
axisNameWidget: const ChartsXAxisTitle(),
sideTitles: SideTitles(
showTitles: chartData.isNotEmpty,
getTitlesWidget: (value, _) => FittedBox(

View File

@ -6,8 +6,8 @@ enum AqiType {
aqi('AQI', '', 'aqi'),
pm25('PM2.5', 'µg/m³', 'pm25'),
pm10('PM10', 'µg/m³', 'pm10'),
hcho('HCHO', 'mg/m³', 'cho2'),
tvoc('TVOC', 'µg/m³', 'voc'),
hcho('HCHO', 'mg/m³', 'ch2o'),
tvoc('TVOC', 'mg/m³', 'voc'),
co2('CO2', 'ppm', 'co2');
const AqiType(this.value, this.unit, this.code);

View File

@ -2,15 +2,18 @@ import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/range_of_aqi_charts_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class RangeOfAqiChart extends StatelessWidget {
final List<RangeOfAqi> chartData;
final AqiType selectedAqiType;
const RangeOfAqiChart({
super.key,
required this.chartData,
required this.selectedAqiType,
});
List<(List<double> values, Color color, Color? dotColor)> get _lines {
@ -45,15 +48,34 @@ class RangeOfAqiChart extends StatelessWidget {
];
}
(double maxY, double interval) get _maxYForAqiType {
const aqiMaxValues = <AqiType, (double maxY, double interval)>{
AqiType.aqi: (401, 100),
AqiType.pm25: (351, 50),
AqiType.pm10: (501, 100),
AqiType.hcho: (301, 50),
AqiType.tvoc: (501, 50),
AqiType.co2: (1251, 250),
};
return aqiMaxValues[selectedAqiType]!;
}
@override
Widget build(BuildContext context) {
return LineChart(
LineChartData(
minY: 0,
maxY: 301,
maxY: _maxYForAqiType.$1,
clipData: const FlClipData.vertical(),
gridData: EnergyManagementChartsHelper.gridData(horizontalInterval: 50),
titlesData: RangeOfAqiChartsHelper.titlesData(context, chartData),
gridData: EnergyManagementChartsHelper.gridData(
horizontalInterval: _maxYForAqiType.$2,
),
titlesData: RangeOfAqiChartsHelper.titlesData(
context,
chartData,
leftSideInterval: _maxYForAqiType.$2,
),
borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: RangeOfAqiChartsHelper.lineTouchData(chartData),
betweenBarsData: [

View File

@ -32,7 +32,12 @@ class RangeOfAqiChartBox extends StatelessWidget {
const SizedBox(height: 10),
const Divider(),
const SizedBox(height: 20),
Expanded(child: RangeOfAqiChart(chartData: state.filteredRangeOfAqi)),
Expanded(
child: RangeOfAqiChart(
chartData: state.filteredRangeOfAqi,
selectedAqiType: state.selectedAqiType,
),
),
],
),
);

View File

@ -1,6 +1,7 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/helpers/format_number_to_kwh.dart';
import 'package:syncrow_web/pages/analytics/widgets/charts_x_axis_title.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -15,6 +16,7 @@ abstract final class EnergyManagementChartsHelper {
return FlTitlesData(
show: true,
bottomTitles: AxisTitles(
axisNameWidget: const ChartsXAxisTitle(),
drawBelowEverything: true,
sideTitles: SideTitles(
interval: 1,
@ -62,17 +64,12 @@ abstract final class EnergyManagementChartsHelper {
);
}
static String getToolTipLabel(num month, double value) {
final monthLabel = month.toString();
final valueLabel = value.formatNumberToKwh;
final labels = [monthLabel, valueLabel];
return labels.where((element) => element.isNotEmpty).join(', ');
}
static String getToolTipLabel(double value) => value.formatNumberToKwh;
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
return touchedSpots.map((spot) {
return LineTooltipItem(
getToolTipLabel(spot.x, spot.y),
getToolTipLabel(spot.y),
const TextStyle(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w600,

View File

@ -28,15 +28,29 @@ class AnalyticsDeviceDropdown extends StatelessWidget {
),
),
child: Visibility(
visible: state.devices.isNotEmpty,
replacement: _buildNoDevicesFound(context),
child: _buildDevicesDropdown(context, state),
visible: state.status != AnalyticsDevicesStatus.loading,
replacement: _buildLoadingIndicator(),
child: Visibility(
visible: state.devices.isNotEmpty,
replacement: _buildNoDevicesFound(context),
child: _buildDevicesDropdown(context, state),
),
),
);
},
);
}
Widget _buildLoadingIndicator() {
return const Center(
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(strokeWidth: 3),
),
);
}
static const _defaultPadding = EdgeInsetsDirectional.symmetric(
horizontal: 20,
vertical: 2,

View File

@ -37,7 +37,7 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerStart,
child: ChartTitle(
title: Text('Energy Consumption per Device'),
title: Text('Device energy consumed'),
),
),
),

View File

@ -14,14 +14,17 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
return Expanded(
child: LineChart(
LineChartData(
maxY: chartData.isEmpty
? null
: chartData.map((e) => e.value).reduce((a, b) => a > b ? a : b) + 250,
clipData: const FlClipData.vertical(),
titlesData: EnergyManagementChartsHelper.titlesData(
context,
leftTitlesInterval: 250,
leftTitlesInterval: 500,
),
gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true,
horizontalInterval: 250,
horizontalInterval: 500,
),
borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
@ -29,7 +32,6 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
),
duration: Duration.zero,
curve: Curves.easeIn,
),
);
}

View File

@ -32,7 +32,7 @@ class TotalEnergyConsumptionChartBox extends StatelessWidget {
child: FittedBox(
alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown,
child: ChartTitle(title: Text('Total Energy Consumption')),
child: ChartTitle(title: Text('Space energy consumed')),
),
),
const Spacer(flex: 4),

View File

@ -81,7 +81,7 @@ abstract final class FetchOccupancyDataHelper {
param: GetAnalyticsDevicesParam(
communityUuid: communityUuid,
spaceUuid: spaceUuid,
deviceTypes: ['WPS', 'CPS'],
deviceTypes: ['WPS', 'CPS', 'NCPS'],
requestType: AnalyticsDeviceRequestType.occupancy,
),
onSuccess: (device) {

View File

@ -20,7 +20,7 @@ class AnalyticsOccupancyView extends StatelessWidget {
child: Column(
spacing: 32,
children: [
SizedBox(height: height * 0.46, child: const OccupancyEndSideBar()),
SizedBox(height: height * 0.8, child: const OccupancyEndSideBar()),
SizedBox(height: height * 0.5, child: const OccupancyChartBox()),
SizedBox(height: height * 0.5, child: const OccupancyHeatMapBox()),
],

View File

@ -39,7 +39,7 @@ class HeatMapTooltip extends StatelessWidget {
),
const Divider(height: 2, thickness: 1),
Text(
'$value Occupants',
'Occupancy detected: $value',
style: context.textTheme.bodySmall?.copyWith(
fontSize: 10,
fontWeight: FontWeight.w500,

View File

@ -52,7 +52,7 @@ class _InteractiveHeatMapState extends State<InteractiveHeatMap> {
color: Colors.transparent,
child: Transform.translate(
offset: Offset(-(widget.cellSize * 2.5), -50),
child: HeatMapTooltip(date: item.date, value: item.value),
child: HeatMapTooltip(date: item.date.toUtc(), value: item.value),
),
),
),

View File

@ -2,6 +2,7 @@ import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
import 'package:syncrow_web/pages/analytics/widgets/charts_x_axis_title.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -88,8 +89,8 @@ class OccupancyChart extends StatelessWidget {
}) {
final data = chartData;
final occupancyValue = double.parse(data[group.x.toInt()].occupancy);
final percentage = '${(occupancyValue).toStringAsFixed(0)}%';
final occupancyValue = double.parse(data[group.x].occupancy);
final percentage = '${occupancyValue.toStringAsFixed(0)}%';
return BarTooltipItem(
percentage,
@ -116,7 +117,7 @@ class OccupancyChart extends StatelessWidget {
alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown,
child: Text(
'${(value).toStringAsFixed(0)}%',
'${value.toStringAsFixed(0)}%',
style: context.textTheme.bodySmall?.copyWith(
fontSize: 12,
color: ColorsManager.greyColor,
@ -128,6 +129,7 @@ class OccupancyChart extends StatelessWidget {
);
final bottomTitles = AxisTitles(
axisNameWidget: const ChartsXAxisTitle(),
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, _) => FittedBox(

View File

@ -23,10 +23,9 @@ class OccupancyEndSideBar extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const AnalyticsSidebarHeader(title: 'Presnce Sensor'),
const AnalyticsSidebarHeader(title: 'Presence Sensor'),
Expanded(
child: SizedBox(
// height: MediaQuery.sizeOf(context).height * 0.2,
child: PowerClampEnergyStatusWidget(
status: [
PowerClampEnergyStatus(

View File

@ -20,14 +20,14 @@ class OccupancyHeatMap extends StatelessWidget {
: 0;
DateTime _getStartingDate() {
final jan1 = DateTime(DateTime.now().year, 1, 1);
final jan1 = DateTime(DateTime.now().year, 1, 1).toUtc();
final startOfWeek = jan1.subtract(Duration(days: jan1.weekday - 1));
return startOfWeek;
}
List<OccupancyPaintItem> _generatePaintItems(DateTime startDate) {
return List.generate(_totalWeeks * 7, (index) {
final date = startDate.add(Duration(days: index));
final date = startDate.toUtc().add(Duration(days: index));
final value = heatMapData[date] ?? 0;
return OccupancyPaintItem(index: index, value: value, date: date);
});

View File

@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class ChartsXAxisTitle extends StatelessWidget {
const ChartsXAxisTitle({
this.label = 'Day of month',
super.key,
});
final String label;
@override
Widget build(BuildContext context) {
return Text(
label,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.lightGreyColor,
fontSize: 8,
),
);
}
}

View File

@ -36,7 +36,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
////////////////////////////// forget password //////////////////////////////////
final TextEditingController forgetEmailController = TextEditingController();
final TextEditingController forgetPasswordController = TextEditingController();
final TextEditingController forgetPasswordController =
TextEditingController();
final TextEditingController forgetOtp = TextEditingController();
final forgetFormKey = GlobalKey<FormState>();
final forgetEmailKey = GlobalKey<FormState>();
@ -53,7 +54,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
return;
}
_remainingTime = 1;
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
try {
forgetEmailValidate = '';
_remainingTime = (await AuthenticationAPI.sendOtp(
@ -90,7 +92,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
_timer?.cancel();
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
} else {
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
}
});
}
@ -100,7 +103,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
}
Future<void> changePassword(
Future<void> changePassword(
ChangePasswordEvent event, Emitter<AuthState> emit) async {
emit(LoadingForgetState());
try {
@ -122,7 +125,6 @@ Future<void> changePassword(
}
}
String? validateCode(String? value) {
if (value == null || value.isEmpty) {
return 'Code is required';
@ -131,7 +133,9 @@ Future<void> changePassword(
}
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime));
emit(TimerState(
isButtonEnabled: event.isButtonEnabled,
remainingTime: event.remainingTime));
}
///////////////////////////////////// login /////////////////////////////////////
@ -151,7 +155,6 @@ Future<void> changePassword(
static UserModel? user;
bool showValidationMessage = false;
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
emit(AuthLoading());
if (isChecked) {
@ -170,11 +173,11 @@ Future<void> changePassword(
);
} on APIException catch (e) {
validate = e.message;
emit(LoginInitial());
emit(LoginFailure(error: validate));
return;
} catch (e) {
validate = 'Something went wrong';
emit(LoginInitial());
emit(LoginFailure(error: validate));
return;
}
@ -197,7 +200,6 @@ Future<void> changePassword(
}
}
checkBoxToggle(
CheckBoxEvent event,
Emitter<AuthState> emit,
@ -339,12 +341,14 @@ Future<void> changePassword(
static Future<String> getTokenAndValidate() async {
try {
const storage = FlutterSecureStorage();
final firstLaunch =
await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(
StringsManager.firstLaunch) ??
true;
if (firstLaunch) {
storage.deleteAll();
}
await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false);
await SharedPreferencesHelper.saveBoolToSP(
StringsManager.firstLaunch, false);
final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
if (value.isEmpty) {
return 'Token not found';
@ -397,7 +401,9 @@ Future<void> changePassword(
final String formattedTime = [
if (days > 0) '${days}d', // Append 'd' for days
if (days > 0 || hours > 0)
hours.toString().padLeft(2, '0'), // Show hours if there are days or hours
hours
.toString()
.padLeft(2, '0'), // Show hours if there are days or hours
minutes.toString().padLeft(2, '0'),
seconds.toString().padLeft(2, '0'),
].join(':');

View File

@ -45,7 +45,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
) async {
emit(AcsLoadingState());
try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
if (deviceStatus.countdown1 != 0) {
final totalMinutes = deviceStatus.countdown1 * 6;
@ -68,12 +69,13 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
}
}
void _listenToChanges(deviceId) {
StreamSubscription<DatabaseEvent>? _deviceStatusSubscription;
void _listenToChanges(String deviceId) {
try {
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
final stream = ref.onValue;
stream.listen((DatabaseEvent event) async {
_deviceStatusSubscription =
ref.onValue.listen((DatabaseEvent event) async {
if (event.snapshot.value == null) return;
Map<dynamic, dynamic> usersMap =
@ -82,10 +84,14 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
List<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList.add(Status(code: element['code'], value: element['value']));
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList);
deviceStatus =
AcStatusModel.fromJson(usersMap['productUuid'], statusList);
print('Device status updated: ${deviceStatus.acSwitch}');
if (!isClosed) {
add(AcStatusUpdated(deviceStatus));
}
@ -106,15 +112,14 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
Emitter<AcsState> emit,
) async {
emit(AcsLoadingState());
_updateDeviceFunctionFromCode(event.code, event.value);
emit(ACStatusLoaded(status: deviceStatus));
try {
final success = await controlDeviceService.controlDevice(
deviceUuid: event.deviceId,
status: Status(code: event.code, value: event.value),
);
_updateDeviceFunctionFromCode(event.code, event.value);
emit(ACStatusLoaded(status: deviceStatus));
if (!success) {
emit(const AcsFailedState(error: 'Failed to control device'));
}
@ -129,8 +134,10 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
) async {
emit(AcsLoadingState());
try {
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = AcStatusModel.fromJson(event.devicesIds.first, status.status);
final status =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus =
AcStatusModel.fromJson(event.devicesIds.first, status.status);
emit(ACStatusLoaded(status: deviceStatus));
} catch (e) {
emit(AcsFailedState(error: e.toString()));
@ -293,13 +300,17 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
totalSeconds--;
scheduledHours = totalSeconds ~/ 3600;
scheduledMinutes = (totalSeconds % 3600) ~/ 60;
add(UpdateTimerEvent());
if (!isClosed) {
add(UpdateTimerEvent());
}
} else {
_countdownTimer?.cancel();
timerActive = false;
scheduledHours = 0;
scheduledMinutes = 0;
add(TimerCompletedEvent());
if (!isClosed) {
add(TimerCompletedEvent());
}
}
});
}
@ -326,7 +337,9 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
_startCountdownTimer(
emit,
);
add(UpdateTimerEvent());
if (!isClosed) {
add(UpdateTimerEvent());
}
}
}
@ -370,6 +383,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
@override
Future<void> close() {
add(OnClose());
_countdownTimer?.cancel();
_deviceStatusSubscription?.cancel();
return super.close();
}
}

View File

@ -62,7 +62,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
final buttonLabel =
(selectedDevices.length > 1) ? 'Batch Control' : 'Control';
final isAnyDeviceOffline =
selectedDevices.any((element) => !(element.online ?? false));
return Row(
children: [
Expanded(child: SpaceTreeView(
@ -103,8 +104,26 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
decoration: containerDecoration,
child: Center(
child: DefaultButton(
backgroundColor: isAnyDeviceOffline
? ColorsManager.primaryColor
.withValues(alpha: 0.1)
: null,
onPressed: isControlButtonEnabled
? () {
if (isAnyDeviceOffline) {
ScaffoldMessenger.of(context)
.showSnackBar(
const SnackBar(
content: Text(
'This Device is Offline',
),
duration:
Duration(seconds: 2),
),
);
return;
}
if (selectedDevices.length == 1) {
showDialog(
context: context,

View File

@ -12,7 +12,8 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
//Smart Power Clamp
class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayout {
class SmartPowerDeviceControl extends StatelessWidget
with HelperResponsiveLayout {
final String deviceId;
const SmartPowerDeviceControl({super.key, required this.deviceId});
@ -145,13 +146,16 @@ class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayou
children: [
IconButton(
icon: const Icon(Icons.arrow_left),
onPressed: () {
blocProvider.add(SmartPowerArrowPressedEvent(-1));
pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
onPressed: blocProvider.currentPage <= 0
? null
: () {
blocProvider
.add(SmartPowerArrowPressedEvent(-1));
pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
),
Text(
currentPage == 0
@ -165,13 +169,16 @@ class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayou
),
IconButton(
icon: const Icon(Icons.arrow_right),
onPressed: () {
blocProvider.add(SmartPowerArrowPressedEvent(1));
pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
onPressed: blocProvider.currentPage >= 3
? null
: () {
blocProvider
.add(SmartPowerArrowPressedEvent(1));
pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
),
],
),
@ -195,8 +202,8 @@ class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayou
blocProvider.add(SelectDateEvent(context: context));
blocProvider.add(FilterRecordsByDateEvent(
selectedDate: blocProvider.dateTime!,
viewType:
blocProvider.views[blocProvider.currentIndex]));
viewType: blocProvider
.views[blocProvider.currentIndex]));
},
widget: blocProvider.dateSwitcher(),
chartData: blocProvider.energyDataList.isNotEmpty

View File

@ -79,6 +79,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
}
Widget _buildDeviceInfoSection() {
final isOnlineDevice = device.online != null && device.online!;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 50),
child: Table(
@ -107,7 +108,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
'Installation Date and Time:',
formatDateTime(
DateTime.fromMillisecondsSinceEpoch(
((device.createTime ?? 0) * 1000),
(device.createTime ?? 0) * 1000,
),
),
),
@ -126,12 +127,16 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
),
TableRow(
children: [
_buildInfoRow('Status:', 'Online', statusColor: Colors.green),
_buildInfoRow(
'Status:',
isOnlineDevice ? 'Online' : 'offline',
statusColor: isOnlineDevice ? Colors.green : Colors.red,
),
_buildInfoRow(
'Last Offline Date and Time:',
formatDateTime(
DateTime.fromMillisecondsSinceEpoch(
((device.updateTime ?? 0) * 1000),
(device.updateTime ?? 0) * 1000,
),
),
),

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/auth/model/user_model.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.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';
@ -12,8 +14,11 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class EditUserDialog extends StatefulWidget {
final String? userId;
const EditUserDialog({super.key, this.userId});
final RolesUserModel? user;
const EditUserDialog({
super.key,
this.user,
});
@override
_EditUserDialogState createState() => _EditUserDialogState();
@ -28,10 +33,11 @@ class _EditUserDialogState extends State<EditUserDialog> {
create: (BuildContext context) => UsersBloc()
// ..add(const LoadCommunityAndSpacesEvent())
..add(const RoleEvent())
..add(GetUserByIdEvent(uuid: widget.userId)),
..add(GetUserByIdEvent(uuid: widget.user!.uuid)),
child: BlocConsumer<UsersBloc, UsersState>(listener: (context, state) {
if (state is SpacesLoadedState) {
BlocProvider.of<UsersBloc>(context).add(GetUserByIdEvent(uuid: widget.userId));
BlocProvider.of<UsersBloc>(context)
.add(GetUserByIdEvent(uuid: widget.user!.uuid));
}
}, builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
@ -39,7 +45,8 @@ class _EditUserDialogState extends State<EditUserDialog> {
return Dialog(
child: Container(
decoration: const BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))),
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20))),
width: 900,
child: Column(
children: [
@ -68,7 +75,8 @@ class _EditUserDialogState extends State<EditUserDialog> {
children: [
_buildStep1Indicator(1, "Basics", _blocRole),
_buildStep2Indicator(2, "Spaces", _blocRole),
_buildStep3Indicator(3, "Role & Permissions", _blocRole),
_buildStep3Indicator(
3, "Role & Permissions", _blocRole),
],
),
),
@ -86,7 +94,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
children: [
const SizedBox(height: 10),
Expanded(
child: _getFormContent(widget.userId),
child: _getFormContent(widget.user!),
),
const SizedBox(height: 20),
],
@ -116,13 +124,14 @@ class _EditUserDialogState extends State<EditUserDialog> {
if (currentStep < 3) {
currentStep++;
if (currentStep == 2) {
_blocRole.add(CheckStepStatus(isEditUser: true));
_blocRole
.add(CheckStepStatus(isEditUser: true));
} else if (currentStep == 3) {
_blocRole.add(const CheckSpacesStepStatus());
}
} else {
_blocRole
.add(EditInviteUsers(context: context, userId: widget.userId!));
_blocRole.add(EditInviteUsers(
context: context, userId: widget.user!.uuid));
}
});
},
@ -131,7 +140,8 @@ class _EditUserDialogState extends State<EditUserDialog> {
style: TextStyle(
color: (_blocRole.isCompleteSpaces == false ||
_blocRole.isCompleteBasics == false ||
_blocRole.isCompleteRolePermissions == false) &&
_blocRole.isCompleteRolePermissions ==
false) &&
currentStep == 3
? ColorsManager.grayColor
: ColorsManager.secondaryColor),
@ -146,15 +156,15 @@ class _EditUserDialogState extends State<EditUserDialog> {
}));
}
Widget _getFormContent(userid) {
Widget _getFormContent(RolesUserModel user) {
switch (currentStep) {
case 1:
return BasicsView(
userId: userid,
userId: user.uuid,
);
case 2:
return SpacesAccessView(
userId: userid,
userId: user.uuid,
);
case 3:
return const RolesAndPermission();
@ -166,6 +176,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
int step3 = 0;
Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) {
final isCurrentStep = currentStep == step;
return GestureDetector(
onTap: () {
setState(() {
@ -189,7 +200,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
child: Row(
children: [
SvgPicture.asset(
currentStep == step
isCurrentStep
? Assets.currentProcessIcon
: bloc.isCompleteBasics == false
? Assets.wrongProcessIcon
@ -204,8 +215,11 @@ class _EditUserDialogState extends State<EditUserDialog> {
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
color: isCurrentStep
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight:
isCurrentStep ? FontWeight.bold : FontWeight.normal,
),
),
],
@ -229,6 +243,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
}
Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) {
final isCurrentStep = currentStep == step;
return GestureDetector(
onTap: () {
setState(() {
@ -248,7 +263,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
child: Row(
children: [
SvgPicture.asset(
currentStep == step
isCurrentStep
? Assets.currentProcessIcon
: bloc.isCompleteSpaces == false
? Assets.wrongProcessIcon
@ -263,8 +278,11 @@ class _EditUserDialogState extends State<EditUserDialog> {
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
color: isCurrentStep
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight:
isCurrentStep ? FontWeight.bold : FontWeight.normal,
),
),
],
@ -288,6 +306,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
}
Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) {
final isCurrentStep = currentStep == step;
return GestureDetector(
onTap: () {
setState(() {
@ -306,7 +325,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
child: Row(
children: [
SvgPicture.asset(
currentStep == step
isCurrentStep
? Assets.currentProcessIcon
: bloc.isCompleteRolePermissions == false
? Assets.wrongProcessIcon
@ -321,8 +340,11 @@ class _EditUserDialogState extends State<EditUserDialog> {
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
color: isCurrentStep
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight:
isCurrentStep ? FontWeight.bold : FontWeight.normal,
),
),
],

View File

@ -19,6 +19,7 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class UsersPage extends StatelessWidget {
UsersPage({super.key});
@ -451,33 +452,31 @@ class UsersPage extends StatelessWidget {
),
Row(
children: [
user.isEnabled != false
? actionButton(
isActive: true,
title: "Edit",
onTap: () {
context
.read<SpaceTreeBloc>()
.add(ClearCachedData());
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return EditUserDialog(
userId: user.uuid);
},
).then((v) {
if (v != null) {
if (v != null) {
_blocRole.add(const GetUsers());
}
}
});
if (user.isEnabled != false)
actionButton(
isActive: true,
title: "Edit",
onTap: () {
context
.read<SpaceTreeBloc>()
.add(ClearCachedData());
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return EditUserDialog(user: user);
},
)
: actionButton(
title: "Edit",
),
).then((v) {
if (v != null) {
_blocRole.add(const GetUsers());
}
});
},
)
else
actionButton(
title: "Edit",
),
actionButton(
title: "Delete",
onTap: () {

View File

@ -289,7 +289,6 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
selectedSpaces: updatedSelectedSpaces,
soldCheck: updatedSoldChecks,
selectedCommunityAndSpaces: communityAndSpaces));
emit(state.copyWith(selectedSpaces: updatedSelectedSpaces));
} catch (e) {
emit(const SpaceTreeErrorState('Something went wrong'));
}
@ -445,10 +444,12 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
List<String> _getThePathToChild(String communityId, String selectedSpaceId) {
List<String> ids = [];
for (var community in state.communityList) {
final communityDataSource =
state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList;
for (final community in communityDataSource) {
if (community.uuid == communityId) {
for (var space in community.spaces) {
List<String> list = [];
for (final space in community.spaces) {
final list = <String>[];
list.add(space.uuid!);
ids = _getAllParentsIds(space, selectedSpaceId, List.from(list));
if (ids.isNotEmpty) {

View File

@ -68,7 +68,7 @@ class VisitorPasswordBloc
DateTime? startTime = DateTime.now();
DateTime? endTime;
String startTimeAccess = 'Start Time';
String startTimeAccess = DateTime.now().toString().split('.').first;
String endTimeAccess = 'End Time';
PasswordStatus? passwordStatus;
selectAccessType(
@ -136,6 +136,27 @@ class VisitorPasswordBloc
);
return;
}
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) {
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) {
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;
}
effectiveTimeTimeStamp = selectedTimestamp;
startTimeAccess = selectedDateTime.toString().split('.').first;
} else {

View File

@ -2,11 +2,13 @@ class FailedOperation {
final bool success;
final dynamic deviceUuid;
final dynamic error;
final String deviceName;
FailedOperation({
required this.success,
required this.deviceUuid,
required this.error,
required this.deviceName,
});
factory FailedOperation.fromJson(Map<String, dynamic> json) {
@ -14,6 +16,7 @@ class FailedOperation {
success: json['success'],
deviceUuid: json['deviceUuid'],
error: json['error'],
deviceName: json['deviceName'] as String? ?? '',
);
}
@ -22,21 +25,22 @@ class FailedOperation {
'success': success,
'deviceUuid': deviceUuid,
'error': error,
'deviceName': deviceName,
};
}
}
class SuccessOperation {
final bool success;
// final Result result;
final String deviceUuid;
final String deviceName;
SuccessOperation({
required this.success,
// required this.result,
required this.deviceUuid,
required this.deviceName,
});
factory SuccessOperation.fromJson(Map<String, dynamic> json) {
@ -44,6 +48,7 @@ class SuccessOperation {
success: json['success'],
// result: Result.fromJson(json['result']),
deviceUuid: json['deviceUuid'],
deviceName: json['deviceName'] as String? ?? '',
);
}
@ -52,6 +57,7 @@ class SuccessOperation {
'success': success,
// 'result': result.toJson(),
'deviceUuid': deviceUuid,
'deviceName': deviceName,
};
}
}
@ -92,8 +98,6 @@ class SuccessOperation {
// }
// }
class PasswordStatus {
final List<SuccessOperation> successOperations;
final List<FailedOperation> failedOperations;
@ -121,4 +125,3 @@ class PasswordStatus {
};
}
}

View File

@ -63,7 +63,7 @@ class VisitorPasswordDialog extends StatelessWidget {
child: Text(visitorBloc
.passwordStatus!
.failedOperations[index]
.deviceUuid)),
.deviceName)),
);
},
),
@ -92,7 +92,7 @@ class VisitorPasswordDialog extends StatelessWidget {
child: Text(visitorBloc
.passwordStatus!
.successOperations[index]
.deviceUuid)),
.deviceName)),
);
},
),
@ -102,7 +102,7 @@ class VisitorPasswordDialog extends StatelessWidget {
],
))
.then((v) {
Navigator.of(context).pop(true);
Navigator.of(context).pop(v);
});
} else if (state is FailedState) {
visitorBloc.stateDialog(
@ -476,7 +476,7 @@ class VisitorPasswordDialog extends StatelessWidget {
child: DefaultButton(
borderRadius: 8,
onPressed: () {
Navigator.of(context).pop(true);
Navigator.of(context).pop(null);
},
backgroundColor: Colors.white,
child: Text(
@ -651,7 +651,7 @@ class VisitorPasswordDialog extends StatelessWidget {
child: DefaultButton(
borderRadius: 8,
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop(null);
},
backgroundColor: Colors.white,
child: Text(