Merge branch 'SP-1703-fe-build-device-overview-page_curtain_module' of https://github.com/SyncrowIOT/web into SP-1703-fe-build-device-overview-page_curtain_module

This commit is contained in:
Faris Armoush
2025-06-29 10:49:04 +03:00
9 changed files with 74 additions and 56 deletions

View File

@ -21,11 +21,11 @@ class OccupancyHeatMapModel extends Equatable {
return OccupancyHeatMapModel( return OccupancyHeatMapModel(
uuid: json['uuid'] as String? ?? '', uuid: json['uuid'] as String? ?? '',
eventDate: DateTime( eventDate: DateTime.utc(
int.parse(year ?? '2025'), int.parse(year ?? '2025'),
int.parse(month ?? '1'), int.parse(month ?? '1'),
int.parse(day ?? '1'), int.parse(day ?? '1'),
).toUtc(), ),
countTotalPresenceDetected: num.parse( countTotalPresenceDetected: num.parse(
json['count_total_presence_detected']?.toString() ?? '0', json['count_total_presence_detected']?.toString() ?? '0',
).toInt(), ).toInt(),

View File

@ -14,14 +14,17 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
return Expanded( return Expanded(
child: LineChart( child: LineChart(
LineChartData( LineChartData(
maxY: chartData.isEmpty
? null
: chartData.map((e) => e.value).reduce((a, b) => a > b ? a : b) + 250,
clipData: const FlClipData.vertical(), clipData: const FlClipData.vertical(),
titlesData: EnergyManagementChartsHelper.titlesData( titlesData: EnergyManagementChartsHelper.titlesData(
context, context,
leftTitlesInterval: 250, leftTitlesInterval: 500,
), ),
gridData: EnergyManagementChartsHelper.gridData().copyWith( gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true, checkToShowHorizontalLine: (value) => true,
horizontalInterval: 250, horizontalInterval: 500,
), ),
borderData: EnergyManagementChartsHelper.borderData(), borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: EnergyManagementChartsHelper.lineTouchData(), lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
@ -29,7 +32,6 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
), ),
duration: Duration.zero, duration: Duration.zero,
curve: Curves.easeIn, curve: Curves.easeIn,
), ),
); );
} }

View File

@ -9,8 +9,13 @@ import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class OccupancyHeatMap extends StatelessWidget { class OccupancyHeatMap extends StatelessWidget {
const OccupancyHeatMap({required this.heatMapData, super.key}); const OccupancyHeatMap({
required this.heatMapData,
required this.selectedDate,
super.key,
});
final Map<DateTime, int> heatMapData; final Map<DateTime, int> heatMapData;
final DateTime selectedDate;
static const _cellSize = 16.0; static const _cellSize = 16.0;
static const _totalWeeks = 53; static const _totalWeeks = 53;
@ -20,14 +25,14 @@ class OccupancyHeatMap extends StatelessWidget {
: 0; : 0;
DateTime _getStartingDate() { DateTime _getStartingDate() {
final jan1 = DateTime(DateTime.now().year, 1, 1).toUtc(); final jan1 = DateTime.utc(selectedDate.year, 1, 1);
final startOfWeek = jan1.subtract(Duration(days: jan1.weekday - 1)); final startOfWeek = jan1.subtract(Duration(days: jan1.weekday - 1));
return startOfWeek; return startOfWeek;
} }
List<OccupancyPaintItem> _generatePaintItems(DateTime startDate) { List<OccupancyPaintItem> _generatePaintItems(DateTime startDate) {
return List.generate(_totalWeeks * 7, (index) { return List.generate(_totalWeeks * 7, (index) {
final date = startDate.toUtc().add(Duration(days: index)); final date = startDate.add(Duration(days: index));
final value = heatMapData[date] ?? 0; final value = heatMapData[date] ?? 0;
return OccupancyPaintItem(index: index, value: value, date: date); return OccupancyPaintItem(index: index, value: value, date: date);
}); });

View File

@ -70,6 +70,8 @@ class OccupancyHeatMapBox extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
Expanded( Expanded(
child: OccupancyHeatMap( child: OccupancyHeatMap(
selectedDate:
context.watch<AnalyticsDatePickerBloc>().state.yearlyDate,
heatMapData: state.heatMapData.asMap().map( heatMapData: state.heatMapData.asMap().map(
(_, value) => MapEntry( (_, value) => MapEntry(
value.eventDate, value.eventDate,

View File

@ -45,8 +45,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
) async { ) async {
emit(AcsLoadingState()); emit(AcsLoadingState());
try { try {
final status = final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status); deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
if (deviceStatus.countdown1 != 0) { if (deviceStatus.countdown1 != 0) {
final totalMinutes = deviceStatus.countdown1 * 6; final totalMinutes = deviceStatus.countdown1 * 6;
@ -74,22 +73,25 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
void _listenToChanges(String deviceId) { void _listenToChanges(String deviceId) {
try { try {
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
_deviceStatusSubscription = _deviceStatusSubscription = ref.onValue.listen((DatabaseEvent event) async {
ref.onValue.listen((DatabaseEvent event) async {
if (event.snapshot.value == null) return; if (event.snapshot.value == null) return;
Map<dynamic, dynamic> usersMap = final usersMap = event.snapshot.value! as Map<dynamic, dynamic>;
event.snapshot.value as Map<dynamic, dynamic>;
List<Status> statusList = []; final statusList = <Status>[];
usersMap['status'].forEach((element) { usersMap['status'].forEach((element) {
statusList statusList.add(Status(code: element['code'], value: element['value']));
.add(Status(code: element['code'], value: element['value']));
}); });
deviceStatus = deviceStatus =
AcStatusModel.fromJson(usersMap['productUuid'], statusList); AcStatusModel.fromJson(usersMap['productUuid'], statusList);
deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList);
print('Device status updated: ${deviceStatus.acSwitch}');
if (!isClosed) { if (!isClosed) {
add(AcStatusUpdated(deviceStatus)); add(AcStatusUpdated(deviceStatus));
} }
@ -109,21 +111,14 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
AcControlEvent event, AcControlEvent event,
Emitter<AcsState> emit, Emitter<AcsState> emit,
) async { ) async {
emit(AcsLoadingState());
try { try {
final success = await controlDeviceService.controlDevice( _updateDeviceFunctionFromCode(event.code, event.value);
emit(ACStatusLoaded(status: deviceStatus));
await controlDeviceService.controlDevice(
deviceUuid: event.deviceId, deviceUuid: event.deviceId,
status: Status(code: event.code, value: event.value), status: Status(code: event.code, value: event.value),
); );
_updateDeviceFunctionFromCode(event.code, event.value); } catch (e) {}
emit(ACStatusLoaded(status: deviceStatus));
if (!success) {
emit(const AcsFailedState(error: 'Failed to control device'));
}
} catch (e) {
emit(AcsFailedState(error: e.toString()));
}
} }
FutureOr<void> _onFetchAcBatchStatus( FutureOr<void> _onFetchAcBatchStatus(
@ -132,10 +127,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
) async { ) async {
emit(AcsLoadingState()); emit(AcsLoadingState());
try { try {
final status = final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = AcStatusModel.fromJson(event.devicesIds.first, status.status);
deviceStatus =
AcStatusModel.fromJson(event.devicesIds.first, status.status);
emit(ACStatusLoaded(status: deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
} catch (e) { } catch (e) {
emit(AcsFailedState(error: e.toString())); emit(AcsFailedState(error: e.toString()));
@ -146,23 +139,16 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
AcBatchControlEvent event, AcBatchControlEvent event,
Emitter<AcsState> emit, Emitter<AcsState> emit,
) async { ) async {
emit(AcsLoadingState());
_updateDeviceFunctionFromCode(event.code, event.value); _updateDeviceFunctionFromCode(event.code, event.value);
emit(ACStatusLoaded(status: deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
try { try {
final success = await batchControlDevicesService.batchControlDevices( await batchControlDevicesService.batchControlDevices(
uuids: event.devicesIds, uuids: event.devicesIds,
code: event.code, code: event.code,
value: event.value, value: event.value,
); );
} catch (e) {}
if (!success) {
emit(const AcsFailedState(error: 'Failed to control devices'));
}
} catch (e) {
emit(AcsFailedState(error: e.toString()));
}
} }
Future<void> _onFactoryReset( Future<void> _onFactoryReset(
@ -195,8 +181,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
void _handleIncreaseTime(IncreaseTimeEvent event, Emitter<AcsState> emit) { void _handleIncreaseTime(IncreaseTimeEvent event, Emitter<AcsState> emit) {
if (state is! ACStatusLoaded) return; if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded; final currentState = state as ACStatusLoaded;
int newHours = scheduledHours; var newHours = scheduledHours;
int newMinutes = scheduledMinutes + 30; var newMinutes = scheduledMinutes + 30;
newHours += newMinutes ~/ 60; newHours += newMinutes ~/ 60;
newMinutes = newMinutes % 60; newMinutes = newMinutes % 60;
if (newHours > 23) { if (newHours > 23) {
@ -218,7 +204,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
) { ) {
if (state is! ACStatusLoaded) return; if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded; final currentState = state as ACStatusLoaded;
int totalMinutes = (scheduledHours * 60) + scheduledMinutes; var totalMinutes = (scheduledHours * 60) + scheduledMinutes;
totalMinutes = (totalMinutes - 30).clamp(0, 1440); totalMinutes = (totalMinutes - 30).clamp(0, 1440);
scheduledHours = totalMinutes ~/ 60; scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60; scheduledMinutes = totalMinutes % 60;
@ -291,7 +277,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
void _startCountdownTimer(Emitter<AcsState> emit) { void _startCountdownTimer(Emitter<AcsState> emit) {
_countdownTimer?.cancel(); _countdownTimer?.cancel();
int totalSeconds = (scheduledHours * 3600) + (scheduledMinutes * 60); var totalSeconds = (scheduledHours * 3600) + (scheduledMinutes * 60);
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (totalSeconds > 0) { if (totalSeconds > 0) {

View File

@ -64,7 +64,7 @@ class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout {
deviceUuid: deviceId, deviceUuid: deviceId,
category: 'CUR_2', category: 'CUR_2',
code: 'control', code: 'control',
value: 'open',
), ),
)); ));
}, },

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/schedule_device/bloc/schedule_bloc.dart'; import 'package:syncrow_web/pages/device_managment/schedule_device/bloc/schedule_bloc.dart';
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/count_down_button.dart'; import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/count_down_button.dart';
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/count_down_inching_view.dart'; import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/count_down_inching_view.dart';
@ -9,6 +10,7 @@ import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widg
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart'; import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart';
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_selector.dart'; import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_selector.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
class BuildScheduleView extends StatelessWidget { class BuildScheduleView extends StatelessWidget {
@ -17,12 +19,10 @@ class BuildScheduleView extends StatelessWidget {
required this.deviceUuid, required this.deviceUuid,
required this.category, required this.category,
this.code, this.code,
this.value,
}); });
final String deviceUuid; final String deviceUuid;
final String category; final String category;
final String? code; final String? code;
final String? value;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -64,15 +64,20 @@ class BuildScheduleView extends StatelessWidget {
final entry = await ScheduleDialogHelper final entry = await ScheduleDialogHelper
.showAddScheduleDialog( .showAddScheduleDialog(
context, context,
schedule: null, schedule: ScheduleEntry(
category: category,
time: '',
function: Status(
code: code.toString(), value: null),
days: [],
),
isEdit: false, isEdit: false,
code: code, code: code,
value: value,
); );
if (entry != null) { if (entry != null) {
context.read<ScheduleBloc>().add( context.read<ScheduleBloc>().add(
ScheduleAddEvent( ScheduleAddEvent(
category: entry.category, category: category,
code: entry.function.code, code: entry.function.code,
time: entry.time, time: entry.time,
functionOn: entry.function.value, functionOn: entry.function.value,

View File

@ -162,11 +162,18 @@ class _ScheduleTableView extends StatelessWidget {
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () { onTap: () {
bool temp;
if (schedule.category == 'CUR_2') {
temp = schedule.function.value == 'open' ? true : false;
} else {
temp = schedule.function.value as bool;
}
context.read<ScheduleBloc>().add( context.read<ScheduleBloc>().add(
ScheduleUpdateEntryEvent( ScheduleUpdateEntryEvent(
category: schedule.category, category: schedule.category,
scheduleId: schedule.scheduleId, scheduleId: schedule.scheduleId,
functionOn: schedule.function.value, functionOn: temp,
// schedule.function.value,
enable: !schedule.enable, enable: !schedule.enable,
), ),
); );
@ -188,7 +195,10 @@ class _ScheduleTableView extends StatelessWidget {
child: Text(_getSelectedDays( child: Text(_getSelectedDays(
ScheduleModel.parseSelectedDays(schedule.days)))), ScheduleModel.parseSelectedDays(schedule.days)))),
Center(child: Text(formatIsoStringToTime(schedule.time, context))), Center(child: Text(formatIsoStringToTime(schedule.time, context))),
Center(child: Text(schedule.function.value ? 'On' : 'Off')), schedule.category == 'CUR_2'
? Center(
child: Text(schedule.function.value == true ? 'open' : 'close'))
: Center(child: Text(schedule.function.value ? 'On' : 'Off')),
Center( Center(
child: Wrap( child: Wrap(
runAlignment: WrapAlignment.center, runAlignment: WrapAlignment.center,

View File

@ -18,7 +18,6 @@ class ScheduleDialogHelper {
ScheduleEntry? schedule, ScheduleEntry? schedule,
bool isEdit = false, bool isEdit = false,
String? code, String? code,
String? value,
}) { }) {
final initialTime = schedule != null final initialTime = schedule != null
? _convertStringToTimeOfDay(schedule.time) ? _convertStringToTimeOfDay(schedule.time)
@ -117,12 +116,21 @@ class ScheduleDialogHelper {
width: 100, width: 100,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
dynamic temp;
if (schedule?.category == 'CUR_2') {
temp = functionOn! ? 'open' : 'close';
} else {
temp = functionOn;
}
print(temp);
final entry = ScheduleEntry( final entry = ScheduleEntry(
category: schedule?.category ?? 'switch_1', category: schedule?.category ?? 'switch_1',
time: _formatTimeOfDayToISO(selectedTime), time: _formatTimeOfDayToISO(selectedTime),
function: Status( function: Status(
code: code ?? 'switch_1', code: code ?? 'switch_1',
value: value ?? functionOn), value: temp,
// functionOn,
),
days: _convertSelectedDaysToStrings(selectedDays), days: _convertSelectedDaysToStrings(selectedDays),
scheduleId: schedule?.scheduleId, scheduleId: schedule?.scheduleId,
); );