diff --git a/lib/pages/analytics/models/occupacy.dart b/lib/pages/analytics/models/occupacy.dart new file mode 100644 index 00000000..ab53e5c2 --- /dev/null +++ b/lib/pages/analytics/models/occupacy.dart @@ -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 json) { + return Occupacy( + date: json['date'] as String, + occupancy: json['occupancy'] as String, + ); + } + + @override + List get props => [date, occupancy]; +} diff --git a/lib/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart b/lib/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart index 94a359f2..b65cff12 100644 --- a/lib/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart +++ b/lib/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart @@ -5,7 +5,7 @@ import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_pa part 'analytics_tab_event.dart'; class AnalyticsTabBloc extends Bloc { - AnalyticsTabBloc() : super(AnalyticsPageTab.energyManagement) { + AnalyticsTabBloc() : super(AnalyticsPageTab.occupancy) { on(_onUpdateAnalyticsTabEvent); } diff --git a/lib/pages/analytics/modules/analytics/views/analytics_page.dart b/lib/pages/analytics/modules/analytics/views/analytics_page.dart index dbbf855f..cec50fe5 100644 --- a/lib/pages/analytics/modules/analytics/views/analytics_page.dart +++ b/lib/pages/analytics/modules/analytics/views/analytics_page.dart @@ -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/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/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_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/realtime_device_service/firebase_realtime_device_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(), ), ), + BlocProvider(create: (context) => OccupancyBloc(FakeOccupacyService())), ], child: const AnalyticsPageForm(), ); diff --git a/lib/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart b/lib/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart index 801af744..a46c2c1e 100644 --- a/lib/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart +++ b/lib/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart @@ -1,7 +1,9 @@ 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/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/occupancy/helpers/fetch_occupancy_data_helper.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; class AnalyticsCommunitiesSidebar extends StatelessWidget { @@ -17,34 +19,9 @@ class AnalyticsCommunitiesSidebar extends StatelessWidget { shouldDisableDeselectingChildrenOfSelectedParent: true, 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().add( - const ClearPowerClampInfoEvent(), - ); - final (selectedCommunities, selectedSpaces) = - FetchEnergyManagementDataHelper - .getSelectedCommunitiesAndSpaces(context); - if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) { - context.read().add( - const ClearPowerClampInfoEvent(), - ); - } else { - FetchEnergyManagementDataHelper.loadPowerClampInfo( - context, - ); - } - } - }, - ); + Future.delayed(const Duration(milliseconds: 100), () { + if (context.mounted) _loadBasedOnSelectedTab(context); + }); }, isSide: false, ), @@ -52,4 +29,16 @@ class AnalyticsCommunitiesSidebar extends StatelessWidget { }, ); } + + void _loadBasedOnSelectedTab(BuildContext context) { + final selectedTab = context.read().state; + return switch (selectedTab) { + AnalyticsPageTab.energyManagement => + FetchEnergyManagementDataHelper.loadEnergyManagementData(context), + AnalyticsPageTab.occupancy => + FetchOccupancyDataHelper.loadOccupancyData(context), + // ignore: unreachable_switch_case + _ => () {}, + }; + } } diff --git a/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart b/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart index d7e2cfef..20d00d83 100644 --- a/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart +++ b/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart @@ -38,9 +38,7 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ ...AnalyticsPageTab.values.map( - (tab) => AnimatedSwitcher( - switchInCurve: Curves.easeIn, - duration: const Duration(milliseconds: 200), + (tab) => _buildAnimation( child: AnalyticsPageTabButton( key: ValueKey(selectedTab), tab: tab, @@ -53,12 +51,18 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget { ), ), const Spacer(), - const Expanded( - flex: 2, - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: AlignmentDirectional.centerEnd, - child: AnalyticsDateFilterButton(), + _buildAnimation( + child: Visibility( + key: ValueKey(selectedTab), + visible: selectedTab == AnalyticsPageTab.energyManagement, + child: const Expanded( + flex: 2, + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.centerEnd, + child: AnalyticsDateFilterButton(), + ), + ), ), ), ], @@ -67,14 +71,18 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget { ), Expanded( flex: 8, - child: AnimatedSwitcher( - switchInCurve: Curves.easeIn, - duration: const Duration(milliseconds: 200), - child: selectedTab.child, - ), + child: _buildAnimation(child: selectedTab.child), ), ], ), ); } + + Widget _buildAnimation({required Widget child}) { + return AnimatedSwitcher( + switchInCurve: Curves.easeIn, + duration: const Duration(milliseconds: 200), + child: child, + ); + } } diff --git a/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart b/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart index 4f329104..4f5e3b5b 100644 --- a/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart +++ b/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart @@ -31,6 +31,23 @@ abstract final class FetchEnergyManagementDataHelper { 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().add(const ClearPowerClampInfoEvent()); + if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) { + context.read().add( + const ClearPowerClampInfoEvent(), + ); + } else { + FetchEnergyManagementDataHelper.loadPowerClampInfo(context); + } + } + static (List selectedCommunities, List selectedSpaces) getSelectedCommunitiesAndSpaces(BuildContext context) { final spaceTreeState = context.read().state; diff --git a/lib/pages/analytics/modules/energy_management/views/analytics_energy_management_view.dart b/lib/pages/analytics/modules/energy_management/views/analytics_energy_management_view.dart index cba5eea5..d3877e98 100644 --- a/lib/pages/analytics/modules/energy_management/views/analytics_energy_management_view.dart +++ b/lib/pages/analytics/modules/energy_management/views/analytics_energy_management_view.dart @@ -1,13 +1,26 @@ 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/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 StatefulWidget { const AnalyticsEnergyManagementView({super.key}); - static const _padding = EdgeInsetsDirectional.all(32); + @override + State createState() => + _AnalyticsEnergyManagementViewState(); +} +class _AnalyticsEnergyManagementViewState + extends State { + @override + void initState() { + FetchEnergyManagementDataHelper.loadEnergyManagementData(context); + super.initState(); + } + + static const _padding = EdgeInsetsDirectional.all(32); @override Widget build(BuildContext context) { return LayoutBuilder( diff --git a/lib/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart b/lib/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart new file mode 100644 index 00000000..6eeda29b --- /dev/null +++ b/lib/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart @@ -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 { + OccupancyBloc(this._occupacyService) : super(const OccupancyState()) { + on(_onLoadOccupancyEvent); + on(_onClearOccupancyEvent); + } + + final OccupacyService _occupacyService; + + Future _onLoadOccupancyEvent( + LoadOccupancyEvent event, + Emitter 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 emit, + ) { + emit(const OccupancyState()); + } +} diff --git a/lib/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_event.dart b/lib/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_event.dart new file mode 100644 index 00000000..8e1ef1c1 --- /dev/null +++ b/lib/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_event.dart @@ -0,0 +1,21 @@ +part of 'occupancy_bloc.dart'; + +sealed class OccupancyEvent extends Equatable { + const OccupancyEvent(); + + @override + List get props => []; +} + +final class LoadOccupancyEvent extends OccupancyEvent { + const LoadOccupancyEvent(this.param); + + final GetOccupancyParam param; + + @override + List get props => [param]; +} + +final class ClearOccupancyEvent extends OccupancyEvent { + const ClearOccupancyEvent(); +} diff --git a/lib/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_state.dart b/lib/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_state.dart new file mode 100644 index 00000000..88997847 --- /dev/null +++ b/lib/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_state.dart @@ -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 chartData; + final OccupancyStatus status; + final String? errorMessage; + + OccupancyState copyWith({ + List? chartData, + OccupancyStatus? status, + String? errorMessage, + }) { + return OccupancyState( + chartData: chartData ?? this.chartData, + status: status ?? this.status, + errorMessage: errorMessage ?? this.errorMessage, + ); + } + + @override + List get props => [chartData, status, errorMessage]; +} diff --git a/lib/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart b/lib/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart new file mode 100644 index 00000000..06ae659b --- /dev/null +++ b/lib/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart @@ -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().add( + LoadOccupancyEvent( + GetOccupancyParam( + monthDate: '04-2022', + spaceUuid: '', + communityUuid: '', + ), + ), + ); + } +} diff --git a/lib/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart b/lib/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart index 8042ff8b..43336b88 100644 --- a/lib/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart +++ b/lib/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart @@ -1,12 +1,67 @@ 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}); + static const _padding = EdgeInsetsDirectional.all(32); + + @override + State createState() => _AnalyticsOccupancyViewState(); +} + +class _AnalyticsOccupancyViewState extends State { + @override + void initState() { + FetchOccupancyDataHelper.loadOccupancyData(context); + super.initState(); + } + @override Widget build(BuildContext context) { - return const Center( - child: Text('AnalyticsOccupancyView is Working!'), + final height = MediaQuery.sizeOf(context).height; + 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()), + ], + ), + ), + ); + }, ); } } diff --git a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart new file mode 100644 index 00000000..fccc5efc --- /dev/null +++ b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart @@ -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 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, + ); + } +} diff --git a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart new file mode 100644 index 00000000..d051cb8d --- /dev/null +++ b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart @@ -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( + 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)), + ], + ), + ); + }, + ); + } +} diff --git a/lib/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart b/lib/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart new file mode 100644 index 00000000..e0526a5d --- /dev/null +++ b/lib/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart @@ -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), + ), + ), + ], + ); + } +} diff --git a/lib/pages/analytics/params/get_occupancy_param.dart b/lib/pages/analytics/params/get_occupancy_param.dart new file mode 100644 index 00000000..ed1b9375 --- /dev/null +++ b/lib/pages/analytics/params/get_occupancy_param.dart @@ -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 toJson() { + return { + 'monthDate': monthDate, + 'spaceUuid': spaceUuid, + 'communityUuid': communityUuid, + }; + } +} diff --git a/lib/pages/analytics/services/occupacy/fake_occupacy_service.dart b/lib/pages/analytics/services/occupacy/fake_occupacy_service.dart new file mode 100644 index 00000000..503a358b --- /dev/null +++ b/lib/pages/analytics/services/occupacy/fake_occupacy_service.dart @@ -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> 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(), + ), + ), + ); + } +} diff --git a/lib/pages/analytics/services/occupacy/occupacy_service.dart b/lib/pages/analytics/services/occupacy/occupacy_service.dart new file mode 100644 index 00000000..ced7d360 --- /dev/null +++ b/lib/pages/analytics/services/occupacy/occupacy_service.dart @@ -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> load(GetOccupancyParam param); +}