mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-15 09:45:25 +00:00
Compare commits
115 Commits
SP-1493-FE
...
ci/cd-enha
Author | SHA1 | Date | |
---|---|---|---|
f19120c754 | |||
6b3eca23af | |||
4f4f11c330 | |||
8a25fa798c | |||
6612e91430 | |||
56c613fb0c | |||
53222bee81 | |||
bfb9158652 | |||
7f03222c12 | |||
5e6c14efeb | |||
9bbf3e75fa | |||
303b0236f1 | |||
4e3e63723e | |||
38ff20f86a | |||
d539e6266e | |||
7467f8d0ea | |||
a11e20147e | |||
55a6974bdc | |||
f8f58a24b8 | |||
682e69e65f | |||
59a59231ec | |||
ad41a2a87e | |||
974aa8f2a4 | |||
428cd34492 | |||
1a6121c452 | |||
e8f9ae944c | |||
7e37aed026 | |||
d89e227599 | |||
5a68b22f0c | |||
38184ca8b2 | |||
4d5de7bc05 | |||
1a3006fa43 | |||
490ca2057e | |||
06637a16bb | |||
696978a78d | |||
818e4e4d51 | |||
af877d7839 | |||
a33b1e3f49 | |||
c3cce334ab | |||
947e9e404c | |||
cd8264b6ce | |||
7467be6980 | |||
0353c73dac | |||
a050792f32 | |||
464f7b7347 | |||
cd54574279 | |||
18acae3e85 | |||
f081a7fc2d | |||
5996ff3928 | |||
a0d1cb988a | |||
c3ec9000d4 | |||
3d6a60b406 | |||
69c9240641 | |||
098013e5c8 | |||
11fb9e4894 | |||
390da9213d | |||
cae8b029fe | |||
6b883c8bb3 | |||
08c99bcbcb | |||
f6448d3eff | |||
a657a9a25e | |||
f55fa25bdf | |||
7242218b2f | |||
e43de3f64c | |||
9c250986b2 | |||
d8faafd1c0 | |||
24c30ddcb5 | |||
bafd2b4d13 | |||
56f9b1fc9a | |||
a9cc92ff86 | |||
3c7edae88a | |||
56c2d11535 | |||
3aa5bff758 | |||
28d1e5a5a7 | |||
fe036a8190 | |||
82e145de9d | |||
ebeb514a5b | |||
6b7e02ee53 | |||
b01136b6e9 | |||
97f8c6c8c9 | |||
6e527503c1 | |||
d6ef06c1b3 | |||
c9aaf2580f | |||
d9cd5d0438 | |||
3eb87dfde1 | |||
f29ff2551f | |||
67dd59ee9c | |||
bb3c3906d1 | |||
3873deca90 | |||
9431dd4500 | |||
63718185e7 | |||
1f4e82d567 | |||
9f68d171ff | |||
6eba640037 | |||
7a088074e3 | |||
8d2d9dd0bb | |||
cfc68f1568 | |||
02e08ad92f | |||
d7899a24f5 | |||
800c0ba47f | |||
fe4e775902 | |||
5247856cb4 | |||
4a8b8a32ba | |||
2abce77eb5 | |||
7efd1c3c87 | |||
7a0d9aefb7 | |||
21cc25cfc4 | |||
e2ec4bbf31 | |||
51b46ae197 | |||
36ee22603a | |||
b0abd42b0c | |||
ba4da78846 | |||
dc20d69f20 | |||
cf6ec231dc | |||
d0530f7fc3 |
26
.github/pull_request_template.md
vendored
Normal file
26
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<!--
|
||||||
|
Thanks for contributing!
|
||||||
|
|
||||||
|
Provide a description of your changes below and a general summary in the title
|
||||||
|
|
||||||
|
Please look at the following checklist to ensure that your PR can be accepted quickly:
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Jira Ticket
|
||||||
|
[SP-0000](https://syncrow.atlassian.net/browse/SP-0000)
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!--- Describe your changes in detail -->
|
||||||
|
|
||||||
|
## Type of Change
|
||||||
|
|
||||||
|
<!--- Put an `x` in all the boxes that apply: -->
|
||||||
|
|
||||||
|
- [ ] ✨ New feature (non-breaking change which adds functionality)
|
||||||
|
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
|
||||||
|
- [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change)
|
||||||
|
- [ ] 🧹 Code refactor
|
||||||
|
- [ ] ✅ Build configuration change
|
||||||
|
- [ ] 📝 Documentation
|
||||||
|
- [ ] 🗑️ Chore
|
@ -4,10 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened, closed]
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_deploy_job:
|
build_and_deploy_job:
|
||||||
|
@ -4,18 +4,12 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened, closed]
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_deploy_job:
|
build_and_deploy_job:
|
||||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Build and Deploy Job
|
name: Build and Deploy Job
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
|
29
.github/workflows/pr-check.yml
vendored
Normal file
29
.github/workflows/pr-check.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
name: Pull Request Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
setup_flutter:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Setup Flutter and Dependencies
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
lfs: false
|
||||||
|
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: '3.27.3'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Run Flutter Build
|
||||||
|
run: flutter build web --web-renderer canvaskit -t lib/main_dev.dart
|
@ -26,6 +26,7 @@ linter:
|
|||||||
rules:
|
rules:
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
prefer_const_constructors: true
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Additional information about this file can be found at
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
6
lib/pages/analytics/helpers/format_number_to_kwh.dart
Normal file
6
lib/pages/analytics/helpers/format_number_to_kwh.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
extension FormatNumberToKwh on num {
|
||||||
|
String get formatNumberToKwh {
|
||||||
|
final regExp = RegExp(r'(\d)(?=(\d{3})+$)');
|
||||||
|
return '${toStringAsFixed(0).replaceAllMapped(regExp, (match) => '${match[1]},')} kWh';
|
||||||
|
}
|
||||||
|
}
|
19
lib/pages/analytics/helpers/get_month_name_from_int.dart
Normal file
19
lib/pages/analytics/helpers/get_month_name_from_int.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
extension GetMonthNameFromNumber on num {
|
||||||
|
String get getMonthName {
|
||||||
|
return switch (this) {
|
||||||
|
1 => 'JAN',
|
||||||
|
2 => 'FEB',
|
||||||
|
3 => 'MAR',
|
||||||
|
4 => 'APR',
|
||||||
|
5 => 'MAY',
|
||||||
|
6 => 'JUN',
|
||||||
|
7 => 'JUL',
|
||||||
|
8 => 'AUG',
|
||||||
|
9 => 'SEP',
|
||||||
|
10 => 'OCT',
|
||||||
|
11 => 'NOV',
|
||||||
|
12 => 'DEC',
|
||||||
|
_ => 'N/A'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
32
lib/pages/analytics/models/device_energy_data_model.dart
Normal file
32
lib/pages/analytics/models/device_energy_data_model.dart
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||||
|
|
||||||
|
class DeviceEnergyDataModel extends Equatable {
|
||||||
|
const DeviceEnergyDataModel({
|
||||||
|
required this.energy,
|
||||||
|
required this.deviceName,
|
||||||
|
required this.deviceId,
|
||||||
|
required this.color,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<EnergyDataModel> energy;
|
||||||
|
final String deviceName;
|
||||||
|
final String deviceId;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [energy, deviceName, deviceId];
|
||||||
|
|
||||||
|
factory DeviceEnergyDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
final energy = (json['energy'] as List<dynamic>? ?? [])
|
||||||
|
.map((e) => EnergyDataModel.fromJson(e))
|
||||||
|
.toList();
|
||||||
|
return DeviceEnergyDataModel(
|
||||||
|
energy: energy,
|
||||||
|
deviceName: json['device_name'] as String? ?? '',
|
||||||
|
deviceId: json['device_id'] as String? ?? '',
|
||||||
|
color: Color(int.parse(json['color'] as String? ?? '0xFF000000')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
21
lib/pages/analytics/models/energy_data_model.dart
Normal file
21
lib/pages/analytics/models/energy_data_model.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
class EnergyDataModel extends Equatable {
|
||||||
|
const EnergyDataModel({
|
||||||
|
required this.date,
|
||||||
|
required this.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DateTime date;
|
||||||
|
final double value;
|
||||||
|
|
||||||
|
factory EnergyDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return EnergyDataModel(
|
||||||
|
date: DateTime.parse(json['date'] as String),
|
||||||
|
value: (json['value'] as num).toDouble(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [date, value];
|
||||||
|
}
|
27
lib/pages/analytics/models/phases_energy_consumption.dart
Normal file
27
lib/pages/analytics/models/phases_energy_consumption.dart
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
class PhasesEnergyConsumption extends Equatable {
|
||||||
|
final int month;
|
||||||
|
final double phaseA;
|
||||||
|
final double phaseB;
|
||||||
|
final double phaseC;
|
||||||
|
|
||||||
|
const PhasesEnergyConsumption({
|
||||||
|
required this.month,
|
||||||
|
required this.phaseA,
|
||||||
|
required this.phaseB,
|
||||||
|
required this.phaseC,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [month, phaseA, phaseB, phaseC];
|
||||||
|
|
||||||
|
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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
13
lib/pages/analytics/models/power_clamp_energy_status.dart
Normal file
13
lib/pages/analytics/models/power_clamp_energy_status.dart
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
class PowerClampEnergyStatus {
|
||||||
|
final String iconPath;
|
||||||
|
final String title;
|
||||||
|
final String value;
|
||||||
|
final String unit;
|
||||||
|
|
||||||
|
const PowerClampEnergyStatus({
|
||||||
|
required this.iconPath,
|
||||||
|
required this.title,
|
||||||
|
required this.value,
|
||||||
|
required this.unit,
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
part 'analytics_date_picker_event.dart';
|
||||||
|
|
||||||
|
class AnalyticsDatePickerBloc extends Bloc<AnalyticsDatePickerEvent, DateTime> {
|
||||||
|
AnalyticsDatePickerBloc() : super(DateTime.now()) {
|
||||||
|
on<UpdateAnalyticsDatePickerEvent>(_onUpdateAnalyticsDatePickerEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onUpdateAnalyticsDatePickerEvent(
|
||||||
|
UpdateAnalyticsDatePickerEvent event,
|
||||||
|
Emitter<DateTime> emit,
|
||||||
|
) {
|
||||||
|
emit(event.date);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
part of 'analytics_date_picker_bloc.dart';
|
||||||
|
|
||||||
|
sealed class AnalyticsDatePickerEvent extends Equatable {
|
||||||
|
const AnalyticsDatePickerEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class UpdateAnalyticsDatePickerEvent extends AnalyticsDatePickerEvent {
|
||||||
|
const UpdateAnalyticsDatePickerEvent(this.date);
|
||||||
|
|
||||||
|
final DateTime date;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [date];
|
||||||
|
}
|
@ -1,13 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/views/analytics_energy_management_view.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/views/analytics_energy_management_view.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/overview/views/analytics_overview_view.dart';
|
|
||||||
|
|
||||||
enum AnalyticsPageTab {
|
enum AnalyticsPageTab {
|
||||||
overview(
|
|
||||||
title: 'Overview',
|
|
||||||
child: AnalyticsOverviewView(),
|
|
||||||
),
|
|
||||||
energyManagement(
|
energyManagement(
|
||||||
title: 'Energy Management',
|
title: 'Energy Management',
|
||||||
child: AnalyticsEnergyManagementView(),
|
child: AnalyticsEnergyManagementView(),
|
||||||
|
@ -1,9 +1,20 @@
|
|||||||
import 'package:flutter/material.dart';
|
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/analytics/blocs/bloc/analytics_tab_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_communities_sidebar.dart';
|
||||||
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_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/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/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/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';
|
||||||
|
|
||||||
@ -12,8 +23,37 @@ class AnalyticsPage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider<AnalyticsTabBloc>(
|
return MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider<AnalyticsTabBloc>(
|
||||||
create: (context) => AnalyticsTabBloc(),
|
create: (context) => AnalyticsTabBloc(),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => TotalEnergyConsumptionBloc(
|
||||||
|
FakeTotalEnergyConsumptionService(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => EnergyConsumptionByPhasesBloc(
|
||||||
|
FakeEnergyConsumptionByPhasesService(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => EnergyConsumptionPerDeviceBloc(
|
||||||
|
FakeEnergyConsumptionPerDeviceService(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => PowerClampInfoBloc(
|
||||||
|
RemotePowerClampInfoService(HTTPService()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocProvider<RealtimeDeviceChangesBloc>(
|
||||||
|
create: (context) => RealtimeDeviceChangesBloc(
|
||||||
|
FirebaseRealtimeDeviceService(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
child: const AnalyticsPageForm(),
|
child: const AnalyticsPageForm(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_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/helpers/fetch_energy_management_data_helper.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
|
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
|
||||||
|
|
||||||
class AnalyticsCommunitiesSidebar extends StatelessWidget {
|
class AnalyticsCommunitiesSidebar extends StatelessWidget {
|
||||||
@ -6,13 +9,47 @@ class AnalyticsCommunitiesSidebar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return Builder(
|
||||||
|
builder: (context) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: SpaceTreeView(
|
child: SpaceTreeView(
|
||||||
title: const Text('Communities'),
|
title: const Text('Communities'),
|
||||||
shouldDisableDeselectingChildrenOfSelectedParent: true,
|
shouldDisableDeselectingChildrenOfSelectedParent: true,
|
||||||
onSelect: () {},
|
onSelect: () {
|
||||||
|
/// Necessary to wait for the state to update before fethcing the data.
|
||||||
|
Future.delayed(
|
||||||
|
const Duration(milliseconds: 100),
|
||||||
|
() {
|
||||||
|
if (context.mounted) {
|
||||||
|
FetchEnergyManagementDataHelper.fetchEnergyManagementData(
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
context.read<PowerClampInfoBloc>().add(
|
||||||
|
const ClearPowerClampInfoEvent(),
|
||||||
|
);
|
||||||
|
final (selectedCommunities, selectedSpaces) =
|
||||||
|
FetchEnergyManagementDataHelper
|
||||||
|
.getSelectedCommunitiesAndSpaces(context);
|
||||||
|
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) {
|
||||||
|
context.read<PowerClampInfoBloc>().add(
|
||||||
|
const ClearPowerClampInfoEvent(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
FetchEnergyManagementDataHelper.loadPowerClampInfo(
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
isSide: false,
|
isSide: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,46 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.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/widgets/month_picker_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.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';
|
||||||
|
|
||||||
class AnalyticsDateFilterButton extends StatelessWidget {
|
class AnalyticsDateFilterButton extends StatefulWidget {
|
||||||
const AnalyticsDateFilterButton({super.key});
|
const AnalyticsDateFilterButton({super.key});
|
||||||
|
|
||||||
static final _color = ColorsManager.blackColor.withValues(alpha: 0.8);
|
static final _color = ColorsManager.blackColor.withValues(alpha: 0.8);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AnalyticsDateFilterButton> createState() =>
|
||||||
|
_AnalyticsDateFilterButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AnalyticsDateFilterButtonState extends State<AnalyticsDateFilterButton> {
|
||||||
|
late final AnalyticsDatePickerBloc _analyticsDatePickerBloc;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_analyticsDatePickerBloc = AnalyticsDatePickerBloc();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_analyticsDatePickerBloc.close();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider.value(
|
||||||
|
value: _analyticsDatePickerBloc,
|
||||||
|
child: Builder(builder: (context) {
|
||||||
|
final selectedDate = context.watch<AnalyticsDatePickerBloc>().state;
|
||||||
return TextButton.icon(
|
return TextButton.icon(
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
foregroundColor: _color,
|
foregroundColor: AnalyticsDateFilterButton._color,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
side: const BorderSide(
|
side: const BorderSide(
|
||||||
@ -31,27 +58,40 @@ class AnalyticsDateFilterButton extends StatelessWidget {
|
|||||||
Assets.blankCalendar,
|
Assets.blankCalendar,
|
||||||
height: 20,
|
height: 20,
|
||||||
width: 20,
|
width: 20,
|
||||||
colorFilter: ColorFilter.mode(_color, BlendMode.srcIn),
|
colorFilter:
|
||||||
|
ColorFilter.mode(AnalyticsDateFilterButton._color, BlendMode.srcIn),
|
||||||
),
|
),
|
||||||
label: Text(
|
label: Text(
|
||||||
_concatenateDate(DateTime(2024, 1), DateTime(2024, 12)),
|
_formatDate(selectedDate),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => MonthPickerWidget(
|
||||||
|
selectedDate: selectedDate,
|
||||||
|
onDateSelected: (value) {
|
||||||
|
_analyticsDatePickerBloc.add(
|
||||||
|
UpdateAnalyticsDatePickerEvent(value),
|
||||||
|
);
|
||||||
|
FetchEnergyManagementDataHelper.fetchEnergyManagementData(
|
||||||
|
context,
|
||||||
|
selectedDate: value,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatDate(DateTime date) {
|
String _formatDate(DateTime? date) {
|
||||||
final formatter = DateFormat('MMM yyyy');
|
final formatter = DateFormat('MMMM yyyy');
|
||||||
final formattedDate = formatter.format(date);
|
final formattedDate = formatter.format(date ?? DateTime.now());
|
||||||
return formattedDate;
|
return formattedDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _concatenateDate(DateTime startDate, DateTime endDate) {
|
|
||||||
final formattedStartDate = _formatDate(startDate);
|
|
||||||
final formattedEndDate = _formatDate(endDate);
|
|
||||||
return '$formattedStartDate - $formattedEndDate';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
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/analytics/blocs/bloc/analytics_tab_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';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
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/analytics/blocs/bloc/analytics_tab_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';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tab_button.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tab_button.dart';
|
||||||
@ -19,7 +19,6 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: MediaQuery.sizeOf(context).width * 1,
|
|
||||||
decoration: subSectionContainerDecoration,
|
decoration: subSectionContainerDecoration,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -68,15 +67,12 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 8,
|
flex: 8,
|
||||||
child: SizedBox(
|
|
||||||
width: MediaQuery.sizeOf(context).width,
|
|
||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
switchInCurve: Curves.easeIn,
|
switchInCurve: Curves.easeIn,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
child: selectedTab.child,
|
child: selectedTab.child,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,194 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class MonthPickerWidget extends StatefulWidget {
|
||||||
|
const MonthPickerWidget({
|
||||||
|
super.key,
|
||||||
|
required this.selectedDate,
|
||||||
|
required this.onDateSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DateTime selectedDate;
|
||||||
|
final ValueChanged<DateTime>? onDateSelected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MonthPickerWidget> createState() => _MonthPickerWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MonthPickerWidgetState extends State<MonthPickerWidget> {
|
||||||
|
late int _currentYear;
|
||||||
|
int? _selectedMonth;
|
||||||
|
|
||||||
|
static const _monthNames = [
|
||||||
|
"January",
|
||||||
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December",
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_currentYear = widget.selectedDate.year;
|
||||||
|
_selectedMonth = widget.selectedDate.month - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsetsDirectional.all(20),
|
||||||
|
width: 320,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_buildYearSelector(),
|
||||||
|
_buildMonthsGrid(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
spacing: 12,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
fixedSize: const Size(106, 40),
|
||||||
|
backgroundColor: const Color(0xFFEDF2F7),
|
||||||
|
padding: const EdgeInsetsDirectional.symmetric(
|
||||||
|
vertical: 10,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Cancel',
|
||||||
|
style: context.textTheme.titleSmall?.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: ColorsManager.grey700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
final date = DateTime(
|
||||||
|
_currentYear,
|
||||||
|
_selectedMonth! + 1,
|
||||||
|
);
|
||||||
|
widget.onDateSelected?.call(date);
|
||||||
|
},
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
fixedSize: const Size(106, 40),
|
||||||
|
backgroundColor: ColorsManager.vividBlue.withValues(
|
||||||
|
alpha: 0.7,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsetsDirectional.symmetric(
|
||||||
|
vertical: 10,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Done',
|
||||||
|
style: context.textTheme.titleSmall?.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Row _buildYearSelector() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'$_currentYear',
|
||||||
|
style: context.textTheme.titleSmall?.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: ColorsManager.grey700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => setState(() => _currentYear = _currentYear - 1),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.chevron_left,
|
||||||
|
color: ColorsManager.grey700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => setState(() => _currentYear = _currentYear + 1),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.chevron_right,
|
||||||
|
color: ColorsManager.grey700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMonthsGrid() {
|
||||||
|
return GridView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: 12,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 3,
|
||||||
|
childAspectRatio: 2.5,
|
||||||
|
mainAxisSpacing: 8,
|
||||||
|
mainAxisExtent: 30,
|
||||||
|
),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final isSelected = _selectedMonth == index;
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => setState(() => _selectedMonth = index),
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected
|
||||||
|
? ColorsManager.vividBlue.withValues(alpha: 0.7)
|
||||||
|
: const Color(0xFFEDF2F7),
|
||||||
|
borderRadius:
|
||||||
|
isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
_monthNames[index],
|
||||||
|
style: context.textTheme.titleSmall?.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
color: isSelected
|
||||||
|
? ColorsManager.whiteColors
|
||||||
|
: ColorsManager.blackColor.withValues(alpha: 0.8),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
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';
|
||||||
|
|
||||||
|
part 'energy_consumption_by_phases_event.dart';
|
||||||
|
part 'energy_consumption_by_phases_state.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionByPhasesBloc
|
||||||
|
extends Bloc<EnergyConsumptionByPhasesEvent, EnergyConsumptionByPhasesState> {
|
||||||
|
EnergyConsumptionByPhasesBloc(
|
||||||
|
this._energyConsumptionByPhasesService,
|
||||||
|
) : super(const EnergyConsumptionByPhasesState()) {
|
||||||
|
on<LoadEnergyConsumptionByPhasesEvent>(_onLoadEnergyConsumptionByPhasesEvent);
|
||||||
|
on<ClearEnergyConsumptionByPhasesEvent>(_onClearEnergyConsumptionByPhasesEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
final EnergyConsumptionByPhasesService _energyConsumptionByPhasesService;
|
||||||
|
|
||||||
|
Future<void> _onLoadEnergyConsumptionByPhasesEvent(
|
||||||
|
LoadEnergyConsumptionByPhasesEvent event,
|
||||||
|
Emitter<EnergyConsumptionByPhasesState> emit,
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(status: EnergyConsumptionByPhasesStatus.loading));
|
||||||
|
try {
|
||||||
|
final chartData = await _energyConsumptionByPhasesService.load(event.param);
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: EnergyConsumptionByPhasesStatus.loaded,
|
||||||
|
chartData: chartData,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: EnergyConsumptionByPhasesStatus.failure,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onClearEnergyConsumptionByPhasesEvent(
|
||||||
|
ClearEnergyConsumptionByPhasesEvent event,
|
||||||
|
Emitter<EnergyConsumptionByPhasesState> emit,
|
||||||
|
) async {
|
||||||
|
emit(const EnergyConsumptionByPhasesState());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
part of 'energy_consumption_by_phases_bloc.dart';
|
||||||
|
|
||||||
|
sealed class EnergyConsumptionByPhasesEvent extends Equatable {
|
||||||
|
const EnergyConsumptionByPhasesEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadEnergyConsumptionByPhasesEvent extends EnergyConsumptionByPhasesEvent {
|
||||||
|
const LoadEnergyConsumptionByPhasesEvent({
|
||||||
|
required this.param,
|
||||||
|
});
|
||||||
|
|
||||||
|
final GetEnergyConsumptionByPhasesParam param;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [param];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ClearEnergyConsumptionByPhasesEvent extends EnergyConsumptionByPhasesEvent {
|
||||||
|
const ClearEnergyConsumptionByPhasesEvent();
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
part of 'energy_consumption_by_phases_bloc.dart';
|
||||||
|
|
||||||
|
enum EnergyConsumptionByPhasesStatus {
|
||||||
|
initial,
|
||||||
|
loading,
|
||||||
|
loaded,
|
||||||
|
failure,
|
||||||
|
}
|
||||||
|
|
||||||
|
final class EnergyConsumptionByPhasesState extends Equatable {
|
||||||
|
const EnergyConsumptionByPhasesState({
|
||||||
|
this.status = EnergyConsumptionByPhasesStatus.initial,
|
||||||
|
this.chartData = const <PhasesEnergyConsumption>[],
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<PhasesEnergyConsumption> chartData;
|
||||||
|
final EnergyConsumptionByPhasesStatus status;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
EnergyConsumptionByPhasesState copyWith({
|
||||||
|
List<PhasesEnergyConsumption>? chartData,
|
||||||
|
EnergyConsumptionByPhasesStatus? status,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return EnergyConsumptionByPhasesState(
|
||||||
|
chartData: chartData ?? this.chartData,
|
||||||
|
status: status ?? this.status,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [chartData, status, errorMessage];
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/device_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';
|
||||||
|
|
||||||
|
part 'energy_consumption_per_device_event.dart';
|
||||||
|
part 'energy_consumption_per_device_state.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionPerDeviceBloc
|
||||||
|
extends Bloc<EnergyConsumptionPerDeviceEvent, EnergyConsumptionPerDeviceState> {
|
||||||
|
EnergyConsumptionPerDeviceBloc(
|
||||||
|
this._energyConsumptionPerDeviceService,
|
||||||
|
) : super(const EnergyConsumptionPerDeviceState()) {
|
||||||
|
on<LoadEnergyConsumptionPerDeviceEvent>(_onLoadEnergyConsumptionPerDeviceEvent);
|
||||||
|
on<ClearEnergyConsumptionPerDeviceEvent>(_onClearEnergyConsumptionPerDeviceEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
final EnergyConsumptionPerDeviceService _energyConsumptionPerDeviceService;
|
||||||
|
|
||||||
|
Future<void> _onLoadEnergyConsumptionPerDeviceEvent(
|
||||||
|
LoadEnergyConsumptionPerDeviceEvent event,
|
||||||
|
Emitter<EnergyConsumptionPerDeviceState> emit,
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(status: EnergyConsumptionPerDeviceStatus.loading));
|
||||||
|
try {
|
||||||
|
final chartData = await _energyConsumptionPerDeviceService.load(event.param);
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: EnergyConsumptionPerDeviceStatus.loaded,
|
||||||
|
chartData: chartData,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: EnergyConsumptionPerDeviceStatus.failure,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onClearEnergyConsumptionPerDeviceEvent(
|
||||||
|
ClearEnergyConsumptionPerDeviceEvent event,
|
||||||
|
Emitter<EnergyConsumptionPerDeviceState> emit,
|
||||||
|
) async {
|
||||||
|
emit(const EnergyConsumptionPerDeviceState());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
part of 'energy_consumption_per_device_bloc.dart';
|
||||||
|
|
||||||
|
sealed class EnergyConsumptionPerDeviceEvent extends Equatable {
|
||||||
|
const EnergyConsumptionPerDeviceEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class LoadEnergyConsumptionPerDeviceEvent
|
||||||
|
extends EnergyConsumptionPerDeviceEvent {
|
||||||
|
const LoadEnergyConsumptionPerDeviceEvent(this.param);
|
||||||
|
|
||||||
|
final GetEnergyConsumptionPerDeviceParam param;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [param];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ClearEnergyConsumptionPerDeviceEvent
|
||||||
|
extends EnergyConsumptionPerDeviceEvent {
|
||||||
|
const ClearEnergyConsumptionPerDeviceEvent();
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
part of 'energy_consumption_per_device_bloc.dart';
|
||||||
|
|
||||||
|
enum EnergyConsumptionPerDeviceStatus { initial, loading, loaded, failure }
|
||||||
|
|
||||||
|
final class EnergyConsumptionPerDeviceState extends Equatable {
|
||||||
|
const EnergyConsumptionPerDeviceState({
|
||||||
|
this.status = EnergyConsumptionPerDeviceStatus.initial,
|
||||||
|
this.chartData = const <DeviceEnergyDataModel>[],
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<DeviceEnergyDataModel> chartData;
|
||||||
|
final EnergyConsumptionPerDeviceStatus status;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
EnergyConsumptionPerDeviceState copyWith({
|
||||||
|
List<DeviceEnergyDataModel>? chartData,
|
||||||
|
EnergyConsumptionPerDeviceStatus? status,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return EnergyConsumptionPerDeviceState(
|
||||||
|
chartData: chartData ?? this.chartData,
|
||||||
|
status: status ?? this.status,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [chartData, status, errorMessage];
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
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/all_devices/models/device_status.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);
|
||||||
|
on<UpdatePowerClampStatusEvent>(_onUpdatePowerClampStatusEvent);
|
||||||
|
on<ClearPowerClampInfoEvent>(_onClearPowerClampInfoEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onUpdatePowerClampStatusEvent(
|
||||||
|
UpdatePowerClampStatusEvent event,
|
||||||
|
Emitter<PowerClampInfoState> emit,
|
||||||
|
) async {
|
||||||
|
final currentModel = state.powerClampModel;
|
||||||
|
if (currentModel == null) return;
|
||||||
|
|
||||||
|
final updatedStatus = PowerStatus.fromStatusList(event.statusList);
|
||||||
|
final updatedModel = currentModel.copyWith(statusPower: updatedStatus);
|
||||||
|
|
||||||
|
emit(state.copyWith(powerClampModel: updatedModel));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onClearPowerClampInfoEvent(
|
||||||
|
ClearPowerClampInfoEvent event,
|
||||||
|
Emitter<PowerClampInfoState> emit,
|
||||||
|
) {
|
||||||
|
emit(const PowerClampInfoState());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final class UpdatePowerClampStatusEvent extends PowerClampInfoEvent {
|
||||||
|
const UpdatePowerClampStatusEvent(this.statusList);
|
||||||
|
|
||||||
|
final List<Status> statusList;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [statusList];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ClearPowerClampInfoEvent extends PowerClampInfoEvent {
|
||||||
|
const ClearPowerClampInfoEvent();
|
||||||
|
}
|
@ -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];
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/realtime_device_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
|
|
||||||
|
part 'realtime_device_changes_event.dart';
|
||||||
|
part 'realtime_device_changes_state.dart';
|
||||||
|
|
||||||
|
class RealtimeDeviceChangesBloc
|
||||||
|
extends Bloc<RealtimeDeviceChangesEvent, RealtimeDeviceChangesState> {
|
||||||
|
RealtimeDeviceChangesBloc(
|
||||||
|
this._realtimeDeviceService,
|
||||||
|
) : super(const RealtimeDeviceChangesState()) {
|
||||||
|
on<RealtimeDeviceChangesStarted>(_onRealtimeDeviceChangesStarted);
|
||||||
|
on<RealtimeDeviceChangesClosed>(_onRealtimeDeviceChangesClosed);
|
||||||
|
on<_RealtimeDeviceChangesUpdated>(_onRealtimeDeviceChangesUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
final RealtimeDeviceService _realtimeDeviceService;
|
||||||
|
StreamSubscription<List<Status>>? _subscription;
|
||||||
|
|
||||||
|
Future<void> _onRealtimeDeviceChangesStarted(
|
||||||
|
RealtimeDeviceChangesStarted event,
|
||||||
|
Emitter<RealtimeDeviceChangesState> emit,
|
||||||
|
) async {
|
||||||
|
await _subscription?.cancel();
|
||||||
|
_subscription = _realtimeDeviceService.subscribe(event.deviceId).listen(
|
||||||
|
(data) {
|
||||||
|
add(_RealtimeDeviceChangesUpdated(data));
|
||||||
|
},
|
||||||
|
onError: (error) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: RealtimeDeviceChangesStatus.failure,
|
||||||
|
errorMessage: '$error',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onRealtimeDeviceChangesClosed(
|
||||||
|
RealtimeDeviceChangesClosed event,
|
||||||
|
Emitter<RealtimeDeviceChangesState> emit,
|
||||||
|
) async {
|
||||||
|
add(const _RealtimeDeviceChangesUpdated([]));
|
||||||
|
await _subscription?.cancel();
|
||||||
|
_subscription = null;
|
||||||
|
emit(const RealtimeDeviceChangesState());
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onRealtimeDeviceChangesUpdated(
|
||||||
|
_RealtimeDeviceChangesUpdated event,
|
||||||
|
Emitter<RealtimeDeviceChangesState> emit,
|
||||||
|
) {
|
||||||
|
final currentState = state;
|
||||||
|
final updatedList = [
|
||||||
|
...currentState.deviceStatusList.where(
|
||||||
|
(device) => !event.deviceStatusList
|
||||||
|
.any((newDevice) => newDevice.code == device.code),
|
||||||
|
),
|
||||||
|
...event.deviceStatusList,
|
||||||
|
];
|
||||||
|
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: RealtimeDeviceChangesStatus.loaded,
|
||||||
|
deviceStatusList: updatedList,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
await _subscription?.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
part of 'realtime_device_changes_bloc.dart';
|
||||||
|
|
||||||
|
sealed class RealtimeDeviceChangesEvent extends Equatable {
|
||||||
|
const RealtimeDeviceChangesEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class RealtimeDeviceChangesStarted extends RealtimeDeviceChangesEvent {
|
||||||
|
const RealtimeDeviceChangesStarted(this.deviceId);
|
||||||
|
|
||||||
|
final String deviceId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [deviceId];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class RealtimeDeviceChangesClosed extends RealtimeDeviceChangesEvent {
|
||||||
|
const RealtimeDeviceChangesClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RealtimeDeviceChangesUpdated extends RealtimeDeviceChangesEvent {
|
||||||
|
final List<Status> deviceStatusList;
|
||||||
|
|
||||||
|
const _RealtimeDeviceChangesUpdated(this.deviceStatusList);
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
part of 'realtime_device_changes_bloc.dart';
|
||||||
|
|
||||||
|
enum RealtimeDeviceChangesStatus { initial, loaded, failure }
|
||||||
|
|
||||||
|
final class RealtimeDeviceChangesState extends Equatable {
|
||||||
|
const RealtimeDeviceChangesState({
|
||||||
|
this.status = RealtimeDeviceChangesStatus.initial,
|
||||||
|
this.deviceStatusList = const <Status>[],
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
final RealtimeDeviceChangesStatus status;
|
||||||
|
final List<Status> deviceStatusList;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
RealtimeDeviceChangesState copyWith({
|
||||||
|
RealtimeDeviceChangesStatus? status,
|
||||||
|
List<Status>? deviceStatusList,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return RealtimeDeviceChangesState(
|
||||||
|
status: status ?? this.status,
|
||||||
|
deviceStatusList: deviceStatusList ?? this.deviceStatusList,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [status, deviceStatusList, errorMessage];
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/total_energy_consumption_service.dart';
|
||||||
|
|
||||||
|
part 'total_energy_consumption_event.dart';
|
||||||
|
part 'total_energy_consumption_state.dart';
|
||||||
|
|
||||||
|
class TotalEnergyConsumptionBloc
|
||||||
|
extends Bloc<TotalEnergyConsumptionEvent, TotalEnergyConsumptionState> {
|
||||||
|
TotalEnergyConsumptionBloc(
|
||||||
|
this._totalEnergyConsumptionService,
|
||||||
|
) : super(const TotalEnergyConsumptionState()) {
|
||||||
|
on<TotalEnergyConsumptionLoadEvent>(_onTotalEnergyConsumptionLoadEvent);
|
||||||
|
on<ClearTotalEnergyConsumptionEvent>(_onClearTotalEnergyConsumptionEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
final TotalEnergyConsumptionService _totalEnergyConsumptionService;
|
||||||
|
|
||||||
|
Future<void> _onTotalEnergyConsumptionLoadEvent(
|
||||||
|
TotalEnergyConsumptionLoadEvent event,
|
||||||
|
Emitter<TotalEnergyConsumptionState> emit,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
emit(state.copyWith(status: TotalEnergyConsumptionStatus.loading));
|
||||||
|
final chartData = await _totalEnergyConsumptionService.load(event.param);
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
chartData: chartData,
|
||||||
|
status: TotalEnergyConsumptionStatus.loaded,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
status: TotalEnergyConsumptionStatus.failure,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onClearTotalEnergyConsumptionEvent(
|
||||||
|
ClearTotalEnergyConsumptionEvent event,
|
||||||
|
Emitter<TotalEnergyConsumptionState> emit,
|
||||||
|
) async {
|
||||||
|
emit(const TotalEnergyConsumptionState());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
part of 'total_energy_consumption_bloc.dart';
|
||||||
|
|
||||||
|
sealed class TotalEnergyConsumptionEvent extends Equatable {
|
||||||
|
const TotalEnergyConsumptionEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class TotalEnergyConsumptionLoadEvent extends TotalEnergyConsumptionEvent {
|
||||||
|
const TotalEnergyConsumptionLoadEvent({required this.param});
|
||||||
|
|
||||||
|
final GetTotalEnergyConsumptionParam param;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [param];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ClearTotalEnergyConsumptionEvent extends TotalEnergyConsumptionEvent {
|
||||||
|
const ClearTotalEnergyConsumptionEvent();
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
part of 'total_energy_consumption_bloc.dart';
|
||||||
|
|
||||||
|
enum TotalEnergyConsumptionStatus {
|
||||||
|
initial,
|
||||||
|
loading,
|
||||||
|
loaded,
|
||||||
|
failure,
|
||||||
|
}
|
||||||
|
|
||||||
|
final class TotalEnergyConsumptionState extends Equatable {
|
||||||
|
const TotalEnergyConsumptionState({
|
||||||
|
this.status = TotalEnergyConsumptionStatus.initial,
|
||||||
|
this.chartData = const <EnergyDataModel>[],
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<EnergyDataModel> chartData;
|
||||||
|
final TotalEnergyConsumptionStatus status;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
TotalEnergyConsumptionState copyWith({
|
||||||
|
List<EnergyDataModel>? chartData,
|
||||||
|
TotalEnergyConsumptionStatus? status,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return TotalEnergyConsumptionState(
|
||||||
|
chartData: chartData ?? this.chartData,
|
||||||
|
status: status ?? this.status,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [chartData, status, errorMessage];
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
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),
|
||||||
|
];
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/helpers/format_number_to_kwh.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
abstract final class EnergyManagementChartsHelper {
|
||||||
|
const EnergyManagementChartsHelper._();
|
||||||
|
|
||||||
|
static FlTitlesData titlesData(
|
||||||
|
BuildContext context, {
|
||||||
|
double? leftTitlesInterval,
|
||||||
|
}) {
|
||||||
|
const emptyTitle = AxisTitles(sideTitles: SideTitles(showTitles: false));
|
||||||
|
return FlTitlesData(
|
||||||
|
show: true,
|
||||||
|
bottomTitles: AxisTitles(
|
||||||
|
drawBelowEverything: true,
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
interval: 1,
|
||||||
|
reservedSize: 32,
|
||||||
|
showTitles: true,
|
||||||
|
maxIncluded: true,
|
||||||
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.only(top: 20.0),
|
||||||
|
child: Text(
|
||||||
|
value.toString(),
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leftTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
maxIncluded: true,
|
||||||
|
interval: leftTitlesInterval,
|
||||||
|
reservedSize: 110,
|
||||||
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.only(end: 12),
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
value.formatNumberToKwh,
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
rightTitles: emptyTitle,
|
||||||
|
topTitles: emptyTitle,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getToolTipLabel(num month, double value) {
|
||||||
|
final monthLabel = month.toString();
|
||||||
|
final valueLabel = value.formatNumberToKwh;
|
||||||
|
final labels = [monthLabel, valueLabel];
|
||||||
|
return labels.where((element) => element.isNotEmpty).join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
|
||||||
|
return touchedSpots.map((spot) {
|
||||||
|
return LineTooltipItem(
|
||||||
|
getToolTipLabel(spot.x, spot.y),
|
||||||
|
const TextStyle(
|
||||||
|
color: ColorsManager.textPrimaryColor,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static LineTouchTooltipData lineTouchTooltipData() {
|
||||||
|
return LineTouchTooltipData(
|
||||||
|
getTooltipColor: (touchTooltipItem) => ColorsManager.whiteColors,
|
||||||
|
tooltipBorder: const BorderSide(color: ColorsManager.semiTransparentBlack),
|
||||||
|
tooltipRoundedRadius: 16,
|
||||||
|
showOnTopOfTheChartBoxArea: false,
|
||||||
|
tooltipPadding: const EdgeInsets.all(8),
|
||||||
|
getTooltipItems: getTooltipItems,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FlBorderData borderData() {
|
||||||
|
return FlBorderData(
|
||||||
|
show: true,
|
||||||
|
border: const Border.symmetric(
|
||||||
|
horizontal: BorderSide(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
style: BorderStyle.solid,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FlGridData gridData() {
|
||||||
|
return const FlGridData(
|
||||||
|
show: true,
|
||||||
|
drawVerticalLine: false,
|
||||||
|
drawHorizontalLine: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LineTouchData lineTouchData() {
|
||||||
|
return LineTouchData(
|
||||||
|
handleBuiltInTouches: true,
|
||||||
|
touchSpotThreshold: 2,
|
||||||
|
touchTooltipData: EnergyManagementChartsHelper.lineTouchTooltipData(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
import 'package:flutter/material.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_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_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';
|
||||||
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
|
|
||||||
|
abstract final class FetchEnergyManagementDataHelper {
|
||||||
|
const FetchEnergyManagementDataHelper._();
|
||||||
|
|
||||||
|
static void fetchEnergyManagementData(
|
||||||
|
BuildContext context, {
|
||||||
|
DateTime? selectedDate,
|
||||||
|
}) {
|
||||||
|
final (selectedCommunities, selectedSpaces) =
|
||||||
|
getSelectedCommunitiesAndSpaces(context);
|
||||||
|
|
||||||
|
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) {
|
||||||
|
clearAllData(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTotalEnergyConsumption(context);
|
||||||
|
loadEnergyConsumptionByPhases(context);
|
||||||
|
loadEnergyConsumptionPerDevice(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static (List<String> selectedCommunities, List<String> selectedSpaces)
|
||||||
|
getSelectedCommunitiesAndSpaces(BuildContext context) {
|
||||||
|
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||||
|
final selectedCommunities = spaceTreeState.selectedCommunities;
|
||||||
|
final selectedSpaces = spaceTreeState.selectedSpaces;
|
||||||
|
|
||||||
|
return (selectedCommunities, selectedSpaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadEnergyConsumptionByPhases(
|
||||||
|
BuildContext context, {
|
||||||
|
DateTime? selectedDate,
|
||||||
|
}) {
|
||||||
|
final param = GetEnergyConsumptionByPhasesParam(
|
||||||
|
startDate: selectedDate,
|
||||||
|
spaceId: '',
|
||||||
|
);
|
||||||
|
context.read<EnergyConsumptionByPhasesBloc>().add(
|
||||||
|
LoadEnergyConsumptionByPhasesEvent(param: param),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadTotalEnergyConsumption(
|
||||||
|
BuildContext context, {
|
||||||
|
DateTime? selectedDate,
|
||||||
|
}) {
|
||||||
|
final (selectedCommunities, selectedSpaces) =
|
||||||
|
getSelectedCommunitiesAndSpaces(context);
|
||||||
|
|
||||||
|
final param = GetTotalEnergyConsumptionParam(
|
||||||
|
spaceId: selectedCommunities.firstOrNull,
|
||||||
|
startDate: selectedDate,
|
||||||
|
);
|
||||||
|
context.read<TotalEnergyConsumptionBloc>().add(
|
||||||
|
TotalEnergyConsumptionLoadEvent(param: param),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadEnergyConsumptionPerDevice(BuildContext context) {
|
||||||
|
const param = GetEnergyConsumptionPerDeviceParam();
|
||||||
|
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
||||||
|
const LoadEnergyConsumptionPerDeviceEvent(param),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadPowerClampInfo(BuildContext context) {
|
||||||
|
context.read<PowerClampInfoBloc>().add(
|
||||||
|
const LoadPowerClampInfoEvent('cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadRealtimeDeviceChanges(BuildContext context) {
|
||||||
|
context.read<RealtimeDeviceChangesBloc>().add(
|
||||||
|
const RealtimeDeviceChangesStarted('cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearAllData(BuildContext context) {
|
||||||
|
context.read<RealtimeDeviceChangesBloc>().add(
|
||||||
|
const RealtimeDeviceChangesClosed(),
|
||||||
|
);
|
||||||
|
|
||||||
|
context.read<PowerClampInfoBloc>().add(
|
||||||
|
const ClearPowerClampInfoEvent(),
|
||||||
|
);
|
||||||
|
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
||||||
|
const ClearEnergyConsumptionPerDeviceEvent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
context.read<TotalEnergyConsumptionBloc>().add(
|
||||||
|
const ClearTotalEnergyConsumptionEvent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
context.read<EnergyConsumptionByPhasesBloc>().add(
|
||||||
|
const ClearEnergyConsumptionByPhasesEvent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,70 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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/total_energy_consumption_chart_box.dart';
|
||||||
|
|
||||||
class AnalyticsEnergyManagementView extends StatelessWidget {
|
class AnalyticsEnergyManagementView extends StatelessWidget {
|
||||||
const AnalyticsEnergyManagementView({super.key});
|
const AnalyticsEnergyManagementView({super.key});
|
||||||
|
|
||||||
|
static const _padding = EdgeInsetsDirectional.all(32);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const Center(
|
return LayoutBuilder(
|
||||||
child: Text('EnergyManagementView is Working!'),
|
builder: (context, constraints) {
|
||||||
|
final isMediumOrLess = constraints.maxWidth <= 900;
|
||||||
|
if (isMediumOrLess) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
padding: _padding,
|
||||||
|
child: Column(
|
||||||
|
spacing: 32,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.sizeOf(context).height * 1.2,
|
||||||
|
child: const PowerClampEnergyDataWidget(),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.sizeOf(context).height * 0.5,
|
||||||
|
child: const TotalEnergyConsumptionChartBox(),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.sizeOf(context).height * 0.5,
|
||||||
|
child: const EnergyConsumptionPerDeviceChartBox(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Container(
|
||||||
|
padding: _padding,
|
||||||
|
height: MediaQuery.sizeOf(context).height * 1,
|
||||||
|
child: const Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
spacing: 32,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Column(
|
||||||
|
spacing: 20,
|
||||||
|
children: [
|
||||||
|
Expanded(child: TotalEnergyConsumptionChartBox()),
|
||||||
|
Expanded(child: EnergyConsumptionPerDeviceChartBox()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(child: PowerClampEnergyDataWidget()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class ChartTitle extends StatelessWidget {
|
||||||
|
const ChartTitle({super.key, required this.title});
|
||||||
|
|
||||||
|
final Widget title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DefaultTextStyle(
|
||||||
|
style: context.textTheme.titleLarge!.copyWith(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
child: title,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,172 @@
|
|||||||
|
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: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';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
||||||
|
const EnergyConsumptionByPhasesChart({
|
||||||
|
super.key,
|
||||||
|
required this.energyData,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<PhasesEnergyConsumption> energyData;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BarChart(
|
||||||
|
BarChartData(
|
||||||
|
gridData: EnergyManagementChartsHelper.gridData(),
|
||||||
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
|
barTouchData: _barTouchData(context),
|
||||||
|
titlesData: _titlesData(context),
|
||||||
|
barGroups: energyData.asMap().entries.map((entry) {
|
||||||
|
final index = entry.key;
|
||||||
|
final data = entry.value;
|
||||||
|
|
||||||
|
return BarChartGroupData(
|
||||||
|
x: index,
|
||||||
|
barRods: [
|
||||||
|
BarChartRodData(
|
||||||
|
color: ColorsManager.vividBlue.withValues(alpha: 0.1),
|
||||||
|
toY: data.phaseA + data.phaseB + data.phaseC,
|
||||||
|
rodStackItems: [
|
||||||
|
BarChartRodStackItem(
|
||||||
|
0,
|
||||||
|
data.phaseA,
|
||||||
|
ColorsManager.vividBlue.withValues(alpha: 0.8),
|
||||||
|
),
|
||||||
|
BarChartRodStackItem(
|
||||||
|
data.phaseA,
|
||||||
|
data.phaseA + data.phaseB,
|
||||||
|
ColorsManager.vividBlue.withValues(alpha: 0.4),
|
||||||
|
),
|
||||||
|
BarChartRodStackItem(
|
||||||
|
data.phaseA + data.phaseB,
|
||||||
|
data.phaseA + data.phaseB + data.phaseC,
|
||||||
|
ColorsManager.vividBlue.withValues(alpha: 0.15),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
width: 16,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(8),
|
||||||
|
topRight: Radius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BarTouchData _barTouchData(BuildContext context) {
|
||||||
|
return BarTouchData(
|
||||||
|
touchTooltipData: BarTouchTooltipData(
|
||||||
|
getTooltipColor: (touchTooltipItem) => ColorsManager.whiteColors,
|
||||||
|
tooltipBorder: const BorderSide(
|
||||||
|
color: ColorsManager.semiTransparentBlack,
|
||||||
|
),
|
||||||
|
tooltipRoundedRadius: 16,
|
||||||
|
tooltipPadding: const EdgeInsets.all(8),
|
||||||
|
getTooltipItem: (group, groupIndex, rod, rodIndex) => getTooltipItem(
|
||||||
|
context: context,
|
||||||
|
group: group,
|
||||||
|
groupIndex: groupIndex,
|
||||||
|
rod: rod,
|
||||||
|
rodIndex: rodIndex,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BarTooltipItem? getTooltipItem({
|
||||||
|
required BuildContext context,
|
||||||
|
required BarChartGroupData group,
|
||||||
|
required int groupIndex,
|
||||||
|
required BarChartRodData rod,
|
||||||
|
required int rodIndex,
|
||||||
|
}) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
return BarTooltipItem(
|
||||||
|
'$month\n',
|
||||||
|
context.textTheme.bodyMedium!.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: 'Phase A: $phaseA\n',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: 'Phase B: $phaseB\n',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: 'Phase C: $phaseC',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlTitlesData _titlesData(BuildContext context) {
|
||||||
|
final titlesData = EnergyManagementChartsHelper.titlesData(
|
||||||
|
context,
|
||||||
|
leftTitlesInterval: 250,
|
||||||
|
);
|
||||||
|
|
||||||
|
final leftTitles = titlesData.leftTitles.copyWith(
|
||||||
|
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
||||||
|
reservedSize: 70,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final bottomTitles = AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
getTitlesWidget: (value, _) {
|
||||||
|
final month = energyData[value.toInt()].month.getMonthName;
|
||||||
|
return FittedBox(
|
||||||
|
alignment: AlignmentDirectional.bottomCenter,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: RotatedBox(
|
||||||
|
quarterTurns: 3,
|
||||||
|
child: Text(
|
||||||
|
month,
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
fontSize: 11,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
reservedSize: 36,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return titlesData.copyWith(
|
||||||
|
leftTitles: leftTitles,
|
||||||
|
bottomTitles: bottomTitles,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter/material.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/widgets/energy_consumption_by_phases_chart.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_title.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionByPhasesChartBox extends StatelessWidget {
|
||||||
|
const EnergyConsumptionByPhasesChartBox({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<EnergyConsumptionByPhasesBloc,
|
||||||
|
EnergyConsumptionByPhasesState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsetsDirectional.all(20),
|
||||||
|
decoration: secondarySection,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 20,
|
||||||
|
children: [
|
||||||
|
AnalyticsErrorWidget(state.errorMessage),
|
||||||
|
EnergyConsumptionByPhasesTitle(isLoading: state.status == EnergyConsumptionByPhasesStatus.loading,),
|
||||||
|
Expanded(
|
||||||
|
child: EnergyConsumptionByPhasesChart(
|
||||||
|
energyData: state.chartData,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
import 'package:flutter/material.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';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionByPhasesTitle extends StatelessWidget {
|
||||||
|
const EnergyConsumptionByPhasesTitle({super.key, required this.isLoading});
|
||||||
|
final bool isLoading;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
ChartsLoadingWidget(isLoading: isLoading),
|
||||||
|
Expanded(
|
||||||
|
flex: 4,
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: ChartTitle(
|
||||||
|
title: Text(
|
||||||
|
'Energy Consumption by Phases',
|
||||||
|
style: context.textTheme.titleLarge?.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: ColorsManager.textPrimaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
...<(String title, double opacity)>[
|
||||||
|
('A', 0.8),
|
||||||
|
('B', 0.4),
|
||||||
|
('C', 0.15),
|
||||||
|
].map((phase) => _buildPhaseCell(context, phase)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPhaseCell(
|
||||||
|
BuildContext context,
|
||||||
|
(String title, double colorOpacity) phase,
|
||||||
|
) {
|
||||||
|
final (title, colorOpacity) = phase;
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
backgroundColor: ColorsManager.vividBlue.withValues(
|
||||||
|
alpha: colorOpacity,
|
||||||
|
),
|
||||||
|
radius: 4,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Phase $title',
|
||||||
|
style: context.textTheme.labelSmall?.copyWith(
|
||||||
|
fontSize: 8,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: ColorsManager.lightGreyColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionPerDeviceChart extends StatelessWidget {
|
||||||
|
const EnergyConsumptionPerDeviceChart({super.key, required this.chartData});
|
||||||
|
|
||||||
|
final List<DeviceEnergyDataModel> chartData;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LineChart(
|
||||||
|
LineChartData(
|
||||||
|
titlesData: EnergyManagementChartsHelper.titlesData(
|
||||||
|
context,
|
||||||
|
leftTitlesInterval: 250,
|
||||||
|
),
|
||||||
|
gridData: EnergyManagementChartsHelper.gridData(),
|
||||||
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
|
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
||||||
|
lineBarsData: chartData.map((e) {
|
||||||
|
return _buildChartBar(
|
||||||
|
color: e.color,
|
||||||
|
spots: e.energy
|
||||||
|
.map(
|
||||||
|
(energy) => FlSpot(
|
||||||
|
energy.date.day.toDouble(),
|
||||||
|
energy.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
duration: Durations.extralong1,
|
||||||
|
curve: Curves.easeIn,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
LineChartBarData _buildChartBar({
|
||||||
|
required Color color,
|
||||||
|
required List<FlSpot> spots,
|
||||||
|
}) {
|
||||||
|
return LineChartBarData(
|
||||||
|
spots: spots,
|
||||||
|
dashArray: [12, 18],
|
||||||
|
isCurved: true,
|
||||||
|
color: color,
|
||||||
|
barWidth: 3,
|
||||||
|
isStrokeCapRound: true,
|
||||||
|
dotData: const FlDotData(show: false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_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/widgets/chart_title.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
|
||||||
|
const EnergyConsumptionPerDeviceChartBox({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<EnergyConsumptionPerDeviceBloc,
|
||||||
|
EnergyConsumptionPerDeviceState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Container(
|
||||||
|
decoration: subSectionContainerDecoration.copyWith(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(30),
|
||||||
|
child: Column(
|
||||||
|
spacing: 20,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AnalyticsErrorWidget(state.errorMessage),
|
||||||
|
Row(
|
||||||
|
spacing: 32,
|
||||||
|
children: [
|
||||||
|
if (state.status == EnergyConsumptionPerDeviceStatus.loading)
|
||||||
|
const ChartsLoadingWidget(isLoading: true),
|
||||||
|
const Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
child: ChartTitle(
|
||||||
|
title: Text('Energy Consumption per Device'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: EnergyConsumptionPerDeviceDevicesList(
|
||||||
|
chartData: state.chartData,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(height: 0),
|
||||||
|
Expanded(
|
||||||
|
child: EnergyConsumptionPerDeviceChart(chartData: state.chartData),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
|
||||||
|
const EnergyConsumptionPerDeviceDevicesList({required this.chartData, super.key});
|
||||||
|
|
||||||
|
final List<DeviceEnergyDataModel> chartData;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
spacing: 16,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: chartData.map((e) => _buildDeviceCell(context, e)).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDeviceCell(BuildContext context, DeviceEnergyDataModel device) {
|
||||||
|
return Container(
|
||||||
|
height: MediaQuery.sizeOf(context).height * 0.0365,
|
||||||
|
padding: const EdgeInsetsDirectional.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 12,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadiusDirectional.circular(8),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Row(
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 4,
|
||||||
|
backgroundColor: device.color,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
device.deviceName,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class PowerClampEnergyDataDeviceDropdown extends StatelessWidget {
|
||||||
|
const PowerClampEnergyDataDeviceDropdown({super.key});
|
||||||
|
|
||||||
|
static final _color = ColorsManager.blackColor.withValues(alpha: 0.8);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: _color,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
side: const BorderSide(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: ColorsManager.transparentColor,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 32,
|
||||||
|
vertical: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Device 1',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
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/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/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';
|
||||||
|
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/constants/assets.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class PowerClampEnergyDataWidget extends StatelessWidget {
|
||||||
|
const PowerClampEnergyDataWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocListener<RealtimeDeviceChangesBloc, RealtimeDeviceChangesState>(
|
||||||
|
listenWhen: (previous, current) =>
|
||||||
|
previous.deviceStatusList != current.deviceStatusList ||
|
||||||
|
previous.status != current.status,
|
||||||
|
listener: (context, state) => context.read<PowerClampInfoBloc>().add(
|
||||||
|
UpdatePowerClampStatusEvent(state.deviceStatusList),
|
||||||
|
),
|
||||||
|
child: BlocBuilder<PowerClampInfoBloc, PowerClampInfoState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final generalDataPoints =
|
||||||
|
state.powerClampModel?.status.general.dataPoints ?? [];
|
||||||
|
return Container(
|
||||||
|
decoration: subSectionContainerDecoration.copyWith(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsetsDirectional.all(32),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AnalyticsErrorWidget(state.errorMessage),
|
||||||
|
_buildHeader(context),
|
||||||
|
Text(
|
||||||
|
'Device ID:',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
SelectableText(
|
||||||
|
state.powerClampModel?.productUuid ?? 'N/A',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: PowerClampEnergyStatusWidget(
|
||||||
|
status: [
|
||||||
|
PowerClampEnergyStatus(
|
||||||
|
iconPath: Assets.powerActiveIcon,
|
||||||
|
title: 'Active',
|
||||||
|
value: _valueFromCode('EnergyConsumed', generalDataPoints),
|
||||||
|
unit: 'W',
|
||||||
|
),
|
||||||
|
PowerClampEnergyStatus(
|
||||||
|
iconPath: Assets.voltMeterIcon,
|
||||||
|
title: 'Current',
|
||||||
|
value: _valueFromCode('Current', generalDataPoints),
|
||||||
|
unit: 'A',
|
||||||
|
),
|
||||||
|
PowerClampEnergyStatus(
|
||||||
|
iconPath: Assets.frequencyIcon,
|
||||||
|
title: 'Frequency',
|
||||||
|
value: _valueFromCode('Frequency', generalDataPoints),
|
||||||
|
unit: 'Hz',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
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 Expanded(flex: 3, child: EnergyConsumptionByPhasesChartBox()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
child: SelectableText(
|
||||||
|
'Smart Power Clamp',
|
||||||
|
style: context.textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: ColorsManager.vividBlue.withValues(alpha: 0.6),
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
const Expanded(
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
|
child: PowerClampEnergyDataDeviceDropdown(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _valueFromCode(String code, List<DataPoint> points) {
|
||||||
|
return points
|
||||||
|
.firstWhere((e) => e.code == code, orElse: () => DataPoint(value: '--'))
|
||||||
|
.value
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class PowerClampEnergyStatusWidget extends StatelessWidget {
|
||||||
|
const PowerClampEnergyStatusWidget({
|
||||||
|
super.key,
|
||||||
|
required this.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<PowerClampEnergyStatus> status;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: secondarySection.copyWith(boxShadow: const []),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: List.generate(
|
||||||
|
status.length * 2 - 1,
|
||||||
|
(index) => index.isEven
|
||||||
|
? Expanded(child: _buildItem(context, status[index ~/ 2]))
|
||||||
|
: _buildDivider(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildItem(BuildContext context, PowerClampEnergyStatus item) {
|
||||||
|
return Center(
|
||||||
|
child: ListTile(
|
||||||
|
titleAlignment: ListTileTitleAlignment.center,
|
||||||
|
leading: SvgPicture.asset(
|
||||||
|
item.iconPath,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
|
height: 18,
|
||||||
|
width: 18,
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
title: Text(
|
||||||
|
item.title,
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor.withValues(alpha: 0.83),
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor.withValues(alpha: 0.83),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(text: '${item.value} '),
|
||||||
|
TextSpan(
|
||||||
|
text: item.unit,
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor.withValues(alpha: 0.83),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDivider() {
|
||||||
|
return Container(
|
||||||
|
height: 1,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Color.fromARGB(20, 0, 0, 0),
|
||||||
|
offset: Offset(0, 1),
|
||||||
|
blurRadius: 1,
|
||||||
|
),
|
||||||
|
BoxShadow(
|
||||||
|
color: Color.fromARGB(30, 0, 0, 0),
|
||||||
|
offset: Offset(0, -2),
|
||||||
|
blurRadius: 3,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class PowerClampPhase extends StatelessWidget {
|
||||||
|
const PowerClampPhase({
|
||||||
|
super.key,
|
||||||
|
required this.iconPath,
|
||||||
|
required this.title,
|
||||||
|
required this.value,
|
||||||
|
this.unit,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String iconPath;
|
||||||
|
final String title;
|
||||||
|
final String value;
|
||||||
|
final String? unit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Expanded(
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 4),
|
||||||
|
decoration: containerWhiteDecoration.copyWith(boxShadow: const []),
|
||||||
|
padding: const EdgeInsetsDirectional.all(8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
_buildIcon(),
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
_buildTitle(context),
|
||||||
|
_buildValue(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildValue(BuildContext context) {
|
||||||
|
final textStyle = context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor.withValues(
|
||||||
|
alpha: 0.83,
|
||||||
|
),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 15,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: AlignmentDirectional.topCenter,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: textStyle,
|
||||||
|
children: [
|
||||||
|
TextSpan(text: '$value '),
|
||||||
|
if (unit != null)
|
||||||
|
TextSpan(
|
||||||
|
text: unit,
|
||||||
|
style: textStyle?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor.withValues(
|
||||||
|
alpha: 0.83,
|
||||||
|
),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTitle(BuildContext context) {
|
||||||
|
return Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: context.textTheme.titleSmall?.copyWith(
|
||||||
|
color: ColorsManager.lightGreyColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildIcon() {
|
||||||
|
return Expanded(
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: SvgPicture.asset(iconPath),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
import 'package:flutter/material.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/constants/assets.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class PowerClampPhasesDataWidget extends StatelessWidget {
|
||||||
|
const PowerClampPhasesDataWidget({
|
||||||
|
required this.phaseA,
|
||||||
|
required this.phaseB,
|
||||||
|
required this.phaseC,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Phase? phaseA;
|
||||||
|
final Phase? phaseB;
|
||||||
|
final Phase? phaseC;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final phases = [phaseA, phaseB, phaseC];
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: secondarySection.copyWith(boxShadow: const []),
|
||||||
|
child: Row(
|
||||||
|
children: List.generate(5, (index) {
|
||||||
|
if (index.isOdd) return _buildSeparator();
|
||||||
|
final phaseIndex = index ~/ 2;
|
||||||
|
final phase = phases[phaseIndex];
|
||||||
|
final phaseSuffix = ['A', 'B', 'C'][phaseIndex];
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.symmetric(horizontal: 14),
|
||||||
|
child: Column(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.center,
|
||||||
|
child: Text(
|
||||||
|
'Phase ${phaseIndex + 1}',
|
||||||
|
style: context.textTheme.titleLarge?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PowerClampPhase(
|
||||||
|
iconPath: Assets.powerActiveIcon,
|
||||||
|
title: 'Active Power',
|
||||||
|
value: _valueFromCode(
|
||||||
|
code: 'ReactivePower$phaseSuffix',
|
||||||
|
points: phase?.dataPoints,
|
||||||
|
),
|
||||||
|
unit: 'W',
|
||||||
|
),
|
||||||
|
PowerClampPhase(
|
||||||
|
iconPath: Assets.voltageIcon,
|
||||||
|
title: 'Voltage',
|
||||||
|
value: _valueFromCode(
|
||||||
|
code: 'Voltage$phaseSuffix',
|
||||||
|
points: phase?.dataPoints,
|
||||||
|
),
|
||||||
|
unit: 'V',
|
||||||
|
),
|
||||||
|
PowerClampPhase(
|
||||||
|
iconPath: Assets.voltMeterIcon,
|
||||||
|
title: 'Current',
|
||||||
|
value: _valueFromCode(
|
||||||
|
code: 'Current$phaseSuffix',
|
||||||
|
points: phase?.dataPoints,
|
||||||
|
),
|
||||||
|
unit: 'A',
|
||||||
|
),
|
||||||
|
PowerClampPhase(
|
||||||
|
iconPath: Assets.speedoMeter,
|
||||||
|
title: 'Power Factor',
|
||||||
|
value: _valueFromCode(
|
||||||
|
code: 'PowerFactor$phaseSuffix',
|
||||||
|
points: phase?.dataPoints,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSeparator() {
|
||||||
|
return Container(
|
||||||
|
height: double.infinity,
|
||||||
|
width: 1,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Color.fromARGB(20, 0, 0, 0),
|
||||||
|
offset: Offset(1, 0),
|
||||||
|
blurRadius: 1,
|
||||||
|
),
|
||||||
|
BoxShadow(
|
||||||
|
color: Color.fromARGB(30, 0, 0, 0),
|
||||||
|
offset: Offset(-2, 0),
|
||||||
|
blurRadius: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _valueFromCode({
|
||||||
|
required String code,
|
||||||
|
required List<DataPoint>? points,
|
||||||
|
}) {
|
||||||
|
final element = points?.firstWhere(
|
||||||
|
(e) => e.code == code,
|
||||||
|
orElse: () => DataPoint(value: '--'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return element?.value.toString() ?? '--';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
// energy_consumption_chart will return id, name and consumption
|
||||||
|
const phasesJson = {
|
||||||
|
"1": {
|
||||||
|
"phaseOne": 1000,
|
||||||
|
"phaseTwo": 2000,
|
||||||
|
"phaseThree": 3000,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TotalEnergyConsumptionChart extends StatelessWidget {
|
||||||
|
const TotalEnergyConsumptionChart({required this.chartData, super.key});
|
||||||
|
|
||||||
|
final List<EnergyDataModel> chartData;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Expanded(
|
||||||
|
child: LineChart(
|
||||||
|
LineChartData(
|
||||||
|
titlesData: EnergyManagementChartsHelper.titlesData(
|
||||||
|
context,
|
||||||
|
leftTitlesInterval: 5000,
|
||||||
|
),
|
||||||
|
gridData: EnergyManagementChartsHelper.gridData(),
|
||||||
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
|
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
||||||
|
lineBarsData: _lineBarsData,
|
||||||
|
),
|
||||||
|
duration: Durations.extralong1,
|
||||||
|
curve: Curves.easeIn,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<LineChartBarData> get _lineBarsData {
|
||||||
|
return [
|
||||||
|
LineChartBarData(
|
||||||
|
preventCurveOvershootingThreshold: 0.1,
|
||||||
|
curveSmoothness: 0.55,
|
||||||
|
preventCurveOverShooting: true,
|
||||||
|
spots: chartData
|
||||||
|
.asMap()
|
||||||
|
.entries
|
||||||
|
.map(
|
||||||
|
(entry) => FlSpot(
|
||||||
|
entry.key.toDouble(),
|
||||||
|
entry.value.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
color: ColorsManager.blueColor.withValues(alpha: 0.6),
|
||||||
|
shadow: const Shadow(color: Colors.black12),
|
||||||
|
show: true,
|
||||||
|
isCurved: true,
|
||||||
|
belowBarData: BarAreaData(
|
||||||
|
show: true,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
ColorsManager.vividBlue.withValues(alpha: 0.3),
|
||||||
|
ColorsManager.vividBlue.withValues(alpha: 0.2),
|
||||||
|
ColorsManager.vividBlue.withValues(alpha: 0.1),
|
||||||
|
Colors.transparent,
|
||||||
|
],
|
||||||
|
begin: Alignment.center,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dotData: const FlDotData(show: false),
|
||||||
|
isStrokeCapRound: true,
|
||||||
|
barWidth: 3,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_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/chart_title.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class TotalEnergyConsumptionChartBox extends StatelessWidget {
|
||||||
|
const TotalEnergyConsumptionChartBox({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<TotalEnergyConsumptionBloc, TotalEnergyConsumptionState>(
|
||||||
|
builder: (context, state) => Container(
|
||||||
|
decoration: subSectionContainerDecoration.copyWith(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(30),
|
||||||
|
child: Column(
|
||||||
|
spacing: 20,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AnalyticsErrorWidget(state.errorMessage),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
ChartsLoadingWidget(
|
||||||
|
isLoading: state.status == TotalEnergyConsumptionStatus.loading,
|
||||||
|
),
|
||||||
|
const Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: ChartTitle(title: Text('Total Energy Consumption')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(flex: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
TotalEnergyConsumptionChart(chartData: state.chartData),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class AnalyticsOverviewView extends StatelessWidget {
|
|
||||||
const AnalyticsOverviewView({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const Center(
|
|
||||||
child: Text('Coming Soon!'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,24 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
class GetEnergyConsumptionByPhasesParam extends Equatable {
|
||||||
|
final DateTime? startDate;
|
||||||
|
final DateTime? endDate;
|
||||||
|
final String? spaceId;
|
||||||
|
|
||||||
|
const GetEnergyConsumptionByPhasesParam({
|
||||||
|
this.startDate,
|
||||||
|
this.endDate,
|
||||||
|
this.spaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'startDate': startDate?.toIso8601String(),
|
||||||
|
'endDate': endDate?.toIso8601String(),
|
||||||
|
'spaceId': spaceId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [startDate, endDate, spaceId];
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
class GetEnergyConsumptionPerDeviceParam {
|
||||||
|
const GetEnergyConsumptionPerDeviceParam();
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
class GetTotalEnergyConsumptionParam {
|
||||||
|
final DateTime? startDate;
|
||||||
|
final DateTime? endDate;
|
||||||
|
final String? spaceId;
|
||||||
|
|
||||||
|
const GetTotalEnergyConsumptionParam({
|
||||||
|
this.startDate,
|
||||||
|
this.endDate,
|
||||||
|
this.spaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'startDate': startDate?.toIso8601String(),
|
||||||
|
'endDate': endDate?.toIso8601String(),
|
||||||
|
'spaceId': spaceId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
abstract interface class EnergyConsumptionByPhasesService {
|
||||||
|
Future<List<PhasesEnergyConsumption>> load(
|
||||||
|
GetEnergyConsumptionByPhasesParam param,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
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),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
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';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
final class RemoteEnergyConsumptionByPhasesService
|
||||||
|
implements EnergyConsumptionByPhasesService {
|
||||||
|
const RemoteEnergyConsumptionByPhasesService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<PhasesEnergyConsumption>> load(
|
||||||
|
GetEnergyConsumptionByPhasesParam param,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: 'endpoint',
|
||||||
|
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 PhasesEnergyConsumption.fromJson(jsonData);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load energy consumption per device: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
||||||
|
|
||||||
|
abstract interface class EnergyConsumptionPerDeviceService {
|
||||||
|
Future<List<DeviceEnergyDataModel>> load(
|
||||||
|
GetEnergyConsumptionPerDeviceParam param,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
import 'dart:math' as math show Random;
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
class FakeEnergyConsumptionPerDeviceService
|
||||||
|
implements EnergyConsumptionPerDeviceService {
|
||||||
|
@override
|
||||||
|
Future<List<DeviceEnergyDataModel>> load(
|
||||||
|
GetEnergyConsumptionPerDeviceParam param,
|
||||||
|
) {
|
||||||
|
final random = math.Random();
|
||||||
|
return Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
|
return [
|
||||||
|
(Colors.redAccent, 1),
|
||||||
|
(Colors.lightBlueAccent, 2),
|
||||||
|
(Colors.purpleAccent, 3),
|
||||||
|
].map((e) {
|
||||||
|
final (color, index) = e;
|
||||||
|
return DeviceEnergyDataModel(
|
||||||
|
color: color,
|
||||||
|
energy: List.generate(30, (i) => i)
|
||||||
|
.map(
|
||||||
|
(index) => EnergyDataModel(
|
||||||
|
date: DateTime(2025, 1, index + 1),
|
||||||
|
value: random.nextInt(100) + (index * 100),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
deviceName: 'Device $index',
|
||||||
|
deviceId: 'device_$index',
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/device_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';
|
||||||
|
|
||||||
|
class RemoteEnergyConsumptionPerDeviceService
|
||||||
|
implements EnergyConsumptionPerDeviceService {
|
||||||
|
const RemoteEnergyConsumptionPerDeviceService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<DeviceEnergyDataModel>> load(
|
||||||
|
GetEnergyConsumptionPerDeviceParam param,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: 'endpoint',
|
||||||
|
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();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load energy consumption per device: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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/$deviceId/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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/realtime_device_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
|
|
||||||
|
class FirebaseRealtimeDeviceService implements RealtimeDeviceService {
|
||||||
|
@override
|
||||||
|
Stream<List<Status>> subscribe(String deviceId) {
|
||||||
|
try {
|
||||||
|
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
|
|
||||||
|
return ref.onValue.asyncMap((event) {
|
||||||
|
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||||
|
|
||||||
|
if (data == null || data['status'] == null) {
|
||||||
|
throw Exception('Invalid data received from Firebase');
|
||||||
|
}
|
||||||
|
|
||||||
|
final statusMap = data['status'] as List<dynamic>;
|
||||||
|
return statusMap.map((status) {
|
||||||
|
if (status is! Map<dynamic, dynamic>) {
|
||||||
|
throw Exception('Invalid status format');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Status(
|
||||||
|
code: status['code']?.toString() ?? '',
|
||||||
|
value: num.tryParse(status['value']?.toString() ?? '0'),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Error subscribing to device status: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
|
|
||||||
|
abstract interface class RealtimeDeviceService {
|
||||||
|
Stream<List<Status>> subscribe(String deviceId);
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/total_energy_consumption_service.dart';
|
||||||
|
|
||||||
|
class FakeTotalEnergyConsumptionService implements TotalEnergyConsumptionService {
|
||||||
|
@override
|
||||||
|
Future<List<EnergyDataModel>> load(
|
||||||
|
GetTotalEnergyConsumptionParam param,
|
||||||
|
) {
|
||||||
|
return Future.value(
|
||||||
|
List.generate(30, (index) {
|
||||||
|
return EnergyDataModel(
|
||||||
|
date: DateTime(2025, 1, index + 1),
|
||||||
|
value: 20000 + (index * 1000) % 5000,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/total_energy_consumption_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
class RemoteTotalEnergyConsumptionService implements TotalEnergyConsumptionService {
|
||||||
|
const RemoteTotalEnergyConsumptionService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<EnergyDataModel>> load(
|
||||||
|
GetTotalEnergyConsumptionParam param,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: 'endpoint',
|
||||||
|
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 EnergyDataModel.fromJson(jsonData);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load total energy consumption: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
|
||||||
|
|
||||||
|
abstract interface class TotalEnergyConsumptionService {
|
||||||
|
Future<List<EnergyDataModel>> load(
|
||||||
|
GetTotalEnergyConsumptionParam param,
|
||||||
|
);
|
||||||
|
}
|
26
lib/pages/analytics/widgets/analytics_error_widget.dart
Normal file
26
lib/pages/analytics/widgets/analytics_error_widget.dart
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class AnalyticsErrorWidget extends StatelessWidget {
|
||||||
|
const AnalyticsErrorWidget(this.errorMessage, {super.key});
|
||||||
|
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Visibility(
|
||||||
|
visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false),
|
||||||
|
child: Text(
|
||||||
|
'$errorMessage ?? "Something went wrong"',
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.red,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
25
lib/pages/analytics/widgets/charts_loading_widget.dart
Normal file
25
lib/pages/analytics/widgets/charts_loading_widget.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ChartsLoadingWidget extends StatelessWidget {
|
||||||
|
const ChartsLoadingWidget({
|
||||||
|
required this.isLoading,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
final bool isLoading;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Visibility(
|
||||||
|
visible: isLoading,
|
||||||
|
child: const SizedBox.square(
|
||||||
|
dimension: 16,
|
||||||
|
child: FittedBox(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsetsDirectional.only(end: 8),
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
// PowerClampModel class to represent the response
|
// PowerClampModel class to represent the response
|
||||||
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
|
|
||||||
class PowerClampModel {
|
class PowerClampModel {
|
||||||
String productUuid;
|
String productUuid;
|
||||||
String productType;
|
String productType;
|
||||||
@ -46,11 +48,27 @@ class PowerStatus {
|
|||||||
|
|
||||||
factory PowerStatus.fromJson(Map<String, dynamic> json) {
|
factory PowerStatus.fromJson(Map<String, dynamic> json) {
|
||||||
return PowerStatus(
|
return PowerStatus(
|
||||||
phaseA: Phase.fromJson(json['phaseA']as List<dynamic>? ?? []),
|
phaseA: Phase.fromJson(json['phaseA'] as List<dynamic>? ?? []),
|
||||||
phaseB: Phase.fromJson(json['phaseB']as List<dynamic>? ?? []),
|
phaseB: Phase.fromJson(json['phaseB'] as List<dynamic>? ?? []),
|
||||||
phaseC: Phase.fromJson(json['phaseC']as List<dynamic>? ?? []),
|
phaseC: Phase.fromJson(json['phaseC'] as List<dynamic>? ?? []),
|
||||||
general: Phase.fromJson(json['general']as List<dynamic>? ?? []
|
general: Phase.fromJson(json['general'] as List<dynamic>? ?? []),
|
||||||
));
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory PowerStatus.fromStatusList(List<Status> statuses) {
|
||||||
|
List<DataPoint> extractPhase(String prefix) {
|
||||||
|
return statuses
|
||||||
|
.where((s) => s.code.endsWith(prefix))
|
||||||
|
.map((s) => DataPoint(code: s.code, value: s.value))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return PowerStatus(
|
||||||
|
phaseA: Phase(dataPoints: extractPhase('A')),
|
||||||
|
phaseB: Phase(dataPoints: extractPhase('B')),
|
||||||
|
phaseC: Phase(dataPoints: extractPhase('C')),
|
||||||
|
general: Phase(dataPoints: extractPhase('')),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,19 +143,6 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
|||||||
color: ColorsManager.primaryColor,
|
color: ColorsManager.primaryColor,
|
||||||
),
|
),
|
||||||
|
|
||||||
HomeItemModel(
|
|
||||||
title: 'Syncrow Analytics',
|
|
||||||
icon: Assets.devicesIcon,
|
|
||||||
active: true,
|
|
||||||
onPress: (context) {
|
|
||||||
context.read<SpaceTreeBloc>().add(ClearCachedData());
|
|
||||||
BlocProvider.of<RoutineBloc>(context)
|
|
||||||
.add(const TriggerSwitchTabsEvent(isRoutineTab: false));
|
|
||||||
context.go(RoutesConst.analytics);
|
|
||||||
},
|
|
||||||
color: ColorsManager.primaryColor,
|
|
||||||
),
|
|
||||||
|
|
||||||
// HomeItemModel(
|
// HomeItemModel(
|
||||||
// title: 'Move in',
|
// title: 'Move in',
|
||||||
// icon: Assets.moveinIcon,
|
// icon: Assets.moveinIcon,
|
||||||
|
@ -19,7 +19,6 @@ 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 UsersPage extends StatelessWidget {
|
class UsersPage extends StatelessWidget {
|
||||||
UsersPage({super.key});
|
UsersPage({super.key});
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||||
import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart';
|
import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart';
|
||||||
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
|
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
|
||||||
@ -90,8 +89,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems);
|
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems);
|
||||||
|
|
||||||
// Find the index of the item in teh current itemsList
|
// Find the index of the item in teh current itemsList
|
||||||
int index =
|
int index = updatedIfItems
|
||||||
updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
||||||
// Replace the map if the index is valid
|
// Replace the map if the index is valid
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
updatedIfItems[index] = event.item;
|
updatedIfItems[index] = event.item;
|
||||||
@ -100,9 +99,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.isTabToRun) {
|
if (event.isTabToRun) {
|
||||||
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
|
emit(state.copyWith(
|
||||||
|
ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
|
||||||
} else {
|
} else {
|
||||||
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
|
emit(state.copyWith(
|
||||||
|
ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,8 +111,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
final currentItems = List<Map<String, dynamic>>.from(state.thenItems);
|
final currentItems = List<Map<String, dynamic>>.from(state.thenItems);
|
||||||
|
|
||||||
// Find the index of the item in teh current itemsList
|
// Find the index of the item in teh current itemsList
|
||||||
int index =
|
int index = currentItems
|
||||||
currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
||||||
// Replace the map if the index is valid
|
// Replace the map if the index is valid
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
currentItems[index] = event.item;
|
currentItems[index] = event.item;
|
||||||
@ -122,7 +123,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
emit(state.copyWith(thenItems: currentItems));
|
emit(state.copyWith(thenItems: currentItems));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter<RoutineState> emit) {
|
void _onAddFunctionsToRoutine(
|
||||||
|
AddFunctionToRoutine event, Emitter<RoutineState> emit) {
|
||||||
try {
|
try {
|
||||||
if (event.functions.isEmpty) return;
|
if (event.functions.isEmpty) return;
|
||||||
|
|
||||||
@ -172,17 +174,20 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
||||||
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
|
if (createRoutineBloc.selectedSpaceId == '' &&
|
||||||
|
createRoutineBloc.selectedCommunityId == '') {
|
||||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||||
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
List<String> spacesList =
|
||||||
|
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||||
for (var spaceId in spacesList) {
|
for (var spaceId in spacesList) {
|
||||||
scenes.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid));
|
scenes
|
||||||
|
.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scenes.addAll(await SceneApi.getScenes(
|
scenes.addAll(await SceneApi.getScenes(createRoutineBloc.selectedSpaceId,
|
||||||
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectUuid));
|
createRoutineBloc.selectedCommunityId, projectUuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
@ -199,7 +204,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onLoadAutomation(LoadAutomation event, Emitter<RoutineState> emit) async {
|
Future<void> _onLoadAutomation(
|
||||||
|
LoadAutomation event, Emitter<RoutineState> emit) async {
|
||||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||||
List<ScenesModel> automations = [];
|
List<ScenesModel> automations = [];
|
||||||
final projectId = await ProjectManager.getProjectUUID() ?? '';
|
final projectId = await ProjectManager.getProjectUUID() ?? '';
|
||||||
@ -207,17 +213,22 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
||||||
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
||||||
try {
|
try {
|
||||||
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
|
if (createRoutineBloc.selectedSpaceId == '' &&
|
||||||
|
createRoutineBloc.selectedCommunityId == '') {
|
||||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||||
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
List<String> spacesList =
|
||||||
|
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||||
for (var spaceId in spacesList) {
|
for (var spaceId in spacesList) {
|
||||||
automations.addAll(await SceneApi.getAutomation(spaceId, communityId, projectId));
|
automations.addAll(
|
||||||
|
await SceneApi.getAutomation(spaceId, communityId, projectId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
automations.addAll(await SceneApi.getAutomation(
|
automations.addAll(await SceneApi.getAutomation(
|
||||||
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectId));
|
createRoutineBloc.selectedSpaceId,
|
||||||
|
createRoutineBloc.selectedCommunityId,
|
||||||
|
projectId));
|
||||||
}
|
}
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
automations: automations,
|
automations: automations,
|
||||||
@ -233,14 +244,16 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onSearchRoutines(SearchRoutines event, Emitter<RoutineState> emit) async {
|
FutureOr<void> _onSearchRoutines(
|
||||||
|
SearchRoutines event, Emitter<RoutineState> emit) async {
|
||||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
emit(state.copyWith(isLoading: false, errorMessage: null));
|
emit(state.copyWith(isLoading: false, errorMessage: null));
|
||||||
emit(state.copyWith(searchText: event.query));
|
emit(state.copyWith(searchText: event.query));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onAddSelectedIcon(AddSelectedIcon event, Emitter<RoutineState> emit) {
|
FutureOr<void> _onAddSelectedIcon(
|
||||||
|
AddSelectedIcon event, Emitter<RoutineState> emit) {
|
||||||
emit(state.copyWith(selectedIcon: event.icon));
|
emit(state.copyWith(selectedIcon: event.icon));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,7 +267,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
return actions.last['deviceId'] == 'delay';
|
return actions.last['deviceId'] == 'delay';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onCreateScene(CreateSceneEvent event, Emitter<RoutineState> emit) async {
|
Future<void> _onCreateScene(
|
||||||
|
CreateSceneEvent event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
// Check if first action is delay
|
// Check if first action is delay
|
||||||
// if (_isFirstActionDelay(state.thenItems)) {
|
// if (_isFirstActionDelay(state.thenItems)) {
|
||||||
@ -343,7 +357,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onCreateAutomation(CreateAutomationEvent event, Emitter<RoutineState> emit) async {
|
Future<void> _onCreateAutomation(
|
||||||
|
CreateAutomationEvent event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
if (state.routineName == null || state.routineName!.isEmpty) {
|
if (state.routineName == null || state.routineName!.isEmpty) {
|
||||||
@ -456,7 +471,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
actions: actions,
|
actions: actions,
|
||||||
);
|
);
|
||||||
|
|
||||||
final result = await SceneApi.createAutomation(createAutomationModel, projectUuid);
|
final result =
|
||||||
|
await SceneApi.createAutomation(createAutomationModel, projectUuid);
|
||||||
if (result['success']) {
|
if (result['success']) {
|
||||||
add(ResetRoutineState());
|
add(ResetRoutineState());
|
||||||
add(const LoadAutomation());
|
add(const LoadAutomation());
|
||||||
@ -477,17 +493,21 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onRemoveDragCard(RemoveDragCard event, Emitter<RoutineState> emit) {
|
FutureOr<void> _onRemoveDragCard(
|
||||||
|
RemoveDragCard event, Emitter<RoutineState> emit) {
|
||||||
if (event.isFromThen) {
|
if (event.isFromThen) {
|
||||||
final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
|
final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
|
||||||
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
final selectedFunctions =
|
||||||
|
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
||||||
|
|
||||||
thenItems.removeAt(event.index);
|
thenItems.removeAt(event.index);
|
||||||
selectedFunctions.remove(event.key);
|
selectedFunctions.remove(event.key);
|
||||||
emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions));
|
emit(state.copyWith(
|
||||||
|
thenItems: thenItems, selectedFunctions: selectedFunctions));
|
||||||
} else {
|
} else {
|
||||||
final ifItems = List<Map<String, dynamic>>.from(state.ifItems);
|
final ifItems = List<Map<String, dynamic>>.from(state.ifItems);
|
||||||
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
final selectedFunctions =
|
||||||
|
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
||||||
|
|
||||||
ifItems.removeAt(event.index);
|
ifItems.removeAt(event.index);
|
||||||
selectedFunctions.remove(event.key);
|
selectedFunctions.remove(event.key);
|
||||||
@ -510,11 +530,13 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
|
FutureOr<void> _onEffectiveTimeEvent(
|
||||||
|
EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
|
||||||
emit(state.copyWith(effectiveTime: event.effectiveTime));
|
emit(state.copyWith(effectiveTime: event.effectiveTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onSetRoutineName(SetRoutineName event, Emitter<RoutineState> emit) {
|
FutureOr<void> _onSetRoutineName(
|
||||||
|
SetRoutineName event, Emitter<RoutineState> emit) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
routineName: event.name,
|
routineName: event.name,
|
||||||
));
|
));
|
||||||
@ -641,7 +663,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// return (thenItems, ifItems, currentFunctions);
|
// return (thenItems, ifItems, currentFunctions);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
Future<void> _onGetSceneDetails(GetSceneDetails event, Emitter<RoutineState> emit) async {
|
Future<void> _onGetSceneDetails(
|
||||||
|
GetSceneDetails event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
@ -690,7 +713,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
deviceCards[deviceId] = {
|
deviceCards[deviceId] = {
|
||||||
'entityId': action.entityId,
|
'entityId': action.entityId,
|
||||||
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
||||||
'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay'
|
'uniqueCustomId':
|
||||||
|
action.type == 'automation' || action.actionExecutor == 'delay'
|
||||||
? action.entityId
|
? action.entityId
|
||||||
: const Uuid().v4(),
|
: const Uuid().v4(),
|
||||||
'title': action.actionExecutor == 'delay'
|
'title': action.actionExecutor == 'delay'
|
||||||
@ -732,7 +756,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
// emit(state.copyWith(automationActionExecutor: action.actionExecutor));
|
// emit(state.copyWith(automationActionExecutor: action.actionExecutor));
|
||||||
} else if (action.executorProperty != null && action.actionExecutor != 'delay') {
|
} else if (action.executorProperty != null &&
|
||||||
|
action.actionExecutor != 'delay') {
|
||||||
final functions = matchingDevice?.functions ?? [];
|
final functions = matchingDevice?.functions ?? [];
|
||||||
final functionCode = action.executorProperty?.functionCode;
|
final functionCode = action.executorProperty?.functionCode;
|
||||||
for (DeviceFunction function in functions) {
|
for (DeviceFunction function in functions) {
|
||||||
@ -798,7 +823,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onResetRoutineState(ResetRoutineState event, Emitter<RoutineState> emit) {
|
FutureOr<void> _onResetRoutineState(
|
||||||
|
ResetRoutineState event, Emitter<RoutineState> emit) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
ifItems: [],
|
ifItems: [],
|
||||||
thenItems: [],
|
thenItems: [],
|
||||||
@ -831,7 +857,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||||
if (state.isTabToRun) {
|
if (state.isTabToRun) {
|
||||||
await SceneApi.deleteScene(
|
await SceneApi.deleteScene(
|
||||||
unitUuid: spaceBloc.state.selectedSpaces[0], sceneId: state.sceneId ?? '');
|
unitUuid: spaceBloc.state.selectedSpaces[0],
|
||||||
|
sceneId: state.sceneId ?? '');
|
||||||
} else {
|
} else {
|
||||||
await SceneApi.deleteAutomation(
|
await SceneApi.deleteAutomation(
|
||||||
unitUuid: spaceBloc.state.selectedSpaces[0],
|
unitUuid: spaceBloc.state.selectedSpaces[0],
|
||||||
@ -876,7 +903,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
|
FutureOr<void> _fetchDevices(
|
||||||
|
FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
|
||||||
emit(state.copyWith(isLoading: true));
|
emit(state.copyWith(isLoading: true));
|
||||||
try {
|
try {
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
@ -885,17 +913,21 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
||||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||||
|
|
||||||
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
|
if (createRoutineBloc.selectedSpaceId == '' &&
|
||||||
|
createRoutineBloc.selectedCommunityId == '') {
|
||||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||||
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
List<String> spacesList =
|
||||||
|
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||||
for (var spaceId in spacesList) {
|
for (var spaceId in spacesList) {
|
||||||
devices.addAll(
|
devices.addAll(await DevicesManagementApi()
|
||||||
await DevicesManagementApi().fetchDevices(communityId, spaceId, projectUuid));
|
.fetchDevices(communityId, spaceId, projectUuid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
devices.addAll(await DevicesManagementApi().fetchDevices(
|
devices.addAll(await DevicesManagementApi().fetchDevices(
|
||||||
createRoutineBloc.selectedCommunityId, createRoutineBloc.selectedSpaceId, projectUuid));
|
createRoutineBloc.selectedCommunityId,
|
||||||
|
createRoutineBloc.selectedSpaceId,
|
||||||
|
projectUuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(state.copyWith(isLoading: false, devices: devices));
|
emit(state.copyWith(isLoading: false, devices: devices));
|
||||||
@ -904,7 +936,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onUpdateScene(UpdateScene event, Emitter<RoutineState> emit) async {
|
FutureOr<void> _onUpdateScene(
|
||||||
|
UpdateScene event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
// Check if first action is delay
|
// Check if first action is delay
|
||||||
// if (_isFirstActionDelay(state.thenItems)) {
|
// if (_isFirstActionDelay(state.thenItems)) {
|
||||||
@ -971,7 +1004,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
actions: actions,
|
actions: actions,
|
||||||
);
|
);
|
||||||
|
|
||||||
final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
|
final result =
|
||||||
|
await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
|
||||||
if (result['success']) {
|
if (result['success']) {
|
||||||
add(ResetRoutineState());
|
add(ResetRoutineState());
|
||||||
add(const LoadScenes());
|
add(const LoadScenes());
|
||||||
@ -990,7 +1024,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onUpdateAutomation(UpdateAutomation event, Emitter<RoutineState> emit) async {
|
FutureOr<void> _onUpdateAutomation(
|
||||||
|
UpdateAutomation event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
if (state.routineName == null || state.routineName!.isEmpty) {
|
if (state.routineName == null || state.routineName!.isEmpty) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
@ -1106,8 +1141,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
if (result['success']) {
|
if (result['success']) {
|
||||||
add(ResetRoutineState());
|
add(ResetRoutineState());
|
||||||
add(LoadAutomation());
|
add(const LoadAutomation());
|
||||||
add(LoadScenes());
|
add(const LoadScenes());
|
||||||
} else {
|
} else {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
@ -1291,10 +1326,13 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final ifItems = deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
|
final ifItems =
|
||||||
|
deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
|
||||||
final thenItems = deviceThenCards.values
|
final thenItems = deviceThenCards.values
|
||||||
.where((card) =>
|
.where((card) =>
|
||||||
card['type'] == 'action' || card['type'] == 'automation' || card['type'] == 'scene')
|
card['type'] == 'action' ||
|
||||||
|
card['type'] == 'automation' ||
|
||||||
|
card['type'] == 'scene')
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
@ -1316,7 +1354,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSceneTrigger(SceneTrigger event, Emitter<RoutineState> emit) async {
|
Future<void> _onSceneTrigger(
|
||||||
|
SceneTrigger event, Emitter<RoutineState> emit) async {
|
||||||
emit(state.copyWith(loadingSceneId: event.sceneId));
|
emit(state.copyWith(loadingSceneId: event.sceneId));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1361,21 +1400,24 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
event.automationStatusUpdate.spaceUuid, event.communityId, projectId);
|
event.automationStatusUpdate.spaceUuid, event.communityId, projectId);
|
||||||
|
|
||||||
// Remove from loading set safely
|
// Remove from loading set safely
|
||||||
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
|
final updatedLoadingIds = {...state.loadingAutomationIds!}
|
||||||
|
..remove(event.automationId);
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
automations: updatedAutomations,
|
automations: updatedAutomations,
|
||||||
loadingAutomationIds: updatedLoadingIds,
|
loadingAutomationIds: updatedLoadingIds,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
|
final updatedLoadingIds = {...state.loadingAutomationIds!}
|
||||||
|
..remove(event.automationId);
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
loadingAutomationIds: updatedLoadingIds,
|
loadingAutomationIds: updatedLoadingIds,
|
||||||
errorMessage: 'Update failed',
|
errorMessage: 'Update failed',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
|
final updatedLoadingIds = {...state.loadingAutomationIds!}
|
||||||
|
..remove(event.automationId);
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
loadingAutomationIds: updatedLoadingIds,
|
loadingAutomationIds: updatedLoadingIds,
|
||||||
errorMessage: 'Update error: ${e.toString()}',
|
errorMessage: 'Update error: ${e.toString()}',
|
||||||
|
@ -23,6 +23,7 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
on<OnCommunityUpdated>(_onCommunityUpdate);
|
on<OnCommunityUpdated>(_onCommunityUpdate);
|
||||||
on<PaginationEvent>(_fetchPaginationSpaces);
|
on<PaginationEvent>(_fetchPaginationSpaces);
|
||||||
on<DebouncedSearchEvent>(_onDebouncedSearch);
|
on<DebouncedSearchEvent>(_onDebouncedSearch);
|
||||||
|
on<SpaceTreeClearSelectionEvent>(_onSpaceTreeClearSelectionEvent);
|
||||||
}
|
}
|
||||||
Timer _timer = Timer(const Duration(microseconds: 0), () {});
|
Timer _timer = Timer(const Duration(microseconds: 0), () {});
|
||||||
|
|
||||||
@ -36,8 +37,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
final updatedCommunity = event.updatedCommunity;
|
final updatedCommunity = event.updatedCommunity;
|
||||||
final updatedCommunities = List<CommunityModel>.from(state.communityList);
|
final updatedCommunities = List<CommunityModel>.from(state.communityList);
|
||||||
|
|
||||||
final index =
|
final index = updatedCommunities
|
||||||
updatedCommunities.indexWhere((community) => community.uuid == updatedCommunity.uuid);
|
.indexWhere((community) => community.uuid == updatedCommunity.uuid);
|
||||||
|
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
updatedCommunities[index] = updatedCommunity;
|
updatedCommunities[index] = updatedCommunity;
|
||||||
@ -93,8 +94,11 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
if (paginationModel.hasNext && state.searchQuery.isEmpty) {
|
if (paginationModel.hasNext && state.searchQuery.isEmpty) {
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
|
|
||||||
paginationModel = await CommunitySpaceManagementApi().fetchCommunitiesAndSpaces(
|
paginationModel = await CommunitySpaceManagementApi()
|
||||||
projectId: projectUuid, page: paginationModel.pageNum, search: state.searchQuery);
|
.fetchCommunitiesAndSpaces(
|
||||||
|
projectId: projectUuid,
|
||||||
|
page: paginationModel.pageNum,
|
||||||
|
search: state.searchQuery);
|
||||||
|
|
||||||
communities.addAll(paginationModel.communities);
|
communities.addAll(paginationModel.communities);
|
||||||
}
|
}
|
||||||
@ -107,16 +111,19 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
paginationIsLoading: false));
|
paginationIsLoading: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onCommunityAdded(OnCommunityAdded event, Emitter<SpaceTreeState> emit) async {
|
void _onCommunityAdded(
|
||||||
|
OnCommunityAdded event, Emitter<SpaceTreeState> emit) async {
|
||||||
final updatedCommunities = List<CommunityModel>.from(state.communityList);
|
final updatedCommunities = List<CommunityModel>.from(state.communityList);
|
||||||
updatedCommunities.add(event.newCommunity);
|
updatedCommunities.add(event.newCommunity);
|
||||||
|
|
||||||
emit(state.copyWith(communitiesList: updatedCommunities));
|
emit(state.copyWith(communitiesList: updatedCommunities));
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCommunityExpanded(OnCommunityExpanded event, Emitter<SpaceTreeState> emit) async {
|
_onCommunityExpanded(
|
||||||
|
OnCommunityExpanded event, Emitter<SpaceTreeState> emit) async {
|
||||||
try {
|
try {
|
||||||
List<String> updatedExpandedCommunityList = List.from(state.expandedCommunities);
|
List<String> updatedExpandedCommunityList =
|
||||||
|
List.from(state.expandedCommunities);
|
||||||
|
|
||||||
if (updatedExpandedCommunityList.contains(event.communityId)) {
|
if (updatedExpandedCommunityList.contains(event.communityId)) {
|
||||||
updatedExpandedCommunityList.remove(event.communityId);
|
updatedExpandedCommunityList.remove(event.communityId);
|
||||||
@ -148,14 +155,18 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCommunitySelected(OnCommunitySelected event, Emitter<SpaceTreeState> emit) async {
|
_onCommunitySelected(
|
||||||
|
OnCommunitySelected event, Emitter<SpaceTreeState> emit) async {
|
||||||
try {
|
try {
|
||||||
List<String> updatedSelectedCommunities =
|
List<String> updatedSelectedCommunities =
|
||||||
List.from(state.selectedCommunities.toSet().toList());
|
List.from(state.selectedCommunities.toSet().toList());
|
||||||
List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList());
|
List<String> updatedSelectedSpaces =
|
||||||
|
List.from(state.selectedSpaces.toSet().toList());
|
||||||
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
|
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
|
||||||
Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
|
Map<String, List<String>> communityAndSpaces =
|
||||||
List<String> selectedSpacesInCommunity = communityAndSpaces[event.communityId] ?? [];
|
Map.from(state.selectedCommunityAndSpaces);
|
||||||
|
List<String> selectedSpacesInCommunity =
|
||||||
|
communityAndSpaces[event.communityId] ?? [];
|
||||||
|
|
||||||
List<String> childrenIds = _getAllChildIds(event.children);
|
List<String> childrenIds = _getAllChildIds(event.children);
|
||||||
|
|
||||||
@ -188,11 +199,14 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
try {
|
try {
|
||||||
List<String> updatedSelectedCommunities =
|
List<String> updatedSelectedCommunities =
|
||||||
List.from(state.selectedCommunities.toSet().toList());
|
List.from(state.selectedCommunities.toSet().toList());
|
||||||
List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList());
|
List<String> updatedSelectedSpaces =
|
||||||
|
List.from(state.selectedSpaces.toSet().toList());
|
||||||
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
|
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
|
||||||
Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
|
Map<String, List<String>> communityAndSpaces =
|
||||||
|
Map.from(state.selectedCommunityAndSpaces);
|
||||||
|
|
||||||
List<String> selectedSpacesInCommunity = communityAndSpaces[event.communityModel.uuid] ?? [];
|
List<String> selectedSpacesInCommunity =
|
||||||
|
communityAndSpaces[event.communityModel.uuid] ?? [];
|
||||||
|
|
||||||
List<String> childrenIds = _getAllChildIds(event.children);
|
List<String> childrenIds = _getAllChildIds(event.children);
|
||||||
bool isChildSelected = false;
|
bool isChildSelected = false;
|
||||||
@ -215,9 +229,11 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
selectedSpacesInCommunity.addAll(childrenIds);
|
selectedSpacesInCommunity.addAll(childrenIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> spaces = _getThePathToChild(event.communityModel.uuid, event.spaceId);
|
List<String> spaces =
|
||||||
|
_getThePathToChild(event.communityModel.uuid, event.spaceId);
|
||||||
for (String space in spaces) {
|
for (String space in spaces) {
|
||||||
if (!updatedSelectedSpaces.contains(space) && !updatedSoldChecks.contains(space)) {
|
if (!updatedSelectedSpaces.contains(space) &&
|
||||||
|
!updatedSoldChecks.contains(space)) {
|
||||||
updatedSoldChecks.add(space);
|
updatedSoldChecks.add(space);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -240,7 +256,9 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
updatedSoldChecks.remove(event.spaceId);
|
updatedSoldChecks.remove(event.spaceId);
|
||||||
|
|
||||||
List<String> parents =
|
List<String> parents =
|
||||||
_getThePathToChild(event.communityModel.uuid, event.spaceId).toSet().toList();
|
_getThePathToChild(event.communityModel.uuid, event.spaceId)
|
||||||
|
.toSet()
|
||||||
|
.toList();
|
||||||
|
|
||||||
if (updatedSelectedSpaces.isEmpty) {
|
if (updatedSelectedSpaces.isEmpty) {
|
||||||
updatedSoldChecks.removeWhere(parents.contains);
|
updatedSoldChecks.removeWhere(parents.contains);
|
||||||
@ -248,7 +266,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
} else {
|
} else {
|
||||||
// Check if any parent has selected children
|
// Check if any parent has selected children
|
||||||
for (String space in parents) {
|
for (String space in parents) {
|
||||||
if (!_noChildrenSelected(event.communityModel, space, updatedSelectedSpaces, parents)) {
|
if (!_noChildrenSelected(
|
||||||
|
event.communityModel, space, updatedSelectedSpaces, parents)) {
|
||||||
updatedSoldChecks.remove(space);
|
updatedSoldChecks.remove(space);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -273,8 +292,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_noChildrenSelected(
|
_noChildrenSelected(CommunityModel community, String spaceId,
|
||||||
CommunityModel community, String spaceId, List<String> selectedSpaces, List<String> parents) {
|
List<String> selectedSpaces, List<String> parents) {
|
||||||
if (selectedSpaces.contains(spaceId)) {
|
if (selectedSpaces.contains(spaceId)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -300,7 +319,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
if (_timer.isActive) {
|
if (_timer.isActive) {
|
||||||
_timer.cancel(); // clear timer
|
_timer.cancel(); // clear timer
|
||||||
}
|
}
|
||||||
_timer = Timer(duration, () async => add(DebouncedSearchEvent(event.searchQuery)));
|
_timer =
|
||||||
|
Timer(duration, () async => add(DebouncedSearchEvent(event.searchQuery)));
|
||||||
|
|
||||||
// List<CommunityModel> communities = List.from(state.communityList);
|
// List<CommunityModel> communities = List.from(state.communityList);
|
||||||
// List<CommunityModel> filteredCommunity = [];
|
// List<CommunityModel> filteredCommunity = [];
|
||||||
@ -324,7 +344,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDebouncedSearch(DebouncedSearchEvent event, Emitter<SpaceTreeState> emit) async {
|
_onDebouncedSearch(
|
||||||
|
DebouncedSearchEvent event, Emitter<SpaceTreeState> emit) async {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isSearching: true,
|
isSearching: true,
|
||||||
));
|
));
|
||||||
@ -333,7 +354,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
|
|
||||||
paginationModel = await CommunitySpaceManagementApi()
|
paginationModel = await CommunitySpaceManagementApi()
|
||||||
.fetchCommunitiesAndSpaces(projectId: projectUuid, page: 1, search: event.searchQuery);
|
.fetchCommunitiesAndSpaces(
|
||||||
|
projectId: projectUuid, page: 1, search: event.searchQuery);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
@ -405,8 +427,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _anySpacesSelectedInCommunity(
|
bool _anySpacesSelectedInCommunity(CommunityModel community,
|
||||||
CommunityModel community, List<String> selectedSpaces, List<String> partialCheckedList) {
|
List<String> selectedSpaces, List<String> partialCheckedList) {
|
||||||
bool result = false;
|
bool result = false;
|
||||||
List<String> ids = _getAllChildIds(community.spaces);
|
List<String> ids = _getAllChildIds(community.spaces);
|
||||||
for (var id in ids) {
|
for (var id in ids) {
|
||||||
@ -435,7 +457,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> _getAllParentsIds(SpaceModel child, String spaceId, List<String> listIds) {
|
List<String> _getAllParentsIds(
|
||||||
|
SpaceModel child, String spaceId, List<String> listIds) {
|
||||||
List<String> ids = listIds;
|
List<String> ids = listIds;
|
||||||
|
|
||||||
ids.add(child.uuid ?? '');
|
ids.add(child.uuid ?? '');
|
||||||
@ -457,6 +480,19 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onSpaceTreeClearSelectionEvent(
|
||||||
|
SpaceTreeClearSelectionEvent event,
|
||||||
|
Emitter<SpaceTreeState> emit,
|
||||||
|
) async {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
selectedCommunities: [],
|
||||||
|
selectedCommunityAndSpaces: {},
|
||||||
|
selectedSpaces: [],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
_timer.cancel();
|
_timer.cancel();
|
||||||
|
@ -108,3 +108,7 @@ class OnCommunityUpdated extends SpaceTreeEvent {
|
|||||||
class ClearAllData extends SpaceTreeEvent {}
|
class ClearAllData extends SpaceTreeEvent {}
|
||||||
|
|
||||||
class ClearCachedData extends SpaceTreeEvent {}
|
class ClearCachedData extends SpaceTreeEvent {}
|
||||||
|
|
||||||
|
class SpaceTreeClearSelectionEvent extends SpaceTreeEvent {
|
||||||
|
const SpaceTreeClearSelectionEvent();
|
||||||
|
}
|
||||||
|
@ -170,13 +170,13 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
|||||||
communities[index].uuid,
|
communities[index].uuid,
|
||||||
),
|
),
|
||||||
onItemSelected: () {
|
onItemSelected: () {
|
||||||
|
widget.onSelect();
|
||||||
context.read<SpaceTreeBloc>().add(
|
context.read<SpaceTreeBloc>().add(
|
||||||
OnCommunitySelected(
|
OnCommunitySelected(
|
||||||
communities[index].uuid,
|
communities[index].uuid,
|
||||||
communities[index].spaces,
|
communities[index].spaces,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
widget.onSelect();
|
|
||||||
},
|
},
|
||||||
children: communities[index].spaces.map(
|
children: communities[index].spaces.map(
|
||||||
(space) {
|
(space) {
|
||||||
@ -195,6 +195,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
|||||||
isParentSelected) {
|
isParentSelected) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
widget.onSelect();
|
||||||
context.read<SpaceTreeBloc>().add(
|
context.read<SpaceTreeBloc>().add(
|
||||||
OnSpaceSelected(
|
OnSpaceSelected(
|
||||||
communities[index],
|
communities[index],
|
||||||
@ -202,7 +203,6 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
|||||||
space.children,
|
space.children,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
widget.onSelect();
|
|
||||||
},
|
},
|
||||||
onExpansionChanged: () =>
|
onExpansionChanged: () =>
|
||||||
context.read<SpaceTreeBloc>().add(
|
context.read<SpaceTreeBloc>().add(
|
||||||
|
@ -457,16 +457,17 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
emit(SpaceManagementLoading());
|
emit(SpaceManagementLoading());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
final spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
||||||
|
|
||||||
final updatedSpaces =
|
final updatedSpaces =
|
||||||
await saveSpacesHierarchically(event.context, event.spaces, event.communityUuid);
|
await saveSpacesHierarchically(event.context, event.spaces, event.communityUuid);
|
||||||
|
|
||||||
final allSpaces = await _fetchSpacesForCommunity(event.communityUuid);
|
final allSpaces = await _fetchSpacesForCommunity(event.communityUuid);
|
||||||
|
|
||||||
emit(SpaceCreationSuccess(spaces: updatedSpaces));
|
emit(SpaceCreationSuccess(spaces: updatedSpaces));
|
||||||
|
|
||||||
if (previousState is SpaceManagementLoaded) {
|
if (previousState is SpaceManagementLoaded) {
|
||||||
await _updateLoadedState(
|
await _updateLoadedState(
|
||||||
event.context,
|
spaceTreeState,
|
||||||
previousState,
|
previousState,
|
||||||
allSpaces,
|
allSpaces,
|
||||||
event.communityUuid,
|
event.communityUuid,
|
||||||
@ -483,15 +484,17 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateLoadedState(
|
Future<void> _updateLoadedState(
|
||||||
BuildContext context,
|
SpaceTreeState spaceTreeState,
|
||||||
SpaceManagementLoaded previousState,
|
SpaceManagementLoaded previousState,
|
||||||
List<SpaceModel> allSpaces,
|
List<SpaceModel> allSpaces,
|
||||||
String communityUuid,
|
String communityUuid,
|
||||||
Emitter<SpaceManagementState> emit,
|
Emitter<SpaceManagementState> emit,
|
||||||
) async {
|
) async {
|
||||||
|
try {
|
||||||
var prevSpaceModels = await fetchSpaceModels();
|
var prevSpaceModels = await fetchSpaceModels();
|
||||||
|
|
||||||
await fetchTags();
|
await fetchTags();
|
||||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
|
||||||
final communities = spaceTreeState.searchQuery.isNotEmpty
|
final communities = spaceTreeState.searchQuery.isNotEmpty
|
||||||
? spaceTreeState.filteredCommunity
|
? spaceTreeState.filteredCommunity
|
||||||
: spaceTreeState.communityList;
|
: spaceTreeState.communityList;
|
||||||
@ -507,12 +510,14 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
selectedCommunity: community,
|
selectedCommunity: community,
|
||||||
selectedSpace: null,
|
selectedSpace: null,
|
||||||
spaceModels: prevSpaceModels,
|
spaceModels: prevSpaceModels,
|
||||||
allTags: _cachedTags ?? []));
|
allTags: _cachedTags ?? [],
|
||||||
|
));
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
print("Community not found");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<SpaceModel>> saveSpacesHierarchically(
|
Future<List<SpaceModel>> saveSpacesHierarchically(
|
||||||
|
@ -53,6 +53,9 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is SpaceManagementLoading) {
|
if (state is SpaceManagementLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
if (state is SpaceManagementInitial) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
} else if (state is BlankState) {
|
} else if (state is BlankState) {
|
||||||
return LoadedSpaceView(
|
return LoadedSpaceView(
|
||||||
communities: state.communities,
|
communities: state.communities,
|
||||||
|
@ -526,6 +526,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
isNameFieldInvalid = true;
|
isNameFieldInvalid = true;
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
} else if (isNameFieldExist) {
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? '');
|
String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? '');
|
||||||
if (newName.isNotEmpty) {
|
if (newName.isNotEmpty) {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.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';
|
|
||||||
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
|
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
|
||||||
|
@ -2,10 +2,10 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/common/widgets/empty_search_result_widget.dart';
|
|
||||||
import 'package:syncrow_web/common/widgets/search_bar.dart';
|
import 'package:syncrow_web/common/widgets/search_bar.dart';
|
||||||
import 'package:syncrow_web/common/widgets/sidebar_communities_list.dart';
|
import 'package:syncrow_web/common/widgets/sidebar_communities_list.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||||
@ -114,7 +114,9 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
|||||||
return Container(
|
return Container(
|
||||||
width: _width,
|
width: _width,
|
||||||
decoration: subSectionContainerDecoration,
|
decoration: subSectionContainerDecoration,
|
||||||
child: Column(
|
child: spaceTreeState is SpaceTreeLoadingState
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -124,10 +126,9 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Visibility(
|
child: Builder(
|
||||||
visible: filteredCommunities.isNotEmpty,
|
builder: (_) {
|
||||||
replacement: const EmptySearchResultWidget(),
|
return SidebarCommunitiesList(
|
||||||
child: SidebarCommunitiesList(
|
|
||||||
scrollController: _scrollController,
|
scrollController: _scrollController,
|
||||||
onScrollToEnd: () {},
|
onScrollToEnd: () {},
|
||||||
communities: filteredCommunities,
|
communities: filteredCommunities,
|
||||||
@ -144,9 +145,15 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _buildCommunityTile(context, filteredCommunities[index]);
|
return _buildCommunityTile(context, filteredCommunities[index]);
|
||||||
}),
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (spaceTreeState.paginationIsLoading || spaceTreeState.isSearching)
|
||||||
|
Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -34,9 +34,8 @@ class UserPermissionApi {
|
|||||||
path: ApiEndpoints.roleTypes,
|
path: ApiEndpoints.roleTypes,
|
||||||
showServerMessage: true,
|
showServerMessage: true,
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
final List<RoleTypeModel> fetchedRoles = (json['data'] as List)
|
final List<RoleTypeModel> fetchedRoles =
|
||||||
.map((item) => RoleTypeModel.fromJson(item))
|
(json['data'] as List).map((item) => RoleTypeModel.fromJson(item)).toList();
|
||||||
.toList();
|
|
||||||
return fetchedRoles;
|
return fetchedRoles;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -48,9 +47,7 @@ class UserPermissionApi {
|
|||||||
path: ApiEndpoints.permission.replaceAll("roleUuid", roleUuid),
|
path: ApiEndpoints.permission.replaceAll("roleUuid", roleUuid),
|
||||||
showServerMessage: true,
|
showServerMessage: true,
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
return (json as List)
|
return (json as List).map((data) => PermissionOption.fromJson(data)).toList();
|
||||||
.map((data) => PermissionOption.fromJson(data))
|
|
||||||
.toList();
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return response ?? [];
|
return response ?? [];
|
||||||
@ -195,14 +192,10 @@ class UserPermissionApi {
|
|||||||
|
|
||||||
Future<bool> changeUserStatusById(userUuid, status, String projectUuid) async {
|
Future<bool> changeUserStatusById(userUuid, status, String projectUuid) async {
|
||||||
try {
|
try {
|
||||||
Map<String, dynamic> bodya = {
|
Map<String, dynamic> bodya = {"disable": status, "projectUuid": projectUuid};
|
||||||
"disable": status,
|
|
||||||
"projectUuid": projectUuid
|
|
||||||
};
|
|
||||||
|
|
||||||
final response = await _httpService.put(
|
final response = await _httpService.put(
|
||||||
path: ApiEndpoints.changeUserStatus
|
path: ApiEndpoints.changeUserStatus.replaceAll("{invitedUserUuid}", userUuid),
|
||||||
.replaceAll("{invitedUserUuid}", userUuid),
|
|
||||||
body: bodya,
|
body: bodya,
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
return json['success'];
|
return json['success'];
|
||||||
|
@ -72,4 +72,5 @@ abstract class ColorsManager {
|
|||||||
//background: #F8F8F8;
|
//background: #F8F8F8;
|
||||||
static const Color vividBlue = Color(0xFF023DFE);
|
static const Color vividBlue = Color(0xFF023DFE);
|
||||||
static const Color semiTransparentRed = Color(0x99FF0000);
|
static const Color semiTransparentRed = Color(0x99FF0000);
|
||||||
|
static const Color grey700 = Color(0xFF2D3748);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
|
|
||||||
abstract class ApiEndpoints {
|
abstract class ApiEndpoints {
|
||||||
static const String projectUuid = "0e62577c-06fa-41b9-8a92-99a21fbaf51c";
|
static const String projectUuid = "bcda711e-9fc2-4168-a05e-171b4026d1ff";
|
||||||
static String baseUrl = dotenv.env['BASE_URL'] ?? '';
|
static String baseUrl = dotenv.env['BASE_URL'] ?? '';
|
||||||
static const String signUp = '/authentication/user/signup';
|
static const String signUp = '/authentication/user/signup';
|
||||||
static const String login = '/authentication/user/login';
|
static const String login = '/authentication/user/login';
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
class TempConst {
|
class TempConst {
|
||||||
static const projectId = '0e62577c-06fa-41b9-8a92-99a21fbaf51c';
|
static const projectId = 'bcda711e-9fc2-4168-a05e-171b4026d1ff';
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'color_manager.dart';
|
import 'color_manager.dart';
|
||||||
|
|
||||||
InputDecoration? textBoxDecoration(
|
InputDecoration? textBoxDecoration({bool suffixIcon = false, double radios = 8}) =>
|
||||||
{bool suffixIcon = false, double radios = 8}) =>
|
|
||||||
InputDecoration(
|
InputDecoration(
|
||||||
focusColor: ColorsManager.grayColor,
|
focusColor: ColorsManager.grayColor,
|
||||||
suffixIcon: suffixIcon ? const Icon(Icons.search) : null,
|
suffixIcon: suffixIcon ? const Icon(Icons.search) : null,
|
||||||
@ -68,10 +67,24 @@ BoxDecoration subSectionContainerDecoration = BoxDecoration(
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final secondarySection = BoxDecoration(
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.1),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 7,
|
||||||
|
offset: const Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
color: ColorsManager.circleRolesBackground,
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(15),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
InputDecoration inputTextFormDeco({hintText}) => InputDecoration(
|
InputDecoration inputTextFormDeco({hintText}) => InputDecoration(
|
||||||
hintText: hintText,
|
hintText: hintText,
|
||||||
border: const OutlineInputBorder(
|
border: const OutlineInputBorder(
|
||||||
|
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
width: 1,
|
width: 1,
|
||||||
color: ColorsManager.textGray, // Border color for unfocused state
|
color: ColorsManager.textGray, // Border color for unfocused state
|
||||||
|
14
pubspec.yaml
14
pubspec.yaml
@ -35,21 +35,21 @@ dependencies:
|
|||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.6
|
cupertino_icons: ^1.0.6
|
||||||
flutter_bloc: ^8.1.5
|
flutter_bloc: ^9.1.0
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
graphview: ^1.2.0
|
graphview: ^1.2.0
|
||||||
flutter_svg: ^2.0.10+1
|
flutter_svg: ^2.0.10+1
|
||||||
dio: ^5.5.0+1
|
dio: ^5.5.0+1
|
||||||
get_it: ^7.6.7
|
get_it: ^8.0.3
|
||||||
flutter_secure_storage: ^9.2.2
|
flutter_secure_storage: ^9.2.2
|
||||||
shared_preferences: ^2.3.0
|
shared_preferences: ^2.3.0
|
||||||
dropdown_button2: ^2.3.9
|
dropdown_button2: ^2.3.9
|
||||||
data_table_2: ^2.5.15
|
data_table_2: ^2.5.15
|
||||||
go_router:
|
go_router:
|
||||||
intl: ^0.19.0
|
intl: ^0.20.2
|
||||||
dropdown_search: ^5.0.6
|
dropdown_search: ^6.0.2
|
||||||
flutter_dotenv: ^5.1.0
|
flutter_dotenv: ^5.1.0
|
||||||
fl_chart: ^0.69.0
|
fl_chart: ^0.71.0
|
||||||
uuid: ^4.4.2
|
uuid: ^4.4.2
|
||||||
time_picker_spinner: ^1.0.0
|
time_picker_spinner: ^1.0.0
|
||||||
intl_phone_field: ^3.2.0
|
intl_phone_field: ^3.2.0
|
||||||
@ -60,7 +60,7 @@ dependencies:
|
|||||||
firebase_core: ^3.11.0
|
firebase_core: ^3.11.0
|
||||||
firebase_crashlytics: ^4.3.2
|
firebase_crashlytics: ^4.3.2
|
||||||
firebase_database: ^11.3.2
|
firebase_database: ^11.3.2
|
||||||
bloc: ^8.1.4
|
bloc: ^9.0.0
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
@ -72,7 +72,7 @@ dev_dependencies:
|
|||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^3.0.0
|
flutter_lints: ^5.0.0
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
Reference in New Issue
Block a user