mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 15:17:31 +00:00
Merge pull request #176 from SyncrowIOT/SP-1510-FE-Build-Occupancy-Bar-Chart-Monthly-Consumption-View
Sp 1510 fe build occupancy bar chart monthly consumption view
This commit is contained in:
18
lib/pages/analytics/models/occupacy.dart
Normal file
18
lib/pages/analytics/models/occupacy.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
class Occupacy extends Equatable {
|
||||||
|
final String date;
|
||||||
|
final String occupancy;
|
||||||
|
|
||||||
|
const Occupacy({required this.date, required this.occupancy});
|
||||||
|
|
||||||
|
factory Occupacy.fromJson(Map<String, dynamic> json) {
|
||||||
|
return Occupacy(
|
||||||
|
date: json['date'] as String,
|
||||||
|
occupancy: json['occupancy'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [date, occupancy];
|
||||||
|
}
|
@ -5,7 +5,7 @@ import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_pa
|
|||||||
part 'analytics_tab_event.dart';
|
part 'analytics_tab_event.dart';
|
||||||
|
|
||||||
class AnalyticsTabBloc extends Bloc<AnalyticsTabEvent, AnalyticsPageTab> {
|
class AnalyticsTabBloc extends Bloc<AnalyticsTabEvent, AnalyticsPageTab> {
|
||||||
AnalyticsTabBloc() : super(AnalyticsPageTab.energyManagement) {
|
AnalyticsTabBloc() : super(AnalyticsPageTab.occupancy) {
|
||||||
on<UpdateAnalyticsTabEvent>(_onUpdateAnalyticsTabEvent);
|
on<UpdateAnalyticsTabEvent>(_onUpdateAnalyticsTabEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,10 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/ener
|
|||||||
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/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/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/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/fake_energy_consumption_by_phases_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/fake_energy_consumption_by_phases_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/fake_energy_consumption_per_device_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/fake_energy_consumption_per_device_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/occupacy/fake_occupacy_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/fake_total_energy_consumption_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/fake_total_energy_consumption_service.dart';
|
||||||
@ -53,6 +55,7 @@ class AnalyticsPage extends StatelessWidget {
|
|||||||
FirebaseRealtimeDeviceService(),
|
FirebaseRealtimeDeviceService(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
BlocProvider(create: (context) => OccupancyBloc(FakeOccupacyService())),
|
||||||
],
|
],
|
||||||
child: const AnalyticsPageForm(),
|
child: const AnalyticsPageForm(),
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
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/energy_management/blocs/power_clamp_info/power_clamp_info_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/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_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 {
|
||||||
@ -17,34 +19,9 @@ class AnalyticsCommunitiesSidebar extends StatelessWidget {
|
|||||||
shouldDisableDeselectingChildrenOfSelectedParent: true,
|
shouldDisableDeselectingChildrenOfSelectedParent: true,
|
||||||
onSelect: () {
|
onSelect: () {
|
||||||
/// Necessary to wait for the state to update before fethcing the data.
|
/// Necessary to wait for the state to update before fethcing the data.
|
||||||
Future.delayed(
|
Future.delayed(const Duration(milliseconds: 100), () {
|
||||||
const Duration(milliseconds: 100),
|
if (context.mounted) _loadBasedOnSelectedTab(context);
|
||||||
() {
|
});
|
||||||
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,
|
||||||
),
|
),
|
||||||
@ -52,4 +29,16 @@ class AnalyticsCommunitiesSidebar extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _loadBasedOnSelectedTab(BuildContext context) {
|
||||||
|
final selectedTab = context.read<AnalyticsTabBloc>().state;
|
||||||
|
return switch (selectedTab) {
|
||||||
|
AnalyticsPageTab.energyManagement =>
|
||||||
|
FetchEnergyManagementDataHelper.loadEnergyManagementData(context),
|
||||||
|
AnalyticsPageTab.occupancy =>
|
||||||
|
FetchOccupancyDataHelper.loadOccupancyData(context),
|
||||||
|
// ignore: unreachable_switch_case
|
||||||
|
_ => () {},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,9 +38,7 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
...AnalyticsPageTab.values.map(
|
...AnalyticsPageTab.values.map(
|
||||||
(tab) => AnimatedSwitcher(
|
(tab) => _buildAnimation(
|
||||||
switchInCurve: Curves.easeIn,
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
child: AnalyticsPageTabButton(
|
child: AnalyticsPageTabButton(
|
||||||
key: ValueKey(selectedTab),
|
key: ValueKey(selectedTab),
|
||||||
tab: tab,
|
tab: tab,
|
||||||
@ -53,12 +51,18 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
const Expanded(
|
_buildAnimation(
|
||||||
flex: 2,
|
child: Visibility(
|
||||||
child: FittedBox(
|
key: ValueKey(selectedTab),
|
||||||
fit: BoxFit.scaleDown,
|
visible: selectedTab == AnalyticsPageTab.energyManagement,
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
child: const Expanded(
|
||||||
child: AnalyticsDateFilterButton(),
|
flex: 2,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
|
child: AnalyticsDateFilterButton(),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -67,14 +71,18 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 8,
|
flex: 8,
|
||||||
child: AnimatedSwitcher(
|
child: _buildAnimation(child: selectedTab.child),
|
||||||
switchInCurve: Curves.easeIn,
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
child: selectedTab.child,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildAnimation({required Widget child}) {
|
||||||
|
return AnimatedSwitcher(
|
||||||
|
switchInCurve: Curves.easeIn,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,23 @@ abstract final class FetchEnergyManagementDataHelper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void loadEnergyManagementData(BuildContext context) {
|
||||||
|
final (selectedCommunities, selectedSpaces) =
|
||||||
|
FetchEnergyManagementDataHelper.getSelectedCommunitiesAndSpaces(context);
|
||||||
|
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) return;
|
||||||
|
|
||||||
|
FetchEnergyManagementDataHelper.fetchEnergyManagementData(context);
|
||||||
|
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(context);
|
||||||
|
context.read<PowerClampInfoBloc>().add(const ClearPowerClampInfoEvent());
|
||||||
|
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) {
|
||||||
|
context.read<PowerClampInfoBloc>().add(
|
||||||
|
const ClearPowerClampInfoEvent(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
FetchEnergyManagementDataHelper.loadPowerClampInfo(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static (List<String> selectedCommunities, List<String> selectedSpaces)
|
static (List<String> selectedCommunities, List<String> selectedSpaces)
|
||||||
getSelectedCommunitiesAndSpaces(BuildContext context) {
|
getSelectedCommunitiesAndSpaces(BuildContext context) {
|
||||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||||
|
@ -1,13 +1,26 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart_box.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart_box.dart';
|
||||||
|
|
||||||
class AnalyticsEnergyManagementView extends StatelessWidget {
|
class AnalyticsEnergyManagementView extends StatefulWidget {
|
||||||
const AnalyticsEnergyManagementView({super.key});
|
const AnalyticsEnergyManagementView({super.key});
|
||||||
|
|
||||||
static const _padding = EdgeInsetsDirectional.all(32);
|
@override
|
||||||
|
State<AnalyticsEnergyManagementView> createState() =>
|
||||||
|
_AnalyticsEnergyManagementViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AnalyticsEnergyManagementViewState
|
||||||
|
extends State<AnalyticsEnergyManagementView> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
FetchEnergyManagementDataHelper.loadEnergyManagementData(context);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const _padding = EdgeInsetsDirectional.all(32);
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/occupacy/occupacy_service.dart';
|
||||||
|
|
||||||
|
part 'occupancy_event.dart';
|
||||||
|
part 'occupancy_state.dart';
|
||||||
|
|
||||||
|
class OccupancyBloc extends Bloc<OccupancyEvent, OccupancyState> {
|
||||||
|
OccupancyBloc(this._occupacyService) : super(const OccupancyState()) {
|
||||||
|
on<LoadOccupancyEvent>(_onLoadOccupancyEvent);
|
||||||
|
on<ClearOccupancyEvent>(_onClearOccupancyEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
final OccupacyService _occupacyService;
|
||||||
|
|
||||||
|
Future<void> _onLoadOccupancyEvent(
|
||||||
|
LoadOccupancyEvent event,
|
||||||
|
Emitter<OccupancyState> emit,
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(status: OccupancyStatus.loading));
|
||||||
|
try {
|
||||||
|
final chartData = await _occupacyService.load(event.param);
|
||||||
|
emit(state.copyWith(chartData: chartData, status: OccupancyStatus.loaded));
|
||||||
|
} catch (e) {
|
||||||
|
emit(state.copyWith(status: OccupancyStatus.failure, errorMessage: '$e'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onClearOccupancyEvent(
|
||||||
|
ClearOccupancyEvent event,
|
||||||
|
Emitter<OccupancyState> emit,
|
||||||
|
) {
|
||||||
|
emit(const OccupancyState());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
part of 'occupancy_bloc.dart';
|
||||||
|
|
||||||
|
sealed class OccupancyEvent extends Equatable {
|
||||||
|
const OccupancyEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class LoadOccupancyEvent extends OccupancyEvent {
|
||||||
|
const LoadOccupancyEvent(this.param);
|
||||||
|
|
||||||
|
final GetOccupancyParam param;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [param];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ClearOccupancyEvent extends OccupancyEvent {
|
||||||
|
const ClearOccupancyEvent();
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
part of 'occupancy_bloc.dart';
|
||||||
|
|
||||||
|
enum OccupancyStatus { initial, loading, loaded, failure }
|
||||||
|
|
||||||
|
final class OccupancyState extends Equatable {
|
||||||
|
const OccupancyState({
|
||||||
|
this.chartData = const [],
|
||||||
|
this.status = OccupancyStatus.initial,
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<Occupacy> chartData;
|
||||||
|
final OccupancyStatus status;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
OccupancyState copyWith({
|
||||||
|
List<Occupacy>? chartData,
|
||||||
|
OccupancyStatus? status,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return OccupancyState(
|
||||||
|
chartData: chartData ?? this.chartData,
|
||||||
|
status: status ?? this.status,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [chartData, status, errorMessage];
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
|
||||||
|
|
||||||
|
abstract final class FetchOccupancyDataHelper {
|
||||||
|
const FetchOccupancyDataHelper._();
|
||||||
|
|
||||||
|
static void loadOccupancyData(BuildContext context) {
|
||||||
|
final (selectedCommunities, selectedSpaces) =
|
||||||
|
FetchEnergyManagementDataHelper.getSelectedCommunitiesAndSpaces(context);
|
||||||
|
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) return;
|
||||||
|
|
||||||
|
context.read<OccupancyBloc>().add(
|
||||||
|
LoadOccupancyEvent(
|
||||||
|
GetOccupancyParam(
|
||||||
|
monthDate: '04-2022',
|
||||||
|
spaceUuid: '',
|
||||||
|
communityUuid: '',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,67 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart';
|
||||||
|
|
||||||
class AnalyticsOccupancyView extends StatelessWidget {
|
class AnalyticsOccupancyView extends StatefulWidget {
|
||||||
const AnalyticsOccupancyView({super.key});
|
const AnalyticsOccupancyView({super.key});
|
||||||
|
|
||||||
|
static const _padding = EdgeInsetsDirectional.all(32);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AnalyticsOccupancyView> createState() => _AnalyticsOccupancyViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AnalyticsOccupancyViewState extends State<AnalyticsOccupancyView> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
FetchOccupancyDataHelper.loadOccupancyData(context);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const Center(
|
final height = MediaQuery.sizeOf(context).height;
|
||||||
child: Text('AnalyticsOccupancyView is Working!'),
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final isMediumOrLess = constraints.maxWidth <= 900;
|
||||||
|
if (isMediumOrLess) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
padding: AnalyticsOccupancyView._padding,
|
||||||
|
child: Column(
|
||||||
|
spacing: 32,
|
||||||
|
children: [
|
||||||
|
SizedBox(height: height * 1.2, child: const OccupancyEndSideBar()),
|
||||||
|
SizedBox(height: height * 0.5, child: const OccupancyChartBox()),
|
||||||
|
SizedBox(height: height * 0.5, child: const Placeholder()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Container(
|
||||||
|
padding: AnalyticsOccupancyView._padding,
|
||||||
|
height: height * 1,
|
||||||
|
child: const Row(
|
||||||
|
spacing: 32,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Column(
|
||||||
|
spacing: 20,
|
||||||
|
children: [
|
||||||
|
Expanded(child: OccupancyChartBox()),
|
||||||
|
Expanded(child: Placeholder()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(child: OccupancyEndSideBar()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,144 @@
|
|||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/occupacy.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 OccupancyChart extends StatelessWidget {
|
||||||
|
const OccupancyChart({required this.chartData, super.key});
|
||||||
|
|
||||||
|
final List<Occupacy> chartData;
|
||||||
|
|
||||||
|
static const _chartWidth = 16.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BarChart(
|
||||||
|
BarChartData(
|
||||||
|
maxY: 1.0,
|
||||||
|
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
||||||
|
checkToShowHorizontalLine: (value) => true,
|
||||||
|
horizontalInterval: 0.25,
|
||||||
|
),
|
||||||
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
|
barTouchData: _barTouchData(context),
|
||||||
|
titlesData: _titlesData(context),
|
||||||
|
barGroups: List.generate(chartData.length, (index) {
|
||||||
|
final actual = chartData[index];
|
||||||
|
return BarChartGroupData(
|
||||||
|
x: index,
|
||||||
|
barsSpace: 0,
|
||||||
|
groupVertically: true,
|
||||||
|
barRods: [
|
||||||
|
BarChartRodData(
|
||||||
|
toY: 1.0,
|
||||||
|
fromY: double.parse(actual.occupancy) + 0.025,
|
||||||
|
color: ColorsManager.graysColor,
|
||||||
|
width: _chartWidth,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
BarChartRodData(
|
||||||
|
toY: double.parse(actual.occupancy),
|
||||||
|
color: ColorsManager.vividBlue.withValues(alpha: 0.8),
|
||||||
|
width: _chartWidth,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = chartData;
|
||||||
|
|
||||||
|
final month = double.parse(data[group.x.toInt()].occupancy).toStringAsFixed(2);
|
||||||
|
|
||||||
|
return BarTooltipItem(
|
||||||
|
month,
|
||||||
|
context.textTheme.bodyMedium!.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlTitlesData _titlesData(BuildContext context) {
|
||||||
|
final titlesData = EnergyManagementChartsHelper.titlesData(
|
||||||
|
context,
|
||||||
|
leftTitlesInterval: 250,
|
||||||
|
);
|
||||||
|
|
||||||
|
final leftTitles = titlesData.leftTitles.copyWith(
|
||||||
|
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
||||||
|
reservedSize: 70,
|
||||||
|
interval: 0.25,
|
||||||
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.only(end: 12),
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
'${(value * 100).toStringAsFixed(0)}%',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final bottomTitles = AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
getTitlesWidget: (value, _) => FittedBox(
|
||||||
|
alignment: AlignmentDirectional.bottomCenter,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
(value + 1).toString(),
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
fontSize: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
reservedSize: 36,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return titlesData.copyWith(
|
||||||
|
leftTitles: leftTitles,
|
||||||
|
bottomTitles: bottomTitles,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class OccupancyChartBox extends StatelessWidget {
|
||||||
|
const OccupancyChartBox({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<OccupancyBloc, OccupancyState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(30),
|
||||||
|
decoration: containerWhiteDecoration,
|
||||||
|
child: Column(
|
||||||
|
spacing: 20,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AnalyticsErrorWidget(state.errorMessage),
|
||||||
|
const Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: ChartTitle(title: Text('Occupancy')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
Expanded(
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: AnalyticsDateFilterButton(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(height: 0),
|
||||||
|
Expanded(child: OccupancyChart(chartData: state.chartData)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
import 'package:flutter/material.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';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
class OccupancyEndSideBar extends StatelessWidget {
|
||||||
|
const OccupancyEndSideBar({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: subSectionContainerDecoration.copyWith(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsetsDirectional.all(32),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildHeader(context),
|
||||||
|
Text(
|
||||||
|
'Device ID:',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
SelectableText(
|
||||||
|
(const Uuid().v4()),
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 50),
|
||||||
|
const Placeholder(fallbackHeight: 150),
|
||||||
|
const SizedBox(height: 50),
|
||||||
|
const Expanded(child: Placeholder()),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
child: SelectableText(
|
||||||
|
'Presnce Sensor',
|
||||||
|
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(),
|
||||||
|
child: Placeholder(fallbackHeight: 30),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
19
lib/pages/analytics/params/get_occupancy_param.dart
Normal file
19
lib/pages/analytics/params/get_occupancy_param.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
class GetOccupancyParam {
|
||||||
|
final String monthDate;
|
||||||
|
final String? spaceUuid;
|
||||||
|
final String communityUuid;
|
||||||
|
|
||||||
|
GetOccupancyParam({
|
||||||
|
required this.monthDate,
|
||||||
|
required this.spaceUuid,
|
||||||
|
required this.communityUuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'monthDate': monthDate,
|
||||||
|
'spaceUuid': spaceUuid,
|
||||||
|
'communityUuid': communityUuid,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/occupacy/occupacy_service.dart';
|
||||||
|
|
||||||
|
class FakeOccupacyService implements OccupacyService {
|
||||||
|
@override
|
||||||
|
Future<List<Occupacy>> load(GetOccupancyParam param) async {
|
||||||
|
return await Future.delayed(
|
||||||
|
const Duration(seconds: 1),
|
||||||
|
() => List.generate(
|
||||||
|
30,
|
||||||
|
(index) => Occupacy(
|
||||||
|
date: DateTime.now().subtract(Duration(days: index)).toString(),
|
||||||
|
occupancy: ((index / 100)).toString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
|
||||||
|
|
||||||
|
abstract interface class OccupacyService {
|
||||||
|
Future<List<Occupacy>> load(GetOccupancyParam param);
|
||||||
|
}
|
Reference in New Issue
Block a user