mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-11-26 09:04:55 +00:00
Compare commits
7 Commits
SP-1510-oc
...
syncrow_an
| Author | SHA1 | Date | |
|---|---|---|---|
| 94e4fbd5db | |||
| 302ef36b17 | |||
| 64a29681de | |||
| 06b320a75d | |||
| a15b5439f0 | |||
| baaf5111b1 | |||
| 745205063e |
@ -1,32 +1,18 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class Occupacy extends Equatable {
|
||||
final DateTime date;
|
||||
final String date;
|
||||
final String occupancy;
|
||||
final String spaceUuid;
|
||||
final int occupiedSeconds;
|
||||
|
||||
const Occupacy({
|
||||
required this.date,
|
||||
required this.occupancy,
|
||||
required this.spaceUuid,
|
||||
required this.occupiedSeconds,
|
||||
});
|
||||
const Occupacy({required this.date, required this.occupancy});
|
||||
|
||||
factory Occupacy.fromJson(Map<String, dynamic> json) {
|
||||
return Occupacy(
|
||||
date: DateTime.parse(json['event_date'] as String? ?? '${DateTime.now()}'),
|
||||
occupancy: (json['occupancy_percentage'] ?? 0).toString(),
|
||||
spaceUuid: json['space_uuid'] as String? ?? '',
|
||||
occupiedSeconds: json['occupied_seconds'] as int? ?? 0,
|
||||
date: json['date'] as String,
|
||||
occupancy: json['occupancy'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
date,
|
||||
occupancy,
|
||||
spaceUuid,
|
||||
occupiedSeconds,
|
||||
];
|
||||
List<Object?> get props => [date, occupancy];
|
||||
}
|
||||
|
||||
@ -14,7 +14,6 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
||||
CommunityModel community,
|
||||
List<SpaceModel> spaces,
|
||||
) {
|
||||
// Add to space tree bloc first
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
OnCommunitySelected(
|
||||
community.uuid,
|
||||
@ -69,7 +68,9 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
||||
CommunityModel community,
|
||||
SpaceModel child,
|
||||
) {
|
||||
// Do nothing else as per original implementation
|
||||
if (child.children.isNotEmpty) {
|
||||
return onSpaceSelected(context, community, child);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -14,23 +14,7 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
|
||||
CommunityModel community,
|
||||
List<SpaceModel> spaces,
|
||||
) {
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
OnCommunitySelected(
|
||||
community.uuid,
|
||||
spaces.isNotEmpty ? [spaces.first] : [],
|
||||
),
|
||||
);
|
||||
|
||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||
if (spaceTreeState.selectedCommunities.contains(community.uuid)) {
|
||||
clearData(context);
|
||||
return;
|
||||
}
|
||||
FetchOccupancyDataHelper.loadOccupancyData(
|
||||
context,
|
||||
communityId: community.uuid,
|
||||
spaceId: spaces.isNotEmpty ? spaces.first.uuid ?? '' : '',
|
||||
);
|
||||
// Do Nothing
|
||||
}
|
||||
|
||||
@override
|
||||
@ -40,26 +24,17 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
|
||||
SpaceModel space,
|
||||
) {
|
||||
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
||||
final selectedSpacesIds = spaceTreeBloc.state.selectedSpaces;
|
||||
final isSpaceSelected = selectedSpacesIds.contains(space.uuid);
|
||||
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.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 ?? '', []));
|
||||
}
|
||||
|
||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||
if (spaceTreeState.selectedCommunities.contains(community.uuid) ||
|
||||
spaceTreeState.selectedSpaces.contains(space.uuid)) {
|
||||
if (isSpaceSelected) {
|
||||
clearData(context);
|
||||
return;
|
||||
}
|
||||
|
||||
spaceTreeBloc
|
||||
..add(const SpaceTreeClearSelectionEvent())
|
||||
..add(OnSpaceSelected(community, space.uuid ?? '', []));
|
||||
|
||||
FetchOccupancyDataHelper.loadOccupancyData(
|
||||
context,
|
||||
communityId: community.uuid,
|
||||
@ -73,7 +48,7 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
|
||||
CommunityModel community,
|
||||
SpaceModel child,
|
||||
) {
|
||||
// Do nothing
|
||||
onSpaceSelected(context, community, child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -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/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/occupacy/remote_occupancy_service.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/occupacy/fake_occupacy_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/realtime_device_service/firebase_realtime_device_service.dart';
|
||||
@ -75,11 +75,7 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
|
||||
FirebaseRealtimeDeviceService(),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => OccupancyBloc(
|
||||
RemoteOccupancyService(_httpService),
|
||||
),
|
||||
),
|
||||
BlocProvider(create: (context) => OccupancyBloc(FakeOccupacyService())),
|
||||
BlocProvider(
|
||||
create: (context) => OccupancyHeatMapBloc(
|
||||
RemoteOccupancyHeatMapService(_httpService),
|
||||
|
||||
@ -30,6 +30,7 @@ abstract final class FetchOccupancyDataHelper {
|
||||
|
||||
loadOccupancyChartData(
|
||||
context,
|
||||
communityUuid: communityId,
|
||||
spaceUuid: spaceId,
|
||||
date: datePickerState.monthlyDate,
|
||||
);
|
||||
@ -58,14 +59,16 @@ abstract final class FetchOccupancyDataHelper {
|
||||
|
||||
static void loadOccupancyChartData(
|
||||
BuildContext context, {
|
||||
required String communityUuid,
|
||||
required String spaceUuid,
|
||||
required DateTime date,
|
||||
}) {
|
||||
context.read<OccupancyBloc>().add(
|
||||
LoadOccupancyEvent(
|
||||
GetOccupancyParam(
|
||||
monthDate: '${date.year}-${date.month.toString().padLeft(2, '0')}',
|
||||
monthDate: '${date.year}-${date.month}',
|
||||
spaceUuid: spaceUuid,
|
||||
communityUuid: communityUuid,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -16,10 +16,10 @@ class OccupancyChart extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return BarChart(
|
||||
BarChartData(
|
||||
maxY: 100.0,
|
||||
maxY: 1.0,
|
||||
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
||||
checkToShowHorizontalLine: (value) => true,
|
||||
horizontalInterval: 20,
|
||||
horizontalInterval: 0.2,
|
||||
),
|
||||
borderData: EnergyManagementChartsHelper.borderData(),
|
||||
barTouchData: _barTouchData(context),
|
||||
@ -33,21 +33,20 @@ class OccupancyChart extends StatelessWidget {
|
||||
),
|
||||
barGroups: List.generate(chartData.length, (index) {
|
||||
final actual = chartData[index];
|
||||
final occupancyValue = double.parse(actual.occupancy);
|
||||
return BarChartGroupData(
|
||||
x: index,
|
||||
barsSpace: 0,
|
||||
groupVertically: true,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: 100.0,
|
||||
fromY: occupancyValue == 0 ? occupancyValue : occupancyValue + 2.5,
|
||||
toY: 1.0,
|
||||
fromY: double.parse(actual.occupancy) + 0.025,
|
||||
color: ColorsManager.graysColor,
|
||||
width: _chartWidth,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: occupancyValue,
|
||||
toY: double.parse(actual.occupancy),
|
||||
color: ColorsManager.vividBlue.withValues(alpha: 0.8),
|
||||
width: _chartWidth,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
@ -89,7 +88,7 @@ class OccupancyChart extends StatelessWidget {
|
||||
final data = chartData;
|
||||
|
||||
final occupancyValue = double.parse(data[group.x.toInt()].occupancy);
|
||||
final percentage = '${(occupancyValue).toStringAsFixed(0)}%';
|
||||
final percentage = '${(occupancyValue * 100).toStringAsFixed(0)}%';
|
||||
|
||||
return BarTooltipItem(
|
||||
percentage,
|
||||
@ -109,14 +108,14 @@ class OccupancyChart extends StatelessWidget {
|
||||
final leftTitles = titlesData.leftTitles.copyWith(
|
||||
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
||||
reservedSize: 70,
|
||||
interval: 20,
|
||||
interval: 0.2,
|
||||
getTitlesWidget: (value, meta) => Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: 12),
|
||||
child: FittedBox(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
'${(value).toStringAsFixed(0)}%',
|
||||
'${(value * 100).toStringAsFixed(0)}%',
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
fontSize: 12,
|
||||
color: ColorsManager.greyColor,
|
||||
|
||||
@ -50,6 +50,9 @@ class OccupancyChartBox extends StatelessWidget {
|
||||
if (spaceTreeState.selectedSpaces.isNotEmpty) {
|
||||
FetchOccupancyDataHelper.loadOccupancyChartData(
|
||||
context,
|
||||
communityUuid:
|
||||
spaceTreeState.selectedCommunities.firstOrNull ??
|
||||
'',
|
||||
spaceUuid:
|
||||
spaceTreeState.selectedSpaces.firstOrNull ?? '',
|
||||
date: value,
|
||||
|
||||
@ -1,11 +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() => {'monthDate': monthDate};
|
||||
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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,32 +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';
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,7 +63,8 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
}
|
||||
}
|
||||
|
||||
bool _compareListOfLists(List<List<dynamic>> oldList, List<List<dynamic>> newList) {
|
||||
bool _compareListOfLists(
|
||||
List<List<dynamic>> oldList, List<List<dynamic>> newList) {
|
||||
// Check if the old and new lists are the same
|
||||
if (oldList.length != newList.length) return false;
|
||||
|
||||
@ -132,46 +133,60 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
children: [
|
||||
if (widget.withCheckBox) _buildSelectAllCheckbox(),
|
||||
...List.generate(widget.headers.length, (index) {
|
||||
return _buildTableHeaderCell(widget.headers[index], index);
|
||||
return _buildTableHeaderCell(
|
||||
widget.headers[index], index);
|
||||
})
|
||||
//...widget.headers.map((header) => _buildTableHeaderCell(header)),
|
||||
],
|
||||
),
|
||||
),
|
||||
widget.isEmpty
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SvgPicture.asset(Assets.emptyTable),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Text(
|
||||
widget.tableName == 'AccessManagement' ? 'No Password ' : 'No Devices',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: ColorsManager.grayColor),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
? SizedBox(
|
||||
height: widget.size.height * 0.5,
|
||||
width: widget.size.width,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SvgPicture.asset(Assets.emptyTable),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Text(
|
||||
widget.tableName == 'AccessManagement'
|
||||
? 'No Password '
|
||||
: 'No Devices',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
color:
|
||||
ColorsManager.grayColor),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
children: List.generate(widget.data.length, (index) {
|
||||
children:
|
||||
List.generate(widget.data.length, (index) {
|
||||
final row = widget.data[index];
|
||||
return Row(
|
||||
children: [
|
||||
if (widget.withCheckBox) _buildRowCheckbox(index, widget.size.height * 0.08),
|
||||
...row.map((cell) => _buildTableCell(cell.toString(), widget.size.height * 0.08)),
|
||||
if (widget.withCheckBox)
|
||||
_buildRowCheckbox(
|
||||
index, widget.size.height * 0.08),
|
||||
...row.map((cell) => _buildTableCell(
|
||||
cell.toString(),
|
||||
widget.size.height * 0.08)),
|
||||
],
|
||||
);
|
||||
}),
|
||||
@ -196,7 +211,9 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
),
|
||||
child: Checkbox(
|
||||
value: _selectAll,
|
||||
onChanged: widget.withSelectAll && widget.data.isNotEmpty ? _toggleSelectAll : null,
|
||||
onChanged: widget.withSelectAll && widget.data.isNotEmpty
|
||||
? _toggleSelectAll
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -238,7 +255,9 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
constraints: const BoxConstraints.expand(height: 40),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: index == widget.headers.length - 1 ? 12 : 8.0, vertical: 4),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
|
||||
vertical: 4),
|
||||
child: Text(
|
||||
title,
|
||||
style: context.textTheme.titleSmall!.copyWith(
|
||||
|
||||
@ -97,7 +97,8 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
|
||||
children: [
|
||||
_buildInfoRow('Space Name:',
|
||||
device.spaces?.firstOrNull?.spaceName ?? 'N/A'),
|
||||
_buildInfoRow('Room:', device.subspace?.subspaceName ?? 'N/A'),
|
||||
_buildInfoRow(
|
||||
'Sub space:', device.subspace?.subspaceName ?? 'N/A'),
|
||||
],
|
||||
),
|
||||
TableRow(
|
||||
|
||||
@ -41,13 +41,24 @@ class _UserDropdownMenuState extends State<UserDropdownMenu> {
|
||||
_isDropdownOpen = false;
|
||||
});
|
||||
},
|
||||
child: Transform.rotate(
|
||||
angle: _isDropdownOpen ? -1.5708 : 1.5708,
|
||||
child: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: Colors.white,
|
||||
size: 16,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 12),
|
||||
if (widget.user != null)
|
||||
Text(
|
||||
'${widget.user!.firstName} ${widget.user!.lastName}',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Transform.rotate(
|
||||
angle: _isDropdownOpen ? -1.5708 : 1.5708,
|
||||
child: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: Colors.white,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -92,13 +92,6 @@ class DesktopAppBar extends StatelessWidget {
|
||||
if (rightBody != null) rightBody!,
|
||||
const SizedBox(width: 24),
|
||||
_UserAvatar(),
|
||||
const SizedBox(width: 12),
|
||||
if (user != null)
|
||||
Text(
|
||||
'${user.firstName} ${user.lastName}',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
UserDropdownMenu(user: user),
|
||||
],
|
||||
);
|
||||
@ -146,14 +139,6 @@ class TabletAppBar extends StatelessWidget {
|
||||
if (rightBody != null) rightBody!,
|
||||
const SizedBox(width: 16),
|
||||
_UserAvatar(),
|
||||
if (user != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'${user.firstName} ${user.lastName}',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: 14),
|
||||
),
|
||||
],
|
||||
UserDropdownMenu(user: user),
|
||||
],
|
||||
);
|
||||
@ -215,14 +200,6 @@ class MobileAppBar extends StatelessWidget {
|
||||
return Row(
|
||||
children: [
|
||||
_UserAvatar(),
|
||||
if (user != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'${user.firstName} ${user.lastName}',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: 14),
|
||||
),
|
||||
],
|
||||
UserDropdownMenu(user: user),
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user