Merge branch 'dev' of https://github.com/SyncrowIOT/web into dev

This commit is contained in:
Faris Armoush
2025-06-28 13:16:16 +03:00
10 changed files with 168 additions and 76 deletions

View File

@ -14,12 +14,21 @@ class OccupancyHeatMapModel extends Equatable {
});
factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) {
final eventDate = json['event_date'] as String?;
final year = eventDate?.split('-')[0];
final month = eventDate?.split('-')[1];
final day = eventDate?.split('-')[2];
return OccupancyHeatMapModel(
uuid: json['uuid'] as String? ?? '',
eventDate: DateTime.parse(
json['event_date'] as String? ?? '${DateTime.now()}',
eventDate: DateTime.utc(
int.parse(year ?? '2025'),
int.parse(month ?? '1'),
int.parse(day ?? '1'),
),
countTotalPresenceDetected: json['count_total_presence_detected'] as int? ?? 0,
countTotalPresenceDetected: num.parse(
json['count_total_presence_detected']?.toString() ?? '0',
).toInt(),
);
}

View File

@ -6,7 +6,7 @@ enum AqiType {
aqi('AQI', '', 'aqi'),
pm25('PM2.5', 'µg/m³', 'pm25'),
pm10('PM10', 'µg/m³', 'pm10'),
hcho('HCHO', 'mg/m³', 'cho2'),
hcho('HCHO', 'mg/m³', 'ch2o'),
tvoc('TVOC', 'mg/m³', 'voc'),
co2('CO2', 'ppm', 'co2');

View File

@ -28,15 +28,29 @@ class AnalyticsDeviceDropdown extends StatelessWidget {
),
),
child: Visibility(
visible: state.devices.isNotEmpty,
replacement: _buildNoDevicesFound(context),
child: _buildDevicesDropdown(context, state),
visible: state.status != AnalyticsDevicesStatus.loading,
replacement: _buildLoadingIndicator(),
child: Visibility(
visible: state.devices.isNotEmpty,
replacement: _buildNoDevicesFound(context),
child: _buildDevicesDropdown(context, state),
),
),
);
},
);
}
Widget _buildLoadingIndicator() {
return const Center(
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(strokeWidth: 3),
),
);
}
static const _defaultPadding = EdgeInsetsDirectional.symmetric(
horizontal: 20,
vertical: 2,

View File

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

View File

@ -52,7 +52,7 @@ class _InteractiveHeatMapState extends State<InteractiveHeatMap> {
color: Colors.transparent,
child: Transform.translate(
offset: Offset(-(widget.cellSize * 2.5), -50),
child: HeatMapTooltip(date: item.date, value: item.value),
child: HeatMapTooltip(date: item.date.toUtc(), value: item.value),
),
),
),

View File

@ -36,7 +36,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
////////////////////////////// forget password //////////////////////////////////
final TextEditingController forgetEmailController = TextEditingController();
final TextEditingController forgetPasswordController = TextEditingController();
final TextEditingController forgetPasswordController =
TextEditingController();
final TextEditingController forgetOtp = TextEditingController();
final forgetFormKey = GlobalKey<FormState>();
final forgetEmailKey = GlobalKey<FormState>();
@ -53,7 +54,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
return;
}
_remainingTime = 1;
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
try {
forgetEmailValidate = '';
_remainingTime = (await AuthenticationAPI.sendOtp(
@ -90,7 +92,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
_timer?.cancel();
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
} else {
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
}
});
}
@ -100,7 +103,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
}
Future<void> changePassword(
Future<void> changePassword(
ChangePasswordEvent event, Emitter<AuthState> emit) async {
emit(LoadingForgetState());
try {
@ -122,7 +125,6 @@ Future<void> changePassword(
}
}
String? validateCode(String? value) {
if (value == null || value.isEmpty) {
return 'Code is required';
@ -131,7 +133,9 @@ Future<void> changePassword(
}
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime));
emit(TimerState(
isButtonEnabled: event.isButtonEnabled,
remainingTime: event.remainingTime));
}
///////////////////////////////////// login /////////////////////////////////////
@ -151,7 +155,6 @@ Future<void> changePassword(
static UserModel? user;
bool showValidationMessage = false;
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
emit(AuthLoading());
if (isChecked) {
@ -170,11 +173,11 @@ Future<void> changePassword(
);
} on APIException catch (e) {
validate = e.message;
emit(LoginInitial());
emit(LoginFailure(error: validate));
return;
} catch (e) {
validate = 'Something went wrong';
emit(LoginInitial());
emit(LoginFailure(error: validate));
return;
}
@ -197,7 +200,6 @@ Future<void> changePassword(
}
}
checkBoxToggle(
CheckBoxEvent event,
Emitter<AuthState> emit,
@ -339,12 +341,14 @@ Future<void> changePassword(
static Future<String> getTokenAndValidate() async {
try {
const storage = FlutterSecureStorage();
final firstLaunch =
await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(
StringsManager.firstLaunch) ??
true;
if (firstLaunch) {
storage.deleteAll();
}
await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false);
await SharedPreferencesHelper.saveBoolToSP(
StringsManager.firstLaunch, false);
final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
if (value.isEmpty) {
return 'Token not found';
@ -397,7 +401,9 @@ Future<void> changePassword(
final String formattedTime = [
if (days > 0) '${days}d', // Append 'd' for days
if (days > 0 || hours > 0)
hours.toString().padLeft(2, '0'), // Show hours if there are days or hours
hours
.toString()
.padLeft(2, '0'), // Show hours if there are days or hours
minutes.toString().padLeft(2, '0'),
seconds.toString().padLeft(2, '0'),
].join(':');

View File

@ -62,7 +62,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
final buttonLabel =
(selectedDevices.length > 1) ? 'Batch Control' : 'Control';
final isAnyDeviceOffline =
selectedDevices.any((element) => !(element.online ?? false));
return Row(
children: [
Expanded(child: SpaceTreeView(
@ -103,8 +104,26 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
decoration: containerDecoration,
child: Center(
child: DefaultButton(
backgroundColor: isAnyDeviceOffline
? ColorsManager.primaryColor
.withValues(alpha: 0.1)
: null,
onPressed: isControlButtonEnabled
? () {
if (isAnyDeviceOffline) {
ScaffoldMessenger.of(context)
.showSnackBar(
const SnackBar(
content: Text(
'This Device is Offline',
),
duration:
Duration(seconds: 2),
),
);
return;
}
if (selectedDevices.length == 1) {
showDialog(
context: context,

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/auth/model/user_model.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
@ -12,8 +14,11 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class EditUserDialog extends StatefulWidget {
final String? userId;
const EditUserDialog({super.key, this.userId});
final RolesUserModel? user;
const EditUserDialog({
super.key,
this.user,
});
@override
_EditUserDialogState createState() => _EditUserDialogState();
@ -28,10 +33,11 @@ class _EditUserDialogState extends State<EditUserDialog> {
create: (BuildContext context) => UsersBloc()
// ..add(const LoadCommunityAndSpacesEvent())
..add(const RoleEvent())
..add(GetUserByIdEvent(uuid: widget.userId)),
..add(GetUserByIdEvent(uuid: widget.user!.uuid)),
child: BlocConsumer<UsersBloc, UsersState>(listener: (context, state) {
if (state is SpacesLoadedState) {
BlocProvider.of<UsersBloc>(context).add(GetUserByIdEvent(uuid: widget.userId));
BlocProvider.of<UsersBloc>(context)
.add(GetUserByIdEvent(uuid: widget.user!.uuid));
}
}, builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
@ -39,7 +45,8 @@ class _EditUserDialogState extends State<EditUserDialog> {
return Dialog(
child: Container(
decoration: const BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))),
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20))),
width: 900,
child: Column(
children: [
@ -68,7 +75,8 @@ class _EditUserDialogState extends State<EditUserDialog> {
children: [
_buildStep1Indicator(1, "Basics", _blocRole),
_buildStep2Indicator(2, "Spaces", _blocRole),
_buildStep3Indicator(3, "Role & Permissions", _blocRole),
_buildStep3Indicator(
3, "Role & Permissions", _blocRole),
],
),
),
@ -86,7 +94,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
children: [
const SizedBox(height: 10),
Expanded(
child: _getFormContent(widget.userId),
child: _getFormContent(widget.user!),
),
const SizedBox(height: 20),
],
@ -116,13 +124,14 @@ class _EditUserDialogState extends State<EditUserDialog> {
if (currentStep < 3) {
currentStep++;
if (currentStep == 2) {
_blocRole.add(CheckStepStatus(isEditUser: true));
_blocRole
.add(CheckStepStatus(isEditUser: true));
} else if (currentStep == 3) {
_blocRole.add(const CheckSpacesStepStatus());
}
} else {
_blocRole
.add(EditInviteUsers(context: context, userId: widget.userId!));
_blocRole.add(EditInviteUsers(
context: context, userId: widget.user!.uuid));
}
});
},
@ -131,7 +140,8 @@ class _EditUserDialogState extends State<EditUserDialog> {
style: TextStyle(
color: (_blocRole.isCompleteSpaces == false ||
_blocRole.isCompleteBasics == false ||
_blocRole.isCompleteRolePermissions == false) &&
_blocRole.isCompleteRolePermissions ==
false) &&
currentStep == 3
? ColorsManager.grayColor
: ColorsManager.secondaryColor),
@ -146,15 +156,15 @@ class _EditUserDialogState extends State<EditUserDialog> {
}));
}
Widget _getFormContent(userid) {
Widget _getFormContent(RolesUserModel user) {
switch (currentStep) {
case 1:
return BasicsView(
userId: userid,
userId: user.uuid,
);
case 2:
return SpacesAccessView(
userId: userid,
userId: user.uuid,
);
case 3:
return const RolesAndPermission();
@ -166,6 +176,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
int step3 = 0;
Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) {
final isCurrentStep = currentStep == step;
return GestureDetector(
onTap: () {
setState(() {
@ -189,7 +200,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
child: Row(
children: [
SvgPicture.asset(
currentStep == step
isCurrentStep
? Assets.currentProcessIcon
: bloc.isCompleteBasics == false
? Assets.wrongProcessIcon
@ -204,8 +215,11 @@ class _EditUserDialogState extends State<EditUserDialog> {
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
color: isCurrentStep
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight:
isCurrentStep ? FontWeight.bold : FontWeight.normal,
),
),
],
@ -229,6 +243,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
}
Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) {
final isCurrentStep = currentStep == step;
return GestureDetector(
onTap: () {
setState(() {
@ -248,7 +263,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
child: Row(
children: [
SvgPicture.asset(
currentStep == step
isCurrentStep
? Assets.currentProcessIcon
: bloc.isCompleteSpaces == false
? Assets.wrongProcessIcon
@ -263,8 +278,11 @@ class _EditUserDialogState extends State<EditUserDialog> {
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
color: isCurrentStep
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight:
isCurrentStep ? FontWeight.bold : FontWeight.normal,
),
),
],
@ -288,6 +306,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
}
Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) {
final isCurrentStep = currentStep == step;
return GestureDetector(
onTap: () {
setState(() {
@ -306,7 +325,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
child: Row(
children: [
SvgPicture.asset(
currentStep == step
isCurrentStep
? Assets.currentProcessIcon
: bloc.isCompleteRolePermissions == false
? Assets.wrongProcessIcon
@ -321,8 +340,11 @@ class _EditUserDialogState extends State<EditUserDialog> {
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
color: isCurrentStep
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight:
isCurrentStep ? FontWeight.bold : FontWeight.normal,
),
),
],

View File

@ -19,6 +19,7 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class UsersPage extends StatelessWidget {
UsersPage({super.key});
@ -451,33 +452,31 @@ class UsersPage extends StatelessWidget {
),
Row(
children: [
user.isEnabled != false
? actionButton(
isActive: true,
title: "Edit",
onTap: () {
context
.read<SpaceTreeBloc>()
.add(ClearCachedData());
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return EditUserDialog(
userId: user.uuid);
},
).then((v) {
if (v != null) {
if (v != null) {
_blocRole.add(const GetUsers());
}
}
});
if (user.isEnabled != false)
actionButton(
isActive: true,
title: "Edit",
onTap: () {
context
.read<SpaceTreeBloc>()
.add(ClearCachedData());
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return EditUserDialog(user: user);
},
)
: actionButton(
title: "Edit",
),
).then((v) {
if (v != null) {
_blocRole.add(const GetUsers());
}
});
},
)
else
actionButton(
title: "Edit",
),
actionButton(
title: "Delete",
onTap: () {

View File

@ -68,7 +68,7 @@ class VisitorPasswordBloc
DateTime? startTime = DateTime.now();
DateTime? endTime;
String startTimeAccess = 'Start Time';
String startTimeAccess = DateTime.now().toString().split('.').first;
String endTimeAccess = 'End Time';
PasswordStatus? passwordStatus;
selectAccessType(
@ -136,6 +136,27 @@ class VisitorPasswordBloc
);
return;
}
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) {
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) {
await showDialog<void>(
context: event.context,
builder: (context) => AlertDialog(
title: const Text('Effective Time cannot be earlier than current time.'),
actionsAlignment: MainAxisAlignment.center,
content:
FilledButton(
onPressed: () {
Navigator.of(event.context).pop();
add(SelectTimeVisitorPassword(context: event.context, isStart: true, isRepeat: false));
},
child: const Text('OK'),
),
),
);
}
return;
}
effectiveTimeTimeStamp = selectedTimestamp;
startTimeAccess = selectedDateTime.toString().split('.').first;
} else {