Compare commits

..

5 Commits

15 changed files with 137 additions and 125 deletions

Binary file not shown.

Before

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

@ -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

@ -63,8 +63,7 @@ class _DynamicTableState extends State<DynamicTable> {
} }
} }
bool _compareListOfLists( bool _compareListOfLists(List<List<dynamic>> oldList, List<List<dynamic>> newList) {
List<List<dynamic>> oldList, List<List<dynamic>> newList) {
// Check if the old and new lists are the same // Check if the old and new lists are the same
if (oldList.length != newList.length) return false; if (oldList.length != newList.length) return false;
@ -133,60 +132,46 @@ class _DynamicTableState extends State<DynamicTable> {
children: [ children: [
if (widget.withCheckBox) _buildSelectAllCheckbox(), if (widget.withCheckBox) _buildSelectAllCheckbox(),
...List.generate(widget.headers.length, (index) { ...List.generate(widget.headers.length, (index) {
return _buildTableHeaderCell( return _buildTableHeaderCell(widget.headers[index], index);
widget.headers[index], index);
}) })
//...widget.headers.map((header) => _buildTableHeaderCell(header)), //...widget.headers.map((header) => _buildTableHeaderCell(header)),
], ],
), ),
), ),
widget.isEmpty widget.isEmpty
? SizedBox( ? Column(
height: widget.size.height * 0.5, mainAxisAlignment: MainAxisAlignment.center,
width: widget.size.width, children: [
child: Column( Row(
mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ mainAxisAlignment: MainAxisAlignment.center,
Row( children: [
crossAxisAlignment: CrossAxisAlignment.center, Column(
mainAxisAlignment: MainAxisAlignment.center, children: [
children: [ SvgPicture.asset(Assets.emptyTable),
Column( const SizedBox(
children: [ height: 15,
SvgPicture.asset(Assets.emptyTable), ),
const SizedBox( Text(
height: 15, widget.tableName == 'AccessManagement' ? 'No Password ' : 'No Devices',
), style: Theme.of(context)
Text( .textTheme
widget.tableName == 'AccessManagement' .bodySmall!
? 'No Password ' .copyWith(color: ColorsManager.grayColor),
: 'No Devices', )
style: Theme.of(context) ],
.textTheme ),
.bodySmall! ],
.copyWith( ),
color: ],
ColorsManager.grayColor),
)
],
),
],
),
],
),
) )
: Column( : Column(
children: children: List.generate(widget.data.length, (index) {
List.generate(widget.data.length, (index) {
final row = widget.data[index]; final row = widget.data[index];
return Row( return Row(
children: [ children: [
if (widget.withCheckBox) if (widget.withCheckBox) _buildRowCheckbox(index, widget.size.height * 0.08),
_buildRowCheckbox( ...row.map((cell) => _buildTableCell(cell.toString(), widget.size.height * 0.08)),
index, widget.size.height * 0.08),
...row.map((cell) => _buildTableCell(
cell.toString(),
widget.size.height * 0.08)),
], ],
); );
}), }),
@ -211,9 +196,7 @@ class _DynamicTableState extends State<DynamicTable> {
), ),
child: Checkbox( child: Checkbox(
value: _selectAll, value: _selectAll,
onChanged: widget.withSelectAll && widget.data.isNotEmpty onChanged: widget.withSelectAll && widget.data.isNotEmpty ? _toggleSelectAll : null,
? _toggleSelectAll
: null,
), ),
); );
} }
@ -255,9 +238,7 @@ class _DynamicTableState extends State<DynamicTable> {
constraints: const BoxConstraints.expand(height: 40), constraints: const BoxConstraints.expand(height: 40),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(horizontal: index == widget.headers.length - 1 ? 12 : 8.0, vertical: 4),
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
vertical: 4),
child: Text( child: Text(
title, title,
style: context.textTheme.titleSmall!.copyWith( style: context.textTheme.titleSmall!.copyWith(

View File

@ -97,8 +97,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
children: [ children: [
_buildInfoRow('Space Name:', _buildInfoRow('Space Name:',
device.spaces?.firstOrNull?.spaceName ?? 'N/A'), device.spaces?.firstOrNull?.spaceName ?? 'N/A'),
_buildInfoRow( _buildInfoRow('Room:', device.subspace?.subspaceName ?? 'N/A'),
'Sub space:', device.subspace?.subspaceName ?? 'N/A'),
], ],
), ),
TableRow( TableRow(

View File

@ -2,7 +2,6 @@ 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

@ -41,24 +41,13 @@ class _UserDropdownMenuState extends State<UserDropdownMenu> {
_isDropdownOpen = false; _isDropdownOpen = false;
}); });
}, },
child: Row( child: Transform.rotate(
children: [ angle: _isDropdownOpen ? -1.5708 : 1.5708,
const SizedBox(width: 12), child: const Icon(
if (widget.user != null) Icons.arrow_forward_ios,
Text( color: Colors.white,
'${widget.user!.firstName} ${widget.user!.lastName}', size: 16,
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,
),
),
],
), ),
), ),
], ],

View File

@ -92,6 +92,13 @@ class DesktopAppBar extends StatelessWidget {
if (rightBody != null) rightBody!, if (rightBody != null) rightBody!,
const SizedBox(width: 24), const SizedBox(width: 24),
_UserAvatar(), _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), UserDropdownMenu(user: user),
], ],
); );
@ -139,6 +146,14 @@ class TabletAppBar extends StatelessWidget {
if (rightBody != null) rightBody!, if (rightBody != null) rightBody!,
const SizedBox(width: 16), const SizedBox(width: 16),
_UserAvatar(), _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), UserDropdownMenu(user: user),
], ],
); );
@ -200,6 +215,14 @@ class MobileAppBar extends StatelessWidget {
return Row( return Row(
children: [ children: [
_UserAvatar(), _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), UserDropdownMenu(user: user),
], ],
); );

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,11 +28,14 @@ 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: Image.asset( child: SvgPicture.asset(
Assets.webBackgroundPng, Assets.webBackground,
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
), ),
Container(
color: Colors.white.withOpacity(0.7),
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [