diff --git a/lib/pages/analytics/models/analytics_device.dart b/lib/pages/analytics/models/analytics_device.dart new file mode 100644 index 00000000..6f066407 --- /dev/null +++ b/lib/pages/analytics/models/analytics_device.dart @@ -0,0 +1,13 @@ +class AnalyticsDevice { + const AnalyticsDevice({required this.name, required this.uuid}); + + final String uuid; + final String name; + + factory AnalyticsDevice.fromJson(Map json) { + return AnalyticsDevice( + uuid: json['uuid'] as String? ?? '', + name: json['name'] as String? ?? '', + ); + } +} diff --git a/lib/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart b/lib/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart new file mode 100644 index 00000000..244b2fa0 --- /dev/null +++ b/lib/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart @@ -0,0 +1,69 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/analytics/models/analytics_device.dart'; +import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart'; +import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart'; + +part 'analytics_devices_event.dart'; +part 'analytics_devices_state.dart'; + +class AnalyticsDevicesBloc + extends Bloc { + AnalyticsDevicesBloc( + this._analyticsDevicesService, + ) : super(const AnalyticsDevicesState()) { + on(_onLoadAnalyticsDevices); + on(_onSelectAnalyticsDevice); + on(_onClearAnalyticsDevice); + } + final AnalyticsDevicesService _analyticsDevicesService; + + Future _onLoadAnalyticsDevices( + LoadAnalyticsDevicesEvent event, + Emitter emit, + ) async { + emit(const AnalyticsDevicesState(status: AnalyticsDevicesStatus.loading)); + + try { + final devices = await _analyticsDevicesService.getDevices(event.param); + emit( + AnalyticsDevicesState( + status: AnalyticsDevicesStatus.loaded, + devices: devices, + selectedDevice: devices.firstOrNull, + ), + ); + if (devices.isNotEmpty) { + event.onSuccess(devices.first); + } + } catch (e) { + emit( + AnalyticsDevicesState( + status: AnalyticsDevicesStatus.failure, + errorMessage: e.toString(), + ), + ); + } + } + + void _onSelectAnalyticsDevice( + SelectAnalyticsDeviceEvent event, + Emitter emit, + ) { + emit( + AnalyticsDevicesState( + selectedDevice: event.device, + devices: state.devices, + errorMessage: state.errorMessage, + status: state.status, + ), + ); + } + + void _onClearAnalyticsDevice( + ClearAnalyticsDeviceEvent event, + Emitter emit, + ) { + emit(const AnalyticsDevicesState()); + } +} diff --git a/lib/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_event.dart b/lib/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_event.dart new file mode 100644 index 00000000..fb61e73b --- /dev/null +++ b/lib/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_event.dart @@ -0,0 +1,31 @@ +part of 'analytics_devices_bloc.dart'; + +sealed class AnalyticsDevicesEvent extends Equatable { + const AnalyticsDevicesEvent(); + + @override + List get props => []; +} + +final class LoadAnalyticsDevicesEvent extends AnalyticsDevicesEvent { + const LoadAnalyticsDevicesEvent({required this.param, required this.onSuccess}); + + final GetAnalyticsDevicesParam param; + final void Function(AnalyticsDevice device) onSuccess; + + @override + List get props => [param]; +} + +final class SelectAnalyticsDeviceEvent extends AnalyticsDevicesEvent { + const SelectAnalyticsDeviceEvent(this.device); + + final AnalyticsDevice device; + + @override + List get props => [device]; +} + +final class ClearAnalyticsDeviceEvent extends AnalyticsDevicesEvent { + const ClearAnalyticsDeviceEvent(); +} diff --git a/lib/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_state.dart b/lib/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_state.dart new file mode 100644 index 00000000..7c1be359 --- /dev/null +++ b/lib/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_state.dart @@ -0,0 +1,20 @@ +part of 'analytics_devices_bloc.dart'; + +enum AnalyticsDevicesStatus { initial, loading, loaded, failure } + +final class AnalyticsDevicesState extends Equatable { + const AnalyticsDevicesState({ + this.status = AnalyticsDevicesStatus.initial, + this.devices = const [], + this.errorMessage, + this.selectedDevice, + }); + + final AnalyticsDevicesStatus status; + final List devices; + final AnalyticsDevice? selectedDevice; + final String? errorMessage; + + @override + List get props => [status, devices, errorMessage, selectedDevice]; +} diff --git a/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart index 2d12ed28..1e251a41 100644 --- a/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart +++ b/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart @@ -14,12 +14,20 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg CommunityModel community, List spaces, ) { + // Add to space tree bloc first context.read().add( OnCommunitySelected( community.uuid, spaces, ), ); + + final spaceTreeState = context.read().state; + if (spaceTreeState.selectedCommunities.contains(community.uuid)) { + clearData(context); + return; + } + FetchEnergyManagementDataHelper.loadEnergyManagementData( context, communityId: community.uuid, @@ -41,6 +49,13 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg ), ); + final spaceTreeState = context.read().state; + if (spaceTreeState.selectedCommunities.contains(community.uuid) || + spaceTreeState.selectedSpaces.contains(space.uuid)) { + clearData(context); + return; + } + FetchEnergyManagementDataHelper.loadEnergyManagementData( context, communityId: community.uuid, @@ -54,7 +69,7 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg CommunityModel community, SpaceModel child, ) { - // Do nothing + // Do nothing else as per original implementation } @override diff --git a/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart index 588a125b..fb93ec30 100644 --- a/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart +++ b/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart @@ -20,6 +20,12 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy { spaces.isNotEmpty ? [spaces.first] : [], ), ); + + final spaceTreeState = context.read().state; + if (spaceTreeState.selectedCommunities.contains(community.uuid)) { + clearData(context); + return; + } FetchOccupancyDataHelper.loadOccupancyData( context, communityId: community.uuid, @@ -47,6 +53,13 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy { ..add(OnSpaceSelected(community, space.uuid ?? '', [])); } + final spaceTreeState = context.read().state; + if (spaceTreeState.selectedCommunities.contains(community.uuid) || + spaceTreeState.selectedSpaces.contains(space.uuid)) { + clearData(context); + return; + } + FetchOccupancyDataHelper.loadOccupancyData( context, communityId: community.uuid, @@ -66,6 +79,6 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy { @override void clearData(BuildContext context) { context.read().add(const SpaceTreeClearSelectionEvent()); - // FetchOccupancyDataHelper.clearAllData(context); + FetchOccupancyDataHelper.clearAllData(context); } } diff --git a/lib/pages/analytics/modules/analytics/views/analytics_page.dart b/lib/pages/analytics/modules/analytics/views/analytics_page.dart index 6674667c..dc8457a5 100644 --- a/lib/pages/analytics/modules/analytics/views/analytics_page.dart +++ b/lib/pages/analytics/modules/analytics/views/analytics_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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/analytics/blocs/analytics_tab/analytics_tab_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart'; @@ -11,6 +12,9 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/real 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/occupancy/blocs/occupancy/occupancy_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/services/analytics_devices/analytics_devices_service_delagate.dart'; +import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart'; +import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart'; import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/fake_energy_consumption_by_phases_service.dart'; import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/fake_energy_consumption_per_device_service.dart'; import 'package:syncrow_web/pages/analytics/services/occupacy/fake_occupacy_service.dart'; @@ -23,9 +27,22 @@ import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; -class AnalyticsPage extends StatelessWidget { +class AnalyticsPage extends StatefulWidget { const AnalyticsPage({super.key}); + @override + State createState() => _AnalyticsPageState(); +} + +class _AnalyticsPageState extends State { + late final HTTPService _httpService; + + @override + void initState() { + super.initState(); + _httpService = HTTPService(); + } + @override Widget build(BuildContext context) { return MultiBlocProvider( @@ -35,7 +52,7 @@ class AnalyticsPage extends StatelessWidget { ), BlocProvider( create: (context) => TotalEnergyConsumptionBloc( - RemoteTotalEnergyConsumptionService(HTTPService()), + RemoteTotalEnergyConsumptionService(_httpService), ), ), BlocProvider( @@ -50,7 +67,7 @@ class AnalyticsPage extends StatelessWidget { ), BlocProvider( create: (context) => PowerClampInfoBloc( - RemotePowerClampInfoService(HTTPService()), + RemotePowerClampInfoService(_httpService), ), ), BlocProvider( @@ -61,10 +78,18 @@ class AnalyticsPage extends StatelessWidget { BlocProvider(create: (context) => OccupancyBloc(FakeOccupacyService())), BlocProvider( create: (context) => OccupancyHeatMapBloc( - RemoteOccupancyHeatMapService(HTTPService()), + RemoteOccupancyHeatMapService(_httpService), ), ), BlocProvider(create: (context) => AnalyticsDatePickerBloc()), + BlocProvider( + create: (context) => AnalyticsDevicesBloc( + AnalyticsDevicesServiceDelegate( + RemoteOccupancyAnalyticsDevicesService(_httpService), + RemoteEnergyManagementAnalyticsDevicesService(_httpService), + ), + ), + ), ], child: const AnalyticsPageForm(), ); diff --git a/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart b/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart index 84afa77d..9346f66e 100644 --- a/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart +++ b/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart @@ -1,11 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/analytics/models/analytics_device.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/energy_consumption_by_phases/energy_consumption_by_phases_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_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/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart'; +import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart'; @@ -13,7 +16,10 @@ import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_ abstract final class FetchEnergyManagementDataHelper { const FetchEnergyManagementDataHelper._(); - static const String _powerClampId = 'cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'; + // static const String _powerClampId = 'cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'; + static AnalyticsDevice? getSelectedDevice(BuildContext context) { + return context.read().state.selectedDevice; + } static void loadEnergyManagementData( BuildContext context, { @@ -28,16 +34,14 @@ abstract final class FetchEnergyManagementDataHelper { final datePickerState = context.read().state; final selectedDate0 = selectedDate ?? datePickerState.monthlyDate; - + loadAnalyticsDevices(context, communityUuid: communityId, spaceUuid: spaceId); loadTotalEnergyConsumption( context, selectedDate: selectedDate0, communityId: communityId, spaceId: spaceId, ); - loadEnergyConsumptionByPhases(context, selectedDate: selectedDate); - loadEnergyConsumptionPerDevice(context); loadRealtimeDeviceChanges(context); loadPowerClampInfo(context); @@ -80,25 +84,57 @@ abstract final class FetchEnergyManagementDataHelper { } static void loadPowerClampInfo(BuildContext context) { - context.read().add( - const LoadPowerClampInfoEvent(_powerClampId), + final selectedDevice = getSelectedDevice(context); + if (selectedDevice case final AnalyticsDevice device) { + context.read().add( + LoadPowerClampInfoEvent(device.uuid), + ); + } + } + + static void loadRealtimeDeviceChanges( + BuildContext context, { + String? deviceUuid, + }) { + final selectedDevice = getSelectedDevice(context); + + context.read().add( + RealtimeDeviceChangesStarted(deviceUuid ?? selectedDevice?.uuid ?? ''), ); } - static void loadRealtimeDeviceChanges(BuildContext context) { - context.read().add( - const RealtimeDeviceChangesStarted(_powerClampId), + static void loadAnalyticsDevices( + BuildContext context, { + required String communityUuid, + required String spaceUuid, + }) { + context.read().add( + LoadAnalyticsDevicesEvent( + onSuccess: (device) { + context.read().add( + LoadPowerClampInfoEvent(device.uuid), + ); + context.read().add( + RealtimeDeviceChangesStarted(device.uuid), + ); + }, + param: GetAnalyticsDevicesParam( + communityUuid: communityUuid, + spaceUuid: spaceUuid, + deviceTypes: ['PC'], + requestType: AnalyticsDeviceRequestType.energyManagement, + ), + ), ); } static void clearAllData(BuildContext context) { - context.read().add( - const RealtimeDeviceChangesClosed(), - ); - context.read().add( const ClearPowerClampInfoEvent(), ); + context.read().add( + const RealtimeDeviceChangesClosed(), + ); context.read().add( const ClearEnergyConsumptionPerDeviceEvent(), @@ -111,5 +147,6 @@ abstract final class FetchEnergyManagementDataHelper { context.read().add( const ClearEnergyConsumptionByPhasesEvent(), ); + context.read().add(const ClearAnalyticsDeviceEvent()); } } diff --git a/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart b/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart new file mode 100644 index 00000000..0d96fb70 --- /dev/null +++ b/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/analytics/models/analytics_device.dart'; +import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class AnalyticsDeviceDropdown extends StatelessWidget { + const AnalyticsDeviceDropdown({required this.onChanged, super.key}); + + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: ColorsManager.greyColor, + width: 1, + ), + ), + child: Visibility( + visible: state.devices.isNotEmpty, + replacement: _buildNoDevicesFound(context), + child: _buildDevicesDropdown(context, state), + ), + ); + }, + ); + } + + static const _defaultPadding = EdgeInsetsDirectional.symmetric( + horizontal: 20, + vertical: 2, + ); + + Widget _buildNoDevicesFound(BuildContext context) { + return Padding( + padding: _defaultPadding, + child: Text( + 'no devices found', + style: _getTextStyle(context), + ), + ); + } + + Widget _buildDevicesDropdown(BuildContext context, AnalyticsDevicesState state) { + return DropdownButton( + value: state.selectedDevice, + isDense: true, + borderRadius: BorderRadius.circular(16), + dropdownColor: ColorsManager.whiteColors, + underline: const SizedBox.shrink(), + icon: const RotatedBox( + quarterTurns: 1, + child: Icon(Icons.chevron_right, size: 16), + ), + style: _getTextStyle(context), + padding: _defaultPadding, + items: state.devices.map((e) { + return DropdownMenuItem( + value: e, + child: Text(e.name), + ); + }).toList(), + onChanged: (value) { + if (value case final AnalyticsDevice device) { + context.read().add( + SelectAnalyticsDeviceEvent(device), + ); + onChanged.call(device); + } + }, + ); + } + + TextStyle? _getTextStyle(BuildContext context) { + return context.textTheme.labelSmall?.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w700, + fontSize: 14, + ); + } +} diff --git a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_device_dropdown.dart b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_device_dropdown.dart deleted file mode 100644 index be9ce98d..00000000 --- a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_device_dropdown.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/extension/build_context_x.dart'; - -class PowerClampEnergyDataDeviceDropdown extends StatelessWidget { - const PowerClampEnergyDataDeviceDropdown({super.key}); - - @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: ColorsManager.greyColor, - width: 1, - ), - ), - child: DropdownButton( - value: 'Device 1', - isDense: true, - borderRadius: BorderRadius.circular(16), - dropdownColor: ColorsManager.whiteColors, - underline: const SizedBox.shrink(), - icon: const RotatedBox( - quarterTurns: 1, - child: Icon(Icons.chevron_right, size: 16), - ), - style: context.textTheme.labelSmall?.copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w700, - fontSize: 14, - ), - padding: const EdgeInsetsDirectional.symmetric( - horizontal: 20, - vertical: 2, - ), - items: [ - for (var i = 1; i < 10; i++) - DropdownMenuItem( - value: 'Device $i', - child: Text( - 'Device $i', - style: context.textTheme.labelSmall?.copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w700, - fontSize: 14, - ), - ), - ), - ], - onChanged: (value) {}, - ), - ); - } -} diff --git a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart index 6bb56071..95f343c2 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.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/power_clamp_info/power_clamp_info_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/modules/energy_management/helpers/fetch_energy_management_data_helper.dart'; +import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart_box.dart'; -import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_device_dropdown.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart'; @@ -50,7 +52,8 @@ class PowerClampEnergyDataWidget extends StatelessWidget { ), const SizedBox(height: 6), SelectableText( - state.powerClampModel?.productUuid ?? 'N/A', + context.watch().state.selectedDevice?.uuid ?? + 'N/A', style: context.textTheme.bodySmall?.copyWith( color: ColorsManager.blackColor, fontWeight: FontWeight.w400, @@ -107,7 +110,7 @@ class PowerClampEnergyDataWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Expanded( - flex: 2, + flex: 3, child: FittedBox( fit: BoxFit.scaleDown, alignment: AlignmentDirectional.centerStart, @@ -122,11 +125,19 @@ class PowerClampEnergyDataWidget extends StatelessWidget { ), ), const Spacer(), - const Expanded( + Expanded( + flex: 2, child: FittedBox( fit: BoxFit.scaleDown, alignment: AlignmentDirectional.centerEnd, - child: PowerClampEnergyDataDeviceDropdown(), + child: AnalyticsDeviceDropdown( + onChanged: (value) { + FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges( + context, + deviceUuid: value.uuid, + ); + }, + ), ), ), ], diff --git a/lib/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart b/lib/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart index 403a752d..be21027b 100644 --- a/lib/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart +++ b/lib/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/analytics/models/analytics_device.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/modules/occupancy/blocs/occupancy/occupancy_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/params/get_analytics_devices_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart'; @@ -17,10 +20,14 @@ abstract final class FetchOccupancyDataHelper { }) { if (communityId.isEmpty && spaceId.isEmpty) { clearAllData(context); + return; } final datePickerState = context.read().state; + loadAnalyticsDevices(context, communityUuid: communityId, spaceUuid: spaceId); + final selectedDevice = context.read().state.selectedDevice; + context.read().add( LoadOccupancyEvent( GetOccupancyParam( @@ -41,11 +48,35 @@ abstract final class FetchOccupancyDataHelper { ), ); - context.read() - ..add(const RealtimeDeviceChangesClosed()) - ..add( - const RealtimeDeviceChangesStarted('14fe6e7e-47af-4a07-ae0a-7c4a26ef8135'), - ); + if (selectedDevice case final AnalyticsDevice device) { + context.read() + ..add(const RealtimeDeviceChangesClosed()) + ..add( + RealtimeDeviceChangesStarted(device.uuid), + ); + } + } + + static void loadAnalyticsDevices( + BuildContext context, { + required String communityUuid, + required String spaceUuid, + }) { + context.read().add( + LoadAnalyticsDevicesEvent( + param: GetAnalyticsDevicesParam( + communityUuid: communityUuid, + spaceUuid: spaceUuid, + deviceTypes: ['WPS', 'CPS'], + requestType: AnalyticsDeviceRequestType.occupancy, + ), + onSuccess: (device) { + context.read() + ..add(const RealtimeDeviceChangesClosed()) + ..add(RealtimeDeviceChangesStarted(device.uuid)); + }, + ), + ); } static void clearAllData(BuildContext context) { @@ -58,5 +89,9 @@ abstract final class FetchOccupancyDataHelper { context.read().add( const RealtimeDeviceChangesClosed(), ); + + context.read().add( + const ClearAnalyticsDeviceEvent(), + ); } } diff --git a/lib/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart b/lib/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart index 788d087e..9f096789 100644 --- a/lib/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart +++ b/lib/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart @@ -1,15 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.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/modules/energy_management/widgets/power_clamp_energy_data_device_dropdown.dart'; +import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart'; +import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; -import 'package:uuid/uuid.dart'; class OccupancyEndSideBar extends StatelessWidget { const OccupancyEndSideBar({super.key}); @@ -37,7 +38,8 @@ class OccupancyEndSideBar extends StatelessWidget { ), const SizedBox(height: 6), SelectableText( - (const Uuid().v4()), + context.watch().state.selectedDevice?.uuid ?? + 'N/A', style: context.textTheme.bodySmall?.copyWith( color: ColorsManager.blackColor, fontWeight: FontWeight.w400, @@ -105,7 +107,7 @@ class OccupancyEndSideBar extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Expanded( - flex: 2, + flex: 3, child: FittedBox( fit: BoxFit.scaleDown, alignment: AlignmentDirectional.centerStart, @@ -120,11 +122,18 @@ class OccupancyEndSideBar extends StatelessWidget { ), ), const Spacer(), - const Expanded( + Expanded( + flex: 2, child: FittedBox( fit: BoxFit.scaleDown, alignment: AlignmentDirectional.centerEnd, - child: PowerClampEnergyDataDeviceDropdown(), + child: AnalyticsDeviceDropdown( + onChanged: (value) => + FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges( + context, + deviceUuid: value.uuid, + ), + ), ), ), ], diff --git a/lib/pages/analytics/params/get_analytics_devices_param.dart b/lib/pages/analytics/params/get_analytics_devices_param.dart new file mode 100644 index 00000000..f8f4e526 --- /dev/null +++ b/lib/pages/analytics/params/get_analytics_devices_param.dart @@ -0,0 +1,22 @@ +enum AnalyticsDeviceRequestType { energyManagement, occupancy } + +class GetAnalyticsDevicesParam { + final String? spaceUuid; + final List deviceTypes; + final String? communityUuid; + final AnalyticsDeviceRequestType requestType; + + const GetAnalyticsDevicesParam({ + required this.requestType, + required this.spaceUuid, + required this.deviceTypes, + required this.communityUuid, + }); + + Map toJson() { + return { + if (spaceUuid != null) 'spaceUuid': spaceUuid, + if (communityUuid != null) 'communityUuid': communityUuid, + }; + } +} diff --git a/lib/pages/analytics/services/analytics_devices/analytics_devices_service.dart b/lib/pages/analytics/services/analytics_devices/analytics_devices_service.dart new file mode 100644 index 00000000..b8bae76a --- /dev/null +++ b/lib/pages/analytics/services/analytics_devices/analytics_devices_service.dart @@ -0,0 +1,6 @@ +import 'package:syncrow_web/pages/analytics/models/analytics_device.dart'; +import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart'; + +abstract interface class AnalyticsDevicesService { + Future> getDevices(GetAnalyticsDevicesParam param); +} diff --git a/lib/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart b/lib/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart new file mode 100644 index 00000000..2d735df6 --- /dev/null +++ b/lib/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart @@ -0,0 +1,24 @@ +import 'package:syncrow_web/pages/analytics/models/analytics_device.dart'; +import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart'; +import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart'; + +class AnalyticsDevicesServiceDelegate implements AnalyticsDevicesService { + const AnalyticsDevicesServiceDelegate( + this._occupancyService, + this._energyManagementService, + ); + + final AnalyticsDevicesService _occupancyService; + final AnalyticsDevicesService _energyManagementService; + + @override + Future> getDevices( + GetAnalyticsDevicesParam param, + ) { + return switch (param.requestType) { + AnalyticsDeviceRequestType.occupancy => _occupancyService.getDevices(param), + AnalyticsDeviceRequestType.energyManagement => + _energyManagementService.getDevices(param), + }; + } +} diff --git a/lib/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart b/lib/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart new file mode 100644 index 00000000..adf8a6fa --- /dev/null +++ b/lib/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart @@ -0,0 +1,36 @@ +import 'package:syncrow_web/pages/analytics/models/analytics_device.dart'; +import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart'; +import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; + +final class RemoteEnergyManagementAnalyticsDevicesService + implements AnalyticsDevicesService { + const RemoteEnergyManagementAnalyticsDevicesService(this._httpService); + + final HTTPService _httpService; + + @override + Future> getDevices(GetAnalyticsDevicesParam param) async { + try { + final response = await _httpService.get( + path: '/devices-space-community/recursive-child', + queryParameters: param.toJson() + ..addAll({'productType': param.deviceTypes.first}), + expectedResponseModel: (response) { + final json = response as Map; + final dailyData = json['data'] as List? ?? []; + + final result = dailyData.map( + (json) => AnalyticsDevice.fromJson(json as Map), + ); + + return result.toList(); + }, + ); + + return response; + } catch (e) { + throw Exception('Failed to load total energy consumption: $e'); + } + } +} diff --git a/lib/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart b/lib/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart new file mode 100644 index 00000000..95a4aacf --- /dev/null +++ b/lib/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart @@ -0,0 +1,62 @@ +import 'package:syncrow_web/pages/analytics/models/analytics_device.dart'; +import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart'; +import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart'; +import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; + +class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService { + const RemoteOccupancyAnalyticsDevicesService(this._httpService); + + final HTTPService _httpService; + + @override + Future> getDevices(GetAnalyticsDevicesParam param) async { + try { + final requests = await Future.wait>( + param.deviceTypes.map((e) { + final mappedParam = GetAnalyticsDevicesParam( + requestType: AnalyticsDeviceRequestType.occupancy, + spaceUuid: param.spaceUuid, + deviceTypes: [e], + communityUuid: param.communityUuid, + ); + return _makeRequest(mappedParam); + }).toList(), + ); + + final result = requests.map((e) => e.first).toList(); + return result; + } catch (e) { + throw Exception('Failed to load total energy consumption: $e'); + } + } + + Future> _makeRequest(GetAnalyticsDevicesParam param) async { + try { + final projectUuid = await ProjectManager.getProjectUUID(); + + final response = await _httpService.get( + path: + '/projects/$projectUuid/communities/${param.communityUuid}/spaces/${param.spaceUuid}/devices', + queryParameters: { + 'requestType': param.requestType.name, + 'communityUuid': param.communityUuid, + 'spaceUuid': param.spaceUuid, + 'productType': param.deviceTypes.first, + }, + expectedResponseModel: (response) { + final json = response as Map; + final dailyData = json['data'] as List? ?? []; + + final result = dailyData.map( + (json) => AnalyticsDevice.fromJson(json as Map), + ); + return result.toList(); + }, + ); + return response; + } catch (e) { + rethrow; + } + } +}