|
|
|
|
@ -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),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|