site apis implemented

export_module_ui_ux_enhancement_signup
WaseemAbbasi22 2 weeks ago
parent 7fc19d9058
commit 2e27f76f04

@ -63,7 +63,7 @@ class URLs {
// 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 getSiteRequests => "$_baseUrl/SiteRequest/GetSiteRequests"; // get site request history
static get getDepartmentsBySites => "$_baseUrl/Customer/GetDepartmentsBySites"; // post get departments by sites
// static get login => "$_baseUrl/MobileAuth/LoginIntegration"; // mobile login

@ -53,17 +53,11 @@ class SiteRequestProvider extends ChangeNotifier {
}
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;
// Fetch request types from API (Step 1)
Future<int> fetchRequestTypes() async {
if (_loading) return -2;
@ -112,12 +106,8 @@ class SiteRequestProvider extends ChangeNotifier {
notifyListeners();
return response.statusCode;
}
// Mock sites as Base models
List<SiteRequestSiteModel> _allSites = [];
List<SiteRequestSiteModel> get allSites => _allSites;
// Fetch sites from API (Step 2)
Future<int> fetchSites() async {
if (_loading) return -2;
@ -221,6 +211,16 @@ class SiteRequestProvider extends ChangeNotifier {
final List<SiteRequestModel> _requestHistory = [];
List<SiteRequestModel> get requestHistory => _requestHistory;
// Pagination state
int _currentPage = 1;
int _pageSize = 10;
bool _hasMoreData = true;
bool _isLoadingMore = false;
int get currentPage => _currentPage;
bool get hasMoreData => _hasMoreData;
bool get isLoadingMore => _isLoadingMore;
// Get available sites based on request type
List<SiteRequestSiteModel> getAvailableSites(String requestType, List<String> assignedSites) {
if (requestType == 'Remove Site') {
@ -324,50 +324,81 @@ class SiteRequestProvider extends ChangeNotifier {
}
}
// Fetch request history from API (Step 5)
Future<int> fetchRequestHistory() async {
if (_loading) return -2;
// Fetch paginated site requests
Future<void> fetchSiteRequests({int? pageSize, int? pageNumber, bool loadMore = false}) async {
final size = pageSize ?? _pageSize;
final page = pageNumber ?? (loadMore ? _currentPage + 1 : 1);
if (size <= 0 || page < 1) {
_errorMessage = 'Invalid pagination parameters';
notifyListeners();
return;
}
// Set loading state
if (loadMore) {
_isLoadingMore = true;
} else {
loading = true;
_currentPage = 1;
_hasMoreData = true;
_requestHistory.clear();
}
_loading = true;
_errorMessage = null;
notifyListeners();
late Response response;
try {
response = await ApiManager.instance.get(
URLs.getSiteRequestHistory,
enableToastMessage: false,
final response = await ApiManager.instance.post(
URLs.getSiteRequests,
body: {
"pageSize": size,
"pageNumber": page,
},
);
} 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;
if (response.statusCode == 200) {
final data = json.decode(response.body);
if (data['isSuccess'] == true) {
List<SiteRequestModel> siteRequests = (data['data'] as List?)
?.map((item) => SiteRequestModel.fromJson(item))
.toList() ?? [];
if (loadMore) {
_requestHistory.addAll(siteRequests);
} else {
_requestHistory.clear();
_requestHistory.addAll(siteRequests);
}
// Update pagination state
_currentPage = page;
_hasMoreData = siteRequests.length >= size;
} else {
_errorMessage = data['message'] ?? 'Failed to fetch site requests';
}
} else {
_errorMessage = 'Failed to fetch site requests';
}
} else {
_errorMessage = 'Failed to fetch request history';
} catch (e) {
_errorMessage = 'Error: ${e.toString()}';
} finally {
loading = false;
_isLoadingMore = false;
notifyListeners();
}
}
_loading = false;
notifyListeners();
return response.statusCode;
// Load more site requests (for pagination)
Future<void> loadMoreSiteRequests() async {
if (!_hasMoreData || _isLoadingMore) return;
await fetchSiteRequests(loadMore: true);
}
// Refresh site requests (reset to first page)
Future<void> refreshSiteRequests() async {
await fetchSiteRequests(pageNumber: 1, pageSize: _pageSize);
}
// Reset provider state

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:provider/provider.dart';
@ -57,6 +59,12 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
WidgetsBinding.instance.addPostFrameCallback((_) async {
_provider = Provider.of<SiteRequestProvider>(context, listen: false);
// Initialize department provider with empty state
final departmentProvider = Provider.of<DepartmentBySitesProvider>(context, listen: false);
departmentProvider.setSiteIds([]);
departmentProvider.items = [];
departmentProvider.loading = false;
// Fetch only request types (sites and departments are loaded by their providers)
await _provider.fetchRequestTypes();
@ -161,11 +169,31 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
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,),
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);
}
@ -229,6 +257,10 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
}
Widget _buildDepartmentMultiSelect() {
// Enable department field only when sites are selected
final isDepartmentEnabled = _selectedSites.isNotEmpty;
log('Selected Sites: ${_selectedSites.length}, isDepartmentEnabled: $isDepartmentEnabled');
return MultipleItemDropDownMenu<Department, DepartmentBySitesProvider>(
context: context,
title: "Departments",
@ -238,6 +270,8 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
showShadow: true,
height: 56.toScreenHeight,
showCancel: true,
enabled: isDepartmentEnabled, // Enable only when sites are selected
loading: !isDepartmentEnabled ? false : null, // Don't show loading when disabled
onSelect: (value) {
if (value != null) {
setState(() {
@ -291,7 +325,8 @@ class _SiteRequestFormScreenState extends State<SiteRequestFormScreen> {
}
final request = SiteRequestModel(
requestType: _selectedRequestType!.value?.toString() ?? '', // Use value instead of id
requestType: _selectedRequestType!.value?.toString() ?? '',
// Use value instead of id
employeeId: _provider.getCurrentUserEmployeeId(_userProvider),
fullName: _provider.getCurrentUserFullName(_userProvider),
emailAddress: _provider.getCurrentUserEmail(_userProvider),

@ -9,7 +9,13 @@ import 'package:test_sa/modules/site_request_module/models/site_request_model.da
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/app_style/app_text_styles.dart';
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
import 'package:test_sa/views/widgets/loaders/no_data_found.dart';
import 'package:test_sa/views/widgets/item_views/info_date_widget.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';
import 'package:test_sa/views/widgets/requests/request_status.dart';
class SiteRequestHistoryScreen extends StatefulWidget {
static const String routeName = "/site_request_history";
@ -24,20 +30,34 @@ class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
late SiteRequestProvider _provider;
late UserProvider _userProvider;
bool _isLoadingData = true;
late ScrollController _scrollController;
@override
void initState() {
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(_onScroll);
_loadHistoryData();
}
@override
void dispose() {
_scrollController.removeListener(_onScroll);
_scrollController.dispose();
super.dispose();
}
void _onScroll() {
if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent * 0.9) {
_provider.loadMoreSiteRequests();
}
}
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();
await _provider.fetchSiteRequests(pageNumber: 1, pageSize: 10);
if (mounted) {
setState(() {
@ -47,6 +67,10 @@ class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
});
}
Future<void> _refreshData() async {
await _provider.refreshSiteRequests();
}
@override
Widget build(BuildContext context) {
_provider = Provider.of<SiteRequestProvider>(context);
@ -88,14 +112,26 @@ class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
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);
},
? const NoDataFound().center
: RefreshIndicator(
onRefresh: _refreshData,
child: ListView.builder(
controller: _scrollController,
padding: EdgeInsets.all(16.toScreenWidth),
itemCount: _provider.requestHistory.length + (_provider.isLoadingMore ? 1 : 0),
itemBuilder: (context, index) {
// Show loading indicator at the bottom
if (index == _provider.requestHistory.length) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 16.toScreenHeight),
child: const Center(child: CircularProgressIndicator()),
);
}
final request = _provider.requestHistory[index];
return _buildRequestCard(request);
},
),
),
);
}
@ -112,266 +148,143 @@ class _SiteRequestHistoryScreenState extends State<SiteRequestHistoryScreen> {
),
16.height,
"No site requests found".heading5(context).custom(
color: AppColor.labelTextStyleColor(context),
),
color: AppColor.labelTextStyleColor(context),
),
8.height,
"Your submitted requests will appear here".bodyText(context).custom(
color: AppColor.lightTextColor(context),
),
color: AppColor.lightTextColor(context),
),
],
),
);
}
Widget _buildRequestCard(SiteRequestModel request) {
return Container(
margin: EdgeInsets.only(bottom: 16.toScreenHeight),
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),
),
],
),
child: Padding(
padding: EdgeInsets.all(16.toScreenWidth),
child: Column(
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Status labels and date - matching ServiceRequestItemView layout
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Status and Date Row
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildStatusLabel(request.status),
_buildRequestTypeLabel(request.requestType),
],
),
),
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),
),
),
],
),
],
StatusLabel(
label: request.status,
textColor: _getStatusTextColor(request.status),
backgroundColor: _getStatusColor(request.status),
),
16.height,
8.width,
StatusLabel(
label: request.requestType,
textColor: AppColor.primary10,
backgroundColor: AppColor.primary10.withValues(alpha: 0.1),
),
1.width.expanded,
InfoDateWidget(request.requestDate.toString()),
],
),
// Request ID
if (request.id != null) ...[
_buildInfoRow("Request ID", request.id!),
12.height,
],
// Request header
InfoHeaderWidget("Site Request #${request.id ?? 'N/A'}"),
Divider(color: AppColor.border2Color(context)),
12.height,
// Request details using InfoTextWidget
InfoTextWidget(label: "Full Name", value: request.fullName),
InfoTextWidget(label: "Employee ID", value: request.employeeId),
InfoTextWidget(label: "Email", value: request.emailAddress),
InfoTextWidget(label: "Mobile", value: request.mobileNumber, showEmptyValue: true),
if (request.extensionNumber != null && request.extensionNumber!.isNotEmpty)
InfoTextWidget(label: "Extension", value: request.extensionNumber!, showEmptyValue: true),
InfoTextWidget(label: "Role", value: request.role),
// Requester Information Section
"Requester Information".heading6(context).custom(
color: AppColor.textStyleColor(context),
fontWeight: FontWeight.w600,
// Sites section with chips
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 100.toScreenWidth,
child: Text(
"Sites:",
style: TextStyle(
fontSize: 14,
color: AppColor.labelTextStyleColor(context),
fontWeight: FontWeight.w500,
),
),
),
12.height,
_buildInfoRow("Full Name", request.fullName),
_buildInfoRow("Employee ID", request.employeeId),
_buildInfoRow("Email", request.emailAddress),
_buildInfoRow("Mobile", request.mobileNumber),
if (request.extensionNumber != null && request.extensionNumber!.isNotEmpty)
_buildInfoRow("Extension", request.extensionNumber!),
_buildInfoRow("Role", request.role),
16.height,
Divider(color: AppColor.border2Color(context)),
12.height,
// Site Information Section
"Site Information".heading6(context).custom(
color: AppColor.textStyleColor(context),
fontWeight: FontWeight.w600,
Expanded(
child: Wrap(
runSpacing: 4.toScreenHeight,
spacing: 4.toScreenWidth,
children: request.siteNames.map((site) => _buildChip(site)).toList(),
),
),
12.height,
_buildInfoRow("Request Type", request.requestType),
12.height,
],
).paddingOnly(bottom: 8),
// Sites List
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Sites:",
// Departments section with chips (if available)
if (request.departmentNames != null && request.departmentNames!.isNotEmpty)
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 100.toScreenWidth,
child: Text(
"Departments:",
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(),
),
),
Expanded(
child: Wrap(
runSpacing: 4.toScreenHeight,
spacing: 4.toScreenWidth,
children: request.departmentNames!.map((dept) => _buildChip(dept)).toList(),
),
],
),
// Departments List (if available)
if (request.departmentNames != null && request.departmentNames!.isNotEmpty) ...[
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(),
),
),
],
),
],
],
),
),
);
),
],
).toShadowContainer(context).paddingOnly(bottom: 16);
}
Widget _buildStatusLabel(String status) {
Color backgroundColor;
Color textColor;
Color _getStatusColor(String status) {
switch (status.toLowerCase()) {
case 'approved':
backgroundColor = AppColor.greenStatusColor;
textColor = AppColor.greenStatusTextColor;
break;
return AppColor.greenStatusColor;
case 'rejected':
backgroundColor = AppColor.redStatusColor;
textColor = AppColor.redStatusTextColor;
break;
return AppColor.redStatusColor;
case 'pending':
default:
backgroundColor = AppColor.yellowStatusColor;
textColor = AppColor.yellowStatusTextColor;
break;
return AppColor.yellowStatusColor;
}
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.toScreenWidth, vertical: 6.toScreenHeight),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(4),
),
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),
),
),
),
],
),
);
Color _getStatusTextColor(String status) {
switch (status.toLowerCase()) {
case 'approved':
return AppColor.greenStatusTextColor;
case 'rejected':
return AppColor.redStatusTextColor;
case 'pending':
default:
return AppColor.yellowStatusTextColor;
}
}
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,
),
return Chip(
backgroundColor: AppColor.backgroundTabBarColor(context),
side: BorderSide(color: AppColor.border2Color(context)),
label: Text(label),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)),
labelStyle: AppTextStyles.tinyFont2.copyWith(
color: AppColor.labelTextStyleColor(context),
),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
padding: EdgeInsets.symmetric(horizontal: 8.toScreenWidth, vertical: 4.toScreenHeight),
);
}
}

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@ -185,12 +187,26 @@ class _MultipleItemDropDownMenuState<T extends Base, X extends LoadingListNotifi
@override
Widget build(BuildContext context) {
final isEmpty = (X == NullableLoadingProvider ? widget.staticData : provider?.items)?.isEmpty ?? true;
// Determine the actual loading state - respect explicit loading prop and enabled state
final bool isActuallyLoading = widget.enabled
? (widget.loading ?? ((X == NullableLoadingProvider) ? false : provider?.loading ?? false))
: false; // Never show loading when disabled
// Provide a valid stateCode when disabled to prevent AppLoadingManager from auto-fetching
// When enabled, use the provider's actual stateCode
final int? effectiveStateCode = widget.enabled
? ((X == NullableLoadingProvider) ? 200 : provider?.stateCode)
: 200; // Return 200 when disabled to indicate "loaded" state (prevents auto-fetch)
return AppLoadingManager(
isLoading: widget.loading ?? ((X == NullableLoadingProvider) ? false : provider?.loading ?? false),
// Provide default value if null
isFailedLoading: (X == NullableLoadingProvider) ? false : provider?.items == null,
stateCode: (X == NullableLoadingProvider) ? 200 : provider?.stateCode,
isLoading: isActuallyLoading,
isFailedLoading: widget.enabled && (X == NullableLoadingProvider) ? false : provider?.items == null,
stateCode: effectiveStateCode,
onRefresh: () async {
// Simple check: Only fetch data if the widget is enabled
if (!widget.enabled) return;
if (X != NullableLoadingProvider) {
provider?.reset();
await provider?.getData(id: widget.requestById);

Loading…
Cancel
Save