mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 15:17:31 +00:00
Compare commits
7 Commits
SP-1509-FE
...
SP-1506-FE
Author | SHA1 | Date | |
---|---|---|---|
c07b53107e | |||
5279020d08 | |||
da481536c4 | |||
f21366268a | |||
c3aef736fd | |||
d45ff262c7 | |||
a9d6c6f4ee |
@ -1,27 +1,66 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class PhasesEnergyConsumption extends Equatable {
|
||||
final int month;
|
||||
final double phaseA;
|
||||
final double phaseB;
|
||||
final double phaseC;
|
||||
final String uuid;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
final String deviceUuid;
|
||||
final DateTime date;
|
||||
final double energyConsumedKw;
|
||||
final double energyConsumedA;
|
||||
final double energyConsumedB;
|
||||
final double energyConsumedC;
|
||||
|
||||
const PhasesEnergyConsumption({
|
||||
required this.month,
|
||||
required this.phaseA,
|
||||
required this.phaseB,
|
||||
required this.phaseC,
|
||||
required this.uuid,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.deviceUuid,
|
||||
required this.date,
|
||||
required this.energyConsumedKw,
|
||||
required this.energyConsumedA,
|
||||
required this.energyConsumedB,
|
||||
required this.energyConsumedC,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [month, phaseA, phaseB, phaseC];
|
||||
List<Object?> get props => [
|
||||
uuid,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
deviceUuid,
|
||||
date,
|
||||
energyConsumedKw,
|
||||
energyConsumedA,
|
||||
energyConsumedB,
|
||||
energyConsumedC,
|
||||
];
|
||||
|
||||
factory PhasesEnergyConsumption.fromJson(Map<String, dynamic> json) {
|
||||
return PhasesEnergyConsumption(
|
||||
month: json['month'] as int,
|
||||
phaseA: (json['phaseA'] as num).toDouble(),
|
||||
phaseB: (json['phaseB'] as num).toDouble(),
|
||||
phaseC: (json['phaseC'] as num).toDouble(),
|
||||
uuid: json['uuid'] as String,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||
deviceUuid: json['deviceUuid'] as String,
|
||||
date: DateTime.parse(json['date'] as String),
|
||||
energyConsumedKw: double.parse(json['energyConsumedKw']),
|
||||
energyConsumedA: double.parse(json['energyConsumedA']),
|
||||
energyConsumedB: double.parse(json['energyConsumedB']),
|
||||
energyConsumedC: double.parse(json['energyConsumedC']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'uuid': uuid,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'updatedAt': updatedAt.toIso8601String(),
|
||||
'deviceUuid': deviceUuid,
|
||||
'date': date.toIso8601String().split('T')[0],
|
||||
'energyConsumedKw': energyConsumedKw.toString(),
|
||||
'energyConsumedA': energyConsumedA.toString(),
|
||||
'energyConsumedB': energyConsumedB.toString(),
|
||||
'energyConsumedC': energyConsumedC.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,8 @@ import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_he
|
||||
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/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/remote_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/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';
|
||||
@ -57,12 +57,12 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => EnergyConsumptionByPhasesBloc(
|
||||
FakeEnergyConsumptionByPhasesService(),
|
||||
RemoteEnergyConsumptionByPhasesService(_httpService),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => EnergyConsumptionPerDeviceBloc(
|
||||
FakeEnergyConsumptionPerDeviceService(),
|
||||
RemoteEnergyConsumptionPerDeviceService(_httpService),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
|
@ -1,20 +0,0 @@
|
||||
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
||||
|
||||
abstract final class EnergyConsumptionByPhasesChartHelper {
|
||||
const EnergyConsumptionByPhasesChartHelper._();
|
||||
|
||||
static const fakeData = <PhasesEnergyConsumption>[
|
||||
PhasesEnergyConsumption(month: 1, phaseA: 200, phaseB: 300, phaseC: 400),
|
||||
PhasesEnergyConsumption(month: 2, phaseA: 300, phaseB: 400, phaseC: 500),
|
||||
PhasesEnergyConsumption(month: 3, phaseA: 400, phaseB: 500, phaseC: 600),
|
||||
PhasesEnergyConsumption(month: 4, phaseA: 100, phaseB: 100, phaseC: 100),
|
||||
PhasesEnergyConsumption(month: 5, phaseA: 300, phaseB: 400, phaseC: 500),
|
||||
PhasesEnergyConsumption(month: 6, phaseA: 300, phaseB: 100, phaseC: 400),
|
||||
PhasesEnergyConsumption(month: 7, phaseA: 300, phaseB: 100, phaseC: 400),
|
||||
PhasesEnergyConsumption(month: 8, phaseA: 500, phaseB: 100, phaseC: 100),
|
||||
PhasesEnergyConsumption(month: 9, phaseA: 500, phaseB: 100, phaseC: 200),
|
||||
PhasesEnergyConsumption(month: 10, phaseA: 100, phaseB: 50, phaseC: 50),
|
||||
PhasesEnergyConsumption(month: 11, phaseA: 600, phaseB: 750, phaseC: 130),
|
||||
PhasesEnergyConsumption(month: 12, phaseA: 100, phaseB: 80, phaseC: 100),
|
||||
];
|
||||
}
|
@ -34,26 +34,44 @@ abstract final class FetchEnergyManagementDataHelper {
|
||||
|
||||
final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
|
||||
final selectedDate0 = selectedDate ?? datePickerState.monthlyDate;
|
||||
loadAnalyticsDevices(context, communityUuid: communityId, spaceUuid: spaceId);
|
||||
loadAnalyticsDevices(
|
||||
context,
|
||||
communityUuid: communityId,
|
||||
spaceUuid: spaceId,
|
||||
selectedDate: selectedDate0,
|
||||
);
|
||||
loadTotalEnergyConsumption(
|
||||
context,
|
||||
selectedDate: selectedDate0,
|
||||
communityId: communityId,
|
||||
spaceId: spaceId,
|
||||
);
|
||||
loadEnergyConsumptionByPhases(context, selectedDate: selectedDate);
|
||||
loadEnergyConsumptionPerDevice(context);
|
||||
final selectedDevice = getSelectedDevice(context);
|
||||
if (selectedDevice case final AnalyticsDevice device) {
|
||||
loadEnergyConsumptionByPhases(
|
||||
context,
|
||||
powerClampUuid: device.uuid,
|
||||
selectedDate: selectedDate0,
|
||||
);
|
||||
}
|
||||
loadEnergyConsumptionPerDevice(
|
||||
context,
|
||||
communityId: communityId,
|
||||
spaceId: spaceId,
|
||||
selectedDate: selectedDate0,
|
||||
);
|
||||
loadRealtimeDeviceChanges(context);
|
||||
loadPowerClampInfo(context);
|
||||
}
|
||||
|
||||
static void loadEnergyConsumptionByPhases(
|
||||
BuildContext context, {
|
||||
required String powerClampUuid,
|
||||
DateTime? selectedDate,
|
||||
}) {
|
||||
final param = GetEnergyConsumptionByPhasesParam(
|
||||
startDate: selectedDate,
|
||||
spaceId: '',
|
||||
date: selectedDate,
|
||||
powerClampUuid: powerClampUuid,
|
||||
);
|
||||
context.read<EnergyConsumptionByPhasesBloc>().add(
|
||||
LoadEnergyConsumptionByPhasesEvent(param: param),
|
||||
@ -76,10 +94,19 @@ abstract final class FetchEnergyManagementDataHelper {
|
||||
);
|
||||
}
|
||||
|
||||
static void loadEnergyConsumptionPerDevice(BuildContext context) {
|
||||
const param = GetEnergyConsumptionPerDeviceParam();
|
||||
static void loadEnergyConsumptionPerDevice(
|
||||
BuildContext context, {
|
||||
DateTime? selectedDate,
|
||||
required String communityId,
|
||||
required String spaceId,
|
||||
}) {
|
||||
final param = GetEnergyConsumptionPerDeviceParam(
|
||||
spaceId: spaceId,
|
||||
communityId: communityId,
|
||||
monthDate: selectedDate,
|
||||
);
|
||||
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
||||
const LoadEnergyConsumptionPerDeviceEvent(param),
|
||||
LoadEnergyConsumptionPerDeviceEvent(param),
|
||||
);
|
||||
}
|
||||
|
||||
@ -107,6 +134,7 @@ abstract final class FetchEnergyManagementDataHelper {
|
||||
BuildContext context, {
|
||||
required String communityUuid,
|
||||
required String spaceUuid,
|
||||
required DateTime selectedDate,
|
||||
}) {
|
||||
context.read<AnalyticsDevicesBloc>().add(
|
||||
LoadAnalyticsDevicesEvent(
|
||||
@ -114,6 +142,11 @@ abstract final class FetchEnergyManagementDataHelper {
|
||||
context.read<PowerClampInfoBloc>().add(
|
||||
LoadPowerClampInfoEvent(device.uuid),
|
||||
);
|
||||
loadEnergyConsumptionByPhases(
|
||||
context,
|
||||
powerClampUuid: device.uuid,
|
||||
selectedDate: selectedDate,
|
||||
);
|
||||
context.read<RealtimeDeviceChangesBloc>().add(
|
||||
RealtimeDeviceChangesStarted(device.uuid),
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/analytics/helpers/get_month_name_from_int.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
@ -31,21 +31,25 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
color: ColorsManager.vividBlue.withValues(alpha: 0.1),
|
||||
toY: data.phaseA + data.phaseB + data.phaseC,
|
||||
toY: data.energyConsumedA +
|
||||
data.energyConsumedB +
|
||||
data.energyConsumedC,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(
|
||||
0,
|
||||
data.phaseA,
|
||||
data.energyConsumedA,
|
||||
ColorsManager.vividBlue.withValues(alpha: 0.8),
|
||||
),
|
||||
BarChartRodStackItem(
|
||||
data.phaseA,
|
||||
data.phaseA + data.phaseB,
|
||||
data.energyConsumedA,
|
||||
data.energyConsumedA + data.energyConsumedB,
|
||||
ColorsManager.vividBlue.withValues(alpha: 0.4),
|
||||
),
|
||||
BarChartRodStackItem(
|
||||
data.phaseA + data.phaseB,
|
||||
data.phaseA + data.phaseB + data.phaseC,
|
||||
data.energyConsumedA + data.energyConsumedB,
|
||||
data.energyConsumedA +
|
||||
data.energyConsumedB +
|
||||
data.energyConsumedC,
|
||||
ColorsManager.vividBlue.withValues(alpha: 0.15),
|
||||
),
|
||||
],
|
||||
@ -91,18 +95,27 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
||||
}) {
|
||||
final data = energyData;
|
||||
|
||||
final month = data[group.x.toInt()].month.getMonthName;
|
||||
final phaseA = data[group.x.toInt()].phaseA;
|
||||
final phaseB = data[group.x.toInt()].phaseB;
|
||||
final phaseC = data[group.x.toInt()].phaseC;
|
||||
final date = DateFormat('dd/MM/yyyy').format(data[group.x.toInt()].date);
|
||||
final phaseA = data[group.x.toInt()].energyConsumedA;
|
||||
final phaseB = data[group.x.toInt()].energyConsumedB;
|
||||
final phaseC = data[group.x.toInt()].energyConsumedC;
|
||||
final total = data[group.x.toInt()].energyConsumedKw;
|
||||
|
||||
return BarTooltipItem(
|
||||
'$month\n',
|
||||
'$date\n',
|
||||
context.textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: 14,
|
||||
),
|
||||
textAlign: TextAlign.start,
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Total: $total\n',
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: 'Phase A: $phaseA\n',
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
@ -144,7 +157,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: (value, _) {
|
||||
final month = energyData[value.toInt()].month.getMonthName;
|
||||
final month = DateFormat('dd/MM').format(energyData[value.toInt()].date);
|
||||
return FittedBox(
|
||||
alignment: AlignmentDirectional.bottomCenter,
|
||||
fit: BoxFit.scaleDown,
|
||||
|
@ -1,6 +1,7 @@
|
||||
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_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/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';
|
||||
@ -11,6 +12,7 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/po
|
||||
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';
|
||||
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.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';
|
||||
@ -132,6 +134,12 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
child: AnalyticsDeviceDropdown(
|
||||
onChanged: (value) {
|
||||
FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases(
|
||||
context,
|
||||
powerClampUuid: value.uuid,
|
||||
selectedDate:
|
||||
context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
|
||||
);
|
||||
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(
|
||||
context,
|
||||
deviceUuid: value.uuid,
|
||||
|
@ -0,0 +1,54 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class HeatMapTooltip extends StatelessWidget {
|
||||
const HeatMapTooltip({
|
||||
required this.date,
|
||||
required this.value,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final DateTime date;
|
||||
final int value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: AlignmentDirectional.topStart,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.grey700,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
DateFormat('MMM d, yyyy').format(date),
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorsManager.whiteColors,
|
||||
),
|
||||
),
|
||||
const Divider(height: 2, thickness: 1),
|
||||
Text(
|
||||
'$value Occupants',
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorsManager.whiteColors,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/heat_map_tooltip.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_painter.dart';
|
||||
|
||||
class InteractiveHeatMap extends StatefulWidget {
|
||||
const InteractiveHeatMap({
|
||||
required this.items,
|
||||
required this.maxValue,
|
||||
required this.cellSize,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<OccupancyPaintItem> items;
|
||||
final int maxValue;
|
||||
final double cellSize;
|
||||
|
||||
@override
|
||||
State<InteractiveHeatMap> createState() => _InteractiveHeatMapState();
|
||||
}
|
||||
|
||||
class _InteractiveHeatMapState extends State<InteractiveHeatMap> {
|
||||
OccupancyPaintItem? _hoveredItem;
|
||||
OverlayEntry? _overlayEntry;
|
||||
final LayerLink _layerLink = LayerLink();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_removeOverlay();
|
||||
_overlayEntry?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _removeOverlay() {
|
||||
_overlayEntry?.remove();
|
||||
_overlayEntry = null;
|
||||
}
|
||||
|
||||
void _showTooltip(OccupancyPaintItem item, Offset localPosition) {
|
||||
_removeOverlay();
|
||||
|
||||
final column = item.index ~/ 7;
|
||||
final row = item.index % 7;
|
||||
final x = column * widget.cellSize;
|
||||
final y = row * widget.cellSize;
|
||||
|
||||
_overlayEntry = OverlayEntry(
|
||||
builder: (context) => Positioned(
|
||||
child: CompositedTransformFollower(
|
||||
link: _layerLink,
|
||||
offset: Offset(x + widget.cellSize, y),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Transform.translate(
|
||||
offset: Offset(-(widget.cellSize * 2.5), -50),
|
||||
child: HeatMapTooltip(date: item.date, value: item.value),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Overlay.of(context).insert(_overlayEntry!);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CompositedTransformTarget(
|
||||
link: _layerLink,
|
||||
child: MouseRegion(
|
||||
onHover: (event) {
|
||||
final column = event.localPosition.dx ~/ widget.cellSize;
|
||||
final row = event.localPosition.dy ~/ widget.cellSize;
|
||||
final index = column * 7 + row;
|
||||
|
||||
if (index >= 0 && index < widget.items.length) {
|
||||
final item = widget.items[index];
|
||||
if (_hoveredItem != item) {
|
||||
setState(() => _hoveredItem = item);
|
||||
_showTooltip(item, event.localPosition);
|
||||
}
|
||||
} else {
|
||||
_removeOverlay();
|
||||
setState(() => _hoveredItem = null);
|
||||
}
|
||||
},
|
||||
onExit: (_) {
|
||||
_removeOverlay();
|
||||
setState(() => _hoveredItem = null);
|
||||
},
|
||||
child: CustomPaint(
|
||||
isComplex: true,
|
||||
size: _painterSize,
|
||||
painter: OccupancyPainter(
|
||||
items: widget.items,
|
||||
maxValue: widget.maxValue,
|
||||
hoveredItem: _hoveredItem,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Size get _painterSize {
|
||||
final height = 7 * widget.cellSize;
|
||||
final width = widget.items.length ~/ 7 * widget.cellSize;
|
||||
return Size(width, height);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import 'dart:math' as math show max;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/interactive_heat_map.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_days.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_gradient.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_months.dart';
|
||||
@ -28,7 +29,7 @@ class OccupancyHeatMap extends StatelessWidget {
|
||||
return List.generate(_totalWeeks * 7, (index) {
|
||||
final date = startDate.add(Duration(days: index));
|
||||
final value = heatMapData[date] ?? 0;
|
||||
return OccupancyPaintItem(index: index, value: value);
|
||||
return OccupancyPaintItem(index: index, value: value, date: date);
|
||||
});
|
||||
}
|
||||
|
||||
@ -58,15 +59,13 @@ class OccupancyHeatMap extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
const OccupancyHeatMapDays(cellSize: _cellSize),
|
||||
CustomPaint(
|
||||
size: const Size(_totalWeeks * _cellSize, 7 * _cellSize),
|
||||
child: CustomPaint(
|
||||
isComplex: true,
|
||||
size: const Size(_totalWeeks * _cellSize, 7 * _cellSize),
|
||||
painter: OccupancyPainter(
|
||||
items: paintItems,
|
||||
maxValue: _maxValue,
|
||||
),
|
||||
SizedBox(
|
||||
width: _totalWeeks * _cellSize,
|
||||
height: 7 * _cellSize,
|
||||
child: InteractiveHeatMap(
|
||||
items: paintItems,
|
||||
maxValue: _maxValue,
|
||||
cellSize: _cellSize,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -4,18 +4,25 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
||||
class OccupancyPaintItem {
|
||||
final int index;
|
||||
final int value;
|
||||
final DateTime date;
|
||||
|
||||
const OccupancyPaintItem({required this.index, required this.value});
|
||||
const OccupancyPaintItem({
|
||||
required this.index,
|
||||
required this.value,
|
||||
required this.date,
|
||||
});
|
||||
}
|
||||
|
||||
class OccupancyPainter extends CustomPainter {
|
||||
OccupancyPainter({
|
||||
required this.items,
|
||||
required this.maxValue,
|
||||
this.hoveredItem,
|
||||
});
|
||||
|
||||
final List<OccupancyPaintItem> items;
|
||||
final int maxValue;
|
||||
final OccupancyPaintItem? hoveredItem;
|
||||
|
||||
static const double cellSize = 16.0;
|
||||
|
||||
@ -25,6 +32,10 @@ class OccupancyPainter extends CustomPainter {
|
||||
final Paint borderPaint = Paint()
|
||||
..color = ColorsManager.grayBorder.withValues(alpha: 0.4)
|
||||
..style = PaintingStyle.stroke;
|
||||
final Paint hoveredBorderPaint = Paint()
|
||||
..color = Colors.black
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5;
|
||||
|
||||
for (final item in items) {
|
||||
final column = item.index ~/ 7;
|
||||
@ -37,22 +48,27 @@ class OccupancyPainter extends CustomPainter {
|
||||
final rect = Rect.fromLTWH(x, y, cellSize, cellSize);
|
||||
canvas.drawRect(rect, fillPaint);
|
||||
|
||||
_drawDashedLine(
|
||||
canvas,
|
||||
Offset(x, y),
|
||||
Offset(x + cellSize, y),
|
||||
borderPaint,
|
||||
);
|
||||
_drawDashedLine(
|
||||
canvas,
|
||||
Offset(x, y + cellSize),
|
||||
Offset(x + cellSize, y + cellSize),
|
||||
borderPaint,
|
||||
);
|
||||
// Highlight the hovered item
|
||||
if (hoveredItem != null && hoveredItem!.index == item.index) {
|
||||
canvas.drawRect(rect, hoveredBorderPaint);
|
||||
} else {
|
||||
_drawDashedLine(
|
||||
canvas,
|
||||
Offset(x, y),
|
||||
Offset(x + cellSize, y),
|
||||
borderPaint,
|
||||
);
|
||||
_drawDashedLine(
|
||||
canvas,
|
||||
Offset(x, y + cellSize),
|
||||
Offset(x + cellSize, y + cellSize),
|
||||
borderPaint,
|
||||
);
|
||||
|
||||
canvas.drawLine(Offset(x, y), Offset(x, y + cellSize), borderPaint);
|
||||
canvas.drawLine(
|
||||
Offset(x + cellSize, y), Offset(x + cellSize, y + cellSize), borderPaint);
|
||||
canvas.drawLine(Offset(x, y), Offset(x, y + cellSize), borderPaint);
|
||||
canvas.drawLine(Offset(x + cellSize, y), Offset(x + cellSize, y + cellSize),
|
||||
borderPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,5 +96,6 @@ class OccupancyPainter extends CustomPainter {
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
bool shouldRepaint(covariant OccupancyPainter oldDelegate) =>
|
||||
oldDelegate.hoveredItem != hoveredItem;
|
||||
}
|
||||
|
@ -1,24 +1,20 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class GetEnergyConsumptionByPhasesParam extends Equatable {
|
||||
final DateTime? startDate;
|
||||
final DateTime? endDate;
|
||||
final String? spaceId;
|
||||
final String powerClampUuid;
|
||||
final DateTime? date;
|
||||
|
||||
const GetEnergyConsumptionByPhasesParam({
|
||||
this.startDate,
|
||||
this.endDate,
|
||||
this.spaceId,
|
||||
required this.powerClampUuid,
|
||||
this.date,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'startDate': startDate?.toIso8601String(),
|
||||
'endDate': endDate?.toIso8601String(),
|
||||
'spaceId': spaceId,
|
||||
'monthDate': '${date?.year}-${date?.month.toString().padLeft(2, '0')}',
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [startDate, endDate, spaceId];
|
||||
List<Object?> get props => [powerClampUuid, date];
|
||||
}
|
||||
|
@ -1,3 +1,19 @@
|
||||
class GetEnergyConsumptionPerDeviceParam {
|
||||
const GetEnergyConsumptionPerDeviceParam();
|
||||
const GetEnergyConsumptionPerDeviceParam({
|
||||
this.monthDate,
|
||||
this.spaceId,
|
||||
this.communityId,
|
||||
});
|
||||
|
||||
final DateTime? monthDate;
|
||||
final String? spaceId;
|
||||
final String? communityId;
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'monthDate':
|
||||
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
|
||||
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
|
||||
'communityUuid': communityId,
|
||||
'groupByDevice': true,
|
||||
};
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ class GetTotalEnergyConsumptionParam {
|
||||
return {
|
||||
'monthDate':
|
||||
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
|
||||
if (communityId == null || communityId!.isEmpty) 'spaceUuid': spaceId,
|
||||
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
|
||||
'communityUuid': communityId,
|
||||
'groupByDevice': false,
|
||||
};
|
||||
|
@ -1,29 +0,0 @@
|
||||
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/energy_consumption_by_phases_service.dart';
|
||||
|
||||
class FakeEnergyConsumptionByPhasesService
|
||||
implements EnergyConsumptionByPhasesService {
|
||||
@override
|
||||
Future<List<PhasesEnergyConsumption>> load(
|
||||
GetEnergyConsumptionByPhasesParam param,
|
||||
) {
|
||||
return Future.delayed(
|
||||
const Duration(milliseconds: 500),
|
||||
() => const [
|
||||
PhasesEnergyConsumption(month: 1, phaseA: 200, phaseB: 300, phaseC: 400),
|
||||
PhasesEnergyConsumption(month: 2, phaseA: 300, phaseB: 400, phaseC: 500),
|
||||
PhasesEnergyConsumption(month: 3, phaseA: 400, phaseB: 500, phaseC: 600),
|
||||
PhasesEnergyConsumption(month: 4, phaseA: 100, phaseB: 100, phaseC: 100),
|
||||
PhasesEnergyConsumption(month: 5, phaseA: 300, phaseB: 400, phaseC: 500),
|
||||
PhasesEnergyConsumption(month: 6, phaseA: 300, phaseB: 100, phaseC: 400),
|
||||
PhasesEnergyConsumption(month: 7, phaseA: 300, phaseB: 100, phaseC: 400),
|
||||
PhasesEnergyConsumption(month: 8, phaseA: 500, phaseB: 100, phaseC: 100),
|
||||
PhasesEnergyConsumption(month: 9, phaseA: 500, phaseB: 100, phaseC: 200),
|
||||
PhasesEnergyConsumption(month: 10, phaseA: 100, phaseB: 50, phaseC: 50),
|
||||
PhasesEnergyConsumption(month: 11, phaseA: 600, phaseB: 750, phaseC: 130),
|
||||
PhasesEnergyConsumption(month: 12, phaseA: 100, phaseB: 100, phaseC: 100),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -15,8 +15,9 @@ final class RemoteEnergyConsumptionByPhasesService
|
||||
) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: 'endpoint',
|
||||
path: '/power-clamp/${param.powerClampUuid}/historical',
|
||||
showServerMessage: true,
|
||||
queryParameters: param.toJson(),
|
||||
expectedResponseModel: (data) {
|
||||
final json = data as Map<String, dynamic>? ?? {};
|
||||
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||
@ -28,7 +29,7 @@ final class RemoteEnergyConsumptionByPhasesService
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
throw Exception('Failed to load energy consumption per device: $e');
|
||||
throw Exception('Failed to load energy consumption per phase: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
@ -15,16 +17,10 @@ class RemoteEnergyConsumptionPerDeviceService
|
||||
) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: 'endpoint',
|
||||
path: '/power-clamp/historical',
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (data) {
|
||||
final json = data as Map<String, dynamic>? ?? {};
|
||||
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||
return mappedData.map((e) {
|
||||
final jsonData = e as Map<String, dynamic>;
|
||||
return DeviceEnergyDataModel.fromJson(jsonData);
|
||||
}).toList();
|
||||
},
|
||||
queryParameters: param.toJson(),
|
||||
expectedResponseModel: _EnergyConsumptionPerDeviceMapper.map,
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
@ -32,3 +28,33 @@ class RemoteEnergyConsumptionPerDeviceService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract final class _EnergyConsumptionPerDeviceMapper {
|
||||
const _EnergyConsumptionPerDeviceMapper._();
|
||||
static List<DeviceEnergyDataModel> map(dynamic data) {
|
||||
final json = data as Map<String, dynamic>? ?? {};
|
||||
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||
return mappedData.where((e) {
|
||||
final deviceData = (e as Map<String, dynamic>)['data'] as List<dynamic>? ?? [];
|
||||
return deviceData.isNotEmpty;
|
||||
}).map((e) {
|
||||
final deviceData = e as Map<String, dynamic>;
|
||||
final energyData = deviceData['data'] as List<dynamic>;
|
||||
|
||||
return DeviceEnergyDataModel(
|
||||
deviceId: deviceData['deviceUuid'] as String,
|
||||
deviceName: deviceData['deviceName'] as String,
|
||||
color: Color((DateTime.now().microsecondsSinceEpoch +
|
||||
deviceData['deviceUuid'].hashCode) |
|
||||
0xFF000000),
|
||||
energy: energyData.map((data) {
|
||||
final energyJson = data as Map<String, dynamic>;
|
||||
return EnergyDataModel(
|
||||
date: DateTime.parse(energyJson['date'] as String),
|
||||
value: double.parse(energyJson['total_energy_consumed_kw'] as String),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user