Power clamp info integration.

This commit is contained in:
Faris Armoush
2025-05-05 12:56:59 +03:00
parent 490ca2057e
commit 1a3006fa43
10 changed files with 259 additions and 62 deletions

View File

@ -5,11 +5,14 @@ import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.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_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/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/total_energy_consumption/total_energy_consumption_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/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/power_clamp_info/remote_power_clamp_info_service.dart';
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/fake_total_energy_consumption_service.dart'; import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/fake_total_energy_consumption_service.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
@ -38,6 +41,11 @@ class AnalyticsPage extends StatelessWidget {
FakeEnergyConsumptionPerDeviceService(), FakeEnergyConsumptionPerDeviceService(),
), ),
), ),
BlocProvider(
create: (context) => PowerClampInfoBloc(
RemotePowerClampInfoService(HTTPService()),
),
),
], ],
child: const AnalyticsPageForm(), child: const AnalyticsPageForm(),
); );

View File

@ -0,0 +1,40 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/power_clamp_info_service.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
part 'power_clamp_info_event.dart';
part 'power_clamp_info_state.dart';
class PowerClampInfoBloc extends Bloc<PowerClampInfoEvent, PowerClampInfoState> {
PowerClampInfoBloc(
this._powerClampInfoService,
) : super(const PowerClampInfoState()) {
on<LoadPowerClampInfoEvent>(_onLoadPowerClampInfoEvent);
}
final PowerClampInfoService _powerClampInfoService;
Future<void> _onLoadPowerClampInfoEvent(
LoadPowerClampInfoEvent event,
Emitter<PowerClampInfoState> emit,
) async {
emit(state.copyWith(status: PowerClampInfoStatus.loading));
try {
final powerClampModel = await _powerClampInfoService.getInfo(event.deviceId);
emit(
state.copyWith(
status: PowerClampInfoStatus.loaded,
powerClampModel: powerClampModel,
),
);
} catch (e) {
emit(
state.copyWith(
status: PowerClampInfoStatus.error,
errorMessage: e.toString(),
),
);
}
}
}

View File

@ -0,0 +1,17 @@
part of 'power_clamp_info_bloc.dart';
sealed class PowerClampInfoEvent extends Equatable {
const PowerClampInfoEvent();
@override
List<Object> get props => [];
}
final class LoadPowerClampInfoEvent extends PowerClampInfoEvent {
const LoadPowerClampInfoEvent(this.deviceId);
final String deviceId;
@override
List<Object> get props => [deviceId];
}

View File

@ -0,0 +1,30 @@
part of 'power_clamp_info_bloc.dart';
enum PowerClampInfoStatus { initial, loading, loaded, error }
final class PowerClampInfoState extends Equatable {
const PowerClampInfoState({
this.status = PowerClampInfoStatus.initial,
this.powerClampModel,
this.errorMessage,
});
final PowerClampInfoStatus status;
final PowerClampModel? powerClampModel;
final String? errorMessage;
PowerClampInfoState copyWith({
PowerClampInfoStatus? status,
PowerClampModel? powerClampModel,
String? errorMessage,
}) {
return PowerClampInfoState(
status: status ?? this.status,
powerClampModel: powerClampModel ?? this.powerClampModel,
errorMessage: errorMessage ?? this.errorMessage,
);
}
@override
List<Object?> get props => [status, powerClampModel, errorMessage];
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_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_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/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/total_energy_consumption/total_energy_consumption_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/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart';
@ -26,6 +27,7 @@ class _AnalyticsEnergyManagementViewState
_loadEnergyConsumptionByPhases(); _loadEnergyConsumptionByPhases();
_loadTotalEnergyConsumption(); _loadTotalEnergyConsumption();
_loadEnergyConsumptionPerDevice(); _loadEnergyConsumptionPerDevice();
_loadPowerClampInfo();
super.initState(); super.initState();
} }
@ -50,6 +52,11 @@ class _AnalyticsEnergyManagementViewState
); );
} }
void _loadPowerClampInfo() {
context.read<PowerClampInfoBloc>().add(
const LoadPowerClampInfoEvent('deviceId'),
);
}
static const _padding = EdgeInsetsDirectional.all(32); static const _padding = EdgeInsetsDirectional.all(32);

View File

@ -15,6 +15,7 @@ class EnergyConsumptionByPhasesTitle extends StatelessWidget {
Expanded( Expanded(
flex: 4, flex: 4,
child: FittedBox( child: FittedBox(
alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: ChartTitle( child: ChartTitle(
title: Text( title: Text(

View File

@ -1,19 +1,25 @@
import 'package:flutter/material.dart'; 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/models/power_clamp_energy_status.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/widgets/energy_consumption_by_phases_chart_box.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_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_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/modules/energy_management/widgets/power_clamp_phases_data_widget.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:uuid/uuid.dart';
class PowerClampEnergyDataWidget extends StatelessWidget { class PowerClampEnergyDataWidget extends StatelessWidget {
const PowerClampEnergyDataWidget({super.key}); const PowerClampEnergyDataWidget({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<PowerClampInfoBloc, PowerClampInfoState>(
builder: (context, state) {
final generalDataPoints =
state.powerClampModel?.status.general.dataPoints ?? [];
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -28,7 +34,7 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
SelectableText( SelectableText(
const Uuid().v6(), state.powerClampModel?.productUuid ?? 'N/A',
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@ -36,37 +42,46 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
), ),
), ),
const Divider(), const Divider(),
const Expanded( Expanded(
flex: 2, flex: 2,
child: PowerClampEnergyStatusWidget( child: PowerClampEnergyStatusWidget(
status: [ status: [
PowerClampEnergyStatus( PowerClampEnergyStatus(
iconPath: Assets.powerActiveIcon, iconPath: Assets.powerActiveIcon,
title: 'Active', title: 'Active',
value: '700', value: _valueFromCode('EnergyConsumed', generalDataPoints),
unit: 'W', unit: 'W',
), ),
PowerClampEnergyStatus( PowerClampEnergyStatus(
iconPath: Assets.voltMeterIcon, iconPath: Assets.voltMeterIcon,
title: 'Current', title: 'Current',
value: '3.06', value: _valueFromCode('Current', generalDataPoints),
unit: 'A', unit: 'A',
), ),
PowerClampEnergyStatus( PowerClampEnergyStatus(
iconPath: Assets.frequencyIcon, iconPath: Assets.frequencyIcon,
title: 'Frequency', title: 'Frequency',
value: '50', value: _valueFromCode('Frequency', generalDataPoints),
unit: 'Hz', unit: 'Hz',
), ),
], ],
), ),
), ),
const SizedBox(height: 14), const SizedBox(height: 14),
const Expanded(flex: 4, child: PowerClampPhasesDataWidget()), Expanded(
flex: 4,
child: PowerClampPhasesDataWidget(
phaseA: state.powerClampModel?.status.phaseA,
phaseB: state.powerClampModel?.status.phaseB,
phaseC: state.powerClampModel?.status.phaseC,
),
),
const SizedBox(height: 14), const SizedBox(height: 14),
const Expanded(flex: 3, child: EnergyConsumptionByPhasesChartBox()), const Expanded(flex: 3, child: EnergyConsumptionByPhasesChartBox()),
], ],
); );
},
);
} }
Widget _buildHeader(BuildContext context) { Widget _buildHeader(BuildContext context) {
@ -99,4 +114,11 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
], ],
); );
} }
String _valueFromCode(String code, List<DataPoint> points) {
return points
.firstWhere((e) => e.code == code, orElse: () => DataPoint(value: '--'))
.value
.toString();
}
} }

View File

@ -1,21 +1,37 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phase.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phase.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class PowerClampPhasesDataWidget extends StatelessWidget { class PowerClampPhasesDataWidget extends StatelessWidget {
const PowerClampPhasesDataWidget({super.key}); const PowerClampPhasesDataWidget({
required this.phaseA,
required this.phaseB,
required this.phaseC,
super.key,
});
final Phase? phaseA;
final Phase? phaseB;
final Phase? phaseC;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final phases = [phaseA, phaseB, phaseC];
return Container( return Container(
width: double.infinity, width: double.infinity,
decoration: secondarySection.copyWith(boxShadow: const []), decoration: secondarySection.copyWith(boxShadow: const []),
child: Row( child: Row(
children: List.generate(5, (index) { children: List.generate(5, (index) {
if (index case 1 || 3) return _buildSeparator(); if (index.isOdd) return _buildSeparator();
final phaseIndex = index ~/ 2;
final phase = phases[phaseIndex];
final phaseSuffix = ['A', 'B', 'C'][phaseIndex];
return Expanded( return Expanded(
child: Padding( child: Padding(
padding: const EdgeInsetsDirectional.symmetric(horizontal: 14), padding: const EdgeInsetsDirectional.symmetric(horizontal: 14),
@ -30,7 +46,7 @@ class PowerClampPhasesDataWidget extends StatelessWidget {
alignment: AlignmentDirectional.center, alignment: AlignmentDirectional.center,
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: Text( child: Text(
'Phase ${index ~/ 2 + 1}', 'Phase ${phaseIndex + 1}',
style: context.textTheme.titleLarge?.copyWith( style: context.textTheme.titleLarge?.copyWith(
color: ColorsManager.textPrimaryColor, color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
@ -39,28 +55,40 @@ class PowerClampPhasesDataWidget extends StatelessWidget {
), ),
), ),
), ),
const PowerClampPhase( PowerClampPhase(
iconPath: Assets.powerActiveIcon, iconPath: Assets.powerActiveIcon,
title: 'Active Power', title: 'Active Power',
value: '393.0', value: _valueFromCode(
code: 'ReactivePower$phaseSuffix',
points: phase?.dataPoints,
),
unit: 'W', unit: 'W',
), ),
const PowerClampPhase( PowerClampPhase(
iconPath: Assets.voltageIcon, iconPath: Assets.voltageIcon,
title: 'Active Power', title: 'Voltage',
value: '228.5', value: _valueFromCode(
code: 'Voltage$phaseSuffix',
points: phase?.dataPoints,
),
unit: 'V', unit: 'V',
), ),
const PowerClampPhase( PowerClampPhase(
iconPath: Assets.voltMeterIcon, iconPath: Assets.voltMeterIcon,
title: 'Current', title: 'Current',
value: '1.72', value: _valueFromCode(
code: 'Current$phaseSuffix',
points: phase?.dataPoints,
),
unit: 'A', unit: 'A',
), ),
const PowerClampPhase( PowerClampPhase(
iconPath: Assets.speedoMeter, iconPath: Assets.speedoMeter,
title: 'Power Factor', title: 'Power Factor',
value: '0.8', value: _valueFromCode(
code: 'PowerFactor$phaseSuffix',
points: phase?.dataPoints,
),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
], ],
@ -92,4 +120,16 @@ class PowerClampPhasesDataWidget extends StatelessWidget {
), ),
); );
} }
String _valueFromCode({
required String code,
required List<DataPoint>? points,
}) {
final element = points?.firstWhere(
(e) => e.code == code,
orElse: () => DataPoint(value: '--'),
);
return element?.value.toString() ?? '--';
}
} }

View File

@ -0,0 +1,5 @@
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
abstract interface class PowerClampInfoService {
Future<PowerClampModel> getInfo(String deviceId);
}

View File

@ -0,0 +1,27 @@
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/power_clamp_info_service.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemotePowerClampInfoService implements PowerClampInfoService {
const RemotePowerClampInfoService(this._httpService);
final HTTPService _httpService;
@override
Future<PowerClampModel> getInfo(String deviceId) async {
try {
final response = await _httpService.get(
path: '/devices/cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa/functions/status',
showServerMessage: true,
expectedResponseModel: (data) {
final json = data as Map<String, Object?>? ?? {};
final mappedData = json['data'] as Map<String, Object?>? ?? {};
return PowerClampModel.fromJson(mappedData);
},
);
return response;
} catch (e) {
throw Exception('Failed to fetch power clamp info: $e');
}
}
}