Merge pull request #182 from SyncrowIOT/SP-1519-FE-Handle-Loading-Skeletons-and-No-Data-Error-States

Sp 1519 fe handle loading skeletons and no data error states
This commit is contained in:
Faris Armoush
2025-05-13 16:55:54 +03:00
committed by GitHub
15 changed files with 143 additions and 138 deletions

View File

@ -20,7 +20,11 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
spaces,
),
);
FetchEnergyManagementDataHelper.loadEnergyManagementData(context);
FetchEnergyManagementDataHelper.loadEnergyManagementData(
context,
communityId: community.uuid,
spaceId: spaces.isNotEmpty ? spaces.first.uuid ?? '' : '',
);
}
@override
@ -36,7 +40,12 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
space.children,
),
);
FetchEnergyManagementDataHelper.loadEnergyManagementData(context);
FetchEnergyManagementDataHelper.loadEnergyManagementData(
context,
communityId: community.uuid,
spaceId: space.uuid ?? '',
);
}
@override
@ -45,14 +54,7 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
CommunityModel community,
SpaceModel child,
) {
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(
community,
child.uuid ?? '',
child.children,
),
);
FetchEnergyManagementDataHelper.loadEnergyManagementData(context);
// Do nothing
}
@override

View File

@ -17,10 +17,14 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(
community.uuid,
spaces,
spaces.isNotEmpty ? [spaces.first] : [],
),
);
FetchOccupancyDataHelper.loadOccupancyData(context);
FetchOccupancyDataHelper.loadOccupancyData(
context,
communityId: community.uuid,
spaceId: spaces.isNotEmpty ? spaces.first.uuid ?? '' : '',
);
}
@override
@ -29,14 +33,25 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
CommunityModel community,
SpaceModel space,
) {
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(
community,
space.uuid ?? '',
space.children,
),
);
FetchOccupancyDataHelper.loadOccupancyData(context);
final spaceTreeBloc = context.read<SpaceTreeBloc>();
final selectedSpacesIds = spaceTreeBloc.state.selectedSpaces;
final isSpaceSelected = selectedSpacesIds.contains(space.uuid);
if (selectedSpacesIds.isEmpty) {
spaceTreeBloc.add(OnCommunitySelected(community.uuid, [space]));
} else if (isSpaceSelected) {
spaceTreeBloc.add(const SpaceTreeClearSelectionEvent());
} else {
spaceTreeBloc
..add(const SpaceTreeClearSelectionEvent())
..add(OnSpaceSelected(community, space.uuid ?? '', []));
}
FetchOccupancyDataHelper.loadOccupancyData(
context,
communityId: community.uuid,
spaceId: space.uuid ?? '',
);
}
@override
@ -45,18 +60,12 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
CommunityModel community,
SpaceModel child,
) {
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(
community,
child.uuid ?? '',
child.children,
),
);
FetchOccupancyDataHelper.loadOccupancyData(context);
// Do nothing
}
@override
void clearData(BuildContext context) {
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
// FetchOccupancyDataHelper.clearAllData(context);
}
}

View File

@ -9,29 +9,21 @@ class AnalyticsCommunitiesSidebar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Builder(
builder: (context) {
final selectedTab = context.read<AnalyticsTabBloc>().state;
final strategy =
AnalyticsDataLoadingStrategyFactory.getStrategy(selectedTab);
final selectedTab = context.watch<AnalyticsTabBloc>().state;
final strategy = AnalyticsDataLoadingStrategyFactory.getStrategy(selectedTab);
// Clear data when tab changes
strategy.clearData(context);
return Expanded(
child: AnalyticsSpaceTreeView(
onSelectCommunity: (community, spaces) {
strategy.onCommunitySelected(context, community, spaces);
},
onSelectSpace: (community, space) {
strategy.onSpaceSelected(context, community, space);
},
onSelectChildSpace: (community, child) {
strategy.onChildSpaceSelected(context, community, child);
},
),
);
},
return Expanded(
child: AnalyticsSpaceTreeView(
onSelectCommunity: (community, spaces) {
strategy.onCommunitySelected(context, community, spaces);
},
onSelectSpace: (community, space) {
strategy.onSpaceSelected(context, community, space);
},
onSelectChildSpace: (community, child) {
strategy.onChildSpaceSelected(context, community, child);
},
),
);
}
}

View File

@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_pa
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/energy_management/helpers/fetch_energy_management_data_helper.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/utils/style.dart';
class AnalyticsPageTabsAndChildren extends StatelessWidget {
@ -13,6 +14,7 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
@override
Widget build(BuildContext context) {
final spaceTreeState = context.read<SpaceTreeBloc>().state;
return BlocBuilder<AnalyticsTabBloc, AnalyticsPageTab>(
buildWhen: (previous, current) => previous != current,
builder: (context, selectedTab) => Column(
@ -66,10 +68,14 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
context.read<AnalyticsDatePickerBloc>().add(
UpdateAnalyticsDatePickerEvent(montlyDate: value),
);
FetchEnergyManagementDataHelper
.fetchEnergyManagementData(
FetchEnergyManagementDataHelper.loadEnergyManagementData(
context,
selectedDate: value,
communityId:
spaceTreeState.selectedCommunities.firstOrNull ??
'',
spaceId:
spaceTreeState.selectedSpaces.firstOrNull ?? '',
);
},
selectedDate: context

View File

@ -9,58 +9,38 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/tota
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(
static const String _powerClampId = 'cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa';
static void loadEnergyManagementData(
BuildContext context, {
required String communityId,
required String spaceId,
DateTime? selectedDate,
}) {
final (selectedCommunities, selectedSpaces) =
getSelectedCommunitiesAndSpaces(context);
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) {
if (communityId.isEmpty && spaceId.isEmpty) {
clearAllData(context);
return;
}
final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
final selectedDate0 = selectedDate ?? datePickerState.monthlyDate;
loadTotalEnergyConsumption(context, selectedDate: datePickerState.monthlyDate);
loadEnergyConsumptionByPhases(
loadTotalEnergyConsumption(
context,
selectedDate: datePickerState.monthlyDate,
selectedDate: selectedDate0,
communityId: communityId,
spaceId: spaceId,
);
loadEnergyConsumptionByPhases(context, selectedDate: selectedDate);
loadEnergyConsumptionPerDevice(context);
return;
}
static void loadEnergyManagementData(BuildContext context) {
final (selectedCommunities, selectedSpaces) =
FetchEnergyManagementDataHelper.getSelectedCommunitiesAndSpaces(context);
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) return;
FetchEnergyManagementDataHelper.fetchEnergyManagementData(context,
selectedDate: DateTime.now());
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)
getSelectedCommunitiesAndSpaces(BuildContext context) {
final spaceTreeState = context.read<SpaceTreeBloc>().state;
final selectedCommunities = spaceTreeState.selectedCommunities;
final selectedSpaces = spaceTreeState.selectedSpaces;
return (selectedCommunities, selectedSpaces);
loadRealtimeDeviceChanges(context);
loadPowerClampInfo(context);
}
static void loadEnergyConsumptionByPhases(
@ -79,13 +59,12 @@ abstract final class FetchEnergyManagementDataHelper {
static void loadTotalEnergyConsumption(
BuildContext context, {
DateTime? selectedDate,
required String communityId,
required String spaceId,
}) {
final (selectedCommunities, selectedSpaces) =
getSelectedCommunitiesAndSpaces(context);
final param = GetTotalEnergyConsumptionParam(
spaceId: selectedCommunities.firstOrNull,
communityId: selectedCommunities.firstOrNull,
spaceId: spaceId,
communityId: communityId,
monthDate: selectedDate,
);
context.read<TotalEnergyConsumptionBloc>().add(
@ -102,13 +81,13 @@ abstract final class FetchEnergyManagementDataHelper {
static void loadPowerClampInfo(BuildContext context) {
context.read<PowerClampInfoBloc>().add(
const LoadPowerClampInfoEvent('cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'),
const LoadPowerClampInfoEvent(_powerClampId),
);
}
static void loadRealtimeDeviceChanges(BuildContext context) {
context.read<RealtimeDeviceChangesBloc>().add(
const RealtimeDeviceChangesStarted('cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'),
const RealtimeDeviceChangesStarted(_powerClampId),
);
}
@ -120,6 +99,7 @@ abstract final class FetchEnergyManagementDataHelper {
context.read<PowerClampInfoBloc>().add(
const ClearPowerClampInfoEvent(),
);
context.read<EnergyConsumptionPerDeviceBloc>().add(
const ClearEnergyConsumptionPerDeviceEvent(),
);

View File

@ -1,8 +1,10 @@
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/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';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
class AnalyticsEnergyManagementView extends StatefulWidget {
const AnalyticsEnergyManagementView({super.key});
@ -16,7 +18,14 @@ class _AnalyticsEnergyManagementViewState
extends State<AnalyticsEnergyManagementView> {
@override
void initState() {
FetchEnergyManagementDataHelper.loadEnergyManagementData(context);
final spaceTreeBloc = context.read<SpaceTreeBloc>();
final communityId = spaceTreeBloc.state.selectedCommunities.firstOrNull;
final spaceId = spaceTreeBloc.state.selectedSpaces.firstOrNull;
FetchEnergyManagementDataHelper.loadEnergyManagementData(
context,
communityId: communityId ?? '',
spaceId: spaceId ?? '',
);
super.initState();
}

View File

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/energy_management/blocs/realtime_device_changes/realtime_device_changes_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/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
@ -11,17 +10,13 @@ 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) {
context.read<OccupancyBloc>().add(
const ClearOccupancyEvent(),
);
context.read<OccupancyHeatMapBloc>().add(
const ClearOccupancyHeatMapEvent(),
);
return;
static void loadOccupancyData(
BuildContext context, {
required String communityId,
required String spaceId,
}) {
if (communityId.isEmpty && spaceId.isEmpty) {
clearAllData(context);
}
final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
@ -31,8 +26,8 @@ abstract final class FetchOccupancyDataHelper {
GetOccupancyParam(
monthDate:
'${datePickerState.monthlyDate.year}-${datePickerState.monthlyDate.month}',
spaceUuid: selectedSpaces.firstOrNull,
communityUuid: selectedCommunities.first,
spaceUuid: spaceId,
communityUuid: communityId,
),
),
);
@ -40,9 +35,8 @@ abstract final class FetchOccupancyDataHelper {
context.read<OccupancyHeatMapBloc>().add(
LoadOccupancyHeatMapEvent(
GetOccupancyHeatMapParam(
spaceId: selectedSpaces.isNotEmpty ? selectedSpaces.first : '',
communityId:
selectedCommunities.isNotEmpty ? selectedCommunities.first : '',
spaceId: spaceId,
communityId: communityId,
year: datePickerState.yearlyDate,
),
),
@ -54,4 +48,16 @@ abstract final class FetchOccupancyDataHelper {
const RealtimeDeviceChangesStarted('14fe6e7e-47af-4a07-ae0a-7c4a26ef8135'),
);
}
static void clearAllData(BuildContext context) {
context.read<OccupancyBloc>().add(
const ClearOccupancyEvent(),
);
context.read<OccupancyHeatMapBloc>().add(
const ClearOccupancyHeatMapEvent(),
);
context.read<RealtimeDeviceChangesBloc>().add(
const RealtimeDeviceChangesClosed(),
);
}
}

View File

@ -1,25 +1,13 @@
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';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_box.dart';
class AnalyticsOccupancyView extends StatefulWidget {
class AnalyticsOccupancyView extends StatelessWidget {
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
Widget build(BuildContext context) {
final height = MediaQuery.sizeOf(context).height;
@ -28,7 +16,7 @@ class _AnalyticsOccupancyViewState extends State<AnalyticsOccupancyView> {
final isMediumOrLess = constraints.maxWidth <= 900;
if (isMediumOrLess) {
return SingleChildScrollView(
padding: AnalyticsOccupancyView._padding,
padding: _padding,
child: Column(
spacing: 32,
children: [
@ -42,7 +30,7 @@ class _AnalyticsOccupancyViewState extends State<AnalyticsOccupancyView> {
return SingleChildScrollView(
child: Container(
padding: AnalyticsOccupancyView._padding,
padding: _padding,
height: height * 0.9,
child: const Row(
spacing: 32,

View File

@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/oc
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.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/utils/style.dart';
class OccupancyChartBox extends StatelessWidget {
@ -14,6 +15,7 @@ class OccupancyChartBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
final spaceTreeState = context.watch<SpaceTreeBloc>().state;
return BlocBuilder<OccupancyBloc, OccupancyState>(
builder: (context, state) {
return Container(
@ -45,7 +47,11 @@ class OccupancyChartBox extends StatelessWidget {
context.read<AnalyticsDatePickerBloc>().add(
UpdateAnalyticsDatePickerEvent(montlyDate: value),
);
FetchOccupancyDataHelper.loadOccupancyData(context);
FetchOccupancyDataHelper.loadOccupancyData(
context,
communityId: spaceTreeState.selectedCommunities.firstOrNull ?? '',
spaceId: spaceTreeState.selectedSpaces.firstOrNull ?? '',
);
},
selectedDate: context
.watch<AnalyticsDatePickerBloc>()

View File

@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_he
import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/utils/style.dart';
class OccupancyHeatMapBox extends StatelessWidget {
@ -14,6 +15,7 @@ class OccupancyHeatMapBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
final spaceTreeState = context.watch<SpaceTreeBloc>().state;
return BlocBuilder<OccupancyHeatMapBloc, OccupancyHeatMapState>(
builder: (context, state) {
return Container(
@ -45,7 +47,12 @@ class OccupancyHeatMapBox extends StatelessWidget {
context.read<AnalyticsDatePickerBloc>().add(
UpdateAnalyticsDatePickerEvent(yearlyDate: value),
);
FetchOccupancyDataHelper.loadOccupancyData(context);
FetchOccupancyDataHelper.loadOccupancyData(
context,
communityId:
spaceTreeState.selectedCommunities.firstOrNull ?? '',
spaceId: spaceTreeState.selectedSpaces.firstOrNull ?? '',
);
},
datePickerType: DatePickerType.year,
selectedDate: context

View File

@ -536,7 +536,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// 'entityId': 'tab_to_run',
// 'uniqueCustomId': const Uuid().v4(),
// 'deviceId': 'tab_to_run',
// 'title': 'Tab to run',
// 'title': 'Tap to run',
// 'productType': 'tab_to_run',
// 'imagePath': Assets.tabToRun,
// }
@ -771,7 +771,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
'entityId': 'tab_to_run',
'uniqueCustomId': const Uuid().v4(),
'deviceId': 'tab_to_run',
'title': 'Tab to run',
'title': 'Tap to run',
'productType': 'tab_to_run',
'imagePath': Assets.tabToRun,
}

View File

@ -162,7 +162,7 @@ class SaveRoutineHelper {
width: 24,
height: 24,
),
title: const Text('Tab to run'),
title: const Text('Tap to run'),
),
if (state.isAutomation)
...state.ifItems.map((item) {

View File

@ -29,11 +29,11 @@ class ConditionsRoutinesDevicesView extends StatelessWidget {
children: [
DraggableCard(
imagePath: Assets.tabToRun,
title: 'Tab to run',
title: 'Tap to run',
deviceData: {
'deviceId': 'tab_to_run',
'type': 'trigger',
'name': 'Tab to run',
'name': 'Tap to run',
},
),
DraggableCard(

View File

@ -43,7 +43,7 @@ class IfContainer extends StatelessWidget {
children: [
DraggableCard(
imagePath: Assets.tabToRun,
title: 'Tab to run',
title: 'Tap to run',
deviceData: {},
),
],

View File

@ -26,7 +26,7 @@ class FetchRoutineScenesAutomation extends StatelessWidget
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_buildListTitle(context, "Scenes (Tab to Run)"),
_buildListTitle(context, "Scenes (Tap to Run)"),
const SizedBox(height: 10),
Visibility(
visible: state.scenes.isNotEmpty,