signup inprogress

export_module_ui_ux_enhancement_signup
WaseemAbbasi22 4 weeks ago
parent 9c7c178f71
commit 14e845b18a

@ -52,6 +52,10 @@ class URLs {
static get updateNewPassword => "$_baseUrl/Account/UpdatenewPassword"; // validate OTP.
static get getUserRota => "$_baseUrl/Account/GetUserRota"; // validate OTP.
// SignUp APIs
static get signUpGetEmployeeFromERP => "$_baseUrl/SignUpRequest/GetEmployeeFromERP";
static get signUpSubmit => "$_baseUrl/SignUpRequest/Submit";
// static get login => "$_baseUrl/MobileAuth/LoginIntegration"; // mobile login
static get register => "$_baseUrl/handle/create/user"; // post
static get updateProfile => "$_baseUrl/update/user/profile"; // post

@ -55,6 +55,10 @@ import 'package:test_sa/modules/medical_gas_inspection/create_medical_gas_reques
import 'package:test_sa/modules/medical_gas_inspection/providers/item_type_provider.dart';
import 'package:test_sa/modules/medical_gas_inspection/providers/medical_gas_inspection_provider.dart';
import 'package:test_sa/modules/medical_gas_inspection/providers/order_type_provider.dart';
import 'package:test_sa/modules/signup/providers/company_provider.dart';
import 'package:test_sa/modules/signup/providers/group_provider.dart';
import 'package:test_sa/modules/signup/providers/signup_provider.dart';
import 'package:test_sa/modules/signup/screens/signup_screen.dart';
import 'package:test_sa/modules/tm_module/gas_refill/create_gas_refill_request.dart';
import 'package:test_sa/modules/tm_module/gas_refill/update_gas_refill_request.dart';
import 'package:test_sa/modules/traf_module/create_traf_request_page.dart';
@ -337,6 +341,11 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider(create: (_) => MedicalDepartmentProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => FlowMedicalDepartmentProvider(), lazy: true),
// SIGNUP MODULE PROVIDERS (3)
ChangeNotifierProvider(create: (_) => CompanyProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => GroupProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => SignUpProvider(), lazy: true),
// EMPLOYEE/USER PROVIDERS (2)
ChangeNotifierProvider(create: (_) => NurseProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => UserSearchProvider(), lazy: true),
@ -412,6 +421,7 @@ class MyApp extends StatelessWidget {
CreateSystemInternalAuditForm.id: (_) => const CreateSystemInternalAuditForm(),
UpdateEquipmentInternalAuditPage.id: (_) => UpdateEquipmentInternalAuditPage(),
UpdateSystemInternalAuditPage.id: (_) => UpdateSystemInternalAuditPage(),
SignUpScreen.routeName: (_) => const SignUpScreen(),
CreateLoanRequestPage.id: (_) => CreateLoanRequestPage(),
CreateMedicalGasRequestPage.id: (_) => CreateMedicalGasRequestPage(),
CreateDemoRequestPage.id: (_) => CreateDemoRequestPage(),

@ -0,0 +1,35 @@
import 'package:test_sa/models/base.dart';
class Company extends Base {
final String? code;
final String? displayName;
Company({
this.code,
this.displayName,
}) : super(identifier: code, name: displayName);
factory Company.fromJson(Map<String, dynamic> json) {
return Company(
code: json['code'],
displayName: json['displayName'],
);
}
Map<String, dynamic> toJson() {
return {
'code': code,
'displayName': displayName,
};
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Company &&
runtimeType == other.runtimeType &&
code == other.code;
@override
int get hashCode => code.hashCode;
}

@ -0,0 +1,39 @@
class EmployeeERPModel {
final String? employeeId;
final String? fullName;
final String? email;
final String? mobileNumber;
final bool? existsInAtoms;
final String? message;
EmployeeERPModel({
this.employeeId,
this.fullName,
this.email,
this.mobileNumber,
this.existsInAtoms,
this.message,
});
factory EmployeeERPModel.fromJson(Map<String, dynamic> json) {
return EmployeeERPModel(
employeeId: json['employeeId']?.toString(),
fullName: json['fullName'],
email: json['email'],
mobileNumber: json['mobileNumber'],
existsInAtoms: json['existsInAtoms'],
message: json['message'],
);
}
Map<String, dynamic> toJson() {
return {
'employeeId': employeeId,
'fullName': fullName,
'email': email,
'mobileNumber': mobileNumber,
'existsInAtoms': existsInAtoms,
'message': message,
};
}
}

@ -0,0 +1,35 @@
import 'package:test_sa/models/base.dart';
class Group extends Base {
final String? code;
final String? displayName;
Group({
this.code,
this.displayName,
}) : super(identifier: code, name: displayName);
factory Group.fromJson(Map<String, dynamic> json) {
return Group(
code: json['code'],
displayName: json['displayName'],
);
}
Map<String, dynamic> toJson() {
return {
'code': code,
'displayName': displayName,
};
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Group &&
runtimeType == other.runtimeType &&
code == other.code;
@override
int get hashCode => code.hashCode;
}

@ -0,0 +1,41 @@
class SignUpRequestModel {
final String? companyCode;
final String? groupCode;
final String? employeeId;
final String? fullName;
final String? email;
final String? mobileNumber;
final String? extensionNumber;
final int? siteId;
final int? departmentId;
final String? role;
SignUpRequestModel({
this.companyCode,
this.groupCode,
this.employeeId,
this.fullName,
this.email,
this.mobileNumber,
this.extensionNumber,
this.siteId,
this.departmentId,
this.role,
});
Map<String, dynamic> toJson() {
return {
'companyCode': companyCode,
'groupCode': groupCode,
'employeeId': employeeId,
'fullName': fullName,
'email': email,
'mobileNumber': mobileNumber,
'extensionNumber': extensionNumber,
'siteId': siteId,
'departmentId': departmentId,
'role': role,
};
}
}

@ -0,0 +1,33 @@
import 'package:test_sa/modules/signup/models/company_model.dart';
import 'package:test_sa/providers/loading_list_notifier.dart';
class CompanyProvider extends LoadingListNotifier<Company> {
@override
Future getData({int? id}) async {
if (loading) return -2;
loading = true;
notifyListeners();
try {
// TODO: Replace with actual API call when available
await Future.delayed(const Duration(milliseconds: 500));
items = [
Company(code: 'FM', displayName: 'Flow Medical (FM)'),
Company(code: 'FMS', displayName: 'Facility Management and Safety (FMS)'),
Company(code: 'Ajaji', displayName: 'Ajaji'),
];
stateCode = 200;
loading = false;
notifyListeners();
return 200;
} catch (error) {
loading = false;
stateCode = -1;
notifyListeners();
return -1;
}
}
}

@ -0,0 +1,32 @@
import 'package:test_sa/modules/signup/models/group_model.dart';
import 'package:test_sa/providers/loading_list_notifier.dart';
class GroupProvider extends LoadingListNotifier<Group> {
@override
Future getData({int? id}) async {
if (loading) return -2;
loading = true;
notifyListeners();
try {
// TODO: Replace with actual API call when available
await Future.delayed(const Duration(milliseconds: 500));
items = [
Group(code: 'HMG', displayName: 'HMG'),
Group(code: 'Non-HMG', displayName: 'Non-HMG'),
];
stateCode = 200;
loading = false;
notifyListeners();
return 200;
} catch (error) {
loading = false;
stateCode = -1;
notifyListeners();
return -1;
}
}
}

@ -0,0 +1,100 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:test_sa/controllers/api_routes/api_manager.dart';
import 'package:test_sa/controllers/api_routes/urls.dart';
import 'package:test_sa/modules/signup/models/employee_erp_model.dart';
import 'package:test_sa/modules/signup/models/signup_request_model.dart';
class SignUpProvider extends ChangeNotifier {
bool _loading = false;
bool get loading => _loading;
set loading(bool value) {
_loading = value;
notifyListeners();
}
EmployeeERPModel? _employeeData;
EmployeeERPModel? get employeeData => _employeeData;
String? _errorMessage;
String? get errorMessage => _errorMessage;
/// Fetch employee details from ERP
Future<EmployeeERPModel?> getEmployeeFromERP(String employeeId) async {
if (loading) return null;
loading = true;
_errorMessage = null;
_employeeData = null;
notifyListeners();
try {
final url = '${URLs.signUpGetEmployeeFromERP}?employeeId=$employeeId';
Response response = await ApiManager.instance.get(url, enableToastMessage: false);
if (response.statusCode >= 200 && response.statusCode < 300) {
final data = json.decode(response.body);
_employeeData = EmployeeERPModel.fromJson(data['data']);
loading = false;
notifyListeners();
return _employeeData;
} else {
final data = json.decode(response.body);
_errorMessage = data['message'] ?? 'Failed to fetch employee details';
loading = false;
notifyListeners();
return null;
}
} catch (error) {
_errorMessage = 'Failed to fetch employee details';
loading = false;
notifyListeners();
return null;
}
}
/// Submit signup request
Future<bool> submitSignUpRequest(SignUpRequestModel request) async {
if (loading) return false;
loading = true;
_errorMessage = null;
notifyListeners();
try {
Response response = await ApiManager.instance.post(
URLs.signUpSubmit,
body: request.toJson(),
);
if (response.statusCode >= 200 && response.statusCode < 300) {
loading = false;
notifyListeners();
return true;
} else {
final data = json.decode(response.body);
_errorMessage = data['message'] ?? 'Failed to submit signup request';
loading = false;
notifyListeners();
return false;
}
} catch (error) {
_errorMessage = 'Failed to submit signup request';
loading = false;
notifyListeners();
return false;
}
}
void reset() {
_loading = false;
_employeeData = null;
_errorMessage = null;
notifyListeners();
}
}

@ -0,0 +1,479 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:provider/provider.dart';
import 'package:test_sa/controllers/validator/validator.dart';
import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/text_extensions.dart';
import 'package:test_sa/models/lookup.dart';
import 'package:test_sa/models/new_models/site.dart';
import 'package:test_sa/modules/signup/models/company_model.dart';
import 'package:test_sa/modules/signup/models/group_model.dart';
import 'package:test_sa/modules/signup/models/signup_request_model.dart';
import 'package:test_sa/modules/signup/providers/company_provider.dart';
import 'package:test_sa/modules/signup/providers/group_provider.dart';
import 'package:test_sa/modules/signup/providers/signup_provider.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/common_widgets/app_lazy_loading.dart';
import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart';
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart';
import 'package:test_sa/providers/department_provider.dart';
import 'package:test_sa/providers/gas_request_providers/site_provider.dart';
class SignUpScreen extends StatefulWidget {
static const String routeName = "/signup_screen";
const SignUpScreen({Key? key}) : super(key: key);
@override
State<SignUpScreen> createState() => _SignUpScreenState();
}
class _SignUpScreenState extends State<SignUpScreen> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late CompanyProvider _companyProvider;
late GroupProvider _groupProvider;
late SignUpProvider _signUpProvider;
late DepartmentProvider _departmentProvider;
// Form field controllers
final TextEditingController _employeeIdController = TextEditingController();
final TextEditingController _fullNameController = TextEditingController();
final TextEditingController _emailController = TextEditingController();
final TextEditingController _mobileController = TextEditingController();
final TextEditingController _extensionController = TextEditingController();
final TextEditingController _roleController = TextEditingController();
// Selected values
Company? _selectedCompany;
Group? _selectedGroup;
Site? _selectedSite;
Lookup? _selectedDepartment;
bool _isHMGGroup = false;
bool _employeeFetched = false;
bool _isSubmitting = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_companyProvider.getData();
_groupProvider.getData();
});
}
@override
void dispose() {
_employeeIdController.dispose();
_fullNameController.dispose();
_emailController.dispose();
_mobileController.dispose();
_extensionController.dispose();
_roleController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
_companyProvider = Provider.of<CompanyProvider>(context);
_groupProvider = Provider.of<GroupProvider>(context);
_signUpProvider = Provider.of<SignUpProvider>(context);
_departmentProvider = Provider.of<DepartmentProvider>(context);
return Scaffold(
backgroundColor: AppColor.scaffoldBackground(context),
appBar: const DefaultAppBar(title: "Sign Up"),
body: Form(
key: _formKey,
child: SingleChildScrollView(
padding: EdgeInsets.all(16.toScreenWidth),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildCompanyDropdown(),
16.height,
_buildGroupDropdown(),
16.height,
if (_selectedGroup != null) ..._buildDynamicFields(),
24.height,
_buildSubmitButton(),
],
),
),
),
);
}
Widget _buildCompanyDropdown() {
return SingleItemDropDownMenu<Company, CompanyProvider>(
context: context,
title: "Company",
initialValue: _selectedCompany,
showAsFullScreenDialog: false,
showAsBottomSheet: true,
onSelect: (value) {
setState(() {
_selectedCompany = value;
});
},
validator: (value) {
if (value == null) return "Company is required";
return null;
},
);
}
Widget _buildGroupDropdown() {
return SingleItemDropDownMenu<Group, GroupProvider>(
context: context,
title: "Group",
initialValue: _selectedGroup,
showAsFullScreenDialog: false,
showAsBottomSheet: true,
onSelect: (value) {
setState(() {
_selectedGroup = value;
_isHMGGroup = value?.code == 'HMG';
_employeeFetched = false;
_clearForm();
});
},
validator: (value) {
if (value == null) return "Group is required";
return null;
},
);
}
List<Widget> _buildDynamicFields() {
if (_isHMGGroup) {
return [
_buildEmployeeIdField(),
16.height,
if (_employeeFetched) ...[
_buildReadOnlyField("Full Name", _fullNameController),
16.height,
_buildReadOnlyField("Email Address", _emailController),
16.height,
_buildReadOnlyField("Mobile Number", _mobileController),
16.height,
_buildExtensionField(),
16.height,
_buildSiteDropdown(),
16.height,
_buildDepartmentDropdown(),
16.height,
_buildRoleField(),
],
];
} else {
return [
_buildReadOnlyEmployeeIdField(),
16.height,
_buildFullNameField(),
16.height,
_buildEmailField(),
16.height,
_buildMobileField(),
16.height,
_buildExtensionField(),
16.height,
_buildSiteDropdown(),
16.height,
_buildDepartmentDropdown(),
16.height,
_buildRoleField(),
];
}
}
Widget _buildEmployeeIdField() {
return AppTextFormField(
controller: _employeeIdController,
labelText: "Employee ID",
hintText: "Enter Employee ID",
textInputType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) return "Employee ID is required";
if (!Validator.isNumeric(value)) return "Employee ID must be numeric";
return null;
},
onChange: (value) {
if (value.isNotEmpty && Validator.isNumeric(value)) {
_fetchEmployeeDetails(value);
}
},
);
}
Widget _buildReadOnlyEmployeeIdField() {
return AppTextFormField(
initialValue: "Will be generated after admin approval",
labelText: "Employee ID",
enable: false,
backgroundColor: AppColor.disableFieldBackground(context),
);
}
Widget _buildReadOnlyField(String label, TextEditingController controller) {
return AppTextFormField(
controller: controller,
labelText: label,
enable: false,
backgroundColor: AppColor.disableFieldBackground(context),
);
}
Widget _buildFullNameField() {
return AppTextFormField(
controller: _fullNameController,
labelText: "Full Name",
hintText: "Enter Full Name",
validator: (value) {
if (value == null || value.isEmpty) return "Full Name is required";
return null;
},
);
}
Widget _buildEmailField() {
return AppTextFormField(
controller: _emailController,
labelText: "Email Address",
hintText: "Enter Email Address",
textInputType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) return "Email is required";
if (!Validator.isEmail(value)) return "Invalid email format";
return null;
},
);
}
Widget _buildMobileField() {
return AppTextFormField(
controller: _mobileController,
labelText: "Mobile Number",
hintText: "Enter Mobile Number",
textInputType: TextInputType.phone,
validator: (value) {
if (value == null || value.isEmpty) return "Mobile Number is required";
if (!Validator.isPhoneNumber(value)) return "Invalid mobile number";
return null;
},
);
}
Widget _buildExtensionField() {
return AppTextFormField(
controller: _extensionController,
labelText: "Extension Number",
hintText: "Enter Extension Number (Optional)",
textInputType: TextInputType.number,
validator: (value) {
if (value != null && value.isNotEmpty && !Validator.isNumeric(value)) {
return "Extension must be numeric";
}
return null;
},
);
}
Widget _buildSiteDropdown() {
return SingleItemDropDownMenu<Site, SiteProvider>(
context: context,
title: "Site",
initialValue: _selectedSite,
showAsFullScreenDialog: false,
showAsBottomSheet: true,
onSelect: (value) {
setState(() {
_selectedSite = value;
_selectedDepartment = null;
});
if (value != null) {
_departmentProvider.getData(id: value.id?.toInt());
}
},
validator: (value) {
if (value == null) return "Site is required";
return null;
},
);
}
Widget _buildDepartmentDropdown() {
return SingleItemDropDownMenu<Lookup, DepartmentProvider>(
context: context,
title: "Department",
initialValue: _selectedDepartment,
showAsFullScreenDialog: false,
showAsBottomSheet: true,
onSelect: (value) {
setState(() {
_selectedDepartment = value;
});
},
validator: (value) {
if (value == null) return "Department is required";
return null;
},
);
}
Widget _buildRoleField() {
return AppTextFormField(
controller: _roleController,
labelText: "Role",
hintText: "Enter Role",
validator: (value) {
if (value == null || value.isEmpty) return "Role is required";
return null;
},
);
}
Widget _buildSubmitButton() {
return AppFilledButton(
label: "Submit",
maxWidth: true,
loading: _isSubmitting,
disableButton: _isSubmitting,
onPressed: _submitForm,
);
}
Future<void> _fetchEmployeeDetails(String employeeId) async {
final employee = await _signUpProvider.getEmployeeFromERP(employeeId);
if (employee != null) {
if (employee.existsInAtoms == true) {
if (mounted) {
_showAlreadyExistsDialog();
}
_clearForm();
_employeeIdController.clear();
return;
}
setState(() {
_employeeFetched = true;
_fullNameController.text = employee.fullName ?? '';
_emailController.text = employee.email ?? '';
_mobileController.text = employee.mobileNumber ?? '';
});
} else {
setState(() {
_employeeFetched = false;
});
Fluttertoast.showToast(
msg: _signUpProvider.errorMessage ?? "Failed to fetch employee details",
);
}
}
void _showAlreadyExistsDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: AppColor.background(context),
title: "Account Exists".bodyText(context),
content: "Already you have an account on Atoms".bodyText(context),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: "OK".bodyText(context).custom(color: AppColor.primary10),
),
],
),
);
}
void _clearForm() {
_fullNameController.clear();
_emailController.clear();
_mobileController.clear();
_extensionController.clear();
_roleController.clear();
_selectedSite = null;
_selectedDepartment = null;
}
Future<void> _submitForm() async {
if (!_formKey.currentState!.validate()) return;
if (_isHMGGroup && !_employeeFetched) {
Fluttertoast.showToast(msg: "Please enter a valid Employee ID");
return;
}
setState(() {
_isSubmitting = true;
});
if (mounted) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const AppLazyLoading(),
);
}
final request = SignUpRequestModel(
companyCode: _selectedCompany?.code,
groupCode: _selectedGroup?.code,
employeeId: _isHMGGroup ? _employeeIdController.text : null,
fullName: _fullNameController.text,
email: _emailController.text,
mobileNumber: _mobileController.text,
extensionNumber: _extensionController.text.isEmpty ? null : _extensionController.text,
siteId: _selectedSite?.id?.toInt(),
departmentId: _selectedDepartment?.id,
role: _roleController.text,
);
final success = await _signUpProvider.submitSignUpRequest(request);
if (mounted) {
Navigator.pop(context); // Close loading dialog
}
setState(() {
_isSubmitting = false;
});
if (success) {
if (mounted) {
_showSuccessDialog();
}
} else {
Fluttertoast.showToast(
msg: _signUpProvider.errorMessage ?? "Failed to submit signup request",
);
}
}
void _showSuccessDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: AppColor.background(context),
title: "Success".bodyText(context),
content: "Your signup request has been submitted successfully. It will be reviewed by the Human Resources Admin."
.bodyText(context),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context); // Close dialog
Navigator.pop(context); // Go back to login
},
child: "OK".bodyText(context).custom(color: AppColor.primary10),
),
],
),
);
}
}

@ -15,6 +15,7 @@ import 'package:test_sa/extensions/widget_extensions.dart';
import 'package:test_sa/models/new_models/general_response_model.dart';
import 'package:test_sa/modules/cx_module/chat/chat_page.dart';
import 'package:test_sa/modules/cx_module/survey/survey_page.dart';
import 'package:test_sa/modules/signup/screens/signup_screen.dart';
import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/new_views/forget_password_module/forget_passwod_verify_otp.dart';
import 'package:test_sa/new_views/pages/land_page/land_page.dart';
@ -227,6 +228,16 @@ class _LoginPageState extends State<LoginPage> {
),
16.height,
AppFilledButton(label: context.translation.signIn, buttonColor: AppColor.primary10, maxWidth: true, onPressed: _login),
16.height,
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
"Don't have an account? ".bodyText(context).custom(color: context.isDark ? AppColor.primary50 : AppColor.neutral50),
"Sign Up".bodyText(context).custom(color: AppColor.primary10, fontWeight: FontWeight.w600).onPress(() {
Navigator.pushNamed(context, SignUpScreen.routeName);
}),
],
),
],
),
),

Loading…
Cancel
Save