Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1591-FE-Implement-Space-Level-Structure-Selection-and-Air-Quality-Device-Dropdown

This commit is contained in:
Faris Armoush
2025-05-22 15:48:47 +03:00
14 changed files with 84 additions and 76 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -1,18 +1,32 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class Occupacy extends Equatable { class Occupacy extends Equatable {
final String date; final DateTime date;
final String occupancy; final String occupancy;
final String spaceUuid;
final int occupiedSeconds;
const Occupacy({required this.date, required this.occupancy}); const Occupacy({
required this.date,
required this.occupancy,
required this.spaceUuid,
required this.occupiedSeconds,
});
factory Occupacy.fromJson(Map<String, dynamic> json) { factory Occupacy.fromJson(Map<String, dynamic> json) {
return Occupacy( return Occupacy(
date: json['date'] as String, date: DateTime.parse(json['event_date'] as String? ?? '${DateTime.now()}'),
occupancy: json['occupancy'] as String, occupancy: (json['occupancy_percentage'] ?? 0).toString(),
spaceUuid: json['space_uuid'] as String? ?? '',
occupiedSeconds: json['occupied_seconds'] as int? ?? 0,
); );
} }
@override @override
List<Object?> get props => [date, occupancy]; List<Object?> get props => [
date,
occupancy,
spaceUuid,
occupiedSeconds,
];
} }

View File

@ -14,7 +14,6 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
CommunityModel community, CommunityModel community,
List<SpaceModel> spaces, List<SpaceModel> spaces,
) { ) {
// Add to space tree bloc first
context.read<SpaceTreeBloc>().add( context.read<SpaceTreeBloc>().add(
OnCommunitySelected( OnCommunitySelected(
community.uuid, community.uuid,
@ -69,7 +68,9 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
CommunityModel community, CommunityModel community,
SpaceModel child, SpaceModel child,
) { ) {
// Do nothing else as per original implementation if (child.children.isNotEmpty) {
return onSpaceSelected(context, community, child);
}
} }
@override @override

View File

@ -14,7 +14,7 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
CommunityModel community, CommunityModel community,
List<SpaceModel> spaces, List<SpaceModel> spaces,
) { ) {
// Do nothing // Do Nothing
} }
@override @override
@ -24,26 +24,17 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
SpaceModel space, SpaceModel space,
) { ) {
final spaceTreeBloc = context.read<SpaceTreeBloc>(); final spaceTreeBloc = context.read<SpaceTreeBloc>();
final selectedSpacesIds = spaceTreeBloc.state.selectedSpaces; final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
final isSpaceSelected = selectedSpacesIds.contains(space.uuid);
if (selectedSpacesIds.isEmpty) { if (isSpaceSelected) {
spaceTreeBloc.add(OnSpaceSelected(community, space.uuid ?? '', []));
} else if (isSpaceSelected) {
spaceTreeBloc.add(const SpaceTreeClearSelectionEvent());
} else {
spaceTreeBloc
..add(const SpaceTreeClearSelectionEvent())
..add(OnSpaceSelected(community, space.uuid ?? '', []));
}
final spaceTreeState = context.read<SpaceTreeBloc>().state;
if (spaceTreeState.selectedCommunities.contains(community.uuid) ||
spaceTreeState.selectedSpaces.contains(space.uuid)) {
clearData(context); clearData(context);
return; return;
} }
spaceTreeBloc
..add(const SpaceTreeClearSelectionEvent())
..add(OnSpaceSelected(community, space.uuid ?? '', []));
FetchOccupancyDataHelper.loadOccupancyData( FetchOccupancyDataHelper.loadOccupancyData(
context, context,
communityId: community.uuid, communityId: community.uuid,

View File

@ -17,7 +17,7 @@ import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_en
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart'; import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart'; import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/remote_energy_consumption_per_device_service.dart'; import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/remote_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/occupacy/remote_occupancy_service.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_service.dart'; import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_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';
@ -75,7 +75,11 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
FirebaseRealtimeDeviceService(), FirebaseRealtimeDeviceService(),
), ),
), ),
BlocProvider(create: (context) => OccupancyBloc(FakeOccupacyService())), BlocProvider(
create: (context) => OccupancyBloc(
RemoteOccupancyService(_httpService),
),
),
BlocProvider( BlocProvider(
create: (context) => OccupancyHeatMapBloc( create: (context) => OccupancyHeatMapBloc(
RemoteOccupancyHeatMapService(_httpService), RemoteOccupancyHeatMapService(_httpService),

View File

@ -30,7 +30,6 @@ abstract final class FetchOccupancyDataHelper {
loadOccupancyChartData( loadOccupancyChartData(
context, context,
communityUuid: communityId,
spaceUuid: spaceId, spaceUuid: spaceId,
date: datePickerState.monthlyDate, date: datePickerState.monthlyDate,
); );
@ -59,16 +58,14 @@ abstract final class FetchOccupancyDataHelper {
static void loadOccupancyChartData( static void loadOccupancyChartData(
BuildContext context, { BuildContext context, {
required String communityUuid,
required String spaceUuid, required String spaceUuid,
required DateTime date, required DateTime date,
}) { }) {
context.read<OccupancyBloc>().add( context.read<OccupancyBloc>().add(
LoadOccupancyEvent( LoadOccupancyEvent(
GetOccupancyParam( GetOccupancyParam(
monthDate: '${date.year}-${date.month}', monthDate: '${date.year}-${date.month.toString().padLeft(2, '0')}',
spaceUuid: spaceUuid, spaceUuid: spaceUuid,
communityUuid: communityUuid,
), ),
), ),
); );

View File

@ -16,10 +16,10 @@ class OccupancyChart extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BarChart( return BarChart(
BarChartData( BarChartData(
maxY: 1.0, maxY: 100.0,
gridData: EnergyManagementChartsHelper.gridData().copyWith( gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true, checkToShowHorizontalLine: (value) => true,
horizontalInterval: 0.2, horizontalInterval: 20,
), ),
borderData: EnergyManagementChartsHelper.borderData(), borderData: EnergyManagementChartsHelper.borderData(),
barTouchData: _barTouchData(context), barTouchData: _barTouchData(context),
@ -33,20 +33,21 @@ class OccupancyChart extends StatelessWidget {
), ),
barGroups: List.generate(chartData.length, (index) { barGroups: List.generate(chartData.length, (index) {
final actual = chartData[index]; final actual = chartData[index];
final occupancyValue = double.parse(actual.occupancy);
return BarChartGroupData( return BarChartGroupData(
x: index, x: index,
barsSpace: 0, barsSpace: 0,
groupVertically: true, groupVertically: true,
barRods: [ barRods: [
BarChartRodData( BarChartRodData(
toY: 1.0, toY: 100.0,
fromY: double.parse(actual.occupancy) + 0.025, fromY: occupancyValue == 0 ? occupancyValue : occupancyValue + 2.5,
color: ColorsManager.graysColor, color: ColorsManager.graysColor,
width: _chartWidth, width: _chartWidth,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
BarChartRodData( BarChartRodData(
toY: double.parse(actual.occupancy), toY: occupancyValue,
color: ColorsManager.vividBlue.withValues(alpha: 0.8), color: ColorsManager.vividBlue.withValues(alpha: 0.8),
width: _chartWidth, width: _chartWidth,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
@ -88,7 +89,7 @@ class OccupancyChart extends StatelessWidget {
final data = chartData; final data = chartData;
final occupancyValue = double.parse(data[group.x.toInt()].occupancy); final occupancyValue = double.parse(data[group.x.toInt()].occupancy);
final percentage = '${(occupancyValue * 100).toStringAsFixed(0)}%'; final percentage = '${(occupancyValue).toStringAsFixed(0)}%';
return BarTooltipItem( return BarTooltipItem(
percentage, percentage,
@ -108,14 +109,14 @@ class OccupancyChart extends StatelessWidget {
final leftTitles = titlesData.leftTitles.copyWith( final leftTitles = titlesData.leftTitles.copyWith(
sideTitles: titlesData.leftTitles.sideTitles.copyWith( sideTitles: titlesData.leftTitles.sideTitles.copyWith(
reservedSize: 70, reservedSize: 70,
interval: 0.2, interval: 20,
getTitlesWidget: (value, meta) => Padding( getTitlesWidget: (value, meta) => Padding(
padding: const EdgeInsetsDirectional.only(end: 12), padding: const EdgeInsetsDirectional.only(end: 12),
child: FittedBox( child: FittedBox(
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: Text( child: Text(
'${(value * 100).toStringAsFixed(0)}%', '${(value).toStringAsFixed(0)}%',
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
fontSize: 12, fontSize: 12,
color: ColorsManager.greyColor, color: ColorsManager.greyColor,

View File

@ -50,9 +50,6 @@ class OccupancyChartBox extends StatelessWidget {
if (spaceTreeState.selectedSpaces.isNotEmpty) { if (spaceTreeState.selectedSpaces.isNotEmpty) {
FetchOccupancyDataHelper.loadOccupancyChartData( FetchOccupancyDataHelper.loadOccupancyChartData(
context, context,
communityUuid:
spaceTreeState.selectedCommunities.firstOrNull ??
'',
spaceUuid: spaceUuid:
spaceTreeState.selectedSpaces.firstOrNull ?? '', spaceTreeState.selectedSpaces.firstOrNull ?? '',
date: value, date: value,

View File

@ -1,19 +1,11 @@
class GetOccupancyParam { class GetOccupancyParam {
final String monthDate; final String monthDate;
final String? spaceUuid; final String? spaceUuid;
final String communityUuid;
GetOccupancyParam({ GetOccupancyParam({
required this.monthDate, required this.monthDate,
required this.spaceUuid, required this.spaceUuid,
required this.communityUuid,
}); });
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() => {'monthDate': monthDate};
return {
'monthDate': monthDate,
'spaceUuid': spaceUuid,
'communityUuid': communityUuid,
};
}
} }

View File

@ -1,19 +0,0 @@
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(),
),
),
);
}
}

View File

@ -0,0 +1,32 @@
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';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemoteOccupancyService implements OccupacyService {
const RemoteOccupancyService(this._httpService);
final HTTPService _httpService;
@override
Future<List<Occupacy>> load(GetOccupancyParam param) async {
try {
final response = await _httpService.get(
path: '/occupancy/duration/space/${param.spaceUuid}',
showServerMessage: true,
queryParameters: param.toJson(),
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 Occupacy.fromJson(jsonData);
}).toList();
},
);
return response;
} catch (e) {
throw Exception('Failed to load energy consumption per phase: $e');
}
}
}

View File

@ -112,8 +112,8 @@ class _DynamicTableState extends State<DynamicTable> {
trackVisibility: true, trackVisibility: true,
child: Scrollbar( child: Scrollbar(
controller: _horizontalScrollController, controller: _horizontalScrollController,
thumbVisibility: false, thumbVisibility: true,
trackVisibility: false, trackVisibility: true,
notificationPredicate: (notif) => notif.depth == 1, notificationPredicate: (notif) => notif.depth == 1,
child: SingleChildScrollView( child: SingleChildScrollView(
controller: _verticalScrollController, controller: _verticalScrollController,

View File

@ -2,6 +2,7 @@ class Assets {
Assets._(); Assets._();
static const String background = "assets/images/Background.png"; static const String background = "assets/images/Background.png";
static const String webBackground = "assets/images/web_Background.svg"; static const String webBackground = "assets/images/web_Background.svg";
static const String webBackgroundPng = "assets/images/web_Background.png";
static const String blackLogo = "assets/images/black-logo.png"; static const String blackLogo = "assets/images/black-logo.png";
static const String logo = "assets/images/Logo.svg"; static const String logo = "assets/images/Logo.svg";
static const String logoHorizontal = "assets/images/logo_horizontal.png"; static const String logoHorizontal = "assets/images/logo_horizontal.png";

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/web_layout/web_app_bar.dart'; import 'package:syncrow_web/web_layout/web_app_bar.dart';
import 'menu_sidebar.dart'; import 'menu_sidebar.dart';
class WebScaffold extends StatelessWidget with HelperResponsiveLayout { class WebScaffold extends StatelessWidget with HelperResponsiveLayout {
@ -28,14 +28,11 @@ class WebScaffold extends StatelessWidget with HelperResponsiveLayout {
SizedBox( SizedBox(
width: MediaQuery.sizeOf(context).width, width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height, height: MediaQuery.sizeOf(context).height,
child: SvgPicture.asset( child: Image.asset(
Assets.webBackground, Assets.webBackgroundPng,
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
), ),
Container(
color: Colors.white.withOpacity(0.7),
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [