site implementation inprogress

export_module_ui_ux_enhancement_signup
WaseemAbbasi22 3 weeks ago
parent c6166d1e75
commit d9eb1ae835

@ -61,6 +61,9 @@ import 'package:test_sa/modules/signup/providers/signup_provider.dart';
import 'package:test_sa/modules/signup/providers/signup_site_provider.dart'; import 'package:test_sa/modules/signup/providers/signup_site_provider.dart';
import 'package:test_sa/modules/signup/providers/signup_department_provider.dart'; import 'package:test_sa/modules/signup/providers/signup_department_provider.dart';
import 'package:test_sa/modules/signup/screens/signup_screen.dart'; import 'package:test_sa/modules/signup/screens/signup_screen.dart';
import 'package:test_sa/modules/site_request_module/providers/site_request_provider.dart';
import 'package:test_sa/modules/site_request_module/screens/site_request_form_screen.dart';
import 'package:test_sa/modules/site_request_module/screens/site_request_history_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/create_gas_refill_request.dart';
import 'package:test_sa/modules/tm_module/gas_refill/update_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'; import 'package:test_sa/modules/traf_module/create_traf_request_page.dart';
@ -350,6 +353,9 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider(create: (_) => SignupSiteProvider(), lazy: true), ChangeNotifierProvider(create: (_) => SignupSiteProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => SignupDepartmentProvider(), lazy: true), ChangeNotifierProvider(create: (_) => SignupDepartmentProvider(), lazy: true),
// SITE REQUEST MODULE PROVIDERS (1)
ChangeNotifierProvider(create: (_) => SiteRequestProvider(), lazy: true),
// EMPLOYEE/USER PROVIDERS (2) // EMPLOYEE/USER PROVIDERS (2)
ChangeNotifierProvider(create: (_) => NurseProvider(), lazy: true), ChangeNotifierProvider(create: (_) => NurseProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => UserSearchProvider(), lazy: true), ChangeNotifierProvider(create: (_) => UserSearchProvider(), lazy: true),
@ -426,6 +432,8 @@ class MyApp extends StatelessWidget {
UpdateEquipmentInternalAuditPage.id: (_) => UpdateEquipmentInternalAuditPage(), UpdateEquipmentInternalAuditPage.id: (_) => UpdateEquipmentInternalAuditPage(),
UpdateSystemInternalAuditPage.id: (_) => UpdateSystemInternalAuditPage(), UpdateSystemInternalAuditPage.id: (_) => UpdateSystemInternalAuditPage(),
SignUpScreen.routeName: (_) => const SignUpScreen(), SignUpScreen.routeName: (_) => const SignUpScreen(),
SiteRequestFormScreen.routeName: (_) => const SiteRequestFormScreen(),
SiteRequestHistoryScreen.routeName: (_) => const SiteRequestHistoryScreen(),
CreateLoanRequestPage.id: (_) => CreateLoanRequestPage(), CreateLoanRequestPage.id: (_) => CreateLoanRequestPage(),
CreateMedicalGasRequestPage.id: (_) => CreateMedicalGasRequestPage(), CreateMedicalGasRequestPage.id: (_) => CreateMedicalGasRequestPage(),
CreateDemoRequestPage.id: (_) => CreateDemoRequestPage(), CreateDemoRequestPage.id: (_) => CreateDemoRequestPage(),

@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -90,25 +88,46 @@ class _SignUpScreenState extends State<SignUpScreen> {
return Scaffold( return Scaffold(
backgroundColor: AppColor.scaffoldBackground(context), backgroundColor: AppColor.scaffoldBackground(context),
appBar: const DefaultAppBar(title: "Sign Up"), appBar: const DefaultAppBar(title: "Sign Up"),
body: Form( body: Column(
key: _formKey, children: [
child: SingleChildScrollView( Expanded(
padding: EdgeInsets.all(16.toScreenWidth), child: Form(
child: Column( key: _formKey,
crossAxisAlignment: CrossAxisAlignment.start, child: SingleChildScrollView(
children: [ padding: EdgeInsets.all(16.toScreenWidth),
_buildAssetGroupDropdown(), child: Column(
16.height, crossAxisAlignment: CrossAxisAlignment.start,
if (_selectedAssetGroup != null) ...[ children: [
_buildCompanyDropdown(), _buildAssetGroupDropdown(),
16.height, 16.height,
], if (_selectedAssetGroup != null) ...[
if (_selectedCompany != null) ..._buildDynamicFields(), _buildCompanyDropdown(),
24.height, 16.height,
if (_selectedCompany != null) _buildSubmitButton(), ],
], if (_selectedCompany != null) ..._buildDynamicFields(),
],
),
),
),
), ),
), if (_selectedCompany != null)
Container(
padding: EdgeInsets.all(16.toScreenWidth),
decoration: BoxDecoration(
color: AppColor.background(context),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, -2),
),
],
),
child: SafeArea(
child: _buildSubmitButton(),
),
),
],
), ),
); );
} }
@ -184,18 +203,17 @@ class _SignUpScreenState extends State<SignUpScreen> {
_buildReadOnlyField("Mobile Number", _mobileController), _buildReadOnlyField("Mobile Number", _mobileController),
16.height, 16.height,
_buildExtensionField(), _buildExtensionField(),
// 16.height, 16.height,
// _buildSiteDropdown(), _buildSiteDropdown(),
// 16.height, 16.height,
// _buildDepartmentDropdown(), _buildDepartmentDropdown(),
16.height, 16.height,
_buildRoleField(), _buildRoleField(),
], ],
]; ];
} else { } else {
return [ return [
// _buildReadOnlyEmployeeIdField(), // Employee ID is auto-generated for Non-HMG, so not shown during signup
// 16.height,
_buildFullNameField(), _buildFullNameField(),
16.height, 16.height,
_buildEmailField(), _buildEmailField(),
@ -256,15 +274,6 @@ class _SignUpScreenState extends State<SignUpScreen> {
); );
} }
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) { Widget _buildReadOnlyField(String label, TextEditingController controller) {
return AppTextFormField( return AppTextFormField(
controller: controller, controller: controller,
@ -396,6 +405,19 @@ class _SignUpScreenState extends State<SignUpScreen> {
final employee = await _signUpProvider.getEmployeeFromERP(employeeId); final employee = await _signUpProvider.getEmployeeFromERP(employeeId);
if (employee != null) { if (employee != null) {
// Check if the error message indicates duplicate account
if (_signUpProvider.errorMessage != null &&
_signUpProvider.errorMessage!.toLowerCase().contains('already')) {
// Show duplicate account popup
_showDuplicateAccountDialog();
setState(() {
_employeeFetched = false;
_employeeERPData = null;
_employeeIdController.clear();
});
return;
}
setState(() { setState(() {
_employeeFetched = true; _employeeFetched = true;
_employeeERPData = employee; // Store the ERP data for later use _employeeERPData = employee; // Store the ERP data for later use
@ -404,17 +426,30 @@ class _SignUpScreenState extends State<SignUpScreen> {
_mobileController.text = employee.employeeMobileNumber ?? ''; _mobileController.text = employee.employeeMobileNumber ?? '';
}); });
} else { } else {
setState(() { // Check if it's a duplicate account error
_employeeFetched = false; if (_signUpProvider.errorMessage != null &&
_employeeERPData = null; (_signUpProvider.errorMessage!.toLowerCase().contains('already') ||
}); _signUpProvider.errorMessage!.toLowerCase().contains('exists') ||
Fluttertoast.showToast( _signUpProvider.errorMessage!.toLowerCase().contains('account'))) {
msg: _signUpProvider.errorMessage ?? "Failed to fetch employee details", _showDuplicateAccountDialog();
); setState(() {
_employeeFetched = false;
_employeeERPData = null;
_employeeIdController.clear();
});
} else {
setState(() {
_employeeFetched = false;
_employeeERPData = null;
});
Fluttertoast.showToast(
msg: _signUpProvider.errorMessage ?? "Failed to fetch employee details",
);
}
} }
} }
void _showAlreadyExistsDialog() { void _showDuplicateAccountDialog() {
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
@ -423,7 +458,9 @@ class _SignUpScreenState extends State<SignUpScreen> {
content: "Already you have an account on Atoms".bodyText(context), content: "Already you have an account on Atoms".bodyText(context),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context), onPressed: () {
Navigator.pop(context);
},
child: "OK".bodyText(context).custom(color: AppColor.primary10), child: "OK".bodyText(context).custom(color: AppColor.primary10),
), ),
], ],
@ -432,6 +469,7 @@ class _SignUpScreenState extends State<SignUpScreen> {
} }
void _clearForm() { void _clearForm() {
_employeeIdController.clear();
_fullNameController.clear(); _fullNameController.clear();
_emailController.clear(); _emailController.clear();
_mobileController.clear(); _mobileController.clear();
@ -439,24 +477,113 @@ class _SignUpScreenState extends State<SignUpScreen> {
_roleController.clear(); _roleController.clear();
_selectedSite = null; _selectedSite = null;
_selectedDepartment = null; _selectedDepartment = null;
_employeeFetched = false;
_employeeERPData = null;
} }
Future<void> _submitForm() async { Future<void> _submitForm() async {
if (!_formKey.currentState!.validate()) return; // Validate form fields
if (!_formKey.currentState!.validate()) {
Fluttertoast.showToast(msg: "Please fill all required fields");
return;
}
// HMG Group specific validations
if (_isHMGGroup) {
// Employee ID is mandatory for HMG
if (_employeeIdController.text.isEmpty) {
Fluttertoast.showToast(msg: "Employee ID is required");
return;
}
// Employee must be fetched from ERP
if (!_employeeFetched) {
Fluttertoast.showToast(msg: "Please search and fetch employee details from ERP");
return;
}
// Validate Employee ID is numeric
if (!Validator.isNumeric(_employeeIdController.text)) {
Fluttertoast.showToast(msg: "Employee ID must be numeric");
return;
}
// Site is mandatory for HMG
if (_selectedSite == null) {
Fluttertoast.showToast(msg: "Site is required");
return;
}
// Department is mandatory for HMG
if (_selectedDepartment == null) {
Fluttertoast.showToast(msg: "Department is required");
return;
}
} else {
// Non-HMG specific validations
// Full Name is mandatory
if (_fullNameController.text.isEmpty) {
Fluttertoast.showToast(msg: "Full Name is required");
return;
}
// Email is mandatory and must be valid format
if (_emailController.text.isEmpty) {
Fluttertoast.showToast(msg: "Email Address is required");
return;
}
if (!Validator.isEmail(_emailController.text)) {
Fluttertoast.showToast(msg: "Please enter a valid email address");
return;
}
// Mobile Number is mandatory and must be numeric
if (_mobileController.text.isEmpty) {
Fluttertoast.showToast(msg: "Mobile Number is required");
return;
}
if (!Validator.isPhoneNumber(_mobileController.text)) {
Fluttertoast.showToast(msg: "Please enter a valid mobile number");
return;
}
// Site is mandatory for Non-HMG
if (_selectedSite == null) {
Fluttertoast.showToast(msg: "Site is required");
return;
}
// Department is mandatory for Non-HMG
if (_selectedDepartment == null) {
Fluttertoast.showToast(msg: "Department is required");
return;
}
}
// Common validations for both HMG and Non-HMG
// Extension Number validation (optional but must be numeric if provided)
if (_extensionController.text.isNotEmpty && !Validator.isNumeric(_extensionController.text)) {
Fluttertoast.showToast(msg: "Extension Number must be numeric");
return;
}
if (_isHMGGroup && !_employeeFetched) { // Role is mandatory
Fluttertoast.showToast(msg: "Please enter a valid Employee ID"); if (_roleController.text.isEmpty) {
Fluttertoast.showToast(msg: "Role is required");
return; return;
} }
log('hmg ${_isHMGGroup}');
if (!_isHMGGroup &&_selectedSite == null) { // Validate Asset Group is selected
Fluttertoast.showToast(msg: "Please select a site"); if (_selectedAssetGroup == null) {
Fluttertoast.showToast(msg: "Asset Group is required");
return; return;
} }
if (!_isHMGGroup&&_selectedDepartment == null) { // Validate Company is selected
Fluttertoast.showToast(msg: "Please select a department"); if (_selectedCompany == null) {
Fluttertoast.showToast(msg: "Company is required");
return; return;
} }
@ -481,10 +608,10 @@ class _SignUpScreenState extends State<SignUpScreen> {
emailAddress: _emailController.text, emailAddress: _emailController.text,
mobileNumber: _mobileController.text, mobileNumber: _mobileController.text,
extensionNumber: _extensionController.text.isEmpty ? null : _extensionController.text, extensionNumber: _extensionController.text.isEmpty ? null : _extensionController.text,
// For HMG: use ERP site/department, For non-HMG: use selected IDs // For HMG: send both selected IDs and ERP data
siteId: _isHMGGroup ? null : _selectedSite?.id, siteId: _selectedSite?.id,
erpSite: _isHMGGroup ? _employeeERPData?.branchCode : null, erpSite: _isHMGGroup ? _employeeERPData?.branchCode : null,
departmentId: _isHMGGroup ? null : _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,

@ -0,0 +1,16 @@
import 'package:test_sa/models/base.dart';
class SiteRequestSiteModel extends Base {
SiteRequestSiteModel({
required String id,
required String name,
}) : super(identifier: id, name: name);
}
class SiteRequestDepartmentModel extends Base {
SiteRequestDepartmentModel({
required String id,
required String name,
}) : super(identifier: id, name: name);
}

@ -0,0 +1,20 @@
class MockUserModel {
final String employeeId;
final String fullName;
final String emailAddress;
final String mobileNumber;
final String extensionNumber;
final String role;
final List<String> assignedSites;
MockUserModel({
required this.employeeId,
required this.fullName,
required this.emailAddress,
required this.mobileNumber,
required this.extensionNumber,
required this.role,
required this.assignedSites,
});
}

@ -0,0 +1,9 @@
import 'package:test_sa/models/base.dart';
class RequestTypeModel extends Base {
RequestTypeModel({
required String id,
required String name,
}) : super(identifier: id, name: name);
}

@ -0,0 +1,106 @@
class SiteRequestModel {
final String? id;
final String requestType; // Add Site, Change Site, Remove Site
final String employeeId;
final String fullName;
final String emailAddress;
final String mobileNumber;
final String? extensionNumber;
final List<String> siteIds;
final List<String> siteNames;
final List<String>? departmentIds;
final List<String>? departmentNames;
final String role;
final String status; // Pending, Approved, Rejected
final DateTime requestDate;
SiteRequestModel({
this.id,
required this.requestType,
required this.employeeId,
required this.fullName,
required this.emailAddress,
required this.mobileNumber,
this.extensionNumber,
required this.siteIds,
required this.siteNames,
this.departmentIds,
this.departmentNames,
required this.role,
required this.status,
required this.requestDate,
});
Map<String, dynamic> toJson() {
return {
'id': id,
'requestType': requestType,
'employeeId': employeeId,
'fullName': fullName,
'emailAddress': emailAddress,
'mobileNumber': mobileNumber,
'extensionNumber': extensionNumber,
'siteIds': siteIds,
'siteNames': siteNames,
'departmentIds': departmentIds,
'departmentNames': departmentNames,
'role': role,
'status': status,
'requestDate': requestDate.toIso8601String(),
};
}
factory SiteRequestModel.fromJson(Map<String, dynamic> json) {
return SiteRequestModel(
id: json['id'],
requestType: json['requestType'] ?? '',
employeeId: json['employeeId'] ?? '',
fullName: json['fullName'] ?? '',
emailAddress: json['emailAddress'] ?? '',
mobileNumber: json['mobileNumber'] ?? '',
extensionNumber: json['extensionNumber'],
siteIds: List<String>.from(json['siteIds'] ?? []),
siteNames: List<String>.from(json['siteNames'] ?? []),
departmentIds: json['departmentIds'] != null ? List<String>.from(json['departmentIds']) : null,
departmentNames: json['departmentNames'] != null ? List<String>.from(json['departmentNames']) : null,
role: json['role'] ?? '',
status: json['status'] ?? 'Pending',
requestDate: json['requestDate'] != null ? DateTime.parse(json['requestDate']) : DateTime.now(),
);
}
SiteRequestModel copyWith({
String? id,
String? requestType,
String? employeeId,
String? fullName,
String? emailAddress,
String? mobileNumber,
String? extensionNumber,
List<String>? siteIds,
List<String>? siteNames,
List<String>? departmentIds,
List<String>? departmentNames,
String? role,
String? status,
DateTime? requestDate,
}) {
return SiteRequestModel(
id: id ?? this.id,
requestType: requestType ?? this.requestType,
employeeId: employeeId ?? this.employeeId,
fullName: fullName ?? this.fullName,
emailAddress: emailAddress ?? this.emailAddress,
mobileNumber: mobileNumber ?? this.mobileNumber,
extensionNumber: extensionNumber ?? this.extensionNumber,
siteIds: siteIds ?? this.siteIds,
siteNames: siteNames ?? this.siteNames,
departmentIds: departmentIds ?? this.departmentIds,
departmentNames: departmentNames ?? this.departmentNames,
role: role ?? this.role,
status: status ?? this.status,
requestDate: requestDate ?? this.requestDate,
);
}
}

@ -0,0 +1,193 @@
import 'package:flutter/material.dart';
import 'package:test_sa/modules/site_request_module/models/dropdown_models.dart';
import 'package:test_sa/modules/site_request_module/models/mock_user_model.dart';
import 'package:test_sa/modules/site_request_module/models/request_type_model.dart';
import 'package:test_sa/modules/site_request_module/models/site_request_model.dart';
class SiteRequestProvider extends ChangeNotifier {
bool _loading = false;
bool get loading => _loading;
set loading(bool value) {
_loading = value;
notifyListeners();
}
String? _errorMessage;
String? get errorMessage => _errorMessage;
// Mock user data
late MockUserModel _currentUser;
MockUserModel get currentUser => _currentUser;
// Request types as Base models
final List<RequestTypeModel> _requestTypes = [
RequestTypeModel(id: 'add_site', name: 'Add Site'),
RequestTypeModel(id: 'change_site', name: 'Change Site'),
RequestTypeModel(id: 'remove_site', name: 'Remove Site'),
];
List<RequestTypeModel> get requestTypes => _requestTypes;
// Mock sites as Base models
final List<SiteRequestSiteModel> _allSites = [
SiteRequestSiteModel(id: 'site_1', name: 'Site A'),
SiteRequestSiteModel(id: 'site_2', name: 'Site B'),
SiteRequestSiteModel(id: 'site_3', name: 'Site C'),
SiteRequestSiteModel(id: 'site_4', name: 'Site D'),
SiteRequestSiteModel(id: 'site_5', name: 'Site E'),
];
List<SiteRequestSiteModel> get allSites => _allSites;
// Mock departments as Base models
final List<SiteRequestDepartmentModel> _allDepartments = [
SiteRequestDepartmentModel(id: 'dept_1', name: 'HR'),
SiteRequestDepartmentModel(id: 'dept_2', name: 'IT'),
SiteRequestDepartmentModel(id: 'dept_3', name: 'Finance'),
SiteRequestDepartmentModel(id: 'dept_4', name: 'Operations'),
SiteRequestDepartmentModel(id: 'dept_5', name: 'Administration'),
];
List<SiteRequestDepartmentModel> get allDepartments => _allDepartments;
// History of submitted requests
final List<SiteRequestModel> _requestHistory = [];
List<SiteRequestModel> get requestHistory => _requestHistory;
SiteRequestProvider() {
_initializeMockUser();
}
void _initializeMockUser() {
_currentUser = MockUserModel(
employeeId: 'EMP001',
fullName: 'John Smith',
emailAddress: 'john.smith@test.com',
mobileNumber: '+966500000000',
extensionNumber: '1234',
role: 'Engineer',
assignedSites: ['Site A', 'Site B'],
);
}
// Get available sites based on request type
List<SiteRequestSiteModel> getAvailableSites(String requestType) {
if (requestType == 'Remove Site') {
// Only return currently assigned sites
return _allSites.where((site) {
return _currentUser.assignedSites.contains(site.name ?? '');
}).toList();
}
// For Add Site and Change Site, return all sites
return _allSites;
}
// Check if user has assigned sites for removal
bool hasAssignedSites() {
return _currentUser.assignedSites.isNotEmpty;
}
// Submit site request (local only, no API)
Future<bool> submitSiteRequest(SiteRequestModel request) async {
loading = true;
_errorMessage = null;
notifyListeners();
// Simulate network delay
await Future.delayed(const Duration(seconds: 1));
try {
// Add request to history with a generated ID
final requestWithId = request.copyWith(
id: 'REQ_${DateTime.now().millisecondsSinceEpoch}',
status: 'Pending',
requestDate: DateTime.now(),
);
_requestHistory.insert(0, requestWithId);
loading = false;
notifyListeners();
return true;
} catch (error) {
_errorMessage = 'Failed to submit request';
loading = false;
notifyListeners();
return false;
}
}
// Get request by ID
SiteRequestModel? getRequestById(String id) {
try {
return _requestHistory.firstWhere((request) => request.id == id);
} catch (e) {
return null;
}
}
// Reset provider state
void reset() {
_loading = false;
_errorMessage = null;
_requestHistory.clear();
_initializeMockUser();
notifyListeners();
}
// Add mock history data for testing
void addMockHistoryData() {
final mockRequests = [
SiteRequestModel(
id: 'REQ_001',
requestType: 'Add Site',
employeeId: _currentUser.employeeId,
fullName: _currentUser.fullName,
emailAddress: _currentUser.emailAddress,
mobileNumber: _currentUser.mobileNumber,
extensionNumber: _currentUser.extensionNumber,
siteIds: ['site_3', 'site_4'],
siteNames: ['Site C', 'Site D'],
departmentIds: ['dept_2'],
departmentNames: ['IT'],
role: _currentUser.role,
status: 'Approved',
requestDate: DateTime.now().subtract(const Duration(days: 5)),
),
SiteRequestModel(
id: 'REQ_002',
requestType: 'Change Site',
employeeId: _currentUser.employeeId,
fullName: _currentUser.fullName,
emailAddress: _currentUser.emailAddress,
mobileNumber: _currentUser.mobileNumber,
extensionNumber: _currentUser.extensionNumber,
siteIds: ['site_1'],
siteNames: ['Site A'],
departmentIds: ['dept_1', 'dept_3'],
departmentNames: ['HR', 'Finance'],
role: _currentUser.role,
status: 'Pending',
requestDate: DateTime.now().subtract(const Duration(days: 2)),
),
SiteRequestModel(
id: 'REQ_003',
requestType: 'Remove Site',
employeeId: _currentUser.employeeId,
fullName: _currentUser.fullName,
emailAddress: _currentUser.emailAddress,
mobileNumber: _currentUser.mobileNumber,
extensionNumber: _currentUser.extensionNumber,
siteIds: ['site_2'],
siteNames: ['Site B'],
role: _currentUser.role,
status: 'Rejected',
requestDate: DateTime.now().subtract(const Duration(days: 10)),
),
];
_requestHistory.addAll(mockRequests);
notifyListeners();
}
}

@ -0,0 +1,278 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:provider/provider.dart';
import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/text_extensions.dart';
import 'package:test_sa/modules/site_request_module/models/dropdown_models.dart';
import 'package:test_sa/modules/site_request_module/models/request_type_model.dart';
import 'package:test_sa/modules/site_request_module/models/site_request_model.dart';
import 'package:test_sa/modules/site_request_module/providers/site_request_provider.dart';
import 'package:test_sa/modules/site_request_module/screens/site_request_history_screen.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/new_views/common_widgets/multiple_item_drop_down_menu.dart';
import 'package:test_sa/providers/loading_list_notifier.dart';
class SiteRequestFormScreen extends StatefulWidget {
static const String routeName = "/site_request_form";
const SiteRequestFormScreen({Key? key}) : super(key: key);
@override
State<SiteRequestFormScreen> createState() => _SiteRequestFormScreenState();
}
class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late SiteRequestProvider _provider;
RequestTypeModel? _selectedRequestType;
List<SiteRequestSiteModel> _selectedSites = [];
List<SiteRequestDepartmentModel> _selectedDepartments = [];
bool _isSubmitting = false;
@override
Widget build(BuildContext context) {
_provider = Provider.of<SiteRequestProvider>(context);
return Scaffold(
backgroundColor: AppColor.scaffoldBackground(context),
appBar: const DefaultAppBar(title: "Site Request"),
body: Column(
children: [
Expanded(
child: Form(
key: _formKey,
child: SingleChildScrollView(
padding: EdgeInsets.all(16.toScreenWidth),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildRequestTypeDropdown(),
16.height,
_buildReadOnlyField("Full Name", _provider.currentUser.fullName),
16.height,
_buildReadOnlyField("Email Address", _provider.currentUser.emailAddress),
16.height,
_buildReadOnlyField("Mobile Number", _provider.currentUser.mobileNumber),
16.height,
_buildReadOnlyField("Extension Number", _provider.currentUser.extensionNumber),
16.height,
if (_selectedRequestType != null) ...[
_buildSiteMultiSelect(),
16.height,
_buildDepartmentMultiSelect(),
16.height,
_buildReadOnlyField("Role", _provider.currentUser.role),
],
],
),
),
),
),
if (_selectedRequestType != null)
Container(
padding: EdgeInsets.all(16.toScreenWidth),
decoration: BoxDecoration(
color: AppColor.background(context),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, -2),
),
],
),
child: SafeArea(
child: _buildSubmitButton(),
),
),
],
),
);
}
Widget _buildRequestTypeDropdown() {
return SingleItemDropDownMenu<RequestTypeModel, NullableLoadingProvider>(
context: context,
title: "Type of Site Request",
initialValue: _selectedRequestType,
staticData: _provider.requestTypes,
showAsFullScreenDialog: false,
showAsBottomSheet: true,
onSelect: (value) {
setState(() {
_selectedRequestType = value;
_selectedSites.clear();
_selectedDepartments.clear();
});
},
validator: (value) {
if (value == null) return "Please select request type";
return null;
},
);
}
Widget _buildReadOnlyField(String label, String value) {
return AppTextFormField(
initialValue: value,
labelText: label,
enable: false,
backgroundColor: AppColor.disableFieldBackground(context),
);
}
Widget _buildSiteMultiSelect() {
final availableSites = _provider.getAvailableSites(_selectedRequestType!.name!);
// Check if user has assigned sites for Remove Site request
if (_selectedRequestType!.name == 'Remove Site' && !_provider.hasAssignedSites()) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.all(16.toScreenWidth),
decoration: BoxDecoration(
color: AppColor.disableFieldBackground(context),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: AppColor.border2Color(context),
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Site *".bodyText(context).custom(
color: AppColor.floatingTextStyleColor(context),
fontWeight: FontWeight.w500,
fontSize: 12,
),
4.height,
"No assigned sites available for removal".bodyText(context).custom(
color: AppColor.labelTextStyleColor(context),
),
],
),
),
],
);
}
return MultipleItemDropDownMenu<SiteRequestSiteModel, NullableLoadingProvider>(
context: context,
title: "Site",
initialValue: _selectedSites,
staticData: availableSites,
showAsFullScreenDialog: true,
showAsBottomSheet: false,
onSelect: (value) {
setState(() {
_selectedSites = value ?? [];
});
},
);
}
Widget _buildDepartmentMultiSelect() {
return MultipleItemDropDownMenu<SiteRequestDepartmentModel, NullableLoadingProvider>(
context: context,
title: "Department (Optional)",
initialValue: _selectedDepartments,
staticData: _provider.allDepartments,
showAsFullScreenDialog: true,
showAsBottomSheet: false,
onSelect: (value) {
setState(() {
_selectedDepartments = value ?? [];
});
},
);
}
Widget _buildSubmitButton() {
// Disable button if Remove Site is selected and no assigned sites
final isDisabled = _selectedRequestType?.name == 'Remove Site' && !_provider.hasAssignedSites();
return AppFilledButton(
label: "Submit",
maxWidth: true,
loading: _isSubmitting,
disableButton: _isSubmitting || isDisabled,
onPressed: isDisabled ? null : _submitForm,
);
}
Future<void> _submitForm() async {
if (!_formKey.currentState!.validate()) {
Fluttertoast.showToast(msg: "Please fill all required fields");
return;
}
if (_selectedRequestType == null) {
Fluttertoast.showToast(msg: "Please select request type");
return;
}
if (_selectedSites.isEmpty) {
Fluttertoast.showToast(msg: "Please select at least one site");
return;
}
setState(() {
_isSubmitting = true;
});
if (mounted) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const AppLazyLoading(),
);
}
final request = SiteRequestModel(
requestType: _selectedRequestType!.name ?? '',
employeeId: _provider.currentUser.employeeId,
fullName: _provider.currentUser.fullName,
emailAddress: _provider.currentUser.emailAddress,
mobileNumber: _provider.currentUser.mobileNumber,
extensionNumber: _provider.currentUser.extensionNumber,
siteIds: _selectedSites.map((s) => s.identifier ?? '').toList(),
siteNames: _selectedSites.map((s) => s.name ?? '').toList(),
departmentIds: _selectedDepartments.isNotEmpty ? _selectedDepartments.map((d) => d.identifier ?? '').toList() : null,
departmentNames: _selectedDepartments.isNotEmpty ? _selectedDepartments.map((d) => d.name ?? '').toList() : null,
role: _provider.currentUser.role,
status: 'Pending',
requestDate: DateTime.now(),
);
final success = await _provider.submitSiteRequest(request);
if (mounted) {
Navigator.pop(context); // Close loading dialog
}
setState(() {
_isSubmitting = false;
});
if (success) {
Fluttertoast.showToast(msg: "Site request submitted successfully");
if (mounted) {
Navigator.pushReplacementNamed(context, SiteRequestHistoryScreen.routeName);
}
} else {
Fluttertoast.showToast(
msg: _provider.errorMessage ?? "Failed to submit site request",
);
}
}
}

@ -0,0 +1,199 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/text_extensions.dart';
import 'package:test_sa/modules/site_request_module/models/site_request_model.dart';
import 'package:test_sa/modules/site_request_module/providers/site_request_provider.dart';
import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
class SiteRequestHistoryScreen extends StatefulWidget {
static const String routeName = "/site_request_history";
const SiteRequestHistoryScreen({Key? key}) : super(key: key);
@override
State<SiteRequestHistoryScreen> createState() => _SiteRequestHistoryScreenState();
}
class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
late SiteRequestProvider _provider;
@override
void initState() {
super.initState();
// Add mock history data for demonstration purposes
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_provider.requestHistory.isEmpty) {
_provider.addMockHistoryData();
}
});
}
@override
Widget build(BuildContext context) {
_provider = Provider.of<SiteRequestProvider>(context);
return Scaffold(
backgroundColor: AppColor.scaffoldBackground(context),
appBar: const DefaultAppBar(title: "Site Request History"),
body: _provider.requestHistory.isEmpty
? _buildEmptyState()
: ListView.builder(
padding: EdgeInsets.all(16.toScreenWidth),
itemCount: _provider.requestHistory.length,
itemBuilder: (context, index) {
final request = _provider.requestHistory[index];
return _buildRequestCard(request);
},
),
);
}
Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.inbox_outlined,
size: 80,
color: AppColor.icon2Color(context).withOpacity(0.5),
),
16.height,
"No site requests found".heading5(context).custom(
color: AppColor.labelTextStyleColor(context),
),
8.height,
"Your submitted requests will appear here".bodyText(context).custom(
color: AppColor.lightTextColor(context),
),
],
),
);
}
Widget _buildRequestCard(SiteRequestModel request) {
return Card(
margin: EdgeInsets.only(bottom: 16.toScreenHeight),
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
color: AppColor.background(context),
child: Padding(
padding: EdgeInsets.all(16.toScreenWidth),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: request.requestType.heading6(context).custom(
color: AppColor.labelTextStyleColor(context),
fontWeight: FontWeight.bold,
),
),
_buildStatusBadge(request.status),
],
),
12.height,
_buildInfoRow("Request ID", request.id ?? "N/A"),
8.height,
_buildInfoRow("Employee ID", request.employeeId),
8.height,
_buildInfoRow("Full Name", request.fullName),
8.height,
_buildInfoRow("Email", request.emailAddress),
8.height,
_buildInfoRow("Mobile", request.mobileNumber),
8.height,
if (request.extensionNumber != null && request.extensionNumber!.isNotEmpty)
_buildInfoRow("Extension", request.extensionNumber!),
8.height,
_buildInfoRow("Role", request.role),
8.height,
_buildInfoRow(
"Site(s)",
request.siteNames.join(", "),
),
if (request.departmentNames != null && request.departmentNames!.isNotEmpty) ...[
8.height,
_buildInfoRow(
"Department(s)",
request.departmentNames!.join(", "),
),
],
8.height,
_buildInfoRow(
"Request Date",
DateFormat('dd MMM yyyy, hh:mm a').format(request.requestDate),
),
],
),
),
);
}
Widget _buildInfoRow(String label, String value) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 120.toScreenWidth,
child: "$label:".bodyText(context).custom(
color: AppColor.lightTextColor(context),
fontSize: 13,
),
),
Expanded(
child: value.bodyText(context).custom(
color: AppColor.labelTextStyleColor(context),
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
],
);
}
Widget _buildStatusBadge(String status) {
Color backgroundColor;
Color textColor;
switch (status.toLowerCase()) {
case 'approved':
backgroundColor = Colors.green.withOpacity(0.1);
textColor = Colors.green;
break;
case 'rejected':
backgroundColor = Colors.red.withOpacity(0.1);
textColor = Colors.red;
break;
case 'pending':
default:
backgroundColor = Colors.orange.withOpacity(0.1);
textColor = Colors.orange;
break;
}
return Container(
padding: EdgeInsets.symmetric(
horizontal: 12.toScreenWidth,
vertical: 6.toScreenHeight,
),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(20),
),
child: status.bodyText(context).custom(
color: textColor,
fontSize: 12,
fontWeight: FontWeight.w600,
),
);
}
}

@ -4,6 +4,7 @@ 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/text_extensions.dart';
import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart';
import 'package:test_sa/modules/site_request_module/screens/site_request_form_screen.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/pages/login_page.dart'; import 'package:test_sa/new_views/pages/login_page.dart';
import 'package:test_sa/new_views/swipe_module/swipe_history_view.dart'; import 'package:test_sa/new_views/swipe_module/swipe_history_view.dart';
@ -70,6 +71,8 @@ class AppDrawer extends StatelessWidget {
// drawerItem("rate_us", context.translation.rateUs, context), // drawerItem("rate_us", context.translation.rateUs, context),
// 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)),
drawerItem("setting", "Site Request", context).onPress(() => Navigator.of(context).pushNamed(SiteRequestFormScreen.routeName)),
18.height,
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)),

@ -26,6 +26,7 @@ abstract class LoadingListNotifier<T extends Base> extends ChangeNotifier {
items = []; items = [];
_loading = false; _loading = false;
stateCode = null; stateCode = null;
notifyListeners();
} }
Future getData({int? id}); Future getData({int? id});

Loading…
Cancel
Save