site implementation inprogress
parent
c6166d1e75
commit
d9eb1ae835
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in New Issue