Compare commits

...

8 Commits

Author SHA1 Message Date
WaseemAbbasi22 7186fb13c0 improvements 5 days ago
WaseemAbbasi22 fb6dc22c05 delete account apis implemented 1 week ago
WaseemAbbasi22 79c4404b0a delete button implemented 2 weeks ago
WaseemAbbasi22 25b9870d93 added icon for site change 2 weeks ago
WaseemAbbasi22 2514fefb05 improvements for demo request form 2 weeks ago
WaseemAbbasi22 8d4b9f2f79 improvements for signup form 2 weeks ago
WaseemAbbasi22 8ad2e79b1e improvements 2 weeks ago
WaseemAbbasi22 61472df110 bug fixes for api error messages 2 weeks ago

@ -0,0 +1,6 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 15.9232C7.5 16.1256 7.5 16.2268 7.5477 16.3009C7.59539 16.375 7.6945 16.4202 7.89273 16.5104C8.114 16.6112 8.3948 16.7491 8.77368 16.9373L11.1668 18.1265C11.3274 18.2063 11.4078 18.2462 11.4804 18.2271C11.553 18.208 11.6074 18.1277 11.716 17.9671C11.9581 17.6092 12.2545 17.2896 12.5518 16.969L12.6531 16.8596L12.8939 16.5989C12.9463 16.5421 12.9725 16.5137 12.9863 16.4786C13 16.4435 13 16.4048 13 16.3275L13 2.82791C13 2.64718 13 2.55681 12.955 2.48426C12.91 2.41171 12.8291 2.37148 12.6671 2.29101L9.89257 0.912339C9.22359 0.579911 8.67553 0.307573 8.20232 0.125115C7.91028 0.0125106 7.76425 -0.0437916 7.63544 0.040665L7.62522 0.0476696C7.5 0.13737 7.5 0.306249 7.5 0.644007L7.5 15.9232Z" fill="#767676"/>
<path d="M21.5 11.0171C21.5 11.3879 21.5 11.5734 21.4008 11.6312C21.3016 11.6891 21.1188 11.5858 20.7532 11.3791L20.7382 11.3706C19.8343 10.8691 18.736 10.8859 17.8478 11.4138C17.4721 11.6371 17.1554 11.9824 16.9045 12.2561L16.831 12.336L15.1938 14.1086C14.9008 14.4259 14.7543 14.5846 14.6271 14.5348C14.5 14.4851 14.5 14.2691 14.5 13.8373L14.5 3.08759C14.5 2.99312 14.5768 2.91662 14.6714 2.91662L16.8029 2.91662C17.7006 2.91659 18.4508 2.91657 19.0466 2.99839C19.6775 3.08502 20.2455 3.27579 20.7003 3.74029C21.1528 4.20251 21.3366 4.77601 21.4204 5.41247C21.5001 6.0178 21.5 6.78116 21.5 7.7005V11.0171Z" fill="#767676"/>
<path d="M5.88415 0.125908C6 0.215928 6 0.379585 6 0.706898L6 16.0517C6 16.2408 6 16.3353 5.95624 16.4071C5.91247 16.4789 5.82195 16.5257 5.64091 16.6191C5.4046 16.7412 5.15134 16.888 4.86822 17.0521C4.24919 17.411 3.67742 17.7425 3.2515 17.9346C2.8207 18.129 2.35335 18.2768 1.86024 18.1889C1.32688 18.0938 0.848022 17.8075 0.508332 17.3863C0.196757 17 0.0932668 16.522 0.046395 16.0462C-2.24411e-05 15.5751 -1.20463e-05 14.9726 5.25059e-07 14.244V6.01037C-1.32388e-05 5.4596 -2.48849e-05 4.99357 0.0374653 4.60746C0.0772777 4.19743 0.163401 3.81905 0.36998 3.45668C0.577053 3.09344 0.858485 2.82811 1.19084 2.58827C1.50247 2.36339 1.90063 2.13259 2.3687 1.86126L3.65724 1.11428C4.30431 0.739152 4.83426 0.431926 5.29509 0.219232C5.56999 0.0923543 5.70744 0.0289154 5.83779 0.0962733C5.85282 0.104036 5.8708 0.115534 5.88415 0.125908Z" fill="#767676"/>
<path d="M18.6143 12.7013C18.4362 12.8072 18.2685 12.9887 17.9332 13.3518L13.7552 17.8754C13.2107 18.4649 12.9385 18.7597 12.7807 19.1227C12.6228 19.4857 12.5914 19.8896 12.5284 20.6973L12.5162 20.8531C12.4939 21.1401 12.4827 21.2836 12.5646 21.3765C12.6466 21.4694 12.7875 21.4731 13.0694 21.4805L13.1954 21.4838C14.1214 21.508 14.5845 21.5201 15.0056 21.3557C15.4268 21.1913 15.7638 20.8669 16.4378 20.2182L20.6694 16.1451C21.0251 15.8028 21.2029 15.6316 21.3066 15.4498C21.5569 15.0109 21.5647 14.4705 21.3272 14.0243C21.2287 13.8394 21.0559 13.663 20.7103 13.3101C20.3646 12.9573 20.1918 12.7808 20.0107 12.6803C19.5736 12.4378 19.0442 12.4458 18.6143 12.7013Z" fill="#767676"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -4,12 +4,12 @@ class URLs {
static const String appReleaseBuildNumber = "40"; static const String appReleaseBuildNumber = "40";
// static const host1 = "https://atomsm.hmg.com"; // production url // static const host1 = "https://atomsm.hmg.com"; // production url
static const host1 = "https://atomsmdev.hmg.com"; // local DEV url // static const host1 = "https://atomsmdev.hmg.com"; // local DEV url
// static const host1 = "https://atomsmuat.hmg.com"; // local UAT url static const host1 = "https://atomsmuat.hmg.com"; // local UAT url
static final String _baseUrl = "$_host/mobile"; // host local UAT // static final String _baseUrl = "$_host/mobile"; // host local UAT
// static final String _baseUrl = "$_host/v1/mobile"; // new V1 apis // static final String _baseUrl = "$_host/v1/mobile"; // new V1 apis
// static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis
// static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM
// static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module
// static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation
@ -44,6 +44,8 @@ class URLs {
static get enableFaceId => "$_baseUrl/MobileAuth/EnabledFaceId"; static get enableFaceId => "$_baseUrl/MobileAuth/EnabledFaceId";
static get deleteUser => "$_baseUrl/Account/DeleteUser"; // delete user account
static get checkLoginValidation => "$_baseUrl/Account/Authenticate"; // web login static get checkLoginValidation => "$_baseUrl/Account/Authenticate"; // web login
static get checkAppVersion => "$_baseUrl/Account/CheckAppVersion"; // web login static get checkAppVersion => "$_baseUrl/Account/CheckAppVersion"; // web login
//Reset Password Apis... //Reset Password Apis...

@ -217,6 +217,29 @@ class UserProvider extends ChangeNotifier {
} }
} }
Future<bool> deleteUser(BuildContext context, {required String employeeId, required String userId}) async {
Response response;
try {
showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading());
response = await ApiManager.instance.post(
URLs.deleteUser,
body: {
"employeeId": employeeId,
"userId": userId,
},
);
if (response.statusCode >= 200 && response.statusCode < 300) {
Navigator.pop(context);
return true;
}
Navigator.pop(context);
return false;
} catch (error) {
Navigator.pop(context);
return false;
}
}
/// sign up with User object; /// sign up with User object;
/// return -2 if request in progress /// return -2 if request in progress
/// return -1 if error happen when sending request /// return -1 if error happen when sending request

@ -439,7 +439,7 @@ class MyApp extends StatelessWidget {
CreateDemoRequestPage.id: (_) => CreateDemoRequestPage(), CreateDemoRequestPage.id: (_) => CreateDemoRequestPage(),
CleanerSearchFormView.routeName: (_) => const CleanerSearchFormView(), CleanerSearchFormView.routeName: (_) => const CleanerSearchFormView(),
CleanerSearchListView.routeName: (_) => const CleanerSearchListView(), CleanerSearchListView.routeName: (_) => const CleanerSearchListView(),
DemoRequestFormScreen.routeName: (_) => const DemoRequestFormScreen(), ApplicationDemoRequestFormScreen.routeName: (_) => const ApplicationDemoRequestFormScreen(),
}, },
), ),
), ),

@ -5,10 +5,11 @@ class ApplicationDemoRequestModel {
final String emailAddress; final String emailAddress;
final String mobileNumber; final String mobileNumber;
final String? companyName; final String? companyName;
final int? companyId;
// final String? companyName;
final List<String> roleIds; final List<String> roleIds;
final List<String> roleNames; final List<String> roleNames;
final String status; // Pending, Approved, Rejected final String? status; // Pending, Approved, Rejected
final DateTime requestDate; final DateTime requestDate;
ApplicationDemoRequestModel({ ApplicationDemoRequestModel({
@ -17,10 +18,10 @@ class ApplicationDemoRequestModel {
required this.emailAddress, required this.emailAddress,
required this.mobileNumber, required this.mobileNumber,
this.companyName, this.companyName,
this.companyId, // this.companyId,
required this.roleIds, required this.roleIds,
required this.roleNames, required this.roleNames,
required this.status, this.status,
required this.requestDate, required this.requestDate,
}); });
@ -31,8 +32,8 @@ class ApplicationDemoRequestModel {
'fullName': fullName, 'fullName': fullName,
'emailAddress': emailAddress, 'emailAddress': emailAddress,
'mobileNumber': mobileNumber, 'mobileNumber': mobileNumber,
// 'companyName': companyId ?? 88, 'companyName': companyName,
'companyId': 88, // 'companyId': 88,
'demoRequestUserRoleIds': roleIds, 'demoRequestUserRoleIds': roleIds,
}; };
} }
@ -45,7 +46,7 @@ class ApplicationDemoRequestModel {
emailAddress: json['emailAddress'] ?? json['email'] ?? '', emailAddress: json['emailAddress'] ?? json['email'] ?? '',
mobileNumber: json['mobileNumber'] ?? json['mobile'] ?? '', mobileNumber: json['mobileNumber'] ?? json['mobile'] ?? '',
companyName: json['companyName'] ?? json['company'] ?? '', companyName: json['companyName'] ?? json['company'] ?? '',
companyId: json['companyId'], // companyId: json['companyId'],
roleIds: _parseList(json['roleIds'] ?? json['demoRequestUserRoleIds']), roleIds: _parseList(json['roleIds'] ?? json['demoRequestUserRoleIds']),
roleNames: _parseList(json['roleNames'] ?? json['roles']), roleNames: _parseList(json['roleNames'] ?? json['roles']),
status: json['status'] ?? 'Pending', status: json['status'] ?? 'Pending',
@ -107,7 +108,7 @@ class ApplicationDemoRequestModel {
emailAddress: emailAddress ?? this.emailAddress, emailAddress: emailAddress ?? this.emailAddress,
mobileNumber: mobileNumber ?? this.mobileNumber, mobileNumber: mobileNumber ?? this.mobileNumber,
companyName: companyName ?? this.companyName, companyName: companyName ?? this.companyName,
companyId: companyId ?? this.companyId, // companyId: companyId ?? this.companyId,
roleIds: roleIds ?? this.roleIds, roleIds: roleIds ?? this.roleIds,
roleNames: roleNames ?? this.roleNames, roleNames: roleNames ?? this.roleNames,
status: status ?? this.status, status: status ?? this.status,

@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
import 'package:test_sa/controllers/validator/validator.dart'; import 'package:test_sa/controllers/validator/validator.dart';
import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/text_extensions.dart';
import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart';
import 'package:test_sa/modules/applicationDemo/enums/otp_method.dart'; import 'package:test_sa/modules/applicationDemo/enums/otp_method.dart';
import 'package:test_sa/modules/applicationDemo/models/application_demo_request_model.dart'; import 'package:test_sa/modules/applicationDemo/models/application_demo_request_model.dart';
@ -15,19 +16,20 @@ import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart';
import 'package:test_sa/new_views/common_widgets/custom_app_bar.dart'; import 'package:test_sa/new_views/common_widgets/custom_app_bar.dart';
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
import 'package:test_sa/new_views/common_widgets/multiple_item_drop_down_menu.dart'; import 'package:test_sa/new_views/common_widgets/multiple_item_drop_down_menu.dart';
import 'package:test_sa/providers/loading_list_notifier.dart'; import 'package:test_sa/providers/loading_list_notifier.dart';
class DemoRequestFormScreen extends StatefulWidget { class ApplicationDemoRequestFormScreen extends StatefulWidget {
static const String routeName = '/demo_request_form'; static const String routeName = '/demo_request_form';
const DemoRequestFormScreen({Key? key}) : super(key: key); const ApplicationDemoRequestFormScreen({Key? key}) : super(key: key);
@override @override
State<DemoRequestFormScreen> createState() => _DemoRequestFormScreenState(); State<ApplicationDemoRequestFormScreen> createState() => _ApplicationDemoRequestFormScreenState();
} }
class _DemoRequestFormScreenState extends State<DemoRequestFormScreen> { class _ApplicationDemoRequestFormScreenState extends State<ApplicationDemoRequestFormScreen> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController _fullNameController = TextEditingController(); final TextEditingController _fullNameController = TextEditingController();
final TextEditingController _emailController = TextEditingController(); final TextEditingController _emailController = TextEditingController();
@ -36,6 +38,8 @@ class _DemoRequestFormScreenState extends State<DemoRequestFormScreen> {
List<ApplicationDemoRoleModel> _selectedRoles = []; List<ApplicationDemoRoleModel> _selectedRoles = [];
bool _isSubmitting = false; bool _isSubmitting = false;
String? _rolesValidationError;
bool _formSubmitted = false; // Track if form has been submitted
@override @override
void initState() { void initState() {
@ -61,12 +65,11 @@ class _DemoRequestFormScreenState extends State<DemoRequestFormScreen> {
final provider = Provider.of<ApplicationDemoRequestProvider>(context); final provider = Provider.of<ApplicationDemoRequestProvider>(context);
return Scaffold( return Scaffold(
backgroundColor: AppColor.background(context), backgroundColor: AppColor.scaffoldBackground(context),
appBar: const CustomAppBar( appBar: const DefaultAppBar(title: 'Demo Request'),
title: 'Demo Request',
),
body: Form( body: Form(
key: _formKey, key: _formKey,
autovalidateMode: AutovalidateMode.disabled,
child: Column( child: Column(
children: [ children: [
Column( Column(
@ -74,53 +77,53 @@ class _DemoRequestFormScreenState extends State<DemoRequestFormScreen> {
children: [ children: [
AppTextFormField( AppTextFormField(
controller: _fullNameController, controller: _fullNameController,
labelText: 'Full Name', labelText: 'Full Name *',
hintText: 'Enter your full name',
textInputType: TextInputType.name, textInputType: TextInputType.name,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) return 'Full Name is required'; if (!_formSubmitted && _fullNameController.text.isEmpty) return null;
if (_fullNameController.text.isEmpty) return 'Full Name is required';
return null; return null;
}, },
), ),
16.height, 16.height,
AppTextFormField( AppTextFormField(
controller: _emailController, controller: _emailController,
labelText: 'Email Address', labelText: 'Email Address *',
hintText: 'Enter your email address',
textInputType: TextInputType.emailAddress, textInputType: TextInputType.emailAddress,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) return 'Email is required'; if (!_formSubmitted && _emailController.text.isEmpty) return null;
if (!Validator.isEmail(value)) return 'Invalid email format'; if (_emailController.text.isEmpty) return 'Email is required';
if (!Validator.isEmail(_emailController.text)) return 'Invalid email format';
return null; return null;
}, },
), ),
16.height, 16.height,
AppTextFormField( AppTextFormField(
controller: _mobileController, controller: _mobileController,
labelText: 'Mobile Number', labelText: 'Mobile Number *',
hintText: 'Enter your mobile number',
textInputType: TextInputType.phone, textInputType: TextInputType.phone,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) return 'Mobile Number is required'; if (!_formSubmitted && _mobileController.text.isEmpty) return null;
if (!Validator.isPhoneNumber(value)) return 'Invalid mobile number'; if (_mobileController.text.isEmpty) return 'Mobile Number is required';
if (!Validator.isPhoneNumber(_mobileController.text)) return 'Invalid mobile number';
return null; return null;
}, },
), ),
16.height, 16.height,
AppTextFormField( AppTextFormField(
controller: _companyNameController, controller: _companyNameController,
labelText: 'Company Name', labelText: 'Company Name *',
hintText: 'Enter your company name',
textInputType: TextInputType.text, textInputType: TextInputType.text,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) return 'Company Name is required'; if (!_formSubmitted && _companyNameController.text.isEmpty) return null;
if (_companyNameController.text.isEmpty) return 'Company Name is required';
return null; return null;
}, },
), ),
16.height, 16.height,
MultipleItemDropDownMenu<ApplicationDemoRoleModel, NullableLoadingProvider>( MultipleItemDropDownMenu<ApplicationDemoRoleModel, NullableLoadingProvider>(
context: context, context: context,
title: 'Select Roles', title: 'Select Roles *',
initialValue: _selectedRoles, initialValue: _selectedRoles,
staticData: provider.roles, staticData: provider.roles,
loading: provider.roleLoading, loading: provider.roleLoading,
@ -128,9 +131,20 @@ class _DemoRequestFormScreenState extends State<DemoRequestFormScreen> {
onSelect: (selectedRoles) { onSelect: (selectedRoles) {
setState(() { setState(() {
_selectedRoles = selectedRoles ?? []; _selectedRoles = selectedRoles ?? [];
_rolesValidationError = null; // Clear error when roles are selected
}); });
}, },
), ),
if (_rolesValidationError != null)
Padding(
padding: const EdgeInsets.only(top: 8, left: 16),
child: Text(
_rolesValidationError!,
style: AppTextStyles.tinyFont.copyWith(
color: AppColor.red30,
),
),
),
], ],
).paddingAll(16).expanded, ).paddingAll(16).expanded,
FooterActionButton.footerContainer( FooterActionButton.footerContainer(
@ -150,14 +164,24 @@ class _DemoRequestFormScreenState extends State<DemoRequestFormScreen> {
} }
Future<void> _submitRequest() async { Future<void> _submitRequest() async {
// Mark form as submitted
setState(() {
_formSubmitted = true;
});
// Validate form fields // Validate form fields
if (!_formKey.currentState!.validate()) { if (!_formKey.currentState!.validate()) {
return; return;
} }
// Validate roles selection
if (_selectedRoles.isEmpty) { if (_selectedRoles.isEmpty) {
context.showConfirmDialog('Please select at least one role'); setState(() {
_rolesValidationError = 'Please select at least one role';
});
return; return;
} }
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
@ -258,10 +282,10 @@ class _DemoRequestFormScreenState extends State<DemoRequestFormScreen> {
fullName: _fullNameController.text.trim(), fullName: _fullNameController.text.trim(),
emailAddress: _emailController.text.trim(), emailAddress: _emailController.text.trim(),
mobileNumber: _mobileController.text.trim(), mobileNumber: _mobileController.text.trim(),
companyId: 0, companyName: _companyNameController.text.trim(),
roleIds: _selectedRoles.map((role) => role.identifier ?? '').toList(), roleIds: _selectedRoles.map((role) => role.identifier ?? '').toList(),
roleNames: _selectedRoles.map((role) => role.name ?? '').toList(), roleNames: _selectedRoles.map((role) => role.name ?? '').toList(),
status: 'Pending', // status: 'Pending',
requestDate: DateTime.now(), requestDate: DateTime.now(),
); );

@ -56,6 +56,15 @@ class SignUpProvider extends ChangeNotifier {
if (response.statusCode >= 200 && response.statusCode < 300) { if (response.statusCode >= 200 && response.statusCode < 300) {
final data = json.decode(response.body); final data = json.decode(response.body);
// Check if data is null (employee not found case)
if (data['data'] == null) {
_errorMessage = data['message'];
loading = false;
notifyListeners();
return null;
}
_employeeData = EmployeeERPModel.fromJson(data['data']); _employeeData = EmployeeERPModel.fromJson(data['data']);
loading = false; loading = false;
@ -63,13 +72,13 @@ class SignUpProvider extends ChangeNotifier {
return _employeeData; return _employeeData;
} else { } else {
final data = json.decode(response.body); final data = json.decode(response.body);
_errorMessage = data['message'] ?? 'Failed to fetch employee details'; _errorMessage = data['message'];
loading = false; loading = false;
notifyListeners(); notifyListeners();
return null; return null;
} }
} catch (error) { } catch (error) {
_errorMessage = 'Failed to fetch employee details'; _errorMessage = error.toString();
loading = false; loading = false;
notifyListeners(); notifyListeners();
return null; return null;
@ -94,19 +103,19 @@ class SignUpProvider extends ChangeNotifier {
if (response.statusCode >= 200 && response.statusCode < 300) { if (response.statusCode >= 200 && response.statusCode < 300) {
final data = json.decode(response.body); final data = json.decode(response.body);
_successMessage = data['message'] ?? 'Your signup request has been submitted successfully.'; _successMessage = data['message'];
loading = false; loading = false;
notifyListeners(); notifyListeners();
return true; return true;
} else { } else {
final data = json.decode(response.body); final data = json.decode(response.body);
_errorMessage = data['message'] ?? 'Failed to submit signup request'; _errorMessage = data['message'];
loading = false; loading = false;
notifyListeners(); notifyListeners();
return false; return false;
} }
} catch (error) { } catch (error) {
_errorMessage = 'Failed to submit signup request'; _errorMessage = error.toString();
loading = false; loading = false;
notifyListeners(); notifyListeners();
return false; return false;
@ -139,13 +148,13 @@ class SignUpProvider extends ChangeNotifier {
return _sites; return _sites;
} else { } else {
final data = json.decode(response.body); final data = json.decode(response.body);
_errorMessage = data['message'] ?? 'Failed to fetch sites'; _errorMessage = data['message'];
loading = false; loading = false;
notifyListeners(); notifyListeners();
return []; return [];
} }
} catch (error) { } catch (error) {
_errorMessage = 'Failed to fetch sites'; _errorMessage = error.toString();
loading = false; loading = false;
notifyListeners(); notifyListeners();
return []; return [];
@ -179,13 +188,13 @@ class SignUpProvider extends ChangeNotifier {
return _departments; return _departments;
} else { } else {
final data = json.decode(response.body); final data = json.decode(response.body);
_errorMessage = data['message'] ?? 'Failed to fetch departments'; _errorMessage = data['message'];
loading = false; loading = false;
notifyListeners(); notifyListeners();
return []; return [];
} }
} catch (error) { } catch (error) {
_errorMessage = 'Failed to fetch departments'; _errorMessage = error.toString();
loading = false; loading = false;
notifyListeners(); notifyListeners();
return []; return [];

@ -46,6 +46,8 @@ class _SignUpScreenState extends State<SignUpScreen> {
final TextEditingController _fullNameController = TextEditingController(); final TextEditingController _fullNameController = TextEditingController();
final TextEditingController _emailController = TextEditingController(); final TextEditingController _emailController = TextEditingController();
final TextEditingController _mobileController = TextEditingController(); final TextEditingController _mobileController = TextEditingController();
final TextEditingController _siteController = TextEditingController();
final TextEditingController _departmentController = TextEditingController();
final TextEditingController _extensionController = TextEditingController(); final TextEditingController _extensionController = TextEditingController();
final TextEditingController _roleController = TextEditingController(); final TextEditingController _roleController = TextEditingController();
@ -73,6 +75,8 @@ class _SignUpScreenState extends State<SignUpScreen> {
_fullNameController.dispose(); _fullNameController.dispose();
_emailController.dispose(); _emailController.dispose();
_mobileController.dispose(); _mobileController.dispose();
_siteController.dispose();
_departmentController.dispose();
_extensionController.dispose(); _extensionController.dispose();
_roleController.dispose(); _roleController.dispose();
super.dispose(); super.dispose();
@ -138,8 +142,8 @@ class _SignUpScreenState extends State<SignUpScreen> {
context: context, context: context,
title: "Asset Group", title: "Asset Group",
initialValue: _selectedAssetGroup, initialValue: _selectedAssetGroup,
showAsFullScreenDialog: false, showAsFullScreenDialog: true,
showAsBottomSheet: true, showAsBottomSheet: false,
onSelect: (value) { onSelect: (value) {
setState(() { setState(() {
_selectedAssetGroup = value; _selectedAssetGroup = value;
@ -166,8 +170,8 @@ class _SignUpScreenState extends State<SignUpScreen> {
context: context, context: context,
title: "Company", title: "Company",
initialValue: _selectedCompany, initialValue: _selectedCompany,
showAsFullScreenDialog: false, showAsFullScreenDialog: true,
showAsBottomSheet: true, showAsBottomSheet: false,
onSelect: (value) { onSelect: (value) {
setState(() { setState(() {
_selectedCompany = value; _selectedCompany = value;
@ -198,16 +202,16 @@ class _SignUpScreenState extends State<SignUpScreen> {
16.height, 16.height,
if (_employeeFetched) ...[ if (_employeeFetched) ...[
_buildReadOnlyField("Full Name", _fullNameController), _buildReadOnlyField("Full Name", _fullNameController),
16.height,
_buildReadOnlyField("Email Address", _emailController), _buildReadOnlyField("Email Address", _emailController),
16.height,
_buildReadOnlyField("Mobile Number", _mobileController), _buildReadOnlyField("Mobile Number", _mobileController),
// _buildReadOnlyField("Site", _siteController),
// _buildReadOnlyField("Department", _departmentController),
16.height, 16.height,
_buildExtensionField(), _buildExtensionField(),
// 16.height, 16.height,
// _buildSiteDropdown(), _buildSiteDropdown(),
// 16.height, 16.height,
// _buildDepartmentDropdown(), _buildDepartmentDropdown(),
16.height, 16.height,
_buildRoleField(), _buildRoleField(),
], ],
@ -240,11 +244,16 @@ class _SignUpScreenState extends State<SignUpScreen> {
child: AppTextFormField( child: AppTextFormField(
controller: _employeeIdController, controller: _employeeIdController,
labelText: "Employee ID", labelText: "Employee ID",
hintText: "Enter Employee ID",
textInputType: TextInputType.number, textInputType: TextInputType.number,
onChange: (value) {
// Clear validation errors on user interaction
// if (_formKey.currentState != null) {
// _formKey.currentState!.validate();
// }
},
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) return "Employee ID is required"; if (_employeeIdController.text.isEmpty) return "Employee ID is required";
if (!Validator.isNumeric(value)) return "Employee ID must be numeric"; // if (!Validator.isNumeric(_employeeIdController.text)) return "Employee ID must be numeric";
return null; return null;
}, },
), ),
@ -276,11 +285,19 @@ class _SignUpScreenState extends State<SignUpScreen> {
} }
Widget _buildReadOnlyField(String label, TextEditingController controller) { Widget _buildReadOnlyField(String label, TextEditingController controller) {
return AppTextFormField( if(controller.text.isEmpty) {
controller: controller, return const SizedBox();
labelText: label, }
enable: false, return Column(
backgroundColor: AppColor.disableFieldBackground(context), children: [
16.height,
AppTextFormField(
controller: controller,
labelText: label,
enable: false,
backgroundColor: AppColor.disableFieldBackground(context),
),
],
); );
} }
@ -288,7 +305,6 @@ class _SignUpScreenState extends State<SignUpScreen> {
return AppTextFormField( return AppTextFormField(
controller: _fullNameController, controller: _fullNameController,
labelText: "Full Name", labelText: "Full Name",
hintText: "Enter Full Name",
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) return "Full Name is required"; if (value == null || value.isEmpty) return "Full Name is required";
return null; return null;
@ -300,7 +316,6 @@ class _SignUpScreenState extends State<SignUpScreen> {
return AppTextFormField( return AppTextFormField(
controller: _emailController, controller: _emailController,
labelText: "Email Address", labelText: "Email Address",
hintText: "Enter Email Address",
textInputType: TextInputType.emailAddress, textInputType: TextInputType.emailAddress,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) return "Email is required"; if (value == null || value.isEmpty) return "Email is required";
@ -314,7 +329,6 @@ class _SignUpScreenState extends State<SignUpScreen> {
return AppTextFormField( return AppTextFormField(
controller: _mobileController, controller: _mobileController,
labelText: "Mobile Number", labelText: "Mobile Number",
hintText: "Enter Mobile Number",
textInputType: TextInputType.phone, textInputType: TextInputType.phone,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) return "Mobile Number is required"; if (value == null || value.isEmpty) return "Mobile Number is required";
@ -328,7 +342,6 @@ class _SignUpScreenState extends State<SignUpScreen> {
return AppTextFormField( return AppTextFormField(
controller: _extensionController, controller: _extensionController,
labelText: "Extension Number", labelText: "Extension Number",
hintText: "Enter Extension Number (Optional)",
textInputType: TextInputType.number, textInputType: TextInputType.number,
validator: (value) { validator: (value) {
if (value != null && value.isNotEmpty && !Validator.isNumeric(value)) { if (value != null && value.isNotEmpty && !Validator.isNumeric(value)) {
@ -383,7 +396,6 @@ class _SignUpScreenState extends State<SignUpScreen> {
return AppTextFormField( return AppTextFormField(
controller: _roleController, controller: _roleController,
labelText: "Role", labelText: "Role",
hintText: "Enter Role",
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) return "Role is required"; if (value == null || value.isEmpty) return "Role is required";
return null; return null;
@ -423,6 +435,9 @@ class _SignUpScreenState extends State<SignUpScreen> {
_fullNameController.text = employee.employeeName ?? ''; _fullNameController.text = employee.employeeName ?? '';
_emailController.text = employee.employeeEmailAddress ?? ''; _emailController.text = employee.employeeEmailAddress ?? '';
_mobileController.text = employee.employeeMobileNumber ?? ''; _mobileController.text = employee.employeeMobileNumber ?? '';
//Todo need to map site and department from ERP data
_departmentController.text = '';
_siteController.text = employee.branchCode ?? '';
}); });
} else { } else {
// Check if it's a duplicate account error // Check if it's a duplicate account error
@ -441,20 +456,24 @@ class _SignUpScreenState extends State<SignUpScreen> {
_employeeFetched = false; _employeeFetched = false;
_employeeERPData = null; _employeeERPData = null;
}); });
Fluttertoast.showToast( if (_signUpProvider.errorMessage != null) {
msg: _signUpProvider.errorMessage ?? "Failed to fetch employee details", Fluttertoast.showToast(
); msg: _signUpProvider.errorMessage!,
);
}
} }
} }
} }
void _showDuplicateAccountDialog() { void _showDuplicateAccountDialog() {
final errorMessage = _signUpProvider.errorMessage ?? "Already you have an account on Atoms";
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
backgroundColor: AppColor.background(context), backgroundColor: AppColor.background(context),
title: "Account Exists".bodyText(context), title: "Account Exists".bodyText(context),
content: "Already you have an account on Atoms".bodyText(context), content: errorMessage.bodyText(context),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
@ -508,16 +527,16 @@ class _SignUpScreenState extends State<SignUpScreen> {
} }
// Site is mandatory for HMG // Site is mandatory for HMG
// if (_selectedSite == null) { if (_selectedSite == null) {
// Fluttertoast.showToast(msg: "Site is required"); Fluttertoast.showToast(msg: "Site is required");
// return; return;
// } }
// //
// // Department is mandatory for HMG // // Department is mandatory for HMG
// if (_selectedDepartment == null) { if (_selectedDepartment == null) {
// Fluttertoast.showToast(msg: "Department is required"); Fluttertoast.showToast(msg: "Department is required");
// return; return;
// } }
} else { } else {
// Non-HMG specific validations // Non-HMG specific validations
@ -609,9 +628,9 @@ class _SignUpScreenState extends State<SignUpScreen> {
extensionNumber: _extensionController.text.isEmpty ? null : _extensionController.text, extensionNumber: _extensionController.text.isEmpty ? null : _extensionController.text,
// For HMG: send both selected IDs and ERP data // For HMG: send both selected IDs and ERP data
siteId: _selectedSite?.id, siteId: _selectedSite?.id,
erpSite: _isHMGGroup ? _employeeERPData?.branchCode : null, // erpSite: _isHMGGroup ? _employeeERPData?.branchCode : null,
departmentId: _selectedDepartment?.id, departmentId: _selectedDepartment?.id,
erpDepartment: _isHMGGroup ? _employeeERPData?.branchName : null, // erpDepartment: _isHMGGroup ? _employeeERPData?.branchName : null,
role: _roleController.text, role: _roleController.text,
isFromERP: _isHMGGroup, isFromERP: _isHMGGroup,
); );
@ -631,9 +650,11 @@ class _SignUpScreenState extends State<SignUpScreen> {
_showSuccessDialog(); _showSuccessDialog();
} }
} else { } else {
Fluttertoast.showToast( if (_signUpProvider.errorMessage != null) {
msg: _signUpProvider.errorMessage ?? "Failed to submit signup request", Fluttertoast.showToast(
); msg: _signUpProvider.errorMessage!,
);
}
} }
} }

@ -3,6 +3,7 @@
class SiteRequestModel { class SiteRequestModel {
final String? id; final String? id;
final String requestType; final String requestType;
final String? requestNumber;
final String employeeId; final String employeeId;
final String fullName; final String fullName;
final String emailAddress; final String emailAddress;
@ -14,13 +15,16 @@ class SiteRequestModel {
final List<String>? oldSiteNames; // For Change Site request final List<String>? oldSiteNames; // For Change Site request
final List<String>? departmentIds; final List<String>? departmentIds;
final List<String>? departmentNames; final List<String>? departmentNames;
final List<String>? oldDepartmentIds; // For Change Site request - old departments
final List<String>? oldDepartmentNames; // For Change Site request - old departments
final String role; final String role;
final String status; // Pending, Approved, Rejected final String ?status; // Pending, Approved, Rejected
final DateTime requestDate; final DateTime? requestDate;
SiteRequestModel({ SiteRequestModel({
this.id, this.id,
required this.requestType, required this.requestType,
this.requestNumber,
required this.employeeId, required this.employeeId,
required this.fullName, required this.fullName,
required this.emailAddress, required this.emailAddress,
@ -32,9 +36,11 @@ class SiteRequestModel {
this.oldSiteNames, this.oldSiteNames,
this.departmentIds, this.departmentIds,
this.departmentNames, this.departmentNames,
this.oldDepartmentIds,
this.oldDepartmentNames,
required this.role, required this.role,
required this.status, this.status,
required this.requestDate, this.requestDate,
}); });
/// Converts model to JSON for API submission /// Converts model to JSON for API submission
@ -42,6 +48,7 @@ class SiteRequestModel {
return { return {
'id': id, 'id': id,
'requestType': requestType, 'requestType': requestType,
'requestNumber': requestNumber,
'employeeId': employeeId, 'employeeId': employeeId,
'fullName': fullName, 'fullName': fullName,
'emailAddress': emailAddress, 'emailAddress': emailAddress,
@ -54,8 +61,8 @@ class SiteRequestModel {
'departmentIds': departmentIds, 'departmentIds': departmentIds,
'departmentNames': departmentNames, 'departmentNames': departmentNames,
'role': role, 'role': role,
'status': status, // 'status': status,
'requestDate': requestDate.toIso8601String(), // 'requestDate': requestDate?.toIso8601String(),
}; };
} }
@ -68,6 +75,7 @@ class SiteRequestModel {
id: json['id']?.toString() ?? json['requestNumber']?.toString() ?? '', id: json['id']?.toString() ?? json['requestNumber']?.toString() ?? '',
requestType: _parseRequestType(json), requestType: _parseRequestType(json),
employeeId: json['employeeId'] ?? '', employeeId: json['employeeId'] ?? '',
requestNumber: json['requestNumber'] ?? '',
fullName: json['userName'] ?? json['fullName'] ?? '', fullName: json['userName'] ?? json['fullName'] ?? '',
emailAddress: json['email'] ?? json['emailAddress'] ?? '', emailAddress: json['email'] ?? json['emailAddress'] ?? '',
mobileNumber: json['mobileNumber'] ?? '', mobileNumber: json['mobileNumber'] ?? '',
@ -78,6 +86,8 @@ class SiteRequestModel {
oldSiteNames: sitesData['oldSiteNames'], oldSiteNames: sitesData['oldSiteNames'],
departmentIds: departmentsData['departmentIds'], departmentIds: departmentsData['departmentIds'],
departmentNames: departmentsData['departmentNames'], departmentNames: departmentsData['departmentNames'],
oldDepartmentIds: departmentsData['oldDepartmentIds'],
oldDepartmentNames: departmentsData['oldDepartmentNames'],
role: json['role'] ?? '', role: json['role'] ?? '',
status: _parseStatus(json), status: _parseStatus(json),
requestDate: _parseDate(json), requestDate: _parseDate(json),
@ -106,6 +116,7 @@ class SiteRequestModel {
return SiteRequestModel( return SiteRequestModel(
id: id ?? this.id, id: id ?? this.id,
requestType: requestType ?? this.requestType, requestType: requestType ?? this.requestType,
requestNumber: requestNumber ?? this.requestNumber,
employeeId: employeeId ?? this.employeeId, employeeId: employeeId ?? this.employeeId,
fullName: fullName ?? this.fullName, fullName: fullName ?? this.fullName,
emailAddress: emailAddress ?? this.emailAddress, emailAddress: emailAddress ?? this.emailAddress,
@ -156,6 +167,8 @@ class SiteRequestModel {
static Map<String, List<String>?> _parseDepartmentsFromJson(Map<String, dynamic> json) { static Map<String, List<String>?> _parseDepartmentsFromJson(Map<String, dynamic> json) {
List<String>? departmentIds; List<String>? departmentIds;
List<String>? departmentNames; List<String>? departmentNames;
List<String>? oldDepartmentIds;
List<String>? oldDepartmentNames;
if (json['departments'] != null && if (json['departments'] != null &&
json['departments'] is List && json['departments'] is List &&
@ -165,12 +178,22 @@ class SiteRequestModel {
for (var dept in json['departments']) { for (var dept in json['departments']) {
departmentIds.add(dept['departmentId']?.toString() ?? ''); departmentIds.add(dept['departmentId']?.toString() ?? '');
departmentNames.add(dept['departmentName'] ?? ''); departmentNames.add(dept['departmentName'] ?? '');
// Check for old department data in Change Site requests
if (dept['oldDepartmentId'] != null && dept['oldDepartmentId'] != 0) {
oldDepartmentIds ??= [];
oldDepartmentNames ??= [];
oldDepartmentIds.add(dept['oldDepartmentId']?.toString() ?? '');
oldDepartmentNames.add(dept['oldDepartmentName'] ?? '');
}
} }
} }
return { return {
'departmentIds': departmentIds, 'departmentIds': departmentIds,
'departmentNames': departmentNames, 'departmentNames': departmentNames,
'oldDepartmentIds': oldDepartmentIds,
'oldDepartmentNames': oldDepartmentNames,
}; };
} }

@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
import 'package:test_sa/controllers/providers/api/user_provider.dart'; import 'package:test_sa/controllers/providers/api/user_provider.dart';
import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/extensions/text_extensions.dart';
import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart';
import 'package:test_sa/models/new_models/site.dart'; import 'package:test_sa/models/new_models/site.dart';
import 'package:test_sa/models/new_models/department.dart'; import 'package:test_sa/models/new_models/department.dart';
@ -42,7 +43,7 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
RequestTypeModel? _selectedRequestType; RequestTypeModel? _selectedRequestType;
List<Site> _selectedSites = []; List<Site> _selectedSites = [];
List<Site> _selectedOldSites = []; // For Change Site - old sites List<Site> _selectedOldSites = []; // For Change Site - old sites
List<Department> _selectedDepartments = []; List<Department> _selectedDepartments = [];
// Map to track replacements: oldSiteId -> {newSite, departments} // Map to track replacements: oldSiteId -> {newSite, departments}
@ -95,73 +96,63 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
_provider = Provider.of<SiteRequestProvider>(context); _provider = Provider.of<SiteRequestProvider>(context);
_userProvider = Provider.of<UserProvider>(context); _userProvider = Provider.of<UserProvider>(context);
return Scaffold( return Scaffold(
backgroundColor: AppColor.scaffoldBackground(context), backgroundColor: AppColor.scaffoldBackground(context),
appBar: const DefaultAppBar(title: "Site Request"), appBar: const DefaultAppBar(title: "Site Request"),
body: _isLoadingData ?const ALoading().center:Column( body: _isLoadingData
children: [ ? const ALoading().center
Expanded( : Column(
child: Form( children: [
key: _formKey, Expanded(
child: SingleChildScrollView( child: Form(
padding: EdgeInsets.all(16.toScreenWidth), key: _formKey,
child: Column( child: SingleChildScrollView(
crossAxisAlignment: CrossAxisAlignment.start, padding: EdgeInsets.all(16.toScreenWidth),
children: [ child: Column(
_buildUserInfoCard(), spacing: 12,
16.height, crossAxisAlignment: CrossAxisAlignment.start,
_buildRequestTypeDropdown(), children: [
16.height, _buildUserInfoCard(),
if (_selectedRequestType != null) ...[ _buildRequestTypeDropdown(),
if (_isChangeSiteRequest()) ...[ if (_selectedRequestType != null) ...[
_buildOldSiteDropdown(), if (_isChangeSiteRequest()) ...[
16.height, _buildOldSiteDropdown(),
// Show replacement containers for each old site // Show replacement containers for each old site
if (_selectedOldSites.isNotEmpty) ...[ if (_selectedOldSites.isNotEmpty) ...[
Text( InfoHeader16Widget("Site Replacements"),
"Site Replacements", ..._buildSiteReplacementContainers(),
style: TextStyle( ],
fontSize: 16, ] else if (_isRemoveSiteRequest()) ...[
fontWeight: FontWeight.w600, _buildRemoveSiteDropdown(),
color: AppColor.textStyleColor(context), ] else ...[
), _buildSiteMultiSelect(),
), _buildDepartmentMultiSelect(),
8.height, ],
..._buildSiteReplacementContainers(), ],
], ],
] else if (_isRemoveSiteRequest()) ...[ ),
_buildRemoveSiteDropdown(), ),
] else ...[ ),
_buildSiteMultiSelect(),
16.height,
_buildDepartmentMultiSelect(),
],
],
],
), ),
), if (_selectedRequestType != null)
), Container(
), padding: EdgeInsets.all(16.toScreenWidth),
if (_selectedRequestType != null) decoration: BoxDecoration(
Container( color: AppColor.background(context),
padding: EdgeInsets.all(16.toScreenWidth), boxShadow: [
decoration: BoxDecoration( BoxShadow(
color: AppColor.background(context), color: Colors.black.withValues(alpha: 0.05),
boxShadow: [ blurRadius: 10,
BoxShadow( offset: const Offset(0, -2),
color: Colors.black.withValues(alpha: 0.05), ),
blurRadius: 10, ],
offset: const Offset(0, -2), ),
child: SafeArea(
child: _buildSubmitButton(),
),
), ),
], ],
),
child: SafeArea(
child: _buildSubmitButton(),
),
), ),
],
),
); );
} }
@ -171,8 +162,7 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
title: "Type of Site Request", title: "Type of Site Request",
initialValue: _selectedRequestType, initialValue: _selectedRequestType,
staticData: _provider.requestTypes, staticData: _provider.requestTypes,
showAsFullScreenDialog: false, showAsFullScreenDialog: true,
showAsBottomSheet: true,
onSelect: (value) async { onSelect: (value) async {
setState(() { setState(() {
_selectedRequestType = value; _selectedRequestType = value;
@ -209,7 +199,7 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
InfoHeader16Widget("Requester Information"), InfoHeader16Widget("Requester Information"),
8.height, // 8.height,
InfoTextWidget( InfoTextWidget(
label: "Full Name", label: "Full Name",
value: _provider.getCurrentUserFullName(_userProvider), value: _provider.getCurrentUserFullName(_userProvider),
@ -242,11 +232,13 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
Widget _buildOldSiteDropdown() { Widget _buildOldSiteDropdown() {
return MultipleItemDropDownMenu<SiteRequestSiteModel, NullableLoadingProvider>( return MultipleItemDropDownMenu<SiteRequestSiteModel, NullableLoadingProvider>(
context: context, context: context,
title: "Old Site (Current Site)", title: "Current Sites",
initialValue: _selectedOldSites.map((s) => SiteRequestSiteModel( initialValue: _selectedOldSites
id: s.identifier ?? '', .map((s) => SiteRequestSiteModel(
name: s.name ?? '', id: s.identifier ?? '',
)).toList(), name: s.name ?? '',
))
.toList(),
staticData: _provider.userCurrentSites, staticData: _provider.userCurrentSites,
showAsBottomSheet: true, showAsBottomSheet: true,
backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white, backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white,
@ -288,12 +280,14 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
return MultipleItemDropDownMenu<SiteRequestSiteModel, NullableLoadingProvider>( return MultipleItemDropDownMenu<SiteRequestSiteModel, NullableLoadingProvider>(
context: context, context: context,
title: "Site to Remove", title: "Site to Remove",
initialValue: _selectedSites.map((s) => SiteRequestSiteModel( initialValue: _selectedSites
id: s.identifier ?? '', .map((s) => SiteRequestSiteModel(
name: s.name ?? '', id: s.identifier ?? '',
)).toList(), name: s.name ?? '',
))
.toList(),
staticData: _provider.userCurrentSites, staticData: _provider.userCurrentSites,
showAsBottomSheet: true, showAsFullScreenDialog: true,
backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white, backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white,
showShadow: true, showShadow: true,
height: 56.toScreenHeight, height: 56.toScreenHeight,
@ -384,92 +378,66 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
return Consumer<SiteProvider>( return Consumer<SiteProvider>(
builder: (context, siteProvider, child) { builder: (context, siteProvider, child) {
// Filter out already selected new sites and the current old site // Get ALL user's current site IDs to exclude from new site selection
final alreadySelectedNewSites = _siteReplacements.values final allUserCurrentSiteIds = _provider.userCurrentSites.map((s) => s.identifier ?? '').toSet();
.where((r) => r.newSite != null)
.map((r) => r.newSite!.identifier) // Filter out already selected new sites and ALL user's current sites
.toSet(); final alreadySelectedNewSites = _siteReplacements.values.where((r) => r.newSite != null).map((r) => r.newSite!.identifier).toSet();
final filteredSites = siteProvider.items.where((site) { final filteredSites = siteProvider.items.where((site) {
// Exclude the old site itself final siteId = site.identifier ?? '';
if (site.identifier == oldSiteId) return false;
// Exclude ALL user's current sites (not just selected old sites)
if (allUserCurrentSiteIds.contains(siteId)) return false;
// Exclude already selected new sites (except current one) // Exclude already selected new sites (except current one)
if (replacement?.newSite?.identifier != site.identifier && if (replacement?.newSite?.identifier != siteId && alreadySelectedNewSites.contains(siteId)) {
alreadySelectedNewSites.contains(site.identifier)) {
return false; return false;
} }
return true; return true;
}).toList(); }).toList();
return Container( return Column(
padding: EdgeInsets.all(16.toScreenWidth), mainAxisSize: MainAxisSize.min,
margin: EdgeInsets.only(bottom: 16.toScreenHeight), crossAxisAlignment: CrossAxisAlignment.start,
decoration: BoxDecoration( spacing: 12,
color: AppColor.background(context), children: [
borderRadius: BorderRadius.circular(8), Text(
border: Border.all( oldSite.name ?? 'Unknown Site',
color: AppColor.neutral40.withValues(alpha: 0.3), style: AppTextStyles.bodyText,
width: 1,
), ),
boxShadow: [ SingleItemDropDownMenu<Site, NullableLoadingProvider>(
BoxShadow( context: context,
color: Colors.black.withValues(alpha: 0.05), title: "New Site",
blurRadius: 8, initialValue: replacement?.newSite,
offset: const Offset(0, 2), staticData: filteredSites,
), showAsFullScreenDialog: true,
], backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white,
), showShadow: true,
child: Column( height: 56.toScreenHeight,
crossAxisAlignment: CrossAxisAlignment.start, onSelect: (value) {
children: [ setState(() {
// Old site name display (simple text only) if (value != null) {
Text( // Remove old site ID from loaded set to allow fetching new departments
oldSite.name ?? 'Unknown Site', if (replacement?.newSite != null) {
style: TextStyle( final oldNewSiteId = int.tryParse(replacement!.newSite!.identifier ?? '0') ?? 0;
fontSize: 16, _loadedDepartmentSiteIds.remove(oldNewSiteId);
fontWeight: FontWeight.w600,
color: AppColor.textStyleColor(context),
),
),
16.height,
// New site single selection
SingleItemDropDownMenu<Site, NullableLoadingProvider>(
context: context,
title: "New Site",
initialValue: replacement?.newSite,
staticData: filteredSites,
showAsBottomSheet: true,
backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white,
showShadow: true,
height: 56.toScreenHeight,
onSelect: (value) {
setState(() {
if (value != null) {
// Remove old site ID from loaded set to allow fetching new departments
if (replacement?.newSite != null) {
final oldNewSiteId = int.tryParse(replacement!.newSite!.identifier ?? '0') ?? 0;
_loadedDepartmentSiteIds.remove(oldNewSiteId);
}
_siteReplacements[oldSiteId] = SiteReplacement(
newSite: value,
departments: [], // Clear departments when new site changes
);
} }
});
},
),
16.height,
// Department multi-select based on new site _siteReplacements[oldSiteId] = SiteReplacement(
if (replacement?.newSite != null) ...[ newSite: value,
_buildDepartmentDropdownForSite(oldSiteId, replacement!.newSite!), departments: [], // Clear departments when new site changes
], );
}
});
},
),
if (replacement?.newSite != null) ...[
_buildDepartmentDropdownForSite(oldSiteId, replacement!.newSite!),
], ],
), ],
); ).toShadowContainer(context);
}, },
); );
}).toList(); }).toList();
@ -500,7 +468,7 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
title: "Departments", title: "Departments",
initialValue: replacement?.departments ?? [], initialValue: replacement?.departments ?? [],
staticData: departmentProvider.items, staticData: departmentProvider.items,
showAsBottomSheet: true, showAsFullScreenDialog: true,
backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white, backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white,
showShadow: true, showShadow: true,
height: 56.toScreenHeight, height: 56.toScreenHeight,
@ -599,8 +567,8 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
departmentIds: departmentIds.isNotEmpty ? departmentIds : null, departmentIds: departmentIds.isNotEmpty ? departmentIds : null,
departmentNames: departmentNames.isNotEmpty ? departmentNames : null, departmentNames: departmentNames.isNotEmpty ? departmentNames : null,
role: _provider.getCurrentUserRole(_userProvider), role: _provider.getCurrentUserRole(_userProvider),
status: 'Pending', // status: 'Pending',
requestDate: DateTime.now(), // requestDate: DateTime.now(),
); );
final success = await _provider.submitSiteRequest(request, _userProvider); final success = await _provider.submitSiteRequest(request, _userProvider);
@ -647,8 +615,8 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
departmentIds: _selectedDepartments.isNotEmpty ? _selectedDepartments.map((d) => d.identifier ?? '').toList() : null, departmentIds: _selectedDepartments.isNotEmpty ? _selectedDepartments.map((d) => d.identifier ?? '').toList() : null,
departmentNames: _selectedDepartments.isNotEmpty ? _selectedDepartments.map((d) => d.name ?? '').toList() : null, departmentNames: _selectedDepartments.isNotEmpty ? _selectedDepartments.map((d) => d.name ?? '').toList() : null,
role: _provider.getCurrentUserRole(_userProvider), role: _provider.getCurrentUserRole(_userProvider),
status: 'Pending', // status: 'Pending',
requestDate: DateTime.now(), // requestDate: DateTime.now(),
); );
final success = await _provider.submitSiteRequest(request, _userProvider); final success = await _provider.submitSiteRequest(request, _userProvider);
@ -663,7 +631,9 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
// API will handle success/error toast messages // API will handle success/error toast messages
if (success && mounted) { if (success && mounted) {
Navigator.pushReplacementNamed(context, SiteRequestHistoryScreen.routeName); UserProvider userProvider = Provider.of<UserProvider>(context, listen: false);
await _provider.fetchSiteRequests(pageNumber: 1, pageSize: 10, userProvider: userProvider);
Navigator.pop(context);
} }
} }
} }

@ -67,7 +67,13 @@ class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
} }
Future<void> _refreshData() async { Future<void> _refreshData() async {
setState(() {
_isLoadingData = true;
});
await _provider.refreshSiteRequests(userProvider: _userProvider); await _provider.refreshSiteRequests(userProvider: _userProvider);
if (mounted) {
setState(() => _isLoadingData = false);
}
} }
@override @override
@ -123,16 +129,14 @@ class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
return RefreshIndicator( return RefreshIndicator(
onRefresh: _refreshData, onRefresh: _refreshData,
child: ListView.builder( child: ListView.separated(
separatorBuilder: (context, index) => 12.height,
controller: _scrollController, controller: _scrollController,
padding: EdgeInsets.all(16.toScreenWidth), padding: EdgeInsets.all(16.toScreenWidth),
itemCount: _provider.requestHistory.length + (_provider.isLoadingMore ? 1 : 0), itemCount: _provider.requestHistory.length + (_provider.isLoadingMore ? 1 : 0),
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == _provider.requestHistory.length) { if (index == _provider.requestHistory.length) {
return Padding( return const Center(child: CircularProgressIndicator());
padding: EdgeInsets.symmetric(vertical: 16.toScreenHeight),
child: const Center(child: CircularProgressIndicator()),
);
} }
return _buildRequestCard(_provider.requestHistory[index]); return _buildRequestCard(_provider.requestHistory[index]);
@ -147,13 +151,28 @@ class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildRequestHeader(request), _buildRequestHeader(request),
InfoHeaderWidget("Site Request #${request.id ?? 'N/A'}"), InfoHeaderWidget("Request No: ${request.requestNumber ?? '-'}"),
_buildUserInfo(request), _buildUserInfo(request),
_buildSitesSection(request), // Show old sites if present (for Change Site requests)
if (request.departmentNames != null && request.departmentNames!.isNotEmpty) if (request.oldSiteNames != null && request.oldSiteNames!.isNotEmpty) _buildOldSitesSection(request),
_buildDepartmentsSection(request), // Show new/current sites only if they are not empty and not all null
if (_hasValidSiteNames(request.siteNames)) _buildSitesSection(request),
// Show old departments if present (for Change Site requests)
if (request.oldDepartmentNames != null && request.oldDepartmentNames!.isNotEmpty) _buildOldDepartmentsSection(request),
// Show new departments only if they are not empty and not all null
if (_hasValidDepartmentNames(request.departmentNames)) _buildDepartmentsSection(request),
], ],
).toShadowContainer(context).paddingOnly(bottom: 16); ).toShadowContainer(context);
}
// Helper method to check if site names list has valid (non-null, non-empty) values
bool _hasValidSiteNames(List<String> siteNames) {
return siteNames.isNotEmpty && siteNames.any((name) => name.isNotEmpty);
}
// Helper method to check if department names list has valid (non-null, non-empty) values
bool _hasValidDepartmentNames(List<String>? departmentNames) {
return departmentNames != null && departmentNames.isNotEmpty && departmentNames.any((name) => name.isNotEmpty);
} }
Widget _buildRequestHeader(SiteRequestModel request) { Widget _buildRequestHeader(SiteRequestModel request) {
@ -162,8 +181,8 @@ class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
children: [ children: [
StatusLabel( StatusLabel(
label: request.status, label: request.status,
textColor: _getStatusTextColor(request.status), textColor: _getStatusTextColor(request.status ?? ''),
backgroundColor: _getStatusColor(request.status), backgroundColor: _getStatusColor(request.status ?? ''),
), ),
8.width, 8.width,
StatusLabel( StatusLabel(
@ -179,24 +198,66 @@ class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
Widget _buildUserInfo(SiteRequestModel request) { Widget _buildUserInfo(SiteRequestModel request) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
InfoTextWidget(label: "Full Name", value: request.fullName), InfoTextWidget(label: "Full Name", value: request.fullName),
InfoTextWidget(label: "Employee ID", value: request.employeeId), InfoTextWidget(label: "Employee ID", value: request.employeeId),
InfoTextWidget(label: "Email", value: request.emailAddress), InfoTextWidget(label: "Email", value: request.emailAddress),
InfoTextWidget(label: "Mobile", value: request.mobileNumber, showEmptyValue: true), InfoTextWidget(label: "Mobile", value: request.mobileNumber, showEmptyValue: true),
if (request.extensionNumber != null && request.extensionNumber!.isNotEmpty) if (request.extensionNumber != null && request.extensionNumber!.isNotEmpty) ...[
InfoTextWidget(label: "Extension", value: request.extensionNumber!, showEmptyValue: true), InfoTextWidget(label: "Extension", value: request.extensionNumber!, showEmptyValue: true),
],
InfoTextWidget(label: "Role", value: request.role), InfoTextWidget(label: "Role", value: request.role),
], ],
); );
} }
Widget _buildOldSitesSection(SiteRequestModel request) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoTextLabelWidget(
label: "Old Sites:",
),
8.height,
Wrap(
runSpacing: 4.toScreenHeight,
spacing: 4.toScreenWidth,
children: request.oldSiteNames!.map((site) => _buildChip(site, isOld: true)).toList(),
),
],
).paddingOnly(bottom: 8);
}
Widget _buildSitesSection(SiteRequestModel request) { Widget _buildSitesSection(SiteRequestModel request) {
// Determine label based on whether old sites exist
final label = (request.oldSiteNames != null && request.oldSiteNames!.isNotEmpty) ? "New Sites:" : "Sites:";
// Filter out null or empty site names
final validSiteNames = request.siteNames.where((site) => site.isNotEmpty).toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoTextLabelWidget(
label: label,
),
8.height,
Wrap(
runSpacing: 4.toScreenHeight,
spacing: 4.toScreenWidth,
children: validSiteNames.map((site) => _buildChip(site)).toList(),
),
],
);
}
Widget _buildOldDepartmentsSection(SiteRequestModel request) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Sites:", "Old Departments:",
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColor.labelTextStyleColor(context), color: AppColor.labelTextStyleColor(context),
@ -207,18 +268,24 @@ class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
Wrap( Wrap(
runSpacing: 4.toScreenHeight, runSpacing: 4.toScreenHeight,
spacing: 4.toScreenWidth, spacing: 4.toScreenWidth,
children: request.siteNames.map((site) => _buildChip(site)).toList(), children: request.oldDepartmentNames!.map((dept) => _buildChip(dept, isOld: true)).toList(),
), ),
], ],
).paddingOnly(bottom: 8); ).paddingOnly(bottom: 8);
} }
Widget _buildDepartmentsSection(SiteRequestModel request) { Widget _buildDepartmentsSection(SiteRequestModel request) {
// Determine label based on whether old departments exist
final label = (request.oldDepartmentNames != null && request.oldDepartmentNames!.isNotEmpty) ? "New Departments:" : "Departments:";
// Filter out null or empty department names
final validDepartmentNames = request.departmentNames!.where((dept) => dept.isNotEmpty).toList();
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Departments:", label,
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColor.labelTextStyleColor(context), color: AppColor.labelTextStyleColor(context),
@ -229,20 +296,22 @@ class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
Wrap( Wrap(
runSpacing: 4.toScreenHeight, runSpacing: 4.toScreenHeight,
spacing: 4.toScreenWidth, spacing: 4.toScreenWidth,
children: request.departmentNames!.map((dept) => _buildChip(dept)).toList(), children: validDepartmentNames.map((dept) => _buildChip(dept)).toList(),
), ),
], ],
); );
} }
Widget _buildChip(String label) { Widget _buildChip(String label, {bool isOld = false}) {
return Chip( return Chip(
backgroundColor: AppColor.backgroundTabBarColor(context), backgroundColor: isOld ? AppColor.redStatusColor.withValues(alpha: 0.1) : AppColor.backgroundTabBarColor(context),
side: BorderSide(color: AppColor.border2Color(context)), side: BorderSide(
color: isOld ? AppColor.redStatusColor.withValues(alpha: 0.3) : AppColor.border2Color(context),
),
label: Text(label), label: Text(label),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)),
labelStyle: AppTextStyles.tinyFont2.copyWith( labelStyle: AppTextStyles.tinyFont2.copyWith(
color: AppColor.labelTextStyleColor(context), color: isOld ? AppColor.redStatusTextColor : AppColor.labelTextStyleColor(context),
), ),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
padding: EdgeInsets.symmetric(horizontal: 8.toScreenWidth, vertical: 4.toScreenHeight), padding: EdgeInsets.symmetric(horizontal: 8.toScreenWidth, vertical: 4.toScreenHeight),

@ -72,7 +72,7 @@ class AppDrawer extends StatelessWidget {
// 18.height, // 18.height,
drawerItem("setting", context.translation.settings, context).onPress(() => Navigator.of(context).pushNamed(SettingsPage.id)), drawerItem("setting", context.translation.settings, context).onPress(() => Navigator.of(context).pushNamed(SettingsPage.id)),
18.height, 18.height,
drawerItem("setting", "Site Request", context).onPress(() => Navigator.of(context).pushNamed(SiteRequestHistoryScreen.routeName)), drawerItem("site_request_icon", "Site Request", context).onPress(() => Navigator.of(context).pushNamed(SiteRequestHistoryScreen.routeName)),
18.height, 18.height,
if (userProvider.user != null && userProvider.user?.employeeIsHMG == false) if (userProvider.user != null && userProvider.user?.employeeIsHMG == false)
drawerItem("swipe", "Swipe History", context).onPress(() => Navigator.of(context).pushNamed(SwipeHistoryView.routeName)), drawerItem("swipe", "Swipe History", context).onPress(() => Navigator.of(context).pushNamed(SwipeHistoryView.routeName)),

@ -239,7 +239,7 @@ class _LoginPageState extends State<LoginPage> {
}), }),
" or ".bodyText(context).custom(color: context.isDark ? AppColor.primary50 : AppColor.neutral50), " or ".bodyText(context).custom(color: context.isDark ? AppColor.primary50 : AppColor.neutral50),
"Request a Demo".bodyText(context).custom(color: AppColor.primary10, fontWeight: FontWeight.w600).onPress(() { "Request a Demo".bodyText(context).custom(color: AppColor.primary10, fontWeight: FontWeight.w600).onPress(() {
Navigator.pushNamed(context, DemoRequestFormScreen.routeName); Navigator.pushNamed(context, ApplicationDemoRequestFormScreen.routeName);
}), }),
], ],
), ),

@ -7,11 +7,17 @@ import 'package:local_auth_darwin/local_auth_darwin.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; import 'package:test_sa/controllers/providers/settings/setting_provider.dart';
import 'package:test_sa/controllers/providers/api/user_provider.dart';
import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/string_extensions.dart';
import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart';
import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart';
import 'package:test_sa/helper/utils.dart';
import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
import 'package:test_sa/new_views/pages/login_page.dart';
import 'package:test_sa/views/widgets/dialogs/dialog.dart';
import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; import 'package:test_sa/views/widgets/item_views/info_header_widget.dart';
import '../common_widgets/default_app_bar.dart'; import '../common_widgets/default_app_bar.dart';
@ -100,76 +106,132 @@ class _SettingsPageState extends State<SettingsPage> {
return Scaffold( return Scaffold(
appBar: DefaultAppBar(title: context.translation.settings), appBar: DefaultAppBar(title: context.translation.settings),
body: Column( body: Column(
mainAxisSize: MainAxisSize.min,
spacing: 12,
children: [ children: [
Row( Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.min,
spacing: 12,
children: [ children: [
InfoHeader16Widget(context.translation.arabic), Row(
AdvancedSwitch( mainAxisAlignment: MainAxisAlignment.spaceBetween,
controller: langController, children: [
initialValue: langController!.value, InfoHeader16Widget(context.translation.arabic),
activeColor: AppColor.green50.withValues(alpha: .25), AdvancedSwitch(
inactiveColor: AppColor.neutral10, controller: langController,
thumb: CircleAvatar(backgroundColor: langController!.value ? AppColor.green50 : AppColor.switchBgColor), initialValue: langController!.value,
borderRadius: const BorderRadius.all(Radius.circular(30)), activeColor: AppColor.green50.withValues(alpha: .25),
width: 42.toScreenWidth, inactiveColor: AppColor.neutral10,
height: 24.toScreenHeight, thumb: CircleAvatar(backgroundColor: langController!.value ? AppColor.green50 : AppColor.switchBgColor),
borderRadius: const BorderRadius.all(Radius.circular(30)),
/// TODO : remove the below attribute to enable the switch width: 42.toScreenWidth,
//enabled: false, height: 24.toScreenHeight,
disabledOpacity: 1,
/// TODO : remove the below attribute to enable the switch
//enabled: false,
disabledOpacity: 1,
),
],
), ),
], // 16.height,
), const Divider().defaultStyle(context),
// 16.height, // 16.height,
const Divider().defaultStyle(context), Row(
// 16.height, mainAxisAlignment: MainAxisAlignment.spaceBetween,
Row( children: [
mainAxisAlignment: MainAxisAlignment.spaceBetween, InfoHeader16Widget(context.translation.lightTheme),
children: [ AdvancedSwitch(
InfoHeader16Widget(context.translation.lightTheme), controller: themeController,
AdvancedSwitch( initialValue: themeController!.value,
controller: themeController, activeColor: AppColor.green50.withValues(alpha: .25),
initialValue: themeController!.value, inactiveColor: AppColor.neutral10,
activeColor: AppColor.green50.withValues(alpha: .25), thumb: CircleAvatar(backgroundColor: themeController!.value ? AppColor.green50 : AppColor.switchBgColor),
inactiveColor: AppColor.neutral10, borderRadius: const BorderRadius.all(Radius.circular(30)),
thumb: CircleAvatar(backgroundColor: themeController!.value ? AppColor.green50 : AppColor.switchBgColor), width: 42.toScreenWidth,
borderRadius: const BorderRadius.all(Radius.circular(30)), height: 24.toScreenHeight,
width: 42.toScreenWidth, disabledOpacity: 1,
height: 24.toScreenHeight, ),
disabledOpacity: 1, ],
), ),
// 16.height,
const Divider().defaultStyle(context),
// 16.height,
FutureBuilder(
future: _settingProvider!.auth.isDeviceSupported(),
builder: (cxt, snapshot) {
bool isDeviceSupported = false;
if (snapshot.hasData) {
isDeviceSupported = snapshot.data as bool;
}
if (!isDeviceSupported) {
return Text("Your device did not support Fingerprint/Face ID.", style: AppTextStyles.tinyFont.copyWith(color: AppColor.red30));
}
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InfoHeader16Widget("Fingerprint/Face ID"),
customSwitch(localAuth).onPress(() {
checkForLocalAuth(localAuth);
}),
],
);
}),
], ],
), ).toShadowContainer(context, borderRadius: 16, padding: 16).paddingAll(16),
// 16.height, // 12.height,
const Divider().defaultStyle(context), //TODO need to test this ...
// 16.height, AppFilledButton(
FutureBuilder( label: "Delete Account",
future: _settingProvider!.auth.isDeviceSupported(), buttonColor: AppColor.red30,
builder: (cxt, snapshot) { onPressed: () async {
bool isDeviceSupported = false; bool result = await showDialog(
if (snapshot.hasData) { context: context,
isDeviceSupported = snapshot.data as bool; builder: (_) => AAlertDialog(
title: "Confirm".addTranslation,
content: "By deleting your account, all your data will be permanently removed. Are you sure you want to proceed?".addTranslation,
),
);
if (result) {
final userProvider = Provider.of<UserProvider>(context, listen: false);
// Check if user data is available
if (userProvider.user == null) {
Utils.showToast("User information not available");
return;
} }
if (!isDeviceSupported) { final employeeId = userProvider.user!.employeeId ?? "";
return Text("Your device did not support Fingerprint/Face ID.", style: AppTextStyles.tinyFont.copyWith(color: AppColor.red30)); final userId = userProvider.user!.userID ?? "";
if (employeeId.isEmpty || userId.isEmpty) {
Utils.showToast("Unable to delete account: missing user information");
return;
} }
return Row( // Call the delete user API
mainAxisAlignment: MainAxisAlignment.spaceBetween, bool isSuccess = await userProvider.deleteUser(
children: [ context,
InfoHeader16Widget("Fingerprint/Face ID"), employeeId: employeeId,
customSwitch(localAuth).onPress(() { userId: userId,
checkForLocalAuth(localAuth);
}),
],
); );
}),
if (isSuccess) {
// Reset settings and user data
Provider.of<SettingProvider>(context, listen: false).resetSettings();
userProvider.reset();
// Navigate to login page
Navigator.of(context).pushNamedAndRemoveUntil(
LoginPage.routeName,
(routes) => false,
);
}
}
},
).paddingAll(16),
], ],
).toShadowContainer(context, borderRadius: 16, padding: 16).paddingAll(16), ),
); );
} }

@ -59,3 +59,7 @@ class AAlertDialog extends StatelessWidget {
); );
} }
} }

Loading…
Cancel
Save