added the site and department dropdown

export_module_ui_ux_enhancement_signup
WaseemAbbasi22 2 weeks ago
parent d9eb1ae835
commit 7fc19d9058

@ -60,6 +60,12 @@ class URLs {
static get signUpGetSiteList => "$_baseUrl/Customer/GetSiteList";
static get signUpGetDepartmentAutoComplete => "$_baseUrl/Department/GetDepartmentAutoCompleteByAssetGroup";
// Site Request APIs
static get getSiteRequestTypes => "$_baseUrl/Lookups/GetLookup?lookupEnum=4016"; // get site request types
static get submitSiteRequest => "$_baseUrl/SiteRequest/CreateSiteRequest"; // post submit site request
static get getSiteRequestHistory => "$_baseUrl/SiteRequest/GetSiteRequestHistory"; // get site request history
static get getDepartmentsBySites => "$_baseUrl/Customer/GetDepartmentsBySites"; // post get departments by sites
// static get login => "$_baseUrl/MobileAuth/LoginIntegration"; // mobile login
static get register => "$_baseUrl/handle/create/user"; // post
static get updateProfile => "$_baseUrl/update/user/profile"; // post

@ -82,6 +82,7 @@ import 'package:test_sa/new_views/swipe_module/swipe_success_view.dart';
import 'package:test_sa/new_views/swipe_module/swipe_view.dart';
import 'package:test_sa/providers/asset_transfer/asset_transfer_status_provider.dart';
import 'package:test_sa/providers/department_provider.dart';
import 'package:test_sa/providers/department_by_sites_provider.dart';
import 'package:test_sa/providers/gas_request_providers/cylinder_size_provider.dart';
import 'package:test_sa/providers/gas_request_providers/cylinder_type_provider.dart';
import 'package:test_sa/providers/gas_request_providers/gas_status_provider.dart';
@ -341,6 +342,7 @@ class MyApp extends StatelessWidget {
// DEPARTMENT/LOCATION PROVIDERS (5)
ChangeNotifierProvider(create: (_) => DepartmentProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => DepartmentBySitesProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => SiteProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => MappedSiteProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => MedicalDepartmentProvider(), lazy: true),

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

@ -1,8 +1,13 @@
import 'package:flutter/material.dart';
import 'package:test_sa/controllers/api_routes/api_manager.dart';
import 'package:test_sa/controllers/api_routes/urls.dart';
import 'package:test_sa/controllers/providers/api/user_provider.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';
import 'package:test_sa/models/lookup.dart';
import 'dart:convert';
import 'package:http/http.dart';
class SiteRequestProvider extends ChangeNotifier {
bool _loading = false;
@ -16,67 +21,212 @@ class SiteRequestProvider extends ChangeNotifier {
String? _errorMessage;
String? get errorMessage => _errorMessage;
// Mock user data
late MockUserModel _currentUser;
MockUserModel get currentUser => _currentUser;
int? stateCode;
// 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'),
];
// Get current user from UserProvider
String getCurrentUserEmployeeId(UserProvider userProvider) {
return userProvider.user?.employeeId ?? '';
}
String getCurrentUserFullName(UserProvider userProvider) {
return userProvider.user?.username ?? '';
}
String getCurrentUserEmail(UserProvider userProvider) {
return userProvider.user?.email ?? '';
}
String getCurrentUserMobile(UserProvider userProvider) {
return userProvider.user?.phoneNumber ?? '';
}
String getCurrentUserExtension(UserProvider userProvider) {
return userProvider.user?.extensionNo ?? '';
}
String getCurrentUserRole(UserProvider userProvider) {
// Get the first role if available
if (userProvider.user?.userRoles != null && userProvider.user!.userRoles!.isNotEmpty) {
return userProvider.user!.userRoles!.first.name ?? '';
}
return '';
}
List<String> getCurrentUserAssignedSites(UserProvider userProvider) {
// You can modify this based on how assigned sites are stored in your User model
// For now, returning empty list as it's not clear from the User model
// You may need to fetch this from another source or API
return [];
}
// Request types from API
List<RequestTypeModel> _requestTypes = [];
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'),
];
// Fetch request types from API (Step 1)
Future<int> fetchRequestTypes() async {
if (_loading) return -2;
_loading = true;
_errorMessage = null;
notifyListeners();
late Response response;
try {
response = await ApiManager.instance.get(
URLs.getSiteRequestTypes,
enableToastMessage: false,
);
} catch (error) {
_loading = false;
stateCode = -1;
_errorMessage = 'Failed to fetch request types';
notifyListeners();
return -1;
}
stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
try {
var responseData = json.decode(response.body);
List listJson = responseData['data'] ?? responseData;
_requestTypes = listJson
.map((item) => Lookup.fromJson(item))
.map((lookup) => RequestTypeModel(
id: lookup.id?.toString() ?? lookup.value?.toString() ?? '',
name: lookup.name ?? '',
value: lookup.value,
))
.toList();
} catch (e) {
_errorMessage = 'Failed to parse request types: $e';
_loading = false;
notifyListeners();
return -1;
}
} else {
_errorMessage = 'Failed to fetch request types';
}
_loading = false;
notifyListeners();
return response.statusCode;
}
// Mock sites as Base models
List<SiteRequestSiteModel> _allSites = [];
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'),
];
// Fetch sites from API (Step 2)
Future<int> fetchSites() async {
if (_loading) return -2;
List<SiteRequestDepartmentModel> get allDepartments => _allDepartments;
_loading = true;
_errorMessage = null;
notifyListeners();
// History of submitted requests
final List<SiteRequestModel> _requestHistory = [];
List<SiteRequestModel> get requestHistory => _requestHistory;
late Response response;
try {
response = await ApiManager.instance.get(
URLs.signUpGetSiteList,
enableToastMessage: false,
);
} catch (error) {
_loading = false;
stateCode = -1;
_errorMessage = 'Failed to fetch sites';
notifyListeners();
return -1;
}
SiteRequestProvider() {
_initializeMockUser();
stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
try {
var responseData = json.decode(response.body);
List listJson = responseData is List ? responseData : responseData['data'] ?? [];
_allSites = listJson
.map((item) => SiteRequestSiteModel(
id: item['id']?.toString() ?? '',
name: item['name']?.toString() ?? '',
))
.toList();
} catch (e) {
_errorMessage = 'Failed to parse sites';
_loading = false;
notifyListeners();
return -1;
}
} else {
_errorMessage = 'Failed to fetch sites';
}
_loading = false;
notifyListeners();
return response.statusCode;
}
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'],
);
// Mock departments as Base models
List<SiteRequestDepartmentModel> _allDepartments = [];
List<SiteRequestDepartmentModel> get allDepartments => _allDepartments;
// Fetch departments from API (Step 3)
Future<int> fetchDepartments() async {
if (_loading) return -2;
_loading = true;
_errorMessage = null;
notifyListeners();
late Response response;
try {
response = await ApiManager.instance.get(
URLs.signUpGetDepartmentAutoComplete,
enableToastMessage: false,
);
} catch (error) {
_loading = false;
stateCode = -1;
_errorMessage = 'Failed to fetch departments';
notifyListeners();
return -1;
}
stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
try {
var responseData = json.decode(response.body);
List listJson = responseData is List ? responseData : responseData['data'] ?? [];
_allDepartments = listJson
.map((item) => SiteRequestDepartmentModel(
id: item['id']?.toString() ?? '',
name: item['name']?.toString() ?? '',
))
.toList();
} catch (e) {
_errorMessage = 'Failed to parse departments';
_loading = false;
notifyListeners();
return -1;
}
} else {
_errorMessage = 'Failed to fetch departments';
}
_loading = false;
notifyListeners();
return response.statusCode;
}
// History of submitted requests
final List<SiteRequestModel> _requestHistory = [];
List<SiteRequestModel> get requestHistory => _requestHistory;
// Get available sites based on request type
List<SiteRequestSiteModel> getAvailableSites(String requestType) {
List<SiteRequestSiteModel> getAvailableSites(String requestType, List<String> assignedSites) {
if (requestType == 'Remove Site') {
// Only return currently assigned sites
return _allSites.where((site) {
return _currentUser.assignedSites.contains(site.name ?? '');
return assignedSites.contains(site.name ?? '');
}).toList();
}
// For Add Site and Change Site, return all sites
@ -84,47 +234,140 @@ class SiteRequestProvider extends ChangeNotifier {
}
// Check if user has assigned sites for removal
bool hasAssignedSites() {
return _currentUser.assignedSites.isNotEmpty;
bool hasAssignedSites(List<String> assignedSites) {
return assignedSites.isNotEmpty;
}
// Submit site request (local only, no API)
Future<bool> submitSiteRequest(SiteRequestModel request) async {
Future<bool> submitSiteRequest(SiteRequestModel request, UserProvider userProvider) async {
loading = true;
_errorMessage = null;
notifyListeners();
// Simulate network delay
await Future.delayed(const Duration(seconds: 1));
late Response response;
try {
// Add request to history with a generated ID
final requestWithId = request.copyWith(
id: 'REQ_${DateTime.now().millisecondsSinceEpoch}',
status: 'Pending',
requestDate: DateTime.now(),
);
// Prepare site IDs array with correct structure
final siteIdsArray = request.siteIds.asMap().entries.map((entry) {
return {
"id": 0,
"siteId": int.tryParse(request.siteIds[entry.key]) ?? 0,
"siteName": request.siteNames[entry.key],
"oldSiteId": 0,
"oldSitName": ""
};
}).toList();
_requestHistory.insert(0, requestWithId);
// Prepare department IDs array with correct structure
final departmentIdsArray = (request.departmentIds != null && request.departmentIds!.isNotEmpty)
? request.departmentIds!.asMap().entries.map((entry) {
return {
"id": 0,
"departmentId": int.tryParse(request.departmentIds![entry.key]) ?? 0,
"departmentName": request.departmentNames![entry.key],
"oldDepartmentId": 0,
"oldDepartmentName": ""
};
}).toList()
: [];
// Prepare the request body matching the API specification
final requestBody = {
"id": 0,
"userId": getCurrentUserEmployeeId(userProvider),
"requestTypeId": int.tryParse(request.requestType) ?? 0,
"siteIds": siteIdsArray,
"departmentIds": departmentIdsArray,
};
response = await ApiManager.instance.post(
URLs.submitSiteRequest,
body: requestBody,
);
} catch (error) {
_errorMessage = 'Failed to submit request: $error';
loading = false;
notifyListeners();
return true;
} catch (error) {
_errorMessage = 'Failed to submit request';
return false;
}
stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
try {
var responseData = json.decode(response.body);
// Add request to history with server response
final requestWithId = request.copyWith(
id: responseData['id']?.toString() ?? 'REQ_${DateTime.now().millisecondsSinceEpoch}',
status: responseData['status']?.toString() ?? 'Pending',
requestDate: DateTime.now(),
);
_requestHistory.insert(0, requestWithId);
loading = false;
notifyListeners();
return true;
} catch (e) {
_errorMessage = 'Request submitted but failed to parse response';
loading = false;
notifyListeners();
return true; // Still return true as request was submitted
}
} else {
try {
var errorData = json.decode(response.body);
_errorMessage = errorData['message'] ?? 'Failed to submit request';
} catch (e) {
_errorMessage = 'Failed to submit request';
}
loading = false;
notifyListeners();
return false;
}
}
// Get request by ID
SiteRequestModel? getRequestById(String id) {
// Fetch request history from API (Step 5)
Future<int> fetchRequestHistory() async {
if (_loading) return -2;
_loading = true;
_errorMessage = null;
notifyListeners();
late Response response;
try {
return _requestHistory.firstWhere((request) => request.id == id);
} catch (e) {
return null;
response = await ApiManager.instance.get(
URLs.getSiteRequestHistory,
enableToastMessage: false,
);
} catch (error) {
_loading = false;
stateCode = -1;
_errorMessage = 'Failed to fetch request history';
notifyListeners();
return -1;
}
stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
try {
var responseData = json.decode(response.body);
List listJson = responseData is List ? responseData : responseData['data'] ?? [];
_requestHistory.clear();
_requestHistory.addAll(
listJson.map((item) => SiteRequestModel.fromJson(item)).toList()
);
} catch (e) {
_errorMessage = 'Failed to parse request history';
_loading = false;
notifyListeners();
return -1;
}
} else {
_errorMessage = 'Failed to fetch request history';
}
_loading = false;
notifyListeners();
return response.statusCode;
}
// Reset provider state
@ -132,56 +375,55 @@ class SiteRequestProvider extends ChangeNotifier {
_loading = false;
_errorMessage = null;
_requestHistory.clear();
_initializeMockUser();
notifyListeners();
}
// Add mock history data for testing
void addMockHistoryData() {
void addMockHistoryData(UserProvider userProvider) {
final mockRequests = [
SiteRequestModel(
id: 'REQ_001',
requestType: 'Add Site',
employeeId: _currentUser.employeeId,
fullName: _currentUser.fullName,
emailAddress: _currentUser.emailAddress,
mobileNumber: _currentUser.mobileNumber,
extensionNumber: _currentUser.extensionNumber,
employeeId: getCurrentUserEmployeeId(userProvider),
fullName: getCurrentUserFullName(userProvider),
emailAddress: getCurrentUserEmail(userProvider),
mobileNumber: getCurrentUserMobile(userProvider),
extensionNumber: getCurrentUserExtension(userProvider),
siteIds: ['site_3', 'site_4'],
siteNames: ['Site C', 'Site D'],
departmentIds: ['dept_2'],
departmentNames: ['IT'],
role: _currentUser.role,
role: getCurrentUserRole(userProvider),
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,
employeeId: getCurrentUserEmployeeId(userProvider),
fullName: getCurrentUserFullName(userProvider),
emailAddress: getCurrentUserEmail(userProvider),
mobileNumber: getCurrentUserMobile(userProvider),
extensionNumber: getCurrentUserExtension(userProvider),
siteIds: ['site_1'],
siteNames: ['Site A'],
departmentIds: ['dept_1', 'dept_3'],
departmentNames: ['HR', 'Finance'],
role: _currentUser.role,
role: getCurrentUserRole(userProvider),
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,
employeeId: getCurrentUserEmployeeId(userProvider),
fullName: getCurrentUserFullName(userProvider),
emailAddress: getCurrentUserEmail(userProvider),
mobileNumber: getCurrentUserMobile(userProvider),
extensionNumber: getCurrentUserExtension(userProvider),
siteIds: ['site_2'],
siteNames: ['Site B'],
role: _currentUser.role,
role: getCurrentUserRole(userProvider),
status: 'Rejected',
requestDate: DateTime.now().subtract(const Duration(days: 10)),
),

@ -1,9 +1,14 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:provider/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/text_extensions.dart';
import 'package:test_sa/modules/site_request_module/models/dropdown_models.dart';
import 'package:test_sa/extensions/context_extension.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/department.dart';
import 'package:test_sa/providers/gas_request_providers/site_provider.dart';
import 'package:test_sa/providers/department_by_sites_provider.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';
@ -16,6 +21,8 @@ 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';
import 'package:test_sa/views/widgets/item_views/info_header_widget.dart';
import 'package:test_sa/views/widgets/item_views/info_text_widget.dart';
class SiteRequestFormScreen extends StatefulWidget {
static const String routeName = "/site_request_form";
@ -30,16 +37,52 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late SiteRequestProvider _provider;
late UserProvider _userProvider;
RequestTypeModel? _selectedRequestType;
List<SiteRequestSiteModel> _selectedSites = [];
List<SiteRequestDepartmentModel> _selectedDepartments = [];
List<Site> _selectedSites = [];
List<Department> _selectedDepartments = [];
bool _isSubmitting = false;
bool _isLoadingData = true;
@override
void initState() {
super.initState();
_loadInitialData();
}
Future<void> _loadInitialData() async {
// Load data after first frame to access context
WidgetsBinding.instance.addPostFrameCallback((_) async {
_provider = Provider.of<SiteRequestProvider>(context, listen: false);
// Fetch only request types (sites and departments are loaded by their providers)
await _provider.fetchRequestTypes();
if (mounted) {
setState(() {
_isLoadingData = false;
});
}
});
}
@override
Widget build(BuildContext context) {
_provider = Provider.of<SiteRequestProvider>(context);
_userProvider = Provider.of<UserProvider>(context);
// Show loading indicator while fetching initial data
if (_isLoadingData) {
return Scaffold(
backgroundColor: AppColor.scaffoldBackground(context),
appBar: const DefaultAppBar(title: "Site Request"),
body: const Center(
child: AppLazyLoading(),
),
);
}
return Scaffold(
backgroundColor: AppColor.scaffoldBackground(context),
@ -54,22 +97,14 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildRequestTypeDropdown(),
_buildUserInfoCard(),
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),
_buildRequestTypeDropdown(),
16.height,
if (_selectedRequestType != null) ...[
_buildSiteMultiSelect(),
16.height,
_buildDepartmentMultiSelect(),
16.height,
_buildReadOnlyField("Role", _provider.currentUser.role),
],
],
),
@ -83,7 +118,7 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
color: AppColor.background(context),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, -2),
),
@ -120,86 +155,103 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
);
}
Widget _buildReadOnlyField(String label, String value) {
return AppTextFormField(
initialValue: value,
labelText: label,
enable: false,
backgroundColor: AppColor.disableFieldBackground(context),
);
Widget _buildUserInfoCard() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoHeader16Widget("Requester Information"),
8.height,
InfoTextWidget(label: "Full Name",value: _provider.getCurrentUserFullName(_userProvider),showEmptyValue: true,),
InfoTextWidget(label: "Email Address",value: _provider.getCurrentUserEmail(_userProvider),showEmptyValue: true,),
InfoTextWidget(label: "Mobile Number",value: _provider.getCurrentUserMobile(_userProvider),showEmptyValue: true,),
InfoTextWidget(label: "Extension Number", value: _provider.getCurrentUserExtension(_userProvider),showEmptyValue: true,),
InfoTextWidget(label: "Role",value: _provider.getCurrentUserRole(_userProvider),showEmptyValue: true,),
],
).toShadowContainer(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(
Widget _buildInfoRow(String label, String value, {bool isLast = false}) {
return Padding(
padding: EdgeInsets.only(bottom: isLast ? 0 : 12.toScreenHeight),
child: Row(
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,
SizedBox(
width: 130.toScreenWidth,
child: Text(
"$label:",
style: TextStyle(
fontSize: 14,
color: AppColor.labelTextStyleColor(context),
fontWeight: FontWeight.w500,
),
),
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),
),
],
),
Expanded(
child: Text(
value.isEmpty ? "-" : value,
style: TextStyle(
fontSize: 14,
color: AppColor.textStyleColor(context),
),
),
),
],
);
}
),
);
}
return MultipleItemDropDownMenu<SiteRequestSiteModel, NullableLoadingProvider>(
Widget _buildSiteMultiSelect() {
return MultipleItemDropDownMenu<Site, SiteProvider>(
context: context,
title: "Site",
title: context.translation.bySite,
initialValue: _selectedSites,
staticData: availableSites,
showAsFullScreenDialog: true,
showAsBottomSheet: false,
showAsBottomSheet: true,
backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white,
showShadow: true,
height: 56.toScreenHeight,
showCancel: true,
onSelect: (value) {
setState(() {
_selectedSites = value ?? [];
});
if (value != null) {
setState(() {
_selectedSites = value;
_selectedDepartments.clear(); // Clear departments when sites change
// Update department provider with new site IDs
final departmentProvider = Provider.of<DepartmentBySitesProvider>(context, listen: false);
final siteIds = value.map((site) => int.tryParse(site.identifier ?? '0') ?? 0).toList();
departmentProvider.setSiteIds(siteIds);
departmentProvider.getData();
});
}
},
);
}
Widget _buildDepartmentMultiSelect() {
return MultipleItemDropDownMenu<SiteRequestDepartmentModel, NullableLoadingProvider>(
return MultipleItemDropDownMenu<Department, DepartmentBySitesProvider>(
context: context,
title: "Department (Optional)",
title: "Departments",
initialValue: _selectedDepartments,
staticData: _provider.allDepartments,
showAsFullScreenDialog: true,
showAsBottomSheet: false,
showAsBottomSheet: true,
backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white,
showShadow: true,
height: 56.toScreenHeight,
showCancel: true,
onSelect: (value) {
setState(() {
_selectedDepartments = value ?? [];
});
if (value != null) {
setState(() {
_selectedDepartments = value;
});
}
},
);
}
Widget _buildSubmitButton() {
// Disable button if Remove Site is selected and no assigned sites
final isDisabled = _selectedRequestType?.name == 'Remove Site' && !_provider.hasAssignedSites();
final assignedSites = _provider.getCurrentUserAssignedSites(_userProvider);
final isDisabled = _selectedRequestType?.name == 'Remove Site' && !_provider.hasAssignedSites(assignedSites);
return AppFilledButton(
label: "Submit",
@ -239,22 +291,22 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
}
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,
requestType: _selectedRequestType!.value?.toString() ?? '', // Use value instead of id
employeeId: _provider.getCurrentUserEmployeeId(_userProvider),
fullName: _provider.getCurrentUserFullName(_userProvider),
emailAddress: _provider.getCurrentUserEmail(_userProvider),
mobileNumber: _provider.getCurrentUserMobile(_userProvider),
extensionNumber: _provider.getCurrentUserExtension(_userProvider),
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,
role: _provider.getCurrentUserRole(_userProvider),
status: 'Pending',
requestDate: DateTime.now(),
);
final success = await _provider.submitSiteRequest(request);
final success = await _provider.submitSiteRequest(request, _userProvider);
if (mounted) {
Navigator.pop(context); // Close loading dialog

@ -1,10 +1,13 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/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/text_extensions.dart';
import 'package:test_sa/extensions/widget_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/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/common_widgets/default_app_bar.dart';
@ -19,14 +22,27 @@ class SiteRequestHistoryScreen extends StatefulWidget {
class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
late SiteRequestProvider _provider;
late UserProvider _userProvider;
bool _isLoadingData = true;
@override
void initState() {
super.initState();
// Add mock history data for demonstration purposes
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_provider.requestHistory.isEmpty) {
_provider.addMockHistoryData();
_loadHistoryData();
}
Future<void> _loadHistoryData() async {
WidgetsBinding.instance.addPostFrameCallback((_) async {
_provider = Provider.of<SiteRequestProvider>(context, listen: false);
_userProvider = Provider.of<UserProvider>(context, listen: false);
// Fetch request history from API
await _provider.fetchRequestHistory();
if (mounted) {
setState(() {
_isLoadingData = false;
});
}
});
}
@ -34,20 +50,53 @@ class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
@override
Widget build(BuildContext context) {
_provider = Provider.of<SiteRequestProvider>(context);
_userProvider = Provider.of<UserProvider>(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);
},
appBar: DefaultAppBar(
title: "Site Request History",
actions: [
Container(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(
color: AppColor.background(context),
border: Border.all(color: AppColor.border2Color(context)),
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.add, size: 20, color: AppColor.textStyleColor(context)),
Text(
"New Request",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColor.textStyleColor(context),
),
),
4.width,
],
),
).onPress(() {
Navigator.pushNamed(context, SiteRequestFormScreen.routeName);
}),
],
),
body: _isLoadingData
? const Center(child: CircularProgressIndicator())
: _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);
},
),
);
}
@ -59,7 +108,7 @@ class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
Icon(
Icons.inbox_outlined,
size: 80,
color: AppColor.icon2Color(context).withOpacity(0.5),
color: AppColor.icon2Color(context).withValues(alpha: 0.5),
),
16.height,
"No site requests found".heading5(context).custom(
@ -75,125 +124,254 @@ class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
}
Widget _buildRequestCard(SiteRequestModel request) {
return Card(
return Container(
margin: EdgeInsets.only(bottom: 16.toScreenHeight),
elevation: 2,
shape: RoundedRectangleBorder(
decoration: BoxDecoration(
color: AppColor.background(context),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
color: AppColor.background(context),
child: Padding(
padding: EdgeInsets.all(16.toScreenWidth),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Status and Date Row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: request.requestType.heading6(context).custom(
color: AppColor.labelTextStyleColor(context),
fontWeight: FontWeight.bold,
child: Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildStatusLabel(request.status),
_buildRequestTypeLabel(request.requestType),
],
),
),
_buildStatusBadge(request.status),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
DateFormat('dd MMM yyyy').format(request.requestDate),
style: TextStyle(
fontSize: 12,
color: AppColor.labelTextStyleColor(context),
),
),
Text(
DateFormat('hh:mm a').format(request.requestDate),
style: TextStyle(
fontSize: 10,
color: AppColor.lightTextColor(context),
),
),
],
),
],
),
16.height,
// Request ID
if (request.id != null) ...[
_buildInfoRow("Request ID", request.id!),
12.height,
],
Divider(color: AppColor.border2Color(context)),
12.height,
// Requester Information Section
"Requester Information".heading6(context).custom(
color: AppColor.textStyleColor(context),
fontWeight: FontWeight.w600,
),
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("Employee ID", request.employeeId),
_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(", "),
16.height,
Divider(color: AppColor.border2Color(context)),
12.height,
// Site Information Section
"Site Information".heading6(context).custom(
color: AppColor.textStyleColor(context),
fontWeight: FontWeight.w600,
),
12.height,
_buildInfoRow("Request Type", request.requestType),
12.height,
// Sites List
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Sites:",
style: TextStyle(
fontSize: 14,
color: AppColor.labelTextStyleColor(context),
fontWeight: FontWeight.w500,
),
),
8.width,
Expanded(
child: Wrap(
spacing: 6,
runSpacing: 6,
children: request.siteNames.map((site) => _buildChip(site)).toList(),
),
),
],
),
// Departments List (if available)
if (request.departmentNames != null && request.departmentNames!.isNotEmpty) ...[
8.height,
_buildInfoRow(
"Department(s)",
request.departmentNames!.join(", "),
12.height,
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Departments:",
style: TextStyle(
fontSize: 14,
color: AppColor.labelTextStyleColor(context),
fontWeight: FontWeight.w500,
),
),
8.width,
Expanded(
child: Wrap(
spacing: 6,
runSpacing: 6,
children: request.departmentNames!.map((dept) => _buildChip(dept)).toList(),
),
),
],
),
],
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) {
Widget _buildStatusLabel(String status) {
Color backgroundColor;
Color textColor;
switch (status.toLowerCase()) {
case 'approved':
backgroundColor = Colors.green.withOpacity(0.1);
textColor = Colors.green;
backgroundColor = AppColor.greenStatusColor;
textColor = AppColor.greenStatusTextColor;
break;
case 'rejected':
backgroundColor = Colors.red.withOpacity(0.1);
textColor = Colors.red;
backgroundColor = AppColor.redStatusColor;
textColor = AppColor.redStatusTextColor;
break;
case 'pending':
default:
backgroundColor = Colors.orange.withOpacity(0.1);
textColor = Colors.orange;
backgroundColor = AppColor.yellowStatusColor;
textColor = AppColor.yellowStatusTextColor;
break;
}
return Container(
padding: EdgeInsets.symmetric(
horizontal: 12.toScreenWidth,
vertical: 6.toScreenHeight,
),
padding: EdgeInsets.symmetric(horizontal: 12.toScreenWidth, vertical: 6.toScreenHeight),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(4),
),
child: status.bodyText(context).custom(
color: textColor,
fontSize: 12,
fontWeight: FontWeight.w600,
child: Text(
status,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: textColor,
),
),
);
}
Widget _buildRequestTypeLabel(String requestType) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.toScreenWidth, vertical: 6.toScreenHeight),
decoration: BoxDecoration(
color: AppColor.primary10.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(4),
border: Border.all(color: AppColor.primary10.withValues(alpha: 0.3)),
),
child: Text(
requestType,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: AppColor.primary10,
),
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: EdgeInsets.only(bottom: 8.toScreenHeight),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 120.toScreenWidth,
child: Text(
"$label:",
style: TextStyle(
fontSize: 14,
color: AppColor.labelTextStyleColor(context),
fontWeight: FontWeight.w500,
),
),
),
Expanded(
child: Text(
value,
style: TextStyle(
fontSize: 14,
color: AppColor.textStyleColor(context),
),
),
),
],
),
);
}
}
Widget _buildChip(String label) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 10.toScreenWidth, vertical: 4.toScreenHeight),
decoration: BoxDecoration(
color: AppColor.primary10.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColor.primary10.withValues(alpha: 0.2)),
),
child: Text(
label,
style: TextStyle(
fontSize: 12,
color: AppColor.primary10,
fontWeight: FontWeight.w500,
),
),
);
}
}

@ -4,7 +4,7 @@ import 'package:test_sa/extensions/context_extension.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/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/new_views/app_style/app_color.dart';
import 'package:test_sa/new_views/pages/login_page.dart';
import 'package:test_sa/new_views/swipe_module/swipe_history_view.dart';
@ -71,8 +71,8 @@ class AppDrawer extends StatelessWidget {
// drawerItem("rate_us", context.translation.rateUs, context),
// 18.height,
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,
drawerItem("setting", "Site Request", context).onPress(() => Navigator.of(context).pushNamed(SiteRequestHistoryScreen.routeName)),
18.height,
if (userProvider.user != null && userProvider.user?.employeeIsHMG == false)
drawerItem("swipe", "Swipe History", context).onPress(() => Navigator.of(context).pushNamed(SwipeHistoryView.routeName)),

@ -0,0 +1,49 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:test_sa/models/new_models/department.dart';
import '../controllers/api_routes/api_manager.dart';
import '../controllers/api_routes/urls.dart';
import 'loading_list_notifier.dart';
class DepartmentBySitesProvider extends LoadingListNotifier<Department> {
List<int> siteIds = [];
void setSiteIds(List<int> ids) {
siteIds = ids;
}
@override
Future getData({int? id}) async {
if (loading ?? false) return -2;
if (siteIds.isEmpty) {
items = [];
notifyListeners();
return 200;
}
loading = true;
notifyListeners();
try {
Response response = await ApiManager.instance.post(
URLs.getDepartmentsBySites,
body: {"siteIds": siteIds},
);
stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
var responseData = json.decode(response.body);
List departmentListJson = responseData is List ? responseData : responseData['data'] ?? [];
items = departmentListJson.map((item) => Department.fromJson(item)).toList();
}
loading = false;
notifyListeners();
return response.statusCode;
} catch (error) {
loading = false;
stateCode = -1;
notifyListeners();
return -1;
}
}
}
Loading…
Cancel
Save