1347 lines
42 KiB
Dart
1347 lines
42 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:frontend/Screens/plans/dynamic_itinerary_stepper.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:responsive_builder/responsive_builder.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import '../../config/apiUrl.dart';
|
|
import '../../data/models/plan.dart';
|
|
import '../../routes/custom_appBar.dart';
|
|
import '../../routes/custom_drawer.dart';
|
|
import '../../widgets/custom_radio_button.dart';
|
|
import '../../widgets/custom_text_field.dart';
|
|
import '../dialog/user_selection_dialog.dart';
|
|
|
|
class CreatePlan extends StatefulWidget {
|
|
const CreatePlan({super.key});
|
|
|
|
@override
|
|
_CreatePlansState createState() => _CreatePlansState();
|
|
}
|
|
|
|
class _CreatePlansState extends State<CreatePlan> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
|
|
|
|
|
final args = GoRouterState.of(context).extra as Map<String, dynamic>? ?? {};
|
|
// final planData = args?['planData'];
|
|
final bool isViewMode = args?['isViewMode'] ?? false;
|
|
|
|
final Map<String, dynamic> planData = args['planData'] as Map<String, dynamic>? ?? {};
|
|
|
|
|
|
// print("isViewMode: $isViewMode");
|
|
|
|
// final bool isViewMode = true;
|
|
// final planData = GoRouterState.of(context).extra as Map<String, dynamic>? ?? {};
|
|
|
|
|
|
print("RECived palndata");
|
|
// print("RECived palndata - ${planData}");
|
|
|
|
return Scaffold(
|
|
backgroundColor: Colors.white,
|
|
body: Column(
|
|
children: [
|
|
Container(
|
|
color: Color(0xFFF4F4FB),
|
|
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16),
|
|
child: Row(children: [
|
|
Row(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Icon(
|
|
Icons.create_new_folder_outlined,
|
|
color: Color(0xFF84869A),
|
|
size: 23,
|
|
),
|
|
),
|
|
Text(
|
|
isViewMode
|
|
? "View Plan"
|
|
: (planData.isNotEmpty ? "Update Plan" : "New Plan"),
|
|
|
|
style: TextStyle(fontSize: 18),
|
|
),
|
|
],
|
|
),
|
|
Spacer(),
|
|
Container(
|
|
color: Color(0xFFE9EBF6),
|
|
child: IconButton(
|
|
icon: Icon(Icons.close),
|
|
onPressed: () {
|
|
context.go('/listPlan');
|
|
},
|
|
),
|
|
)
|
|
]),
|
|
),
|
|
Expanded(
|
|
child: Container(
|
|
color: Colors.white,
|
|
child: SingleChildScrollView(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(26.0),
|
|
child: CreateNewPlan(isDesktop: isDesktop, selectedPlanData: planData, isViewMode : isViewMode),
|
|
),
|
|
),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
class CreateNewPlan extends StatefulWidget {
|
|
final bool isDesktop;
|
|
final bool isViewMode;
|
|
final Map<String, dynamic> selectedPlanData;
|
|
const CreateNewPlan({super.key, required this.isDesktop, required this.selectedPlanData, required this.isViewMode});
|
|
|
|
@override
|
|
_CreateNewPlansState createState() => _CreateNewPlansState();
|
|
}
|
|
|
|
class _CreateNewPlansState extends State<CreateNewPlan> {
|
|
|
|
final TextEditingController _tripTitleController = TextEditingController();
|
|
final TextEditingController _descriptionController = TextEditingController();
|
|
|
|
final FocusNode _tripTitleFocusNode = FocusNode();
|
|
final FocusNode _descriptionFocusNode = FocusNode(); // Declare FocusNode
|
|
|
|
bool _isTripTitleFocused = false;
|
|
bool _isdescriptionFocused = false;
|
|
late String _selectedOption = "Option 1";
|
|
// late String? _selectedIsBillable = "Billable";
|
|
|
|
String? selectedPlanId;
|
|
|
|
String? userDetails;
|
|
String? userName;
|
|
String? selfId;
|
|
String? otherUserName;
|
|
String? selectedplanUserId;
|
|
bool? selectedIstravelUser;
|
|
|
|
|
|
|
|
Map<String, dynamic>? apiData; // Store API response here
|
|
List<dynamic>? apiCountryData;
|
|
List<dynamic>? apiCostData; // Store API response here
|
|
bool isLoading = true; // Track loading state
|
|
|
|
String? planUsrId;
|
|
String? planTravlrId;
|
|
String? _selectedTripType;
|
|
String? selectedCostCenterId;
|
|
String? _selectedIsBillable ;
|
|
String? selectedFuncDept;
|
|
String? selectedPurpose;
|
|
|
|
Map<String, String?> validationErrors = {};
|
|
List<Map<String, dynamic>> miscellaneousList = [];
|
|
// List<Map<String, dynamic>> miscellaneousList = [{"special_request": 1, "comments": "posta", "indx": 1}];
|
|
List<Map<String, dynamic>> visaList = [];
|
|
List<Map<String, dynamic>> insuranceList = [];
|
|
List<Map<String, dynamic>> accommodationList = [];
|
|
List<Map<String, dynamic>> trainList = [];
|
|
List<Map<String, dynamic>> busList = [];
|
|
List<Map<String, dynamic>> taxiList = [];
|
|
List<Map<String, dynamic>> forexList = [];
|
|
List<Map<String, dynamic>> flightList = [];
|
|
|
|
//Getter Method
|
|
Map<String, dynamic> get planData => {
|
|
"user_id": planUsrId,
|
|
"traveller_id": planTravlrId,
|
|
"trip_title": _tripTitleController.text,
|
|
"trip_type": _selectedTripType,
|
|
"cost_center_id": selectedCostCenterId,
|
|
"is_billable": _selectedIsBillable,
|
|
"purpose_of_travel": selectedPurpose,
|
|
"description": _descriptionController.text,
|
|
"functional_department": selectedFuncDept,
|
|
"so_number": "12345",
|
|
"status": "0",
|
|
"created_by": selfId,
|
|
"updated_by": selfId,
|
|
"is_active": "1",
|
|
"flight":flightList,
|
|
"accomodation": accommodationList,
|
|
"bus": busList,
|
|
"taxi": taxiList,
|
|
"train": trainList,
|
|
"visa": visaList,
|
|
"forex": forexList,
|
|
"insurance": insuranceList,
|
|
"miscellaneous": miscellaneousList,
|
|
};
|
|
|
|
|
|
|
|
// // Function to update miscellaneous list
|
|
// void updateMiscellaneousData(List<Map<String, dynamic>> newMiscellaneousList) {
|
|
// setState(() {
|
|
// miscellaneousList = newMiscellaneousList; // Update miscellaneous data
|
|
// });
|
|
// print("Updated Miscellaneous Data in CreateNewPlan: $miscellaneousList");
|
|
// }
|
|
|
|
|
|
void handleItineraryUpdate(String type, List<Map<String, dynamic>> newList) {
|
|
setState(() {
|
|
switch (type) {
|
|
case "Miscellaneous":
|
|
miscellaneousList = newList;
|
|
break;
|
|
case "Visa":
|
|
visaList = newList;
|
|
break;
|
|
case "Insurance":
|
|
insuranceList = newList;
|
|
break;
|
|
case "Train":
|
|
trainList = newList;
|
|
break;
|
|
case "Bus":
|
|
busList = newList;
|
|
break;
|
|
case "Taxi":
|
|
taxiList = newList;
|
|
break;
|
|
case "Forex":
|
|
forexList = newList;
|
|
break;
|
|
case "Flight":
|
|
flightList = newList;
|
|
break;
|
|
case "Accomodation":
|
|
accommodationList = newList;
|
|
break;
|
|
default:
|
|
print("Unknown itinerary type: $type");
|
|
}
|
|
});
|
|
print("Updated $type List: $newList");
|
|
}
|
|
|
|
@override
|
|
void initState(){
|
|
super.initState();
|
|
fetchUserDetails();
|
|
|
|
|
|
fetchPlans();
|
|
fetchCostCenter();
|
|
fetchCountryList();
|
|
|
|
// _tripTitleController.addListener(() {
|
|
// print("Current Value: ${_tripTitleController.text}");
|
|
// });
|
|
|
|
_tripTitleFocusNode.addListener(() {
|
|
setState(() {
|
|
_isTripTitleFocused = _tripTitleFocusNode.hasFocus;
|
|
});
|
|
});
|
|
|
|
_descriptionFocusNode.addListener(() {
|
|
setState(() {
|
|
_isdescriptionFocused = _descriptionFocusNode.hasFocus;
|
|
});
|
|
});
|
|
|
|
handleUpdateData();
|
|
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_tripTitleFocusNode.dispose();
|
|
_descriptionFocusNode.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
|
|
|
|
void handleUpdateData() {
|
|
if (widget.selectedPlanData != null) {
|
|
setState(() {
|
|
|
|
|
|
planUsrId = widget.selectedPlanData['user_id'] ?? '';
|
|
_tripTitleController.text = widget.selectedPlanData['trip_title'] ?? '';
|
|
_descriptionController.text = widget.selectedPlanData['description'] ?? '';
|
|
|
|
_selectedTripType = widget.selectedPlanData['trip_type'];
|
|
_selectedIsBillable = widget.selectedPlanData['is_billable'] == "1" ? "1" : "2";
|
|
|
|
// selectedCostCenterId = widget.selectedPlanData['cost_center_id']?.toString() ;
|
|
// selectedPurpose = widget.selectedPlanData['purpose_of_travel']?.toString();
|
|
// selectedFuncDept =widget.selectedPlanData['functional_department']?.toString();
|
|
|
|
if (widget.selectedPlanData!["cost_center_id"] != null) {
|
|
selectedCostCenterId = widget.selectedPlanData!["cost_center_id"].toString();
|
|
}
|
|
|
|
//
|
|
if (widget.selectedPlanData!["purpose_of_travel"] != null) {
|
|
selectedPurpose = widget.selectedPlanData!["purpose_of_travel"].toString();
|
|
}
|
|
|
|
|
|
|
|
if (widget.selectedPlanData!["functional_department"] != null) {
|
|
// selectedFuncDept = widget.selectedPlanData!["functional_department"].toString();
|
|
selectedFuncDept = widget.selectedPlanData!["functional_department"];
|
|
}
|
|
|
|
|
|
|
|
// Assign lists from selectedPlanData, ensuring they are properly formatted
|
|
flightList = List<Map<String, dynamic>>.from(widget.selectedPlanData['flight'] ?? []);
|
|
accommodationList = List<Map<String, dynamic>>.from(widget.selectedPlanData['accomodation'] ?? []);
|
|
busList = List<Map<String, dynamic>>.from(widget.selectedPlanData['bus'] ?? []);
|
|
taxiList = List<Map<String, dynamic>>.from(widget.selectedPlanData['taxi'] ?? []);
|
|
trainList = List<Map<String, dynamic>>.from(widget.selectedPlanData['train'] ?? []);
|
|
visaList = List<Map<String, dynamic>>.from(widget.selectedPlanData['visa'] ?? []);
|
|
forexList = List<Map<String, dynamic>>.from(widget.selectedPlanData['forex'] ?? []);
|
|
insuranceList = List<Map<String, dynamic>>.from(widget.selectedPlanData['insurance'] ?? []);
|
|
miscellaneousList = List<Map<String, dynamic>>.from(widget.selectedPlanData['miscellaneous'] ?? []);
|
|
|
|
});
|
|
|
|
if (widget.selectedPlanData.containsKey('plan_id') && widget.selectedPlanData['plan_id'] != null) {
|
|
print("Plan ID exists: ${widget.selectedPlanData['plan_id']}");
|
|
selectedPlanId = widget.selectedPlanData['plan_id']?.toString();
|
|
} else {
|
|
print("Plan ID is missing or null");
|
|
}
|
|
|
|
|
|
print("updatedPlanDAta - $planData");
|
|
}
|
|
}
|
|
|
|
|
|
void getSelectedPlanFor(){
|
|
if (!mounted) return;
|
|
|
|
setState(() {
|
|
if(selectedplanUserId != null){
|
|
|
|
if(selectedIstravelUser!){
|
|
planUsrId = "";
|
|
planTravlrId = selectedplanUserId;
|
|
}else {
|
|
planUsrId = selectedplanUserId;
|
|
planTravlrId = "";
|
|
}
|
|
|
|
}
|
|
else {
|
|
planUsrId = selfId;
|
|
planTravlrId = "";
|
|
}
|
|
});
|
|
|
|
print("USER ID - $planUsrId , TRAVELLER ID - $planTravlrId");
|
|
}
|
|
|
|
|
|
void fetchUserDetails() async {
|
|
final details = await getUserDetails();
|
|
|
|
print("details- $details");
|
|
|
|
|
|
if (details != null) {
|
|
setState(() {
|
|
userDetails = details.toString(); // Store the full Map
|
|
userName = details['name']; // Extract the name
|
|
selfId = details['user_id'];
|
|
});
|
|
}
|
|
|
|
print("userDetails - $selfId");
|
|
getSelectedPlanFor();
|
|
}
|
|
|
|
Future<String?> getToken() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
return prefs.getString('auth_token');
|
|
}
|
|
|
|
|
|
Future<String?> getUserId() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
return prefs.getString('userId');
|
|
}
|
|
|
|
Future <Map<String,String>?> getUserDetails() async{
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final userData = prefs.getString('user_data');
|
|
|
|
if(userData!= null){
|
|
final decodedData = jsonDecode(userData);
|
|
|
|
return {
|
|
'user_id': decodedData['user_id'].toString(),
|
|
'name': "${decodedData['first_name']} ${decodedData['last_name']}",
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
Future<void> fetchPlans() async {
|
|
final String apiUrldata = '$apiUrl/api/getDropdownMaster';
|
|
|
|
final token = await getToken();
|
|
|
|
if (token == null) {
|
|
throw Exception('Token not found. Please log in.');
|
|
}
|
|
|
|
final response = await http.get(
|
|
Uri.parse(apiUrldata),
|
|
headers: {
|
|
'Authorization': 'Bearer $token',
|
|
'Content-Type': 'application/json',
|
|
},
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
try {
|
|
final data = json.decode(response.body);
|
|
print(data);
|
|
|
|
if (!data.containsKey('data') || data['data'] is! Map) {
|
|
throw Exception("Invalid response format: 'data' field is missing or not a Map");
|
|
}
|
|
|
|
Map<String, dynamic> plansJson = data['data']; // 'data' is a Map, not a List
|
|
setState(() {
|
|
apiData = plansJson; // Store API response in state
|
|
isLoading = false;
|
|
});
|
|
|
|
|
|
} catch (e) {
|
|
throw Exception('Error parsing response: $e');
|
|
}
|
|
} else {
|
|
throw Exception('Failed to load plans');
|
|
}
|
|
}
|
|
|
|
Future<void> fetchCostCenter() async {
|
|
final String apiUrldata = '$apiUrl/api/getCostCenterMaster';
|
|
|
|
final token = await getToken();
|
|
|
|
final userId = await getUserId();
|
|
|
|
// print("SUSRTRT- $userId");
|
|
//
|
|
if (token == null) {
|
|
throw Exception('Token not found. Please log in.');
|
|
}
|
|
|
|
final response = await http.get(
|
|
Uri.parse(apiUrldata),
|
|
headers: {
|
|
'Authorization': 'Bearer $token',
|
|
'Content-Type': 'application/json',
|
|
},
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
try {
|
|
final data = json.decode(response.body);
|
|
print(data);
|
|
|
|
if (!data.containsKey('data') || data['data'] is!List) {
|
|
throw Exception("Invalid response format: 'data' field is missing or not a List");
|
|
}
|
|
|
|
List <dynamic> plansJson = data['data']; // 'data' is a Map, not a List
|
|
setState(() {
|
|
apiCostData = plansJson; // Store API response in state
|
|
// if(apiCostData!.isNotEmpty){
|
|
// selectedCostCenterId =apiCostData?.first['department_id'];
|
|
// }
|
|
|
|
if (apiCostData != null && apiCostData!.isNotEmpty) {
|
|
selectedCostCenterId ??= apiCostData!.first['department_id']?.toString();
|
|
}
|
|
});
|
|
print('plansJSON');
|
|
|
|
} catch (e) {
|
|
throw Exception('Error parsing response: $e');
|
|
}
|
|
} else {
|
|
throw Exception('Failed to load plans');
|
|
}
|
|
}
|
|
|
|
|
|
Future<void> fetchCountryList() async {
|
|
final String apiUrldata = '$apiUrl/api/getcountryMaster';
|
|
|
|
final token = await getToken();
|
|
|
|
final userId = await getUserId();
|
|
|
|
// print("SUSRTRT- $userId");
|
|
//
|
|
if (token == null) {
|
|
throw Exception('Token not found. Please log in.');
|
|
}
|
|
|
|
final response = await http.get(
|
|
Uri.parse(apiUrldata),
|
|
headers: {
|
|
'Authorization': 'Bearer $token',
|
|
'Content-Type': 'application/json',
|
|
},
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
try {
|
|
final data = json.decode(response.body);
|
|
print("Country - $data");
|
|
|
|
if (!data.containsKey('data') || data['data'] is!List) {
|
|
throw Exception("Invalid response format: 'data' field is missing or not a List");
|
|
}
|
|
|
|
|
|
List <dynamic> plansJson = data['data']; // 'data' is a Map, not a List
|
|
|
|
if (data['data'] is List) {
|
|
List<dynamic> plansJson = data['data'];
|
|
print("plansJson.length - ${plansJson.length}");
|
|
} else {
|
|
print("The 'data' key does not contain a list.");
|
|
}
|
|
|
|
setState(() {
|
|
apiCountryData = plansJson; // Store API response in state
|
|
|
|
});
|
|
print('plansJSONContry - $plansJson');
|
|
|
|
} catch (e) {
|
|
throw Exception('Error parsing response: $e');
|
|
}
|
|
} else {
|
|
throw Exception('Failed to load plans');
|
|
}
|
|
}
|
|
|
|
// Handle Submit
|
|
|
|
bool validateForm(){
|
|
validationErrors.clear(); // Clear previous errors
|
|
|
|
// Ensure either "user_id" or "traveller_id" is provided
|
|
if ((planUsrId == null || planUsrId!.isEmpty) && (planTravlrId == null || planTravlrId!.isEmpty)) {
|
|
validationErrors["user_id"] = "Either User ID or Traveller ID is required";
|
|
validationErrors["traveller_id"] = "Either User ID or Traveller ID is required";
|
|
}
|
|
|
|
final requiredFields = {
|
|
"trip_type": _selectedTripType,
|
|
"cost_center_id": selectedCostCenterId,
|
|
"functional_department": selectedFuncDept,
|
|
"purpose_of_travel": selectedPurpose,
|
|
};
|
|
|
|
for (var entry in requiredFields.entries) {
|
|
if (entry.value == null || entry.value!.isEmpty) {
|
|
validationErrors[entry.key] = "${entry.key.replaceAll('_', ' ').toUpperCase()} is required";
|
|
}
|
|
}
|
|
|
|
return validationErrors.isEmpty; // Returns true if no errors
|
|
}
|
|
|
|
void handleSubmit() {
|
|
setState(() {
|
|
if(validateForm()){
|
|
print("Form submitted successfully: $planData");
|
|
postPlanData(planData);
|
|
context.go('/listPlan');
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> postPlanData(Map<String, dynamic> planData) async {
|
|
final String apiUrldata = '$apiUrl/api/plans/createOrEditPlan';
|
|
final token = await getToken(); // Fetch token
|
|
|
|
if (token == null) {
|
|
throw Exception('Token not found. Please log in.');
|
|
}
|
|
|
|
if (selectedPlanId != null && selectedPlanId!.isNotEmpty) {
|
|
planData['plan_id'] = selectedPlanId; // Add plan_id for update
|
|
}
|
|
|
|
try {
|
|
final response = await http.post(
|
|
Uri.parse(apiUrldata),
|
|
headers: {
|
|
'Authorization': 'Bearer $token',
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: jsonEncode(planData), // Convert map to JSON
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
print("Plan submitted successfully!");
|
|
print("Response: ${response.body}");
|
|
} else {
|
|
print("Failed to submit plan. Status: ${response.statusCode}");
|
|
print("Error: ${response.body}");
|
|
}
|
|
} catch (e) {
|
|
print(" Error submitting plan: $e");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
|
bool isMobile = sizingInfo.isMobile;
|
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Text(
|
|
// "Plan This Trip For : ${userName} ", // Your label
|
|
// style: TextStyle(
|
|
// fontSize: 12,
|
|
// fontWeight: FontWeight.w600,
|
|
// color: Color(0xFF575A74)),
|
|
// ),
|
|
|
|
Text.rich(
|
|
TextSpan(
|
|
text: "Plan This Trip For: ", // Static text
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74), // Default color
|
|
),
|
|
children: [
|
|
TextSpan(
|
|
text: otherUserName ?? userName ?? " ", // Dynamic username
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.blueAccent, // Change this to any color
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
SizedBox(height: 7),
|
|
isMobile
|
|
? SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: Row(
|
|
children: _buildPlanTrip(isMobile),
|
|
),
|
|
)
|
|
: Row(
|
|
children: _buildPlanTrip(isMobile),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Divider(
|
|
color: Color(0xFFE6E7F5), // Change color
|
|
thickness: 0.5,
|
|
),
|
|
),
|
|
Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Trip Title", // Your label
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldWrapper(
|
|
isFocused: _isTripTitleFocused,
|
|
isDesktop: widget.isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
focusNode: _tripTitleFocusNode,
|
|
controller: _tripTitleController,
|
|
style: TextStyle(fontSize: 12),
|
|
enabled: !widget.isViewMode,
|
|
decoration: InputDecoration(
|
|
labelText: "Trip Title",
|
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
SizedBox(
|
|
height: 10,
|
|
),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Trip Type *", // Your label
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
isMobile
|
|
? SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: Row(
|
|
children: _buildTripType(isMobile),
|
|
),
|
|
)
|
|
: Row(
|
|
children: _buildTripType(isMobile),
|
|
),
|
|
if (validationErrors["trip_type"] != null)
|
|
Padding(
|
|
padding: EdgeInsets.only(top: 4),
|
|
child: Text(
|
|
validationErrors["trip_type"]!,
|
|
style: TextStyle(color: Colors.red, fontSize: 10),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(
|
|
height: 15,
|
|
),
|
|
isDesktop
|
|
? Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: _buildCostIsBillable())
|
|
: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: _buildCostIsBillable()),
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
|
|
isDesktop
|
|
? Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildNonDescriptionColumn(),
|
|
SizedBox(width: 25),
|
|
_buildDescriptionColumn(isDesktop),
|
|
],
|
|
)
|
|
: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildNonDescriptionColumn(),
|
|
SizedBox(height: 15),
|
|
_buildDescriptionColumn(isDesktop),
|
|
|
|
|
|
],
|
|
),
|
|
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Divider(
|
|
color: Color(0xFFE6E7F5), // Change color
|
|
thickness: 0.5,
|
|
),
|
|
),
|
|
|
|
Row(
|
|
children: [
|
|
Expanded(child: DynamicItinerary(apiData: apiData, apiCountryData: apiCountryData,
|
|
onItineraryUpdate: handleItineraryUpdate,loginUser: selfId,selectedPlanData: planData, isViewMode:widget.isViewMode ,
|
|
)), // Wrap with Expanded if needed
|
|
],
|
|
),
|
|
SizedBox(height: 15),
|
|
isDesktop
|
|
? Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: _buildSubmit(isDesktop),)
|
|
:Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: _buildSubmit(isDesktop),)
|
|
|
|
],
|
|
);
|
|
});
|
|
}
|
|
|
|
|
|
/// Extracted helper function
|
|
List<Widget> _buildCostIsBillable() {
|
|
|
|
List<dynamic> purposeList = apiData?['plan_is_billable'] ?? [];
|
|
|
|
return [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Cost Center *", // Your label
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldWrapper(
|
|
isFocused: false, // Dropdown doesn't use focus
|
|
isDesktop: widget.isDesktop,
|
|
child: SizedBox(
|
|
height: 45, // Set appropriate height
|
|
child: DropdownButtonFormField<String>(
|
|
value: selectedCostCenterId,
|
|
style: TextStyle(fontSize: 12),
|
|
decoration: InputDecoration(
|
|
border: InputBorder.none,
|
|
contentPadding:
|
|
EdgeInsets.symmetric(horizontal: 10), // Proper padding
|
|
),
|
|
onChanged: widget.isViewMode ? null : (newValue) {
|
|
setState(() {
|
|
selectedCostCenterId = newValue;
|
|
});
|
|
},
|
|
items:apiCostData?.map<DropdownMenuItem<String>>((item){
|
|
return DropdownMenuItem(
|
|
value: item['department_id'], // ID as value
|
|
child: Text(item['name'] ?? "Unknown"),
|
|
);
|
|
}).toList(),
|
|
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
SizedBox(width: 25,height: 5,),
|
|
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Is Billable ", // Your label
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
Row(
|
|
children: purposeList.map<Widget>((item) {
|
|
return Row(
|
|
children: [
|
|
Radio<String>(
|
|
// value: item['dropdown_key'], // Use dropdown_value as value
|
|
|
|
value: item['dropdown_key'].toString(),// Convert to String
|
|
groupValue: _selectedIsBillable,
|
|
activeColor: Colors.blueAccent,
|
|
onChanged: widget.isViewMode ? null :(value) {
|
|
setState(() {
|
|
_selectedIsBillable = value;
|
|
});
|
|
print("SELECBILL - $_selectedIsBillable");
|
|
},
|
|
),
|
|
|
|
Text(item['dropdown_value'] ?? ''), // Display dropdown_value
|
|
SizedBox(width: 20), // Spacing
|
|
],
|
|
);
|
|
}).toList(),
|
|
),
|
|
|
|
])
|
|
// Column(
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
// children: [
|
|
// Text(
|
|
// "Is Billable ", // Your label
|
|
// style: TextStyle(
|
|
// fontSize: 12,
|
|
// fontWeight: FontWeight.w600,
|
|
// color: Color(0xFF575A74)),
|
|
// ),
|
|
// SizedBox(height: 5),
|
|
// Row(
|
|
// children: [
|
|
// Row(
|
|
// children: [
|
|
// Radio<String>(
|
|
// value: "Billable",
|
|
// groupValue: _selectedIsBillable,
|
|
// activeColor: Colors.blueAccent,
|
|
// onChanged: (value) {
|
|
// setState(() {
|
|
// _selectedIsBillable = value;
|
|
// });
|
|
// },
|
|
// ),
|
|
// Text("Billable"),
|
|
// ],
|
|
// ),
|
|
// SizedBox(width: 20), // Spacing
|
|
// Row(
|
|
// children: [
|
|
// Radio<String>(
|
|
// value: "Non Billable",
|
|
// groupValue: _selectedIsBillable,
|
|
// activeColor: Colors.blueAccent,
|
|
// onChanged: (value) {
|
|
// setState(() {
|
|
// _selectedIsBillable = value;
|
|
// });
|
|
// },
|
|
// ),
|
|
// Text("Non Billable"),
|
|
// ],
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// ],
|
|
// )
|
|
];
|
|
}
|
|
|
|
|
|
List<Widget> _buildPlanTrip(bool isDesktop) {
|
|
List<Map<String, String>> options = [
|
|
{"title": "Self", "value": "Option 1"},
|
|
{"title": "Other Employee", "value": "Option 2"},
|
|
{"title": "Others", "value": "Option 3"},
|
|
];
|
|
|
|
|
|
return options.map((option) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 5),
|
|
child: CustomTextFieldWrapper(
|
|
color: Color(0xFFF4F4FB),
|
|
width: option["value"] == "Option 2" ? 175 : 120, // Adjust width conditionally
|
|
isFocused: _selectedOption == option["value"],
|
|
isDesktop: isDesktop,
|
|
child: RadioListTile<String>(
|
|
activeColor: Colors.blueAccent,
|
|
contentPadding: EdgeInsets.zero,
|
|
dense: true,
|
|
title: Text(option["title"]!),
|
|
value: option["value"]!,
|
|
groupValue: _selectedOption,
|
|
onChanged: widget.isViewMode ? null : (value) {
|
|
setState(() {
|
|
_selectedOption = value!;
|
|
if(value == 'Option 2' || value == 'Option 3' ){
|
|
_showInputDialog(option["title"]!);
|
|
}
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}).toList();
|
|
}
|
|
|
|
|
|
List<Widget> _buildTripType(bool isMobile) {
|
|
return [
|
|
|
|
CustomTextFieldWrapper(
|
|
color: Color(0xFFF4F4FB),
|
|
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
width: 120,
|
|
isFocused: _selectedTripType == "1",
|
|
isDesktop: widget.isDesktop,
|
|
child: SizedBox(
|
|
height: 35,
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: RadioListTile<String>(
|
|
activeColor: Colors.blueAccent,
|
|
contentPadding: EdgeInsets.zero,
|
|
visualDensity: VisualDensity.compact,
|
|
dense: true,
|
|
title: Text("Domestic"),
|
|
value: "1",
|
|
groupValue: _selectedTripType,
|
|
onChanged: widget.isViewMode ? null : (value) {
|
|
setState(() {
|
|
_selectedTripType = value!;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 20),
|
|
CustomTextFieldWrapper(
|
|
color: Color(0xFFF4F4FB),
|
|
width: 150,
|
|
padding: EdgeInsets.symmetric(horizontal: 5, vertical: 2),
|
|
isFocused: _selectedTripType == "2",
|
|
isDesktop: widget.isDesktop,
|
|
child: RadioListTile<String>(
|
|
activeColor: Colors.blueAccent,
|
|
contentPadding: EdgeInsets.zero,
|
|
dense: true,
|
|
title: Text("International"),
|
|
value: "2",
|
|
groupValue: _selectedTripType,
|
|
onChanged: widget.isViewMode ? null :(value) {
|
|
setState(() {
|
|
_selectedTripType = value!;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
|
|
|
|
];
|
|
}
|
|
|
|
|
|
Widget _buildNonDescriptionColumn (){
|
|
// if (apiData == null) {
|
|
// return Center(child: CircularProgressIndicator()); // Show loading indicator
|
|
// }
|
|
|
|
// 'plan_purpose_of_travel' Starts ------------------------------------------------------
|
|
|
|
List<dynamic> purposeList = apiData?['plan_purpose_of_travel'] ?? [];
|
|
|
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
|
.map((item)=>DropdownMenuItem<String>(
|
|
// value: item['dropdown_key'],
|
|
value: item['dropdown_key']?.toString(),
|
|
// value: item['dropdown_key'].toString(),
|
|
child: Text(item['dropdown_value']),
|
|
)).toList();
|
|
|
|
if (dropdownItems.isEmpty) {
|
|
dropdownItems.add(
|
|
DropdownMenuItem<String>(
|
|
// value: null,
|
|
value: "1",
|
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
|
),
|
|
);
|
|
}
|
|
|
|
|
|
// Ensure Selected Value Exists in the Dropdown List
|
|
List<String> dropdownKeys = dropdownItems.map((e) => e.value ?? "").toList();
|
|
|
|
|
|
|
|
selectedPurpose ??= dropdownItems.isNotEmpty ? dropdownItems.first.value.toString() : "No options";
|
|
|
|
print("Dropdown Purpose List: ${dropdownItems.map((e) => e.value).toList()}");
|
|
print("Selected Purpose: $selectedPurpose");
|
|
|
|
|
|
|
|
|
|
// 'plan_functional_department' Starts ---------------------------------------------
|
|
|
|
List<dynamic> funcDeptList = apiData?['plan_functional_department'] ?? [];
|
|
|
|
List<DropdownMenuItem<String>> dropdownFuncDeptItems = funcDeptList
|
|
.map((item)=>DropdownMenuItem<String>(
|
|
// value: item['dropdown_key'],
|
|
value: item['dropdown_key']?.toString(),
|
|
child: Text(item['dropdown_value']),
|
|
)).toList();
|
|
|
|
if (dropdownFuncDeptItems.isEmpty) {
|
|
dropdownFuncDeptItems.add(
|
|
DropdownMenuItem<String>(
|
|
// value: null,
|
|
value: "1",
|
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
|
),
|
|
);
|
|
}
|
|
|
|
// selectedFuncDept ??= dropdownFuncDeptItems.isNotEmpty ? dropdownFuncDeptItems.first.value.toString() : null;
|
|
selectedFuncDept ??= dropdownFuncDeptItems.isNotEmpty ? dropdownFuncDeptItems.first.value.toString() : "No options";
|
|
|
|
print("Dropdown Functional Department List: ${dropdownFuncDeptItems.map((e) => e.value).toList()}");
|
|
print("Selected Functional Department: $selectedFuncDept");
|
|
|
|
|
|
return Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Purpose of Travel *", // Your label
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldWrapper(
|
|
isFocused: false, // Dropdown doesn't use focus
|
|
isDesktop: widget.isDesktop,
|
|
child: SizedBox(
|
|
height: 45, // Set appropriate height
|
|
child: apiData == null
|
|
? Center(child: CircularProgressIndicator()) // Show loading inside dropdown
|
|
:
|
|
DropdownButtonFormField<String>(
|
|
value: selectedPurpose,
|
|
style: TextStyle(fontSize: 12),
|
|
decoration: InputDecoration(
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 10), // Proper padding
|
|
),
|
|
onChanged: widget.isViewMode ? null : purposeList.isNotEmpty
|
|
? (newValue) {
|
|
setState(() {
|
|
selectedPurpose = newValue;
|
|
});
|
|
print("selectedPurpose - $selectedPurpose");
|
|
}
|
|
: null,
|
|
items: dropdownItems,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Functional Department", // Your label
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldWrapper(
|
|
isFocused: false, // Dropdown doesn't use focus
|
|
isDesktop: widget.isDesktop,
|
|
child: SizedBox(
|
|
height: 45, // Set appropriate height
|
|
child:
|
|
apiData == null
|
|
? Center(child: CircularProgressIndicator()) // Show loading inside dropdown
|
|
:DropdownButtonFormField<String>(
|
|
value: selectedFuncDept,
|
|
style: TextStyle(fontSize: 12),
|
|
decoration: InputDecoration(
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 10), // Proper padding
|
|
),
|
|
onChanged: widget.isViewMode ? null : funcDeptList.isNotEmpty
|
|
? (newValue) {
|
|
setState(() {
|
|
selectedFuncDept = newValue;
|
|
});
|
|
}
|
|
: null,
|
|
items: dropdownFuncDeptItems,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
SizedBox(
|
|
height: 15,
|
|
),
|
|
]
|
|
);
|
|
}
|
|
|
|
|
|
Widget _buildDescriptionColumn(isDesktop){
|
|
return Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Description", // Your label
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldWrapper(
|
|
isFocused: _isdescriptionFocused,
|
|
width: isDesktop? MediaQuery.of(context).size.width * 0.5 :
|
|
MediaQuery.of(context).size.width * 0.85 ,
|
|
isDesktop: widget.isDesktop,
|
|
child: TextField(
|
|
focusNode: _descriptionFocusNode,
|
|
controller: _descriptionController,
|
|
maxLines: 6,
|
|
keyboardType: TextInputType.multiline,
|
|
style: TextStyle(fontSize: 12),
|
|
enabled: !widget.isViewMode,
|
|
decoration: InputDecoration(
|
|
labelText: "Description",
|
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(vertical: 4),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
List<Widget> _buildSubmit(isDesktop){
|
|
return[
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.white,
|
|
foregroundColor: Colors.blueAccent,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
side: BorderSide(color: Colors.blueAccent, width: 2),
|
|
),
|
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
),
|
|
onPressed: (){
|
|
context.go('/listPlan');
|
|
},
|
|
child: Text("Cancel")
|
|
|
|
),
|
|
SizedBox(width: 20,),
|
|
MouseRegion(
|
|
cursor: widget.isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click,
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: widget.isViewMode ? Colors.blueAccent : Colors.blueAccent, // Keep original color
|
|
foregroundColor: widget.isViewMode ? Colors.white : Colors.white, // Keep original color
|
|
disabledBackgroundColor: Colors.blueAccent, // Ensure color remains when disabled
|
|
disabledForegroundColor: Colors.white,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
side: BorderSide(color: Colors.blueAccent, width: 2),
|
|
),
|
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
),
|
|
onPressed: widget.isViewMode ? null : handleSubmit, // Disable when in view mode
|
|
child: Text("Submit"),
|
|
),
|
|
)
|
|
|
|
|
|
];
|
|
}
|
|
|
|
void _showInputDialog(String title){
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context){
|
|
return UserSelectionDialog(
|
|
title:title,
|
|
onSubmit: (input,userId,isTraveller){
|
|
setState(() {
|
|
otherUserName = input;
|
|
selectedplanUserId = userId;
|
|
selectedIstravelUser = isTraveller;
|
|
});
|
|
print("USer entered : $otherUserName $userId $isTraveller");
|
|
getSelectedPlanFor();
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}
|
|
}
|