Merge branch 'dev' into 1511-occupancy-heat-map-tooltip

This commit is contained in:
Faris Armoush
2025-05-14 12:05:34 +03:00
committed by GitHub
9 changed files with 64 additions and 49 deletions

View File

@ -1,22 +1,28 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class OccupancyHeatMapModel extends Equatable { class OccupancyHeatMapModel extends Equatable {
final DateTime date; final String uuid;
final int occupancy; final DateTime eventDate;
final int countTotalPresenceDetected;
const OccupancyHeatMapModel({ const OccupancyHeatMapModel({
required this.date, required this.uuid,
required this.occupancy, required this.eventDate,
required this.countTotalPresenceDetected,
}); });
factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) { factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) {
return OccupancyHeatMapModel( return OccupancyHeatMapModel(
date: DateTime.parse(json['date'] as String), uuid: json['uuid'] as String? ?? '',
occupancy: json['occupancy'] as int, eventDate: DateTime.parse(
json['event_date'] as String? ?? '${DateTime.now()}',
),
countTotalPresenceDetected: json['count_total_presence_detected'] as int? ?? 0,
); );
} }
@override @override
List<Object?> get props => [date, occupancy]; List<Object?> get props => [uuid, eventDate, countTotalPresenceDetected];
} }

View File

@ -14,7 +14,7 @@ import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_he
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_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/energy_consumption_per_device/fake_energy_consumption_per_device_service.dart';
import 'package:syncrow_web/pages/analytics/services/occupacy/fake_occupacy_service.dart'; import 'package:syncrow_web/pages/analytics/services/occupacy/fake_occupacy_service.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/fake_occupancy_heat_map_service.dart'; import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_service.dart';
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart'; import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart';
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart'; import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart';
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/remote_total_energy_consumption_service.dart'; import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/remote_total_energy_consumption_service.dart';
@ -60,7 +60,9 @@ class AnalyticsPage extends StatelessWidget {
), ),
BlocProvider(create: (context) => OccupancyBloc(FakeOccupacyService())), BlocProvider(create: (context) => OccupancyBloc(FakeOccupacyService())),
BlocProvider( BlocProvider(
create: (context) => OccupancyHeatMapBloc(FakeOccupancyHeatMapService()), create: (context) => OccupancyHeatMapBloc(
RemoteOccupancyHeatMapService(HTTPService()),
),
), ),
BlocProvider(create: (context) => AnalyticsDatePickerBloc()), BlocProvider(create: (context) => AnalyticsDatePickerBloc()),
], ],

View File

@ -35,8 +35,7 @@ abstract final class FetchOccupancyDataHelper {
context.read<OccupancyHeatMapBloc>().add( context.read<OccupancyHeatMapBloc>().add(
LoadOccupancyHeatMapEvent( LoadOccupancyHeatMapEvent(
GetOccupancyHeatMapParam( GetOccupancyHeatMapParam(
spaceId: spaceId, spaceUuid: spaceId,
communityId: communityId,
year: datePickerState.yearlyDate, year: datePickerState.yearlyDate,
), ),
), ),

View File

@ -15,8 +15,9 @@ class OccupancyHeatMap extends StatelessWidget {
static const _cellSize = 16.0; static const _cellSize = 16.0;
static const _totalWeeks = 53; static const _totalWeeks = 53;
int get _maxValue => int get _maxValue => heatMapData.isNotEmpty
heatMapData.isNotEmpty ? heatMapData.values.reduce(math.max) : 0; ? heatMapData.keys.map((key) => heatMapData[key] ?? 0).reduce(math.max)
: 0;
DateTime _getStartingDate() { DateTime _getStartingDate() {
final jan1 = DateTime(DateTime.now().year, 1, 1); final jan1 = DateTime(DateTime.now().year, 1, 1);

View File

@ -68,7 +68,10 @@ class OccupancyHeatMapBox extends StatelessWidget {
Expanded( Expanded(
child: OccupancyHeatMap( child: OccupancyHeatMap(
heatMapData: state.heatMapData.asMap().map( heatMapData: state.heatMapData.asMap().map(
(_, value) => MapEntry(value.date, value.occupancy), (_, value) => MapEntry(
value.eventDate,
value.countTotalPresenceDetected,
),
), ),
), ),
), ),

View File

@ -1,19 +1,13 @@
class GetOccupancyHeatMapParam { class GetOccupancyHeatMapParam {
final DateTime year; final DateTime year;
final String communityId; final String spaceUuid;
final String spaceId;
const GetOccupancyHeatMapParam({ const GetOccupancyHeatMapParam({
required this.year, required this.year,
required this.communityId, required this.spaceUuid,
required this.spaceId,
}); });
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {'year': year.year};
'year': year.toIso8601String(),
'communityId': communityId,
'spaceId': spaceId,
};
} }
} }

View File

@ -1,25 +0,0 @@
import 'package:syncrow_web/pages/analytics/models/occupancy_heat_map_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/occupancy_heat_map_service.dart';
class FakeOccupancyHeatMapService implements OccupancyHeatMapService {
@override
Future<List<OccupancyHeatMapModel>> load(GetOccupancyHeatMapParam param) {
return Future.delayed(const Duration(milliseconds: 200), () {
final now = DateTime.now();
final startOfYear = DateTime(now.year, 1, 1);
final endOfYear = DateTime(now.year, 12, 31);
final daysInYear = endOfYear.difference(startOfYear).inDays + 1;
final List<OccupancyHeatMapModel> data = List.generate(
daysInYear,
(index) => OccupancyHeatMapModel(
date: startOfYear.add(Duration(days: index)),
occupancy: ((index + 1) * 10) % 100,
),
);
return data;
});
}
}

View File

@ -0,0 +1,35 @@
import 'package:syncrow_web/pages/analytics/models/occupancy_heat_map_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/occupancy_heat_map_service.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemoteOccupancyHeatMapService implements OccupancyHeatMapService {
const RemoteOccupancyHeatMapService(this._httpService);
final HTTPService _httpService;
@override
Future<List<OccupancyHeatMapModel>> load(GetOccupancyHeatMapParam param) async {
try {
final response = await _httpService.get(
path: '/occupancy/heat-map/space/${param.spaceUuid}',
showServerMessage: true,
queryParameters: param.toJson(),
expectedResponseModel: (response) {
final json = response as Map<String, dynamic>;
final dailyData = json['data'] as List<dynamic>? ?? <dynamic>[];
final result = dailyData.map(
(json) => OccupancyHeatMapModel.fromJson(json as Map<String, dynamic>),
);
return result.toList();
},
);
return response;
} catch (e) {
throw Exception('Failed to load total energy consumption:');
}
}
}

View File

@ -12,7 +12,7 @@ class AnalyticsErrorWidget extends StatelessWidget {
return Visibility( return Visibility(
visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false), visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false),
child: Text( child: Text(
'$errorMessage ?? "Something went wrong"', errorMessage ?? 'Something went wrong',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(