diff --git a/lib/pages/analytics/models/air_quality_data_model.dart b/lib/pages/analytics/models/air_quality_data_model.dart index d65f1418..2eab2ddb 100644 --- a/lib/pages/analytics/models/air_quality_data_model.dart +++ b/lib/pages/analytics/models/air_quality_data_model.dart @@ -1,24 +1,24 @@ +import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class AirQualityDataModel { +class AirQualityDataModel extends Equatable { const AirQualityDataModel({ required this.date, - this.good, - this.moderate, - this.poor, - this.unhealthy, - this.severe, - this.hazardous, + required this.data, }); final DateTime date; - final double? good; - final double? moderate; - final double? poor; - final double? unhealthy; - final double? severe; - final double? hazardous; + final List data; + + factory AirQualityDataModel.fromJson(Map json) { + return AirQualityDataModel( + date: DateTime.parse(json['date'] as String), + data: (json['data'] as List) + .map((e) => AirQualityPercentageData.fromJson(e as Map)) + .toList(), + ); + } static final Map metricColors = { 'good': ColorsManager.goodGreen.withValues(alpha: 0.7), @@ -28,4 +28,30 @@ class AirQualityDataModel { 'severe': ColorsManager.severePink.withValues(alpha: 0.7), 'hazardous': ColorsManager.hazardousPurple.withValues(alpha: 0.7), }; + + @override + List get props => [date, data]; +} + +class AirQualityPercentageData extends Equatable { + const AirQualityPercentageData({ + required this.type, + required this.name, + required this.percentage, + }); + + final String type; + final String name; + final double percentage; + + factory AirQualityPercentageData.fromJson(Map json) { + return AirQualityPercentageData( + type: json['type'] as String? ?? '', + name: json['name'] as String? ?? '', + percentage: (json['percentage'] as num?)?.toDouble() ?? 0, + ); + } + + @override + List get props => [type, name, percentage]; } diff --git a/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart b/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart index a81724a2..fb7e2352 100644 --- a/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart +++ b/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart @@ -1,6 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart'; import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart'; import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart'; @@ -9,13 +10,14 @@ part 'air_quality_distribution_state.dart'; class AirQualityDistributionBloc extends Bloc { - final AirQualityDistributionService _service; + final AirQualityDistributionService _aqiDistributionService; AirQualityDistributionBloc( - this._service, + this._aqiDistributionService, ) : super(const AirQualityDistributionState()) { on(_onLoadAirQualityDistribution); on(_onClearAirQualityDistribution); + on(_onUpdateAqiTypeEvent); } Future _onLoadAirQualityDistribution( @@ -23,16 +25,15 @@ class AirQualityDistributionBloc Emitter emit, ) async { try { - emit( - const AirQualityDistributionState( - status: AirQualityDistributionStatus.loading, - ), + emit(state.copyWith(status: AirQualityDistributionStatus.loading)); + final result = await _aqiDistributionService.getAirQualityDistribution( + event.param, ); - final result = await _service.getAirQualityDistribution(event.param); emit( - AirQualityDistributionState( + state.copyWith( status: AirQualityDistributionStatus.success, chartData: result, + filteredChartData: _arrangeChartDataByType(result, state.selectedAqiType), ), ); } catch (e) { @@ -40,6 +41,7 @@ class AirQualityDistributionBloc AirQualityDistributionState( status: AirQualityDistributionStatus.failure, errorMessage: e.toString(), + selectedAqiType: state.selectedAqiType, ), ); } @@ -51,4 +53,29 @@ class AirQualityDistributionBloc ) async { emit(const AirQualityDistributionState()); } + + void _onUpdateAqiTypeEvent( + UpdateAqiTypeEvent event, + Emitter emit, + ) { + emit( + state.copyWith( + selectedAqiType: event.aqiType, + filteredChartData: _arrangeChartDataByType(state.chartData, event.aqiType), + ), + ); + } + + List _arrangeChartDataByType( + List data, + AqiType aqiType, + ) { + final filteredData = data.map( + (data) => AirQualityDataModel( + date: data.date, + data: data.data.where((value) => value.type == aqiType.code).toList(), + ), + ); + return filteredData.toList(); + } } diff --git a/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_event.dart b/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_event.dart index 2e1d291f..b91dafe5 100644 --- a/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_event.dart +++ b/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_event.dart @@ -16,6 +16,15 @@ final class LoadAirQualityDistribution extends AirQualityDistributionEvent { List get props => [param]; } +final class UpdateAqiTypeEvent extends AirQualityDistributionEvent { + const UpdateAqiTypeEvent(this.aqiType); + + final AqiType aqiType; + + @override + List get props => [aqiType]; +} + final class ClearAirQualityDistribution extends AirQualityDistributionEvent { const ClearAirQualityDistribution(); } diff --git a/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_state.dart b/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_state.dart index 0db95e2d..65665882 100644 --- a/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_state.dart +++ b/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_state.dart @@ -8,16 +8,36 @@ enum AirQualityDistributionStatus { } class AirQualityDistributionState extends Equatable { - final AirQualityDistributionStatus status; - final List chartData; - final String? errorMessage; - const AirQualityDistributionState({ this.status = AirQualityDistributionStatus.initial, this.chartData = const [], + this.filteredChartData = const [], this.errorMessage, + this.selectedAqiType = AqiType.aqi, }); + final AirQualityDistributionStatus status; + final List chartData; + final List filteredChartData; + final String? errorMessage; + final AqiType selectedAqiType; + + AirQualityDistributionState copyWith({ + AirQualityDistributionStatus? status, + List? chartData, + List? filteredChartData, + String? errorMessage, + AqiType? selectedAqiType, + }) { + return AirQualityDistributionState( + status: status ?? this.status, + chartData: chartData ?? this.chartData, + filteredChartData: filteredChartData ?? this.filteredChartData, + errorMessage: errorMessage ?? this.errorMessage, + selectedAqiType: selectedAqiType ?? this.selectedAqiType, + ); + } + @override - List get props => [status, chartData, errorMessage]; + List get props => [status, chartData, errorMessage, selectedAqiType]; } diff --git a/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart b/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart index 1919f518..e212dedf 100644 --- a/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart +++ b/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart @@ -14,8 +14,10 @@ abstract final class FetchAirQualityDataHelper { static void loadAirQualityData( BuildContext context, { + required DateTime date, required String communityUuid, required String spaceUuid, + bool shouldFetchAnalyticsDevices = true, }) { final date = context.read().state.monthlyDate; loadAnalyticsDevices( diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart.dart index 89b6dd1d..373e36ca 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart.dart @@ -16,6 +16,11 @@ class AqiDistributionChart extends StatelessWidget { @override Widget build(BuildContext context) { + final sortedData = List.from(chartData) + ..sort( + (a, b) => a.date.compareTo(b.date), + ); + return BarChart( BarChartData( maxY: 100.1, @@ -25,45 +30,29 @@ class AqiDistributionChart extends StatelessWidget { borderData: EnergyManagementChartsHelper.borderData(), barTouchData: _barTouchData(context), titlesData: _titlesData(context), - barGroups: _buildBarGroups(), + barGroups: _buildBarGroups(sortedData), ), duration: Duration.zero, ); } - List _buildBarGroups() { - return List.generate(chartData.length, (index) { - final data = chartData[index]; + List _buildBarGroups(List sortedData) { + return List.generate(sortedData.length, (index) { + final data = sortedData[index]; final stackItems = []; double currentY = 0; bool isFirstElement = true; - if (data.good != null) { - stackItems.add( - BarChartRodData( - fromY: currentY, - toY: currentY + data.good!, - color: AirQualityDataModel.metricColors['good']!, - borderRadius: isFirstElement - ? const BorderRadius.only( - topLeft: Radius.circular(22), - topRight: Radius.circular(22), - ) - // ignore: dead_code - : _barBorderRadius, - width: _barWidth, - ), - ); - currentY += data.good! + _rodStackItemsSpacing; - isFirstElement = false; - } + // Sort data by type to ensure consistent order + final sortedPercentageData = List.from(data.data) + ..sort((a, b) => a.type.compareTo(b.type)); - if (data.moderate != null) { + for (final percentageData in sortedPercentageData) { stackItems.add( BarChartRodData( fromY: currentY, - toY: currentY + data.moderate!, - color: AirQualityDataModel.metricColors['moderate']!, + toY: currentY + percentageData.percentage , + color: AirQualityDataModel.metricColors[percentageData.name]!, borderRadius: isFirstElement ? const BorderRadius.only( topLeft: Radius.circular(22), @@ -73,83 +62,7 @@ class AqiDistributionChart extends StatelessWidget { width: _barWidth, ), ); - currentY += data.moderate! + _rodStackItemsSpacing; - isFirstElement = false; - } - - if (data.poor != null) { - stackItems.add( - BarChartRodData( - fromY: currentY, - toY: currentY + data.poor!, - color: AirQualityDataModel.metricColors['poor']!, - borderRadius: isFirstElement - ? const BorderRadius.only( - topLeft: Radius.circular(22), - topRight: Radius.circular(22), - ) - : _barBorderRadius, - width: _barWidth, - ), - ); - currentY += data.poor! + _rodStackItemsSpacing; - isFirstElement = false; - } - - if (data.unhealthy != null) { - stackItems.add( - BarChartRodData( - fromY: currentY, - toY: currentY + data.unhealthy!, - color: AirQualityDataModel.metricColors['unhealthy']!, - borderRadius: isFirstElement - ? const BorderRadius.only( - topLeft: Radius.circular(22), - topRight: Radius.circular(22), - ) - : _barBorderRadius, - width: _barWidth, - ), - ); - currentY += data.unhealthy! + _rodStackItemsSpacing; - isFirstElement = false; - } - - if (data.severe != null) { - stackItems.add( - BarChartRodData( - fromY: currentY, - toY: currentY + data.severe!, - color: AirQualityDataModel.metricColors['severe']!, - borderRadius: isFirstElement - ? const BorderRadius.only( - topLeft: Radius.circular(22), - topRight: Radius.circular(22), - ) - : _barBorderRadius, - width: _barWidth, - ), - ); - currentY += data.severe! + _rodStackItemsSpacing; - isFirstElement = false; - } - - if (data.hazardous != null) { - stackItems.add( - BarChartRodData( - fromY: currentY, - toY: currentY + data.hazardous!, - color: AirQualityDataModel.metricColors['hazardous']!, - borderRadius: isFirstElement - ? const BorderRadius.only( - topLeft: Radius.circular(22), - topRight: Radius.circular(22), - ) - : _barBorderRadius, - width: _barWidth, - ), - ); - currentY += data.hazardous! + _rodStackItemsSpacing; + currentY += percentageData.percentage + _rodStackItemsSpacing; isFirstElement = false; } @@ -180,44 +93,14 @@ class AqiDistributionChart extends StatelessWidget { fontSize: 12, ); - if (data.good != null) { - children.add(TextSpan( - text: '\nGOOD: ${data.good!.toStringAsFixed(1)}%', - style: textStyle, - )); - } + // Sort data by type to ensure consistent order + final sortedPercentageData = List.from(data.data) + ..sort((a, b) => a.type.compareTo(b.type)); - if (data.moderate != null) { + for (final percentageData in sortedPercentageData) { children.add(TextSpan( - text: '\nMODERATE: ${data.moderate!.toStringAsFixed(1)}%', - style: textStyle, - )); - } - - if (data.poor != null) { - children.add(TextSpan( - text: '\nPOOR: ${data.poor!.toStringAsFixed(1)}%', - style: textStyle, - )); - } - - if (data.unhealthy != null) { - children.add(TextSpan( - text: '\nUNHEALTHY: ${data.unhealthy!.toStringAsFixed(1)}%', - style: textStyle, - )); - } - - if (data.severe != null) { - children.add(TextSpan( - text: '\nSEVERE: ${data.severe!.toStringAsFixed(1)}%', - style: textStyle, - )); - } - - if (data.hazardous != null) { - children.add(TextSpan( - text: '\nHAZARDOUS: ${data.hazardous!.toStringAsFixed(1)}%', + text: + '\n${percentageData.type.toUpperCase()}: ${percentageData.percentage.toStringAsFixed(1)}%', style: textStyle, )); } diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart index 8347a15b..8a57fe0b 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart @@ -32,7 +32,9 @@ class AqiDistributionChartBox extends StatelessWidget { const SizedBox(height: 10), const Divider(), const SizedBox(height: 20), - Expanded(child: AqiDistributionChart(chartData: state.chartData)), + Expanded( + child: AqiDistributionChart(chartData: state.filteredChartData), + ), ], ), ); diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart index 5045316b..e32043c5 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart'; import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart'; @@ -16,7 +18,7 @@ class AqiDistributionChartTitle extends StatelessWidget { const Expanded( flex: 3, child: FittedBox( - fit: BoxFit.scaleDown, + fit: BoxFit.scaleDown, alignment: AlignmentDirectional.centerStart, child: ChartTitle( title: Text('Distribution over Air Quality Index'), @@ -27,7 +29,13 @@ class AqiDistributionChartTitle extends StatelessWidget { alignment: AlignmentDirectional.centerEnd, fit: BoxFit.scaleDown, child: AqiTypeDropdown( - onChanged: (value) {}, + onChanged: (value) { + if (value != null) { + context + .read() + .add(UpdateAqiTypeEvent(value)); + } + }, ), ), ], diff --git a/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart b/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart index fefb7a9c..6548c696 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart @@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_title.dart'; -import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -27,18 +26,8 @@ class RangeOfAqiChartBox extends StatelessWidget { AnalyticsErrorWidget(state.errorMessage), const SizedBox(height: 10), ], - GestureDetector( - onTap: () { - context.read().add(LoadRangeOfAqiEvent( - GetRangeOfAqiParam( - spaceUuid: '123', - date: DateTime.now().subtract(const Duration(days: 30)), - ), - )); - }, - child: RangeOfAqiChartTitle( - isLoading: state.status == RangeOfAqiStatus.loading, - ), + RangeOfAqiChartTitle( + isLoading: state.status == RangeOfAqiStatus.loading, ), const SizedBox(height: 10), const Divider(), diff --git a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart index dc3b1c5e..5d62029f 100644 --- a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart +++ b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.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/strategies/analytics_data_loading_strategy.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; @@ -39,6 +40,7 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg context, communityUuid: community.uuid, spaceUuid: space.uuid ?? '', + date: context.read().state.monthlyDate, ); } diff --git a/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart b/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart index 5e9e347a..f6197e46 100644 --- a/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart +++ b/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.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_tab/analytics_tab_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart'; @@ -56,33 +57,16 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget { const Spacer(), Visibility( key: ValueKey(selectedTab), - visible: selectedTab == AnalyticsPageTab.energyManagement, + visible: selectedTab == AnalyticsPageTab.energyManagement || + selectedTab == AnalyticsPageTab.airQuality, child: Expanded( flex: 2, child: FittedBox( fit: BoxFit.scaleDown, alignment: AlignmentDirectional.centerEnd, child: AnalyticsDateFilterButton( - onDateSelected: (DateTime value) { - context.read().add( - UpdateAnalyticsDatePickerEvent(montlyDate: value), - ); - - final spaceTreeState = - context.read().state; - if (spaceTreeState.selectedSpaces.isNotEmpty) { - FetchEnergyManagementDataHelper - .loadEnergyManagementData( - context, - shouldFetchAnalyticsDevices: false, - selectedDate: value, - communityId: - spaceTreeState.selectedCommunities.firstOrNull ?? - '', - spaceId: - spaceTreeState.selectedSpaces.firstOrNull ?? '', - ); - } + onDateSelected: (value) { + _onDateChanged(context, value, selectedTab); }, selectedDate: context .watch() @@ -112,4 +96,73 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget { child: child, ); } + + void _onDateChanged( + BuildContext context, + DateTime date, + AnalyticsPageTab selectedTab, + ) { + context.read().add( + UpdateAnalyticsDatePickerEvent(montlyDate: date), + ); + + final spaceTreeState = context.read().state; + final communities = spaceTreeState.selectedCommunities; + final spaces = spaceTreeState.selectedSpaces; + if (spaceTreeState.selectedSpaces.isNotEmpty) { + switch (selectedTab) { + case AnalyticsPageTab.energyManagement: + _onEnergyManagementDateChanged( + context, + date: date, + communityUuid: communities.firstOrNull ?? '', + spaceUuid: spaces.firstOrNull ?? '', + ); + break; + case AnalyticsPageTab.airQuality: + _onAirQualityDateChanged( + context, + date: date, + communityUuid: communities.firstOrNull ?? '', + spaceUuid: spaces.firstOrNull ?? '', + ); + default: + break; + } + } + } + + void _onEnergyManagementDateChanged( + BuildContext context, { + required DateTime date, + required String communityUuid, + required String spaceUuid, + }) { + context.read().add( + UpdateAnalyticsDatePickerEvent(montlyDate: date), + ); + + FetchEnergyManagementDataHelper.loadEnergyManagementData( + context, + shouldFetchAnalyticsDevices: false, + selectedDate: date, + communityId: communityUuid, + spaceId: spaceUuid, + ); + } + + void _onAirQualityDateChanged( + BuildContext context, { + required DateTime date, + required String communityUuid, + required String spaceUuid, + }) { + FetchAirQualityDataHelper.loadAirQualityData( + context, + date: date, + communityUuid: communityUuid, + spaceUuid: spaceUuid, + shouldFetchAnalyticsDevices: false, + ); + } } diff --git a/lib/pages/analytics/services/air_quality_distribution/fake_air_quality_distribution_service.dart b/lib/pages/analytics/services/air_quality_distribution/fake_air_quality_distribution_service.dart index 264addab..e0023f53 100644 --- a/lib/pages/analytics/services/air_quality_distribution/fake_air_quality_distribution_service.dart +++ b/lib/pages/analytics/services/air_quality_distribution/fake_air_quality_distribution_service.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart'; import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart'; import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart'; @@ -19,30 +20,56 @@ class FakeAirQualityDistributionService implements AirQualityDistributionService final values = _generateRandomPercentages(); final nullMask = List.generate(6, (_) => _shouldBeNull()); - // If all values are null, force at least one to be non-null if (nullMask.every((isNull) => isNull)) { nullMask[_random.nextInt(6)] = false; } - // Redistribute percentages among non-null values final nonNullValues = _redistributePercentages(values, nullMask); return AirQualityDataModel( date: date, - good: nullMask[0] ? null : nonNullValues[0], - moderate: nullMask[1] ? null : nonNullValues[1], - poor: nullMask[2] ? null : nonNullValues[2], - unhealthy: nullMask[3] ? null : nonNullValues[3], - severe: nullMask[4] ? null : nonNullValues[4], - hazardous: nullMask[5] ? null : nonNullValues[5], + data: [ + AirQualityPercentageData( + type: AqiType.aqi.code, + percentage: nonNullValues[0], + name: 'good', + ), + AirQualityPercentageData( + name: 'moderate', + type: AqiType.co2.code, + percentage: nonNullValues[1], + ), + AirQualityPercentageData( + name: 'poor', + percentage: nonNullValues[2], + type: AqiType.hcho.code, + + ), + AirQualityPercentageData( + name: 'unhealthy', + percentage: nonNullValues[3], + type: AqiType.pm10.code, + ), + AirQualityPercentageData( + name: 'severe', + type: AqiType.pm25.code, + percentage: nonNullValues[4], + ), + AirQualityPercentageData( + name: 'hazardous', + percentage: nonNullValues[5], + type: AqiType.co2.code, + ), + ], ); }), ); } List _redistributePercentages( - List originalValues, List nullMask) { - // Calculate total of non-null values + List originalValues, + List nullMask, + ) { double nonNullSum = 0; for (int i = 0; i < originalValues.length; i++) { if (!nullMask[i]) { @@ -50,7 +77,6 @@ class FakeAirQualityDistributionService implements AirQualityDistributionService } } - // Redistribute percentages to maintain 100% total return List.generate(originalValues.length, (i) { if (nullMask[i]) return 0; return (originalValues[i] / nonNullSum * 100).roundToDouble(); diff --git a/lib/pages/analytics/services/air_quality_distribution/remote_air_quality_distribution_service.dart b/lib/pages/analytics/services/air_quality_distribution/remote_air_quality_distribution_service.dart new file mode 100644 index 00000000..dcf00600 --- /dev/null +++ b/lib/pages/analytics/services/air_quality_distribution/remote_air_quality_distribution_service.dart @@ -0,0 +1,36 @@ +import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart'; +import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart'; +import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; + +class RemoteAirQualityDistributionService implements AirQualityDistributionService { + RemoteAirQualityDistributionService(this._httpService); + + final HTTPService _httpService; + + @override + Future> getAirQualityDistribution( + GetAirQualityDistributionParam param, + ) async { + try { + final response = await _httpService.get( + path: 'endpoint', + queryParameters: { + 'spaceUuid': param.spaceUuid, + 'date': param.date.toIso8601String(), + }, + expectedResponseModel: (data) { + final json = data as Map? ?? {}; + final mappedData = json['data'] as List? ?? []; + return mappedData.map((e) { + final jsonData = e as Map; + return AirQualityDataModel.fromJson(jsonData); + }).toList(); + }, + ); + return response; + } catch (e) { + throw Exception('Failed to load energy consumption per phase: $e'); + } + } +}