Plan Creation

This commit is contained in:
Venba Team 2025-03-26 08:46:22 +05:30
parent 7b1e7cf624
commit a82f4a271c
26 changed files with 2532 additions and 1995 deletions

View File

@ -21,6 +21,35 @@ class _LoginWidgetState extends State<LoginWidget> {
final TextEditingController _passwordController = TextEditingController();
bool _obscureText = true;
Future<void> storeUserDetails(String token) async{
try{
final parts = token.split('.');
if (parts.length != 3) throw Exception('Invalid token format');
final payload = json.decode(
utf8.decode(base64Url.decode(base64Url.normalize(parts[1])))
);
final userData = payload['data'];
final prefs = await SharedPreferences.getInstance();
await prefs.setString('auth_token', token);
await prefs.setString('user_data', jsonEncode(userData)); // Store full user data
if(userData != null){
final pref = await SharedPreferences.getInstance();
await pref.setString('auth_token', token);
await pref.setString('user_data', jsonEncode(userData));
}
}
catch(e){
print('Error decoding token: $e');
}
}
void _login(BuildContext context) async {
if (_formKey.currentState!.validate()) {
const String url = '$apiUrl/auth/login';
@ -40,12 +69,10 @@ class _LoginWidgetState extends State<LoginWidget> {
print("data- $data");
final token = data['token']; // Assuming the token is in response
final userId = data['user_id'].toString();
// final userId = data['user_id'].toString();
await storeUserDetails(token);
// Save token to SharedPreferences
final prefs = await SharedPreferences.getInstance();
await prefs.setString('auth_token', token);
await prefs.setString('userId', userId);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Login Successful")),

View File

@ -6,14 +6,15 @@ import 'package:shared_preferences/shared_preferences.dart';
import '../../config/apiUrl.dart';
import '../../data/models/Searchtraveller.dart';
import '../../data/models/searchUser.dart';
import '../../widgets/custom_text_traveller.dart';
class UserSelectionDialog extends StatefulWidget{
final String title;
final void Function(String) onSubmit;
final void Function(String,String, bool) onSubmit;
UserSelectionDialog({Key? key, required this.title, required this.onSubmit}) : super(key: key);
UserSelectionDialog({Key? key, required this.title, required this.onSubmit,}) : super(key: key);
@override
_UserSelectionDialogState createState() => _UserSelectionDialogState();
@ -25,7 +26,13 @@ class _UserSelectionDialogState extends State<UserSelectionDialog>{
// List<String> _filteredUsers = [];
// List<String> _users = [];
List<SearchUser> _users = [];
List<SearchTraveler> _traveller = [];
List<SearchUser> _filteredUsers = [];
List<Map<String, dynamic>> _filteredList = [];
List<SearchTraveler> _filteredTraveller = [];
String userIdSelected = " ";
bool isTraveller = false;
bool _showTravellerForm = false;
final _formKey = GlobalKey<FormState>();
@ -93,7 +100,7 @@ class _UserSelectionDialogState extends State<UserSelectionDialog>{
}
}
void _filterUsers(String query) {
void _filterUsers1(String query) {
print("Filtering users...");
setState(() {
if (query.isEmpty) {
@ -104,7 +111,6 @@ class _UserSelectionDialogState extends State<UserSelectionDialog>{
"${user.firstName} ${user.lastName}".toLowerCase(),
user.email.toLowerCase() ?? "",
user.userId.toLowerCase() ?? "",
user.travellerId ?? "",
user.mobileNo ?? "",
user.alternateMobileNo ?? ""
];
@ -121,12 +127,136 @@ class _UserSelectionDialogState extends State<UserSelectionDialog>{
}
void _filterUsers(String query) {
print("Filtering _filterUsersTravellers...");
setState(() {
_filteredList.clear(); // Reset the list before filtering
if (query.isEmpty) {
_filteredList = [
..._users.map((user) => {"type": "user", "data": user}),
];
} else {
_filteredList = [
..._users.where((user) {
List<String> searchFields = [
"${user.firstName} ${user.lastName}".toLowerCase(),
user.email.toLowerCase() ?? "",
user.userId.toLowerCase() ?? "",
user.mobileNo ?? "",
user.alternateMobileNo ?? ""
];
return searchFields.any((field) => field.contains(query.toLowerCase()));
}).map((user) => {"type": "user", "data": user}),
];
}
});
print("Filtered List:");
for (var item in _filteredList) {
var user = item["data"];
print("${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}");
}
}
void _filterUsersTravellers(String query) {
print("Filtering _filterUsersTravellers...");
setState(() {
_filteredList.clear(); // Reset the list before filtering
if (query.isEmpty) {
_filteredList = [
..._users.map((user) => {"type": "user", "data": user}),
..._traveller.map((traveller) => {"type": "traveller", "data": traveller}),
];
} else {
_filteredList = [
..._users.where((user) {
List<String> searchFields = [
"${user.firstName} ${user.lastName}".toLowerCase(),
user.email.toLowerCase() ?? "",
user.userId.toLowerCase() ?? "",
user.mobileNo ?? "",
user.alternateMobileNo ?? ""
];
return searchFields.any((field) => field.contains(query.toLowerCase()));
}).map((user) => {"type": "user", "data": user}),
..._traveller.where((traveller) {
List<String> searchFields = [
"${traveller.firstName} ${traveller.lastName}".toLowerCase(),
traveller.email.toLowerCase() ?? "",
traveller.travellerId.toLowerCase() ?? "",
traveller.mobileNo ?? "",
];
return searchFields.any((field) => field.contains(query.toLowerCase()));
}).map((traveller) => {"type": "traveller", "data": traveller}),
];
}
});
print("Filtered List:");
for (var item in _filteredList) {
var user = item["data"];
print("${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}");
}
}
Future<void> fetchTraveller() async {
final String apiUrldata = '$apiUrl/api/travellers';
try {
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) {
final Map<String,dynamic> responseBody = json.decode(response.body);
print("API Response: $responseBody"); // Debugging
if (responseBody.containsKey('data') && responseBody['data'] is List) {
List<dynamic> travellerList = responseBody['data'];
setState(() {
_traveller = travellerList.map((user) => SearchTraveler.fromJson(user)).toList();
_filteredTraveller = List.from(_traveller);
});
print("Users fetched: ${_users.length}");
for (var travvelr in _traveller) {
print("${travvelr.firstName} ${travvelr.lastName}");
}
} else {
throw Exception("Unexpected response format: Expected a List but got ${responseBody.runtimeType}");
}
} else {
throw Exception('Failed to load users. Status Code: ${response.statusCode}');
}
} catch (e) {
print("Error fetching traveller: $e");
}
}
@override
void initState() {
super.initState();
fetchUsers();
fetchTraveller();
}
@override
@ -150,7 +280,9 @@ class _UserSelectionDialogState extends State<UserSelectionDialog>{
setState(() {
_showTravellerForm = false;
});
widget.title == "Others"? _filterUsersTravellers(query):
_filterUsers(query);
},
decoration: InputDecoration(
hintText: "Search for a user",
@ -189,7 +321,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog>{
_searchController.text.isNotEmpty
? SizedBox(
height: 300, // Limit height to avoid overflow
child: _filteredUsers.isEmpty
// child: _filteredUsers.isEmpty
child: _filteredList.isEmpty
? Center(
child: Text(
"No users found",
@ -197,18 +330,27 @@ class _UserSelectionDialogState extends State<UserSelectionDialog>{
),
)
: ListView.builder(
itemCount: _filteredUsers.length,
// itemCount: _filteredUsers.length,
itemCount: _filteredList.length,
itemBuilder: (context, index) {
final user = _filteredUsers[index];
// final user = _filteredUsers[index];
final item = _filteredList[index];
final user = item["data"]; // Extract user object
final userType = item["type"]; // "user" or "traveller"
return ListTile(
title: Text("${user.firstName ?? "Unknown"} ${user.lastName ?? ""}"),
subtitle: Text("ID: ${user.userId}"),
subtitle: Text("ID: ${userType == "user" ? user.userId : user.travellerId}"),
onTap: () {
String selectedUser = "${user.firstName ?? "Unknown"} ${user.lastName ?? ""}";
setState(() {
_searchController.text = selectedUser;
userIdSelected = userType == "user" ? user.userId : user.travellerId;
isTraveller = userType == "traveller";
});
print("Selected: $selectedUser, ID: ${user.userId}");
print("Selected: $selectedUser, ID: ${userType == "user" ? user.userId : user.travellerId},"
" isTraveller: $userIdSelected");
},
);
},
@ -224,6 +366,9 @@ class _UserSelectionDialogState extends State<UserSelectionDialog>{
padding: const EdgeInsets.all(16.0),
child: TravelerForm(
formKey: _formKey,
onSubmit: (String fullName, String travellerId, bool isTraveller) {
widget.onSubmit(fullName, travellerId,isTraveller); // Pass the data up
},
firstNameController: TextEditingController(),
lastNameController: TextEditingController(),
emailController: TextEditingController(),
@ -261,7 +406,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog>{
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
widget.onSubmit(_searchController.text);
print("Submitting: ${_searchController.text}, ID: $userIdSelected");
widget.onSubmit(_searchController.text,userIdSelected,isTraveller);
Navigator.pop(context);
},
child: Text("Submit"),
@ -282,6 +428,7 @@ class TravelerForm extends StatefulWidget {
final TextEditingController emailController;
final TextEditingController mobileController;
final GlobalKey<FormState> formKey;
final void Function(String, String, bool) onSubmit;
TravelerForm({
required this.formKey,
@ -289,6 +436,7 @@ class TravelerForm extends StatefulWidget {
required this.lastNameController,
required this.emailController,
required this.mobileController,
required this.onSubmit
});
@override
@ -371,8 +519,26 @@ class _TravelerFormState extends State<TravelerForm> {
if (response.statusCode == 200 || response.statusCode == 201) {
if (response.statusCode == 200 || response.statusCode == 201) {
final Map<String, dynamic> responseData = jsonDecode(response.body); // Parse response
if (responseData["success"] == true && responseData.containsKey("data")) {
final travellerData = responseData["data"];
String travellerId = travellerData["traveller_id"];
String firstName = travellerData["first_name"];
String lastName = travellerData["last_name"];
print("Traveller Added: ID: $travellerId, Name: $firstName $lastName");
// Pass data to callback
widget.onSubmit("$firstName $lastName", travellerId, true);
// Close the dialog
Navigator.pop(context);
}
}
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(

View File

@ -6,12 +6,13 @@ import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
class AccomodationScreen extends StatefulWidget {
final Map<String, String> formData;
final Function(String tab, String key, String value) updateFormData;
final Function(bool) onClose; // Callback function
AccomodationScreen({required this.formData, required this.updateFormData,
required this.onClose,});
final Function(bool) onClose; // Callback function
final Function(Map<String,dynamic>) onSaveAccomadation;
final Map<String, dynamic>? selectedItem;
AccomodationScreen({
required this.onClose, required this.onSaveAccomadation, required this.selectedItem});
@override
_AccomodationScreenState createState() => _AccomodationScreenState();
@ -28,7 +29,6 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
final FocusNode _checkOutTimeFocusNode = FocusNode();
final FocusNode _commentsFocusNode = FocusNode();
late Map<String, TextEditingController> _controllers;
late TextEditingController _destinationController = TextEditingController();
late TextEditingController _hotelNameController = TextEditingController();
@ -46,105 +46,65 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
bool _checkOutTimeFocus = false;
bool _commentsFocus = false;
void _addFocusListener(FocusNode node, Function(bool) onFocusChange) {
node.addListener(() {
setState(() {
onFocusChange(node.hasFocus);
});
});
}
Map<String, dynamic> get accomadationData {
Map<String,dynamic> data ={
"destination_city": _destinationController.text,
"hotel_name": _hotelNameController.text,
"checkin_date": _checkInController.text ,
"checkin_time": _checkInTimeController.text,
"checkout_date": _checkOutController.text,
"checkout_time": _checkOutTimeController.text,
"comments": _commentsController.text,
};
if (widget.selectedItem != null) {
if (widget.selectedItem?["indx"] != null && widget.selectedItem?["indx"] != 0) {
data["indx"] = widget.selectedItem!["indx"];
} else if (widget.selectedItem?["accomodation_id"] != null && widget.selectedItem?["accomodation_id"] != 0) {
data["accomodation_id"] = widget.selectedItem!["accomodation_id"];
}
}
return data;
}
TextEditingController initController(String key) {
return TextEditingController(text: widget.selectedItem?[key] ?? "");
}
@override
void initState() {
super.initState();
_destinationFocusNode.addListener(() {
setState(() {
_destinationFocused = _destinationFocusNode.hasFocus;
});
_hotelNameFocusNode.addListener(() {
setState(() {
_isHotelNameFocused = _hotelNameFocusNode.hasFocus;
});
});
_checkInFocusNode.addListener(() {
setState(() {
_checkInFocus = _checkInFocusNode.hasFocus;
});
});
_checkInTimeFocusNode.addListener(() {
setState(() {
_checkInTimeFocus = _checkInTimeFocusNode.hasFocus;
});
});
_checkOutFocusNode.addListener(() {
setState(() {
_checkInFocus = _checkInFocusNode.hasFocus;
});
});
_checkOutTimeFocusNode.addListener(() {
setState(() {
_checkInTimeFocus = _checkOutTimeFocusNode.hasFocus;
});
});
_commentsFocusNode.addListener(() {
setState(() {
_commentsFocus = _commentsFocusNode.hasFocus;
});
});
});
_addFocusListener(_destinationFocusNode, (focus) => _destinationFocused = focus);
_addFocusListener(_hotelNameFocusNode, (focus) => _isHotelNameFocused = focus);
_addFocusListener(_checkInFocusNode, (focus) => _checkInFocus = focus);
_addFocusListener(_checkInTimeFocusNode, (focus) => _checkInTimeFocus = focus);
_addFocusListener(_checkOutFocusNode, (focus) => _checkOutFocus = focus);
_addFocusListener(_checkOutTimeFocusNode, (focus) => _checkOutTimeFocus = focus);
_addFocusListener(_commentsFocusNode, (focus) => _commentsFocus = focus);
List<String> fields = [
"_destination",
"_hotelName",
"_Check_In",
"_Check_In_Time",
"_Check_Out",
"_Check_Out_Time",
"_comments"
];
_destinationController = initController("destination_city");
_hotelNameController = initController("hotel_name");
_checkInController = initController("checkin_date");
_checkInTimeController = initController("checkin_time");
_checkOutController = initController("checkout_date");
_checkOutTimeController = initController("checkout_time");
_commentsController = initController("comments");
_destinationController =
TextEditingController(text: widget.formData["destination"] ?? "");
_hotelNameController =
TextEditingController(text: widget.formData["_hotelName"] ?? "");
_checkInController =
TextEditingController(text: widget.formData["_Check_In"] ?? "");
_checkInTimeController =
TextEditingController(text: widget.formData["_Check_In_Time"] ?? "");
_checkOutController =
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
_checkOutTimeController =
TextEditingController(text: widget.formData["_Check_Out_Time"] ?? "");
_commentsController =
TextEditingController(text: widget.formData["_comments"] ?? "");
// Save data when user types
_destinationController.addListener(() {
widget.updateFormData(
"Accommodation", "destination", _destinationController.text);
}); // Save data when user types
_hotelNameController.addListener(() {
widget.updateFormData(
"Accommodation", "_hotelName", _hotelNameController.text);
});
_checkInController.addListener(() {
widget.updateFormData(
"Accommodation", "_Check_In", _checkInController.text);
});
_checkInTimeController.addListener(() {
widget.updateFormData(
"Accommodation", "_Check_In_Time", _checkInTimeController.text);
});
_checkOutController.addListener(() {
widget.updateFormData(
"Accommodation", "_Check_Out", _checkOutController.text);
});
_checkOutTimeController.addListener(() {
widget.updateFormData(
"Accommodation", "_Check_Out_Time", _checkOutTimeController.text);
});
_commentsController.addListener(() {
widget.updateFormData(
"Accommodation", "_comments", _commentsController.text);
});
}
@override
@ -160,6 +120,19 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
super.dispose();
}
void handleSave(){
print( "Handle Save accomadtion $accomadationData");
widget.onSaveAccomadation(accomadationData); // Send object to parent
widget.onClose(false);// Close screen after saving
// Clear only if this is a new entry
// if (widget.selectedItem == null) {
// _commentsController.clear();
// }
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
@ -642,7 +615,7 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
// Close Button
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(); // Close the dialog or screen
widget.onClose(false); // Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color
@ -661,7 +634,7 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
// Save Changes Button
ElevatedButton(
onPressed: () {
// TODO: Implement save logic
handleSave();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue, // Primary color for save

View File

@ -6,13 +6,14 @@ import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
class BusScreen extends StatefulWidget {
final Map<String, String> formData;
final Map<String, dynamic>? apiData;
final Function(String tab, String key, String value) updateFormData;
final Function(bool) onClose;
BusScreen({required this.formData, required this.updateFormData,
required this.onClose, this.apiData});
final Map<String, dynamic>? apiData;
final Function(bool) onClose;
final Function(Map<String, dynamic>)onSaveBus;
final Map<String, dynamic>? selectedItem;
BusScreen({
required this.onClose, this.apiData, required this.onSaveBus, required this.selectedItem});
@override
_BusScreenState createState() => _BusScreenState();
@ -39,7 +40,7 @@ class _BusScreenState extends State<BusScreen> {
late TextEditingController _toController = TextEditingController();
late TextEditingController _dateController = TextEditingController();
late TextEditingController _timeController = TextEditingController();
late TextEditingController _commentsController = TextEditingController();
late TextEditingController _buscommentsController = TextEditingController();
bool _tripTypeFocused = false;
bool _isHotelNameFocused = false;
@ -49,6 +50,33 @@ class _BusScreenState extends State<BusScreen> {
bool _timeFocus = false;
bool _commentsFocus = false;
Map<String, dynamic> get busData{
Map<String,dynamic> data = {
"from": _fromController.text,
"to": _toController.text,
"date": _dateController.text,
"time": _timeController.text,
"comments": _buscommentsController.text,
};
if (widget.selectedItem != null) {
if (widget.selectedItem?["indx"] != null && widget.selectedItem?["indx"] != 0) {
data["indx"] = widget.selectedItem!["indx"];
} else if (widget.selectedItem?["bus_id"] != null && widget.selectedItem?["bus_id"] != 0) {
data["bus_id"] = widget.selectedItem!["bus_id"];
}
}
return data;
}
TextEditingController initController(String key) {
return TextEditingController(text: widget.selectedItem?[key] ?? "");
}
@override
void initState() {
super.initState();
@ -57,6 +85,7 @@ class _BusScreenState extends State<BusScreen> {
setState(() {
_tripTypeFocused = _tripTypeFocusNode.hasFocus;
});
});
_hotelNameFocusNode.addListener(() {
setState(() {
@ -91,54 +120,15 @@ class _BusScreenState extends State<BusScreen> {
_commentsFocus = _commentsFocusNode.hasFocus;
});
});
});
_tripTypeController =
TextEditingController(text: widget.formData["tripType"] ?? "");
_hotelNameController =
TextEditingController(text: widget.formData["_hotelName"] ?? "");
_fromController =
TextEditingController(text: widget.formData["_from"] ?? "");
_toController =
TextEditingController(text: widget.formData["_Check_In_Time"] ?? "");
_dateController =
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
_timeController =
TextEditingController(text: widget.formData["_Check_Out_Time"] ?? "");
_commentsController =
TextEditingController(text: widget.formData["_comments"] ?? "");
_buscommentsController = initController("comments");
_fromController = initController("from");
_toController = initController("to");
_dateController = initController("date");
_timeController = initController("time");
// Save data when user types
_tripTypeController.addListener(() {
widget.updateFormData(
"Bus", "_tripType", _tripTypeController.text);
}); // Save data when user types
_hotelNameController.addListener(() {
widget.updateFormData(
"Bus", "_hotelName", _hotelNameController.text);
});
_fromController.addListener(() {
widget.updateFormData(
"Bus", "_from", _fromController.text);
});
_toController.addListener(() {
widget.updateFormData(
"Bus", "_Check_In_Time", _toController.text);
});
_dateController.addListener(() {
widget.updateFormData(
"Bus", "_Check_Out", _dateController.text);
});
_timeController.addListener(() {
widget.updateFormData(
"Bus", "_Check_Out_Time", _timeController.text);
});
_commentsController.addListener(() {
widget.updateFormData(
"Bus", "_comments", _commentsController.text);
});
}
@override
@ -150,12 +140,23 @@ class _BusScreenState extends State<BusScreen> {
_toController.dispose();
_dateController.dispose();
_timeController.dispose();
_commentsController.dispose();
_buscommentsController.dispose();
super.dispose();
}
void handleSave(){
print( "Handle Save miscellaneousData $busData");
widget.onSaveBus(busData); // Send object to parent
widget.onClose(false);// Close screen after saving
// Clear only if this is a new entry
// if (widget.selectedItem == null) {
// _commentsController.clear();
// }
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
@ -277,7 +278,7 @@ class _BusScreenState extends State<BusScreen> {
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
)).toList();
@ -318,7 +319,6 @@ class _BusScreenState extends State<BusScreen> {
print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
widget.updateFormData("Flight", "trip_type", newValue ?? "");
}
: null,
@ -654,7 +654,7 @@ class _BusScreenState extends State<BusScreen> {
: MediaQuery.of(context).size.width * 0.66,
child: TextField(
focusNode: _commentsFocusNode,
controller: _commentsController,
controller: _buscommentsController,
maxLines: 6,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
@ -677,7 +677,7 @@ class _BusScreenState extends State<BusScreen> {
// Close Button
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(); // Close the dialog or screen
widget.onClose(false);// Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color
@ -696,7 +696,7 @@ class _BusScreenState extends State<BusScreen> {
// Save Changes Button
ElevatedButton(
onPressed: () {
// TODO: Implement save logic
handleSave();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue, // Primary color for save

File diff suppressed because it is too large Load Diff

View File

@ -6,13 +6,15 @@ import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
class InsuranceScreen extends StatefulWidget {
final Map<String, String> formData;
final Map<String, dynamic>? apiData;
final Function(String tab, String key, String value) updateFormData;
final Function(bool) onClose;
InsuranceScreen({required this.formData, required this.updateFormData,
required this.onClose, required this.apiData});
final Map<String, dynamic>? apiData;
final Function(bool) onClose;
final Function(Map<String, dynamic>) onSaveInsurance;
final Map<String,dynamic>? selectedItem;
InsuranceScreen({
required this.onClose, required this.apiData, required this.onSaveInsurance, required this.selectedItem});
@override
_InsuranceScreenState createState() => _InsuranceScreenState();
@ -26,17 +28,14 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
final FocusNode _tripTypeFocusNode = FocusNode();
final FocusNode _hotelNameFocusNode = FocusNode();
final FocusNode _fromFocusNode = FocusNode();
final FocusNode _toFocusNode = FocusNode();
final FocusNode _dateFocusNode = FocusNode();
final FocusNode _timeFocusNode = FocusNode();
final FocusNode _commentsFocusNode = FocusNode();
late Map<String, TextEditingController> _controllers;
late TextEditingController _tripTypeController = TextEditingController();
late TextEditingController _startdateController = TextEditingController();
late TextEditingController _endDateController = TextEditingController();
late TextEditingController _commentsController = TextEditingController();
late TextEditingController _insuranceCommentsController = TextEditingController();
bool _isHotelNameFocused = false;
bool _dateFocus = false;
@ -44,62 +43,53 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
String? selectedTripType;
String? selectedInsuranceType;
Map<String, dynamic> get InsuranceData{
Map<String, dynamic> data = {
"type_of_insurance": selectedInsuranceType,
"start_date": _startdateController.text,
"end_date": _endDateController.text,
"comments": _insuranceCommentsController.text,
};
if (widget.selectedItem != null) {
if (widget.selectedItem?["indx"] != null && widget.selectedItem?["indx"] != 0) {
data["indx"] = widget.selectedItem!["indx"];
} else if (widget.selectedItem?["insurance_id"] != null && widget.selectedItem?["insurance_id"] != 0) {
data["insurance_id"] = widget.selectedItem!["insurance_id"];
}
}
return data;
}
@override
void initState() {
super.initState();
// _tripTypeFocusNode.addListener(() {
// setState(() {
// _tripTypeFocused = _tripTypeFocusNode.hasFocus;
// });
// });
_hotelNameFocusNode.addListener(() {
setState(() {
_isHotelNameFocused = _hotelNameFocusNode.hasFocus;
});
});
_dateFocusNode.addListener(() {
setState(() {
_dateFocus = _fromFocusNode.hasFocus;
});
});
setState(() {_isHotelNameFocused = _hotelNameFocusNode.hasFocus;});});
_dateFocusNode.addListener(() {
setState(() {_dateFocus = _fromFocusNode.hasFocus;});});
_commentsFocusNode.addListener(() {
setState(() {
_commentsFocus = _commentsFocusNode.hasFocus;
});
});
setState(() {_commentsFocus = _commentsFocusNode.hasFocus;});});
_tripTypeController =
TextEditingController(text: widget.formData["tripType"] ?? "");
_insuranceCommentsController =
TextEditingController(text: widget.selectedItem?["comments"] ?? "");
_startdateController =
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
_commentsController =
TextEditingController(text: widget.formData["_comments"] ?? "");
TextEditingController(text: widget.selectedItem?["start_date"] ?? "");
_endDateController =
TextEditingController(text: widget.selectedItem?['end_date'] ?? "");
// Save data when user types
_tripTypeController.addListener(() {
widget.updateFormData(
"Insurance", "_tripType", _tripTypeController.text);
}); // Save data when user types
// Set the selected value if available
if (widget.selectedItem != null && widget.selectedItem!["type_of_insurance"] != null) {
selectedInsuranceType = widget.selectedItem!["type_of_insurance"].toString();
}
_startdateController.addListener(() {
widget.updateFormData(
"Insurance", "_Check_Out", _startdateController.text);
});
_commentsController.addListener(() {
widget.updateFormData(
"Insurance", "_comments", _commentsController.text);
});
}
@ -111,12 +101,25 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
});
}
void handleSave(){
print( "Handle Save InsuranceData $InsuranceData");
widget.onSaveInsurance(InsuranceData); // Send object to parent
widget.onClose(false);// Close screen after saving
// // Clear only if this is a new entry
// if (widget.selectedItem == null) {
// _commentsController.clear();
// }
}
@override
void dispose() {
_tripTypeFocusNode.dispose();
_tripTypeController.dispose();
_startdateController.dispose();
_commentsController.dispose();
_insuranceCommentsController.dispose();
super.dispose();
}
@ -244,7 +247,7 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
)).toList();
@ -258,7 +261,7 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
}
// Default selected value
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
selectedInsuranceType ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
return [
@ -270,7 +273,7 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
child: DropdownButtonFormField<String>(
focusNode: _tripTypeFocusNode, // Assign the correct focus node
value: selectedPurpose,
value: selectedInsuranceType,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
@ -280,10 +283,10 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedPurpose = newValue;
selectedInsuranceType = newValue;
});
print(selectedPurpose);
print(selectedInsuranceType);
}
: null,
@ -298,85 +301,6 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
}
List<Widget> _builClassType(bool isDesktop){
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
child: Text(item['dropdown_value']),
)).toList();
if (dropdownItems.isEmpty) {
dropdownItems.add(
DropdownMenuItem<String>(
value: null,
child: Text("No options available", style: TextStyle(color: Colors.grey)),
),
);
}
// Default selected value
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Class *",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: _isHotelNameFocused,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
focusNode: _hotelNameFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
value: selectedPurpose,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 10), // Proper padding
),
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedPurpose = newValue;
});
}
: null,
items: dropdownItems,
),
// child: TextField(
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
// style: TextStyle(fontSize: 12),
// decoration: InputDecoration(
// labelText: "Select Class",
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
// ),
// ),
),
),
],
),
];
}
List<Widget> _buildSecondRow(bool isDesktop) {
@ -554,7 +478,7 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
: MediaQuery.of(context).size.width * 0.66,
child: TextField(
focusNode: _commentsFocusNode,
controller: _commentsController,
controller: _insuranceCommentsController,
maxLines: 6,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
@ -577,7 +501,7 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
// Close Button
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(); // Close the dialog or screen
widget.onClose(false); // Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color
@ -596,7 +520,7 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
// Save Changes Button
ElevatedButton(
onPressed: () {
// TODO: Implement save logic
handleSave();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue, // Primary color for save

View File

@ -6,13 +6,16 @@ import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
class MiscellaneousScreen extends StatefulWidget {
final Map<String, String> formData;
final Map<String, dynamic>? apiData;
final Function(String tab, String key, String value) updateFormData;
final Function(bool) onClose;
MiscellaneousScreen({required this.formData, required this.updateFormData,
required this.onClose, required this.apiData});
final Map<String, dynamic>? apiData;
final Function(bool) onClose;
final Function(Map<String, dynamic>) onSaveMiscellaneous;
final Map<String, dynamic>? selectedItem;
final int? selectedIndex;
MiscellaneousScreen({
required this.onClose, required this.apiData, required this.onSaveMiscellaneous,
this.selectedItem, this.selectedIndex,});
@override
_MiscellaneousScreenState createState() => _MiscellaneousScreenState();
@ -25,36 +28,46 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
final FocusNode _tripTypeFocusNode = FocusNode();
final FocusNode _hotelNameFocusNode = FocusNode();
final FocusNode _fromFocusNode = FocusNode();
final FocusNode _toFocusNode = FocusNode();
final FocusNode _dateFocusNode = FocusNode();
final FocusNode _timeFocusNode = FocusNode();
final FocusNode _commentsFocusNode = FocusNode();
late Map<String, TextEditingController> _controllers;
late TextEditingController _tripTypeController = TextEditingController();
late TextEditingController _startdateController = TextEditingController();
late TextEditingController _endDateController = TextEditingController();
late TextEditingController _commentsController = TextEditingController();
bool _isHotelNameFocused = false;
bool _dateFocus = false;
bool _commentsFocus = false;
String? selectedTripType;
String? selectedSpecialType;
Map<String, dynamic> get miscellaneousData {
Map<String, dynamic> data = {
"special_request": selectedSpecialType,
"comments": _commentsController.text,
};
if (widget.selectedItem != null) {
if (widget.selectedItem?["indx"] != null && widget.selectedItem?["indx"] != 0) {
data["indx"] = widget.selectedItem!["indx"];
} else if (widget.selectedItem?["miscellaneous_id"] != null && widget.selectedItem?["miscellaneous_id"] != 0) {
data["miscellaneous_id"] = widget.selectedItem!["miscellaneous_id"];
}
}
return data;
}
@override
void initState() {
super.initState();
// _tripTypeFocusNode.addListener(() {
// setState(() {
// _tripTypeFocused = _tripTypeFocusNode.hasFocus;
// });
// });
if (widget.selectedItem == null) {
_commentsController.clear();
}
_hotelNameFocusNode.addListener(() {
setState(() {
@ -62,13 +75,6 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
});
});
_dateFocusNode.addListener(() {
setState(() {
_dateFocus = _fromFocusNode.hasFocus;
});
});
_commentsFocusNode.addListener(() {
setState(() {
_commentsFocus = _commentsFocusNode.hasFocus;
@ -77,50 +83,37 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
_tripTypeController =
TextEditingController(text: widget.formData["tripType"] ?? "");
_startdateController =
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
_commentsController =
TextEditingController(text: widget.formData["_comments"] ?? "");
TextEditingController(text: widget.selectedItem?["comments"] ?? "");
// Save data when user types
_tripTypeController.addListener(() {
widget.updateFormData(
"Miscellaneous", "_tripType", _tripTypeController.text);
}); // Save data when user types
// Set the selected value if available
if (widget.selectedItem != null && widget.selectedItem!["special_request"] != null) {
selectedSpecialType = widget.selectedItem!["special_request"].toString();
}
_startdateController.addListener(() {
widget.updateFormData(
"Miscellaneous", "_Check_Out", _startdateController.text);
});
_commentsController.addListener(() {
widget.updateFormData(
"Miscellaneous", "_comments", _commentsController.text);
});
}
void _addFocusListener(FocusNode node, Function(bool) updateState) {
node.addListener(() {
setState(() {
updateState(node.hasFocus);
});
});
}
@override
void dispose() {
_tripTypeFocusNode.dispose();
_tripTypeController.dispose();
_startdateController.dispose();
_commentsController.dispose();
super.dispose();
}
void handleSave(){
print( "Handle Save miscellaneousData $miscellaneousData");
widget.onSaveMiscellaneous(miscellaneousData); // Send object to parent
widget.onClose(false);// Close screen after saving
// Clear only if this is a new entry
if (widget.selectedItem == null) {
_commentsController.clear();
}
}
@override
Widget build(BuildContext context) {
@ -140,6 +133,7 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
alignment: Alignment.centerRight,
child: InkWell(
onTap: () {
_commentsController.clear();
widget.onClose(false);
},
child: Icon(
@ -177,18 +171,11 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
];
}
List<List<Widget>> rowBuilders = [
// _builClassType(isDesktop),
_buildSecondRow(isDesktop)
];
return [
...buildResponsiveRow(_buildFirstRow(isDesktop)),
// // Iterate over rowBuilders and wrap each in a responsive container
// ...rowBuilders.expand((row) => buildResponsiveRow(row)),
...buildResponsiveRow(_buildThirdRow(isDesktop)),
// Actions row remains a Row
@ -244,7 +231,7 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
)).toList();
@ -258,7 +245,7 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
}
// Default selected value
String? selectedSpecialType = dropdownItems.isNotEmpty ? dropdownItems.first.value : "No options";
selectedSpecialType ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : "No options";
return [
@ -287,7 +274,6 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
}
: null,
items: dropdownItems,
),
@ -298,240 +284,6 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
}
List<Widget> _builClassType(bool isDesktop){
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
child: Text(item['dropdown_value']),
)).toList();
if (dropdownItems.isEmpty) {
dropdownItems.add(
DropdownMenuItem<String>(
value: null,
child: Text("No options available", style: TextStyle(color: Colors.grey)),
),
);
}
// Default selected value
String? selectedSpecialType = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Class *",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: _isHotelNameFocused,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
focusNode: _hotelNameFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
value: selectedSpecialType,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 10), // Proper padding
),
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedSpecialType = newValue;
});
}
: null,
items: dropdownItems,
),
// child: TextField(
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
// style: TextStyle(fontSize: 12),
// decoration: InputDecoration(
// labelText: "Select Class",
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
// ),
// ),
),
),
],
),
];
}
List<Widget> _buildSecondRow(bool isDesktop) {
DateTime? _selectedCheckOutDate;
TimeOfDay? _selectedCheckOutTime;
Future<void> _selectCheckOutDate(BuildContext context) async {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
? _selectedCheckOutDate!
: today,
firstDate: today,
lastDate: DateTime(2100),
);
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
setState(() {
_selectedCheckOutDate = pickedDate;
_startdateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
});
}
}
Future<void> _selectEndCheckOutDate(BuildContext context) async {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
? _selectedCheckOutDate!
: today,
firstDate: today,
lastDate: DateTime(2100),
);
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
setState(() {
_selectedCheckOutDate = pickedDate;
_endDateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
});
}
}
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Start Date",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: _dateFocus,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () => _selectCheckOutDate(context),
child: AbsorbPointer(
child: TextField(
focusNode: _dateFocusNode,
controller: _startdateController,
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Select Date",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(Icons.calendar_today,
size: 16, color: Colors.grey),
),
),
),
),
),
),
],
),
if (isDesktop)
Spacer()
else
SizedBox(
height: 8,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"End Date",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: _dateFocus,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () => _selectEndCheckOutDate(context),
child: AbsorbPointer(
child: TextField(
focusNode: _dateFocusNode,
controller: _endDateController,
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Select Date",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(Icons.calendar_today,
size: 16, color: Colors.grey),
),
),
),
),
),
),
],
),
if (isDesktop)
Spacer()
else
SizedBox(
height: 8,
),
];
}
List<Widget> _buildThirdRow(bool isDesktop) {
return [
@ -577,7 +329,9 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
// Close Button
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(); // Close the dialog or screen
_commentsController.clear();
widget.onClose(false);// Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color
@ -596,7 +350,7 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
// Save Changes Button
ElevatedButton(
onPressed: () {
// TODO: Implement save logic
handleSave();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue, // Primary color for save

View File

@ -6,13 +6,13 @@ import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
class TaxiScreen extends StatefulWidget {
final Map<String, String> formData;
final Map<String, dynamic>? apiData;
final Function(String tab, String key, String value) updateFormData;
final Function(bool) onClose;
final Function(Map<String,dynamic>) onSavetaxi;
final Map<String,dynamic>? selectedItem;
TaxiScreen({required this.formData, required this.updateFormData,
required this.onClose, this.apiData});
TaxiScreen({
required this.onClose, this.apiData, required this.onSavetaxi, required this.selectedItem});
@override
_TaxiScreenState createState() => _TaxiScreenState();
@ -23,86 +23,101 @@ class _TaxiScreenState extends State<TaxiScreen> {
Map<String, String?> selectedValues = {};
final FocusNode _tripTypeFocusNode = FocusNode();
final FocusNode _hotelNameFocusNode = FocusNode();
final FocusNode _fromFocusNode = FocusNode();
final FocusNode _destinationFocusNode = FocusNode();
final FocusNode _locationFocusNode = FocusNode();
final FocusNode _taxiReqFocusNode = FocusNode();
final FocusNode _toFocusNode = FocusNode();
final FocusNode _dateFocusNode = FocusNode();
final FocusNode _timeFocusNode = FocusNode();
final FocusNode _numPassengerFocusNode = FocusNode();
final FocusNode _commentsFocusNode = FocusNode();
late Map<String, TextEditingController> _controllers;
late TextEditingController _tripTypeController = TextEditingController();
late TextEditingController _hotelNameController = TextEditingController();
late TextEditingController _fromController = TextEditingController();
late TextEditingController _toController = TextEditingController();
late TextEditingController _destinationController = TextEditingController();
late TextEditingController _locationController = TextEditingController();
late TextEditingController _dateController = TextEditingController();
late TextEditingController _timeController = TextEditingController();
late TextEditingController _commentsController = TextEditingController();
late TextEditingController _numPassengerController = TextEditingController();
late TextEditingController _taxiCommentsController = TextEditingController();
bool _tripTypeFocused = false;
bool _isHotelNameFocused = false;
bool _fromFocus = false;
bool _destinationFocus = false;
bool _locationFocus = false;
bool _toFocus = false;
bool _dateFocus = false;
bool _taxiReqFocused = false;
bool _numPassengerFocus = false;
bool _timeFocus = false;
bool _commentsFocus = false;
String? selectedReqTaxi;
String? selectedCarType;
Map<String , dynamic> get taxiData {
Map<String, dynamic> data ={
"destination_city": _destinationController.text,
"date": _dateController.text,
"time": _timeController.text,
"location_of_pickup": _locationController.text,
"car_required_for": selectedReqTaxi,
"no_of_passengers": _numPassengerController.text,
"car_type": selectedCarType,
"comments": _taxiCommentsController.text,
// "updated_on": ,
// "updated_by": ,
};
if (widget.selectedItem != null) {
if (widget.selectedItem?["indx"] != null && widget.selectedItem?["indx"] != 0) {
data["indx"] = widget.selectedItem!["indx"];
} else if (widget.selectedItem?["taxi_id"] != null && widget.selectedItem?["taxi_id"] != 0) {
data["taxi_id"] = widget.selectedItem!["taxi_id"];
}
}
return data;
}
TextEditingController initController(String key) {
return TextEditingController(text: widget.selectedItem?[key] ?? "");
}
@override
void initState() {
super.initState();
_addFocusListener(_tripTypeFocusNode, (focus) => _tripTypeFocused = focus);
_addFocusListener(_hotelNameFocusNode, (focus) => _isHotelNameFocused = focus);
_addFocusListener(_fromFocusNode, (focus) => _fromFocus = focus);
_addFocusListener(_toFocusNode, (focus) => _toFocus = focus);
_addFocusListener(_destinationFocusNode, (focus) => _destinationFocus = focus);
_addFocusListener(_locationFocusNode, (focus) => _locationFocus = focus);
_addFocusListener(_dateFocusNode, (focus) => _dateFocus = focus);
_addFocusListener(_timeFocusNode, (focus) => _timeFocus = focus);
_addFocusListener(_taxiReqFocusNode, (focus) => _taxiReqFocused = focus);
_addFocusListener(_numPassengerFocusNode, (focus) => _numPassengerFocus = focus);
_addFocusListener(_commentsFocusNode, (focus) => _commentsFocus = focus);
// _commentsFocusNode.addListener(() {
// setState(() {
// _commentsFocus = _commentsFocusNode.hasFocus;
// });
// });
// });
_destinationController = initController("destination_city");
_dateController = initController("date");
_timeController = initController("time");
_locationController = initController("location_of_pickup");
_numPassengerController = initController("no_of_passengers");
_taxiCommentsController = initController("comments");
_tripTypeController =
TextEditingController(text: widget.formData["tripType"] ?? "");
_hotelNameController =
TextEditingController(text: widget.formData["_hotelName"] ?? "");
_fromController =
TextEditingController(text: widget.formData["_from"] ?? "");
_toController =
TextEditingController(text: widget.formData["_Check_In_Time"] ?? "");
_dateController =
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
_timeController =
TextEditingController(text: widget.formData["_Check_Out_Time"] ?? "");
_commentsController =
TextEditingController(text: widget.formData["_comments"] ?? "");
// Save data when user types
List<TextEditingController> controllers = [
_tripTypeController, _hotelNameController, _fromController, _toController, _dateController,
_timeController, _commentsController
];
List<String> keys = [
"_tripType", "_hotelName", "_from", "_Check_In_Time", "_Check_Out",
"_Check_Out_Time", "_comments"
];
for (int i = 0; i < controllers.length; i++) {
controllers[i].addListener(() {
widget.updateFormData("Flight", keys[i], controllers[i].text);
});
// Set the selected value if available
if (widget.selectedItem != null && widget.selectedItem!["car_required_for"] != null) {
selectedReqTaxi = widget.selectedItem!["car_required_for"].toString();
}
// Set the selected value if available
if (widget.selectedItem != null && widget.selectedItem!["car_type"] != null) {
selectedCarType = widget.selectedItem!["car_type"].toString();
}
}
void _addFocusListener(FocusNode node, Function(bool) updateState) {
@ -113,21 +128,33 @@ class _TaxiScreenState extends State<TaxiScreen> {
});
}
@override
void dispose() {
_tripTypeFocusNode.dispose();
_tripTypeController.dispose();
_hotelNameController.dispose();
_fromController.dispose();
_toController.dispose();
_destinationFocusNode.dispose();
_locationFocusNode.dispose();
_destinationController.dispose();
_dateController.dispose();
_timeController.dispose();
_commentsController.dispose();
_taxiCommentsController.dispose();
super.dispose();
}
void handleSave(){
print( "Handle Save taxiData $taxiData");
widget.onSavetaxi(taxiData); // Send object to parent
widget.onClose(false);// Close screen after saving
// Clear only if this is a new entry
// if (widget.selectedItem == null) {
// _commentsController.clear();
// }
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
@ -213,7 +240,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
)).toList();
@ -227,7 +254,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
}
// Default selected value
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
selectedCarType ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
return [
@ -271,16 +298,16 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
isFocused: _fromFocus,
isFocused: _numPassengerFocus,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
focusNode: _fromFocusNode,
controller: _fromController,
focusNode: _numPassengerFocusNode,
controller: _numPassengerController,
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Destination",
labelText: "Number of Passenger",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
@ -316,8 +343,8 @@ class _TaxiScreenState extends State<TaxiScreen> {
height: 40,
child: DropdownButtonFormField<String>(
focusNode: _tripTypeFocusNode, // Assign the correct focus node
value: selectedPurpose,
focusNode: _toFocusNode, // Assign the correct focus node
value: selectedCarType,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
@ -327,12 +354,10 @@ class _TaxiScreenState extends State<TaxiScreen> {
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedPurpose = newValue;
selectedCarType = newValue;
});
print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
widget.updateFormData("Flight", "trip_type", newValue ?? "");
}
: null,
@ -360,7 +385,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
)).toList();
@ -374,19 +399,19 @@ class _TaxiScreenState extends State<TaxiScreen> {
}
// Default selected value
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
selectedReqTaxi ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
return [
CustomTextFieldWrapper(
isFocused: _isHotelNameFocused,
isFocused: _taxiReqFocused,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
focusNode: _tripTypeFocusNode, // Assign the correct focus node
value: selectedPurpose,
child: DropdownButtonFormField<String>(
focusNode: _taxiReqFocusNode, // Assign the correct focus node
value: selectedReqTaxi,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
@ -396,12 +421,9 @@ class _TaxiScreenState extends State<TaxiScreen> {
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedPurpose = newValue;
selectedReqTaxi = newValue;
});
print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
widget.updateFormData("Flight", "trip_type", newValue ?? "");
}
: null,
@ -415,86 +437,6 @@ class _TaxiScreenState extends State<TaxiScreen> {
}
List<Widget> _builClassType(bool isDesktop){
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
child: Text(item['dropdown_value']),
)).toList();
if (dropdownItems.isEmpty) {
dropdownItems.add(
DropdownMenuItem<String>(
value: null,
child: Text("No options available", style: TextStyle(color: Colors.grey)),
),
);
}
// Default selected value
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Class *",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: _isHotelNameFocused,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
focusNode: _hotelNameFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
value: selectedPurpose,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 10), // Proper padding
),
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedPurpose = newValue;
});
}
: null,
items: dropdownItems,
),
// child: TextField(
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
// style: TextStyle(fontSize: 12),
// decoration: InputDecoration(
// labelText: "Select Class",
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
// ),
// ),
),
),
],
),
];
}
List<Widget> _buildSecondRow(bool isDesktop) {
@ -558,13 +500,13 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
isFocused: _fromFocus,
isFocused: _destinationFocus,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
focusNode: _fromFocusNode,
controller: _fromController,
focusNode: _destinationFocusNode,
controller: _destinationController,
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Destination",
@ -597,14 +539,14 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
isFocused: _toFocus,
isFocused: _locationFocus,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
focusNode: _toFocusNode,
controller: _toController,
focusNode: _locationFocusNode,
controller: _locationController,
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Location of Pickup",
@ -711,8 +653,6 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
],
),
];
}
@ -737,7 +677,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
: MediaQuery.of(context).size.width * 0.66,
child: TextField(
focusNode: _commentsFocusNode,
controller: _commentsController,
controller: _taxiCommentsController,
maxLines: 6,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
@ -760,7 +700,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
// Close Button
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(); // Close the dialog or screen
widget.onClose(false);// Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color
@ -779,7 +719,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
// Save Changes Button
ElevatedButton(
onPressed: () {
// TODO: Implement save logic
handleSave();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue, // Primary color for save

View File

@ -6,19 +6,20 @@ import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
class TrainScreen extends StatefulWidget {
final Map<String, String> formData;
final Map<String, dynamic>? apiData;
final Function(String tab, String key, String value) updateFormData;
final Function(bool) onClose;
TrainScreen({required this.formData, required this.updateFormData,
required this.onClose, this.apiData});
final Map<String, dynamic>? apiData;
final Function(Map<String, dynamic>)onSavetrain;
final Function(bool) onClose;
final Map<String, dynamic>? selectedItem;
TrainScreen({
required this.onClose, this.apiData, required this.onSavetrain, required this.selectedItem});
@override
_BusScreenState createState() => _BusScreenState();
_TrainScreenState createState() => _TrainScreenState();
}
class _BusScreenState extends State<TrainScreen> {
class _TrainScreenState extends State<TrainScreen> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
Map<String, String?> selectedValues = {};
@ -31,7 +32,6 @@ class _BusScreenState extends State<TrainScreen> {
final FocusNode _timeFocusNode = FocusNode();
final FocusNode _commentsFocusNode = FocusNode();
late Map<String, TextEditingController> _controllers;
late TextEditingController _trainNoController = TextEditingController();
late TextEditingController _hotelNameController = TextEditingController();
@ -39,7 +39,7 @@ class _BusScreenState extends State<TrainScreen> {
late TextEditingController _toController = TextEditingController();
late TextEditingController _dateController = TextEditingController();
late TextEditingController _timeController = TextEditingController();
late TextEditingController _commentsController = TextEditingController();
late TextEditingController _trainCommentsController = TextEditingController();
bool _trainNoFocused = false;
bool _isHotelNameFocused = false;
@ -49,11 +49,43 @@ class _BusScreenState extends State<TrainScreen> {
bool _timeFocus = false;
bool _commentsFocus = false;
String? selectedClass;
Map<String , dynamic> get trainData {
Map<String, dynamic> data ={
"train_no": _trainNoController.text,
"class": selectedClass,
"from": _fromController.text,
"to": _toController.text,
"date": _dateController.text,
"time": _timeController.text,
"comments": _trainCommentsController.text,
};
if (widget.selectedItem != null) {
if (widget.selectedItem?["indx"] != null && widget.selectedItem?["indx"] != 0) {
data["indx"] = widget.selectedItem!["indx"];
} else if (widget.selectedItem?["train_id"] != null && widget.selectedItem?["train_id"] != 0) {
data["train_id"] = widget.selectedItem!["train_id"];
}
}
return data;
}
TextEditingController initController(String key) {
return TextEditingController(text: widget.selectedItem?[key] ?? "");
}
@override
void initState() {
super.initState();
_trainNoFocusNode.addListener(() {
_trainNoFocusNode.addListener(() {
setState(() {
_trainNoFocused = _trainNoFocusNode.hasFocus;
});
@ -68,7 +100,6 @@ class _BusScreenState extends State<TrainScreen> {
_fromFocus = _fromFocusNode.hasFocus;
});
});
_toFocusNode.addListener(() {
setState(() {
_toFocus = _toFocusNode.hasFocus;
@ -79,13 +110,11 @@ class _BusScreenState extends State<TrainScreen> {
_dateFocus = _fromFocusNode.hasFocus;
});
});
_timeFocusNode.addListener(() {
setState(() {
_timeFocus = _timeFocusNode.hasFocus;
});
});
_commentsFocusNode.addListener(() {
setState(() {
_commentsFocus = _commentsFocusNode.hasFocus;
@ -93,53 +122,20 @@ class _BusScreenState extends State<TrainScreen> {
});
_trainCommentsController = initController("comments");
_trainNoController = initController("train_no");
_fromController = initController("from");
_toController = initController("to");
_dateController = initController("date");
_timeController = initController("time");
_trainNoController =
TextEditingController(text: widget.formData["trainNo"] ?? "");
_hotelNameController =
TextEditingController(text: widget.formData["_hotelName"] ?? "");
_fromController =
TextEditingController(text: widget.formData["_from"] ?? "");
_toController =
TextEditingController(text: widget.formData["_Check_In_Time"] ?? "");
_dateController =
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
_timeController =
TextEditingController(text: widget.formData["_Check_Out_Time"] ?? "");
_commentsController =
TextEditingController(text: widget.formData["_comments"] ?? "");
// Save data when user types
_trainNoController.addListener(() {
widget.updateFormData(
"Train", "_trainNo", _trainNoController.text);
}); // Save data when user types
_hotelNameController.addListener(() {
widget.updateFormData(
"Train", "_hotelName", _hotelNameController.text);
});
_fromController.addListener(() {
widget.updateFormData(
"Train", "_from", _fromController.text);
});
_toController.addListener(() {
widget.updateFormData(
"Train", "_Check_In_Time", _toController.text);
});
_dateController.addListener(() {
widget.updateFormData(
"Train", "_Check_Out", _dateController.text);
});
_timeController.addListener(() {
widget.updateFormData(
"Train", "_Check_Out_Time", _timeController.text);
});
_commentsController.addListener(() {
widget.updateFormData(
"Train", "_comments", _commentsController.text);
});
// Set the selected value if available
if (widget.selectedItem != null && widget.selectedItem!["class"] != null) {
selectedClass = widget.selectedItem!["class"].toString();
}
}
@override
void dispose() {
_trainNoFocusNode.dispose();
@ -149,11 +145,22 @@ class _BusScreenState extends State<TrainScreen> {
_toController.dispose();
_dateController.dispose();
_timeController.dispose();
_commentsController.dispose();
_trainCommentsController.dispose();
super.dispose();
}
void handleSave(){
print( "Handle Save trainData $trainData");
widget.onSavetrain(trainData); // Send object to parent
widget.onClose(false);// Close screen after saving
// Clear only if this is a new entry
// if (widget.selectedItem == null) {
// _commentsController.clear();
// }
}
@override
Widget build(BuildContext context) {
@ -312,29 +319,6 @@ class _BusScreenState extends State<TrainScreen> {
),
),
// child: DropdownButtonFormField<String>(
// focusNode: _trainNoFocusNode, // Assign the correct focus node
// value: selectedPurpose,
// style: TextStyle(fontSize: 12),
// decoration: InputDecoration(
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(
// horizontal: 10), // Proper padding
// ),
// onChanged: purposeList.isNotEmpty
// ? (newValue) {
// setState(() {
// selectedPurpose = newValue;
// });
// print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
//
//
// widget.updateFormData("Flight", "trip_type", newValue ?? "");
// }
// : null,
//
// items: dropdownItems,
// ),
),
@ -349,7 +333,7 @@ class _BusScreenState extends State<TrainScreen> {
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
)).toList();
@ -363,7 +347,7 @@ class _BusScreenState extends State<TrainScreen> {
}
// Default selected value
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
selectedClass ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
return [
Column(
@ -386,7 +370,7 @@ class _BusScreenState extends State<TrainScreen> {
child: DropdownButtonFormField<String>(
focusNode: _hotelNameFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
value: selectedPurpose,
value: selectedClass,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
@ -396,25 +380,14 @@ class _BusScreenState extends State<TrainScreen> {
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedPurpose = newValue;
selectedClass = newValue;
});
}
: null,
items: dropdownItems,
),
// child: TextField(
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
// style: TextStyle(fontSize: 12),
// decoration: InputDecoration(
// labelText: "Select Class",
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
// ),
// ),
),
),
],
@ -665,12 +638,12 @@ class _BusScreenState extends State<TrainScreen> {
: MediaQuery.of(context).size.width * 0.66,
child: TextField(
focusNode: _commentsFocusNode,
controller: _commentsController,
controller: _trainCommentsController,
maxLines: 6,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Description",
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
@ -688,7 +661,7 @@ class _BusScreenState extends State<TrainScreen> {
// Close Button
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(); // Close the dialog or screen
widget.onClose(false);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color
@ -707,7 +680,7 @@ class _BusScreenState extends State<TrainScreen> {
// Save Changes Button
ElevatedButton(
onPressed: () {
// TODO: Implement save logic
handleSave();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue, // Primary color for save

View File

@ -1,3 +1,4 @@
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:responsive_builder/responsive_builder.dart';
@ -6,13 +7,17 @@ import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
class VisaScreen extends StatefulWidget {
final Map<String, String> formData;
final Map<String, dynamic>? apiData;
final Function(String tab, String key, String value) updateFormData;
final Function(bool) onClose;
VisaScreen({required this.formData, required this.updateFormData,
required this.onClose, this.apiData});
final Map<String, dynamic>? apiData;
final List<dynamic>? apiCountryData;
final Function(bool) onClose;
final Function(Map<String,dynamic>) onSaveVisa;
final Map<String, dynamic>? selectedItem;
VisaScreen({
required this.onClose,required this.onSaveVisa, this.apiData, required this.selectedItem, required this.apiCountryData});
@override
_VisaScreenState createState() => _VisaScreenState();
@ -23,15 +28,13 @@ class _VisaScreenState extends State<VisaScreen> {
Map<String, String?> selectedValues = {};
List<dynamic> countryList = [];
final FocusNode _tripTypeFocusNode = FocusNode();
final FocusNode _hotelNameFocusNode = FocusNode();
final FocusNode _fromFocusNode = FocusNode();
final FocusNode _toFocusNode = FocusNode();
final FocusNode _dateFocusNode = FocusNode();
final FocusNode _timeFocusNode = FocusNode();
final FocusNode _commentsFocusNode = FocusNode();
late Map<String, TextEditingController> _controllers;
late TextEditingController _tripTypeController = TextEditingController();
late TextEditingController _hotelNameController = TextEditingController();
@ -39,72 +42,65 @@ class _VisaScreenState extends State<VisaScreen> {
late TextEditingController _toController = TextEditingController();
late TextEditingController _dateController = TextEditingController();
late TextEditingController _timeController = TextEditingController();
late TextEditingController _commentsController = TextEditingController();
late TextEditingController _visaCommentsController = TextEditingController();
bool _tripTypeFocused = false;
bool _isHotelNameFocused = false;
bool _fromFocus = false;
bool _toFocus = false;
bool _dateFocus = false;
bool _timeFocus = false;
bool _commentsFocus = false;
String? selectedPurpose;
String? selectedCountry;
Map<String, dynamic> get visaData{
Map<String, dynamic> data ={
"type_of_visa" :selectedPurpose,
"country": selectedCountry,
// "country_code": selectedCountry,
"start_date": _dateController.text,
"comments":_visaCommentsController.text
};
if (widget.selectedItem != null) {
if (widget.selectedItem?["indx"] != null && widget.selectedItem?["indx"] != 0) {
data["indx"] = widget.selectedItem!["indx"];
} else if (widget.selectedItem?["visa_id"] != null && widget.selectedItem?["visa_id"] != 0) {
data["visa_id"] = widget.selectedItem!["visa_id"];
}
}
return data;
}
@override
void initState() {
super.initState();
_addFocusListener(_tripTypeFocusNode, (focus) => _tripTypeFocused = focus);
_addFocusListener(_hotelNameFocusNode, (focus) => _isHotelNameFocused = focus);
_addFocusListener(_fromFocusNode, (focus) => _fromFocus = focus);
_addFocusListener(_toFocusNode, (focus) => _toFocus = focus);
_addFocusListener(_dateFocusNode, (focus) => _dateFocus = focus);
_addFocusListener(_timeFocusNode, (focus) => _timeFocus = focus);
_addFocusListener(_commentsFocusNode, (focus) => _commentsFocus = focus);
// _commentsFocusNode.addListener(() {
// setState(() {
// _commentsFocus = _commentsFocusNode.hasFocus;
// });
// });
// });
_tripTypeController =
TextEditingController(text: widget.formData["tripType"] ?? "");
_hotelNameController =
TextEditingController(text: widget.formData["_hotelName"] ?? "");
_fromController =
TextEditingController(text: widget.formData["_from"] ?? "");
_toController =
TextEditingController(text: widget.formData["_Check_In_Time"] ?? "");
_visaCommentsController =
TextEditingController(text: widget.selectedItem?["comments"] ?? "");
_dateController =
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
_timeController =
TextEditingController(text: widget.formData["_Check_Out_Time"] ?? "");
_commentsController =
TextEditingController(text: widget.formData["_comments"] ?? "");
TextEditingController(text: widget.selectedItem?["start_date"] ?? "");
// Save data when user types
List<TextEditingController> controllers = [
_tripTypeController, _hotelNameController, _fromController, _toController, _dateController,
_timeController, _commentsController
];
List<String> keys = [
"_tripType", "_hotelName", "_from", "_Check_In_Time", "_Check_Out",
"_Check_Out_Time", "_comments"
];
for (int i = 0; i < controllers.length; i++) {
controllers[i].addListener(() {
widget.updateFormData("Flight", keys[i], controllers[i].text);
});
// Set the selected value if available
if (widget.selectedItem != null && widget.selectedItem!["type_of_visa"] != null) {
selectedPurpose = widget.selectedItem!["type_of_visa"].toString();
}
if (widget.selectedItem != null && widget.selectedItem!["selectedCountry"] != null) {
selectedPurpose = widget.selectedItem!["selectedCountry"].toString();
}
}
void _addFocusListener(FocusNode node, Function(bool) updateState) {
node.addListener(() {
setState(() {
@ -122,11 +118,23 @@ class _VisaScreenState extends State<VisaScreen> {
_toController.dispose();
_dateController.dispose();
_timeController.dispose();
_commentsController.dispose();
_visaCommentsController.dispose();
super.dispose();
}
void handleSave(){
print( "Handle Save miscellaneousData $visaData");
widget.onSaveVisa(visaData); // Send object to parent
widget.onClose(false);// Close screen after saving
// // Clear only if this is a new entry
// if (widget.selectedItem == null) {
// _visaCommentsController.clear();
// }
}
@override
Widget build(BuildContext context) {
@ -184,7 +192,6 @@ class _VisaScreenState extends State<VisaScreen> {
}
List<List<Widget>> rowBuilders = [
// _builClassType(isDesktop),
_buildSecondRow(isDesktop)
];
@ -249,7 +256,7 @@ class _VisaScreenState extends State<VisaScreen> {
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
)).toList();
@ -263,7 +270,7 @@ class _VisaScreenState extends State<VisaScreen> {
}
// Default selected value
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
selectedPurpose ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
return [
@ -290,7 +297,6 @@ class _VisaScreenState extends State<VisaScreen> {
print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
widget.updateFormData("Flight", "trip_type", newValue ?? "");
}
: null,
@ -304,110 +310,51 @@ class _VisaScreenState extends State<VisaScreen> {
}
List<Widget> _builClassType(bool isDesktop){
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
child: Text(item['dropdown_value']),
)).toList();
if (dropdownItems.isEmpty) {
dropdownItems.add(
DropdownMenuItem<String>(
value: null,
child: Text("No options available", style: TextStyle(color: Colors.grey)),
),
);
}
// Default selected value
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Country",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: _isHotelNameFocused,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
focusNode: _hotelNameFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
value: selectedPurpose,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 10), // Proper padding
),
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedPurpose = newValue;
});
}
: null,
items: dropdownItems,
),
// child: TextField(
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
// style: TextStyle(fontSize: 12),
// decoration: InputDecoration(
// labelText: "Select Class",
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
// ),
// ),
),
),
],
),
];
}
List<Widget> _buildSecondRow(bool isDesktop) {
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
// List<dynamic> countryList = widget.apiCountryData ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
child: Text(item['dropdown_value']),
)).toList();
//
// List<DropdownMenuItem<String>> dropdownItems = countryList
// .map((item)=>DropdownMenuItem<String>(
// value: item['country_code'], // Use 'country_code' from API response
// child: Text(item['country_name']),
// )).toList();
//
// if (dropdownItems.isEmpty) {
// dropdownItems.add(
// DropdownMenuItem<String>(
// value: null,
// child: Text("No options available", style: TextStyle(color: Colors.grey)),
// ),
// );
// }
//
// // Default selected value
// selectedCountry ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
// List<String> countryNames = countryList.map((item) => item['country_name'] as String).toList();
//
if (dropdownItems.isEmpty) {
dropdownItems.add(
DropdownMenuItem<String>(
value: null,
child: Text("No options available", style: TextStyle(color: Colors.grey)),
),
);
late Map<String, String> countryMap; // Mapping country_code -> country_name
late List<String> countryCodes; // List of country codes
countryList = widget.apiCountryData ?? [];
// Map country codes to country names
countryMap = {
for (var item in countryList) item['country_code'] as String: item['country_name'] as String
};
// Extract only country codes for processing
countryCodes = countryMap.keys.toList();
// Set default selected value
if (selectedCountry == null && countryCodes.isNotEmpty) {
selectedCountry = countryCodes.first;
}
// Default selected value
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
// ____________
DateTime? _selectedCheckOutDate;
TimeOfDay? _selectedCheckOutTime;
@ -454,41 +401,71 @@ class _VisaScreenState extends State<VisaScreen> {
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
focusNode: _hotelNameFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
value: selectedPurpose,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 10), // Proper padding
child: DropdownSearch<String>(
selectedItem: countryMap[selectedCountry],
popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search Country...",
contentPadding: EdgeInsets.symmetric(horizontal: 10),
),
),
),
onChanged: purposeList.isNotEmpty
? (newValue) {
items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1,),
),
),
dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Country",
style: TextStyle(fontSize: 12),
),
),
onChanged: (String? newValue) {
setState(() {
selectedPurpose = newValue;
// Find the country_code based on selected country_name
selectedCountry = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
});
}
: null,
items: dropdownItems,
},
),
// child: TextField(
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
// style: TextStyle(fontSize: 12),
// decoration: InputDecoration(
// labelText: "Select Class",
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
// ),
// ),
),
),
// CustomTextFieldWrapper(
// isFocused: _isHotelNameFocused,
// isDesktop: isDesktop,
// child: SizedBox(
// height: 40,
//
// child: DropdownButtonFormField<String>(
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
// // controller: _hotelNameController,
// value: selectedCountry,
// style: TextStyle(fontSize: 12),
// decoration: InputDecoration(
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(
// horizontal: 10), // Proper padding
// ),
// onChanged: countryList.isNotEmpty
// ? (newValue) {
// setState(() {
// selectedCountry = newValue;
// });
// }
// : null,
// items: dropdownItems,
// ),
//
//
// ),
// ),
],
),
@ -502,7 +479,7 @@ class _VisaScreenState extends State<VisaScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Date",
"Start Date",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -566,7 +543,7 @@ class _VisaScreenState extends State<VisaScreen> {
: MediaQuery.of(context).size.width * 0.66,
child: TextField(
focusNode: _commentsFocusNode,
controller: _commentsController,
controller: _visaCommentsController,
maxLines: 6,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
@ -589,7 +566,7 @@ class _VisaScreenState extends State<VisaScreen> {
// Close Button
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(); // Close the dialog or screen
widget.onClose(false); // Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color
@ -608,7 +585,7 @@ class _VisaScreenState extends State<VisaScreen> {
// Save Changes Button
ElevatedButton(
onPressed: () {
// TODO: Implement save logic
handleSave();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue, // Primary color for save

View File

@ -1,7 +1,12 @@
import 'package:flutter/material.dart';
class AccomodationListWidget extends StatelessWidget {
const AccomodationListWidget({super.key});
final List<Map<String,dynamic>> accommodationList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String, dynamic>) onDeleteAccommodation;
const AccomodationListWidget({super.key, required this.accommodationList, required this.onOpen, required this.onDeleteAccommodation});
@override
Widget build(BuildContext context) {
@ -26,10 +31,11 @@ class AccomodationListWidget extends StatelessWidget {
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('Trip Type')),
DataColumn(label: Text('Class')),
DataColumn(label: Text('From')),
DataColumn(label: Text('To')),
DataColumn(label: Text('#')),
DataColumn(label: Text('Destination City')),
DataColumn(label: Text('Hotel Name')),
DataColumn(label: Text('CheckIn Date')),
DataColumn(label: Text('CheckOut Date')),
DataColumn(label: Text('Actions')),
],
rows: _buildDataRows(),
@ -42,17 +48,19 @@ class AccomodationListWidget extends StatelessWidget {
}
List<DataRow> _buildDataRows() {
List<Map<String, String>> data = [
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
];
return data.map((bus) {
return accommodationList.asMap() .entries.map((entry) {
final Map<String, dynamic> item = entry.value;
return DataRow(cells: [
DataCell(Text(bus["tripType"]!)),
DataCell(Text(bus["class"]!)),
DataCell(Text(bus["from"]!)),
DataCell(Text(bus["to"]!)),
DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
DataCell(Text(item["destination_city"]!)),
DataCell(Text(item["hotel_name"]!)),
DataCell(Text(item["checkin_date"]!)),
DataCell(Text(item["checkout_date"]!)),
DataCell(Row(
children: [
IconButton(
@ -64,13 +72,13 @@ class AccomodationListWidget extends StatelessWidget {
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
onPressed: () {
// Edit action
onOpen(true, item, "Accommodation");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
// Delete action
onDeleteAccommodation(item);
},
),
],

View File

@ -8,61 +8,12 @@ import '../../config/apiUrl.dart';
import '../../data/models/plan.dart';
class BusListWidget extends StatefulWidget{
const BusListWidget({super.key});
class BusListWidget extends StatelessWidget{
final List<Map<String, dynamic>> busList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String,dynamic>) onDeleteBus;
const BusListWidget({super.key, required this.busList, required this.onOpen, required this.onDeleteBus});
@override
_BusListWidgetState createState() => _BusListWidgetState();
}
class _BusListWidgetState extends State<BusListWidget> {
// const BusListWidget({super.key});
late Future<List<Plan>> futurePlans;
@override
void initState(){
super.initState();
futurePlans = fetchPlans();
}
Future<String?> getToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('auth_token');
}
// Fetch API Data
Future<List<Plan>> fetchPlans() async {
final String apiUrldata = '$apiUrl/api/plans';
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', // Add token here
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
List<dynamic> plansJson = data['data'];
return plansJson.map((json) => Plan.fromJson(json)).toList();
} else {
throw Exception('Failed to load plans');
}
}
@override
Widget build(BuildContext context) {
@ -91,10 +42,12 @@ Future<List<Plan>> fetchPlans() async {
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('Trip Type')),
DataColumn(label: Text('Class')),
DataColumn(label: Text('#')),
DataColumn(label: Text('From')),
DataColumn(label: Text('To')),
DataColumn(label: Text('Date')),
DataColumn(label: Text('Time')),
DataColumn(label: Text('Actions')),
],
@ -113,17 +66,18 @@ Future<List<Plan>> fetchPlans() async {
}
List<DataRow> _buildDataRows() {
List<Map<String, String>> data = [
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
];
return data.map((bus) {
return busList.asMap().entries.map((entry) {
final Map<String, dynamic> item = entry.value;
return DataRow(cells: [
DataCell(Text(bus["tripType"]!)),
DataCell(Text(bus["class"]!)),
DataCell(Text(bus["from"]!)),
DataCell(Text(bus["to"]!)),
DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
DataCell(Text(item["from"]!)),
DataCell(Text(item["to"]!)),
DataCell(Text(item["date"]!)),
DataCell(Text(item["time"]!)),
DataCell(Row(
children: [
IconButton(
@ -135,13 +89,13 @@ Future<List<Plan>> fetchPlans() async {
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
onPressed: () {
// Edit action
onOpen(true, item, "Bus");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
// Delete action
onDeleteBus(item);
},
),
],

View File

@ -1,67 +1,14 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:responsive_builder/responsive_builder.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../config/apiUrl.dart';
import '../../data/models/plan.dart';
class ForexListWidget extends StatefulWidget{
const ForexListWidget({super.key});
class ForexListWidget extends StatelessWidget{
final List<Map<String,dynamic>> forexList;
final Function(bool, Map<String,dynamic>, String)onOpen;
final Function(Map<String,dynamic>) onDeleteForex;
@override
_ForexListWidgetState createState() => _ForexListWidgetState();
}
class _ForexListWidgetState extends State<ForexListWidget> {
// const BusListWidget({super.key});
late Future<List<Plan>> futurePlans;
@override
void initState(){
super.initState();
futurePlans = fetchPlans();
}
Future<String?> getToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('auth_token');
}
// Fetch API Data
Future<List<Plan>> fetchPlans() async {
final String apiUrldata = '$apiUrl/api/plans';
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', // Add token here
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
List<dynamic> plansJson = data['data'];
return plansJson.map((json) => Plan.fromJson(json)).toList();
} else {
throw Exception('Failed to load plans');
}
}
const ForexListWidget({super.key, required this.forexList, required this.onOpen, required this.onDeleteForex});
@override
Widget build(BuildContext context) {
@ -90,11 +37,11 @@ class _ForexListWidgetState extends State<ForexListWidget> {
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('Trip Type')),
DataColumn(label: Text('Class')),
DataColumn(label: Text('From')),
DataColumn(label: Text('To')),
DataColumn(label: Text('#')),
DataColumn(label: Text('Forex Start Date')),
DataColumn(label: Text('Forex End Date')),
DataColumn(label: Text('Country')),
DataColumn(label: Text('Perdiem Amount')),
DataColumn(label: Text('Actions')),
],
rows: _buildDataRows(),
@ -112,17 +59,18 @@ class _ForexListWidgetState extends State<ForexListWidget> {
}
List<DataRow> _buildDataRows() {
List<Map<String, String>> data = [
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
];
return data.map((bus) {
return forexList.asMap().entries.map((entry) {
Map<String,dynamic> item = entry.value;
return DataRow(cells: [
DataCell(Text(bus["tripType"]!)),
DataCell(Text(bus["class"]!)),
DataCell(Text(bus["from"]!)),
DataCell(Text(bus["to"]!)),
DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
DataCell(Text(item["start_date"] ?? "N/A")),
DataCell(Text(item["end_date"] ?? "N/A")),
DataCell(Text(item["country_code"] ?? "N/A")),
DataCell(Text(item["perdiem_amount"] ?? "N/A")),
DataCell(Row(
children: [
IconButton(
@ -134,13 +82,13 @@ class _ForexListWidgetState extends State<ForexListWidget> {
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
onPressed: () {
// Edit action
onOpen(true, item, "Forex");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
// Delete action
onDeleteForex(item);
},
),
],

View File

@ -1,7 +1,12 @@
import 'dart:js_interop';
import 'package:flutter/material.dart';
class InsuranceListWidget extends StatelessWidget {
const InsuranceListWidget({super.key});
final List<Map<String,dynamic>> insuranceList;
final Function(bool, Map<String,dynamic>, String)onOpen;
final Function(Map<String,dynamic>)onDeleteInsurance;
const InsuranceListWidget({super.key, required this.insuranceList, required this.onOpen,required this.onDeleteInsurance});
@override
Widget build(BuildContext context) {
@ -27,10 +32,10 @@ class InsuranceListWidget extends StatelessWidget {
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('Trip Type')),
DataColumn(label: Text('Class')),
DataColumn(label: Text('From')),
DataColumn(label: Text('To')),
DataColumn(label: Text('#')),
DataColumn(label: Text('Insurance Type')),
DataColumn(label: Text('Start Date')),
DataColumn(label: Text('End Date')),
DataColumn(label: Text('Actions')),
],
rows: _buildDataRows(),
@ -45,17 +50,16 @@ class InsuranceListWidget extends StatelessWidget {
}
List<DataRow> _buildDataRows() {
List<Map<String, String>> data = [
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
];
return data.map((bus) {
return insuranceList.asMap().entries.map((entry) {
Map<String,dynamic> item = entry.value;
return DataRow(cells: [
DataCell(Text(bus["tripType"]!)),
DataCell(Text(bus["class"]!)),
DataCell(Text(bus["from"]!)),
DataCell(Text(bus["to"]!)),
DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
DataCell(Text(item["type_of_insurance"]!)),
DataCell(Text(item["start_date"]!)),
DataCell(Text(item["end_date"]!)),
DataCell(Row(
children: [
IconButton(
@ -67,13 +71,13 @@ class InsuranceListWidget extends StatelessWidget {
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
onPressed: () {
// Edit action
onOpen(true, item, "Insurance");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
// Delete action
onDeleteInsurance(item);
},
),
],

View File

@ -1,7 +1,13 @@
import 'package:flutter/material.dart';
class MiscellaneousListWidget extends StatelessWidget {
const MiscellaneousListWidget({super.key});
final List<Map<String,dynamic>> miscellaneousList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String, dynamic>) onDeleteMiscellaneous;
const MiscellaneousListWidget({super.key, required this.miscellaneousList, required this.onOpen,
required this.onDeleteMiscellaneous});
@override
Widget build(BuildContext context) {
@ -27,10 +33,10 @@ class MiscellaneousListWidget extends StatelessWidget {
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('Trip Type')),
DataColumn(label: Text('Class')),
DataColumn(label: Text('From')),
DataColumn(label: Text('To')),
DataColumn(label: Text('#')),
DataColumn(label: Text('Special Request')),
DataColumn(label: Text('Comments')),
DataColumn(label: Text('Created On')),
DataColumn(label: Text('Actions')),
],
rows: _buildDataRows(),
@ -45,17 +51,17 @@ class MiscellaneousListWidget extends StatelessWidget {
}
List<DataRow> _buildDataRows() {
List<Map<String, String>> data = [
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
];
print("miscellaneousList - $miscellaneousList");
return data.map((bus) {
return miscellaneousList.asMap().entries.map( (entry) {
int index = entry.key + 1; // To start index from 1
Map<String, dynamic> item = entry.value;
print(item);
return DataRow(cells: [
DataCell(Text(bus["tripType"]!)),
DataCell(Text(bus["class"]!)),
DataCell(Text(bus["from"]!)),
DataCell(Text(bus["to"]!)),
DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
DataCell(Text(item["special_request"] ?? "N/A")),
DataCell(Text(item["comments"] ?? "N/A")),
DataCell(Text(item["created_on"] ?? "N/A")),
DataCell(Row(
children: [
IconButton(
@ -67,12 +73,14 @@ class MiscellaneousListWidget extends StatelessWidget {
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
onPressed: () {
onOpen(true, item, "Miscellaneous");
// Edit action
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
onDeleteMiscellaneous(item);
// Delete action
},
),

View File

@ -1,7 +1,11 @@
import 'package:flutter/material.dart';
class TaxiListWidget extends StatelessWidget {
const TaxiListWidget({super.key});
final List<Map<String,dynamic>> taxiList;
final Function(bool, Map<String,dynamic>, String) onOpen;
final Function(Map<String,dynamic>) onDeleteTaxi;
const TaxiListWidget({super.key, required this.taxiList, required this.onOpen, required this.onDeleteTaxi});
@override
Widget build(BuildContext context) {
@ -26,10 +30,11 @@ class TaxiListWidget extends StatelessWidget {
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('Trip Type')),
DataColumn(label: Text('Class')),
DataColumn(label: Text('From')),
DataColumn(label: Text('To')),
DataColumn(label: Text('#')),
DataColumn(label: Text('Destination')),
DataColumn(label: Text('Location Of Pickup')),
DataColumn(label: Text('Date')),
DataColumn(label: Text('Taxi Required For')),
DataColumn(label: Text('Actions')),
],
rows: _buildDataRows(),
@ -43,17 +48,18 @@ class TaxiListWidget extends StatelessWidget {
}
List<DataRow> _buildDataRows() {
List<Map<String, String>> data = [
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
];
return data.map((bus) {
return taxiList.asMap().entries.map((entry) {
final Map<String, dynamic> item = entry.value;
return DataRow(cells: [
DataCell(Text(bus["tripType"]!)),
DataCell(Text(bus["class"]!)),
DataCell(Text(bus["from"]!)),
DataCell(Text(bus["to"]!)),
DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
DataCell(Text(item["destination_city"]!)),
DataCell(Text(item["location_of_pickup"]!)),
DataCell(Text(item["date"]!)),
DataCell(Text(item["car_required_for"]!)),
DataCell(Row(
children: [
IconButton(
@ -65,13 +71,13 @@ class TaxiListWidget extends StatelessWidget {
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
onPressed: () {
// Edit action
onOpen(true, item, "Taxi");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
// Delete action
onDeleteTaxi(item);
},
),
],

View File

@ -1,7 +1,10 @@
import 'package:flutter/material.dart';
class TrainListWidget extends StatelessWidget {
const TrainListWidget({super.key});
final List<Map<String,dynamic>> trainList;
final Function(bool, Map<String,dynamic>, String) onOpen;
final Function(Map<String,dynamic>) onDeleteTrain;
const TrainListWidget({super.key, required this.trainList, required this.onOpen, required this.onDeleteTrain});
@override
Widget build(BuildContext context) {
@ -27,7 +30,8 @@ class TrainListWidget extends StatelessWidget {
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('Trip Type')),
DataColumn(label: Text('#')),
DataColumn(label: Text('Train Number')),
DataColumn(label: Text('Class')),
DataColumn(label: Text('From')),
DataColumn(label: Text('To')),
@ -45,17 +49,17 @@ class TrainListWidget extends StatelessWidget {
}
List<DataRow> _buildDataRows() {
List<Map<String, String>> data = [
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
];
return data.map((bus) {
return trainList.asMap().entries.map((entry) {
final Map<String,dynamic> item = entry.value;
return DataRow(cells: [
DataCell(Text(bus["tripType"]!)),
DataCell(Text(bus["class"]!)),
DataCell(Text(bus["from"]!)),
DataCell(Text(bus["to"]!)),
DataCell(Text(item["indx"]?.toString() ?? "N/A")),
DataCell(Text(item["train_no"]!)),
DataCell(Text(item["class"]!)),
DataCell(Text(item["from"]!)),
DataCell(Text(item["to"]!)),
DataCell(Row(
children: [
IconButton(
@ -67,13 +71,13 @@ class TrainListWidget extends StatelessWidget {
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
onPressed: () {
// Edit action
onOpen(true, item, "Train");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
// Delete action
onDeleteTrain(item);
},
),
],

View File

@ -1,7 +1,11 @@
import 'package:flutter/material.dart';
class VisaListWidget extends StatelessWidget {
const VisaListWidget({super.key});
final List<Map<String, dynamic>> visaList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String, dynamic>) onDeleteMiscellaneous;
const VisaListWidget({super.key, required this.visaList, required this.onOpen,required this.onDeleteMiscellaneous});
@override
Widget build(BuildContext context) {
@ -27,10 +31,10 @@ class VisaListWidget extends StatelessWidget {
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('Trip Type')),
DataColumn(label: Text('Class')),
DataColumn(label: Text('From')),
DataColumn(label: Text('To')),
DataColumn(label: Text('#')),
DataColumn(label: Text('Type of Visa')),
DataColumn(label: Text('Country')),
DataColumn(label: Text('Start Date')),
DataColumn(label: Text('Actions')),
],
rows: _buildDataRows(),
@ -45,17 +49,17 @@ class VisaListWidget extends StatelessWidget {
}
List<DataRow> _buildDataRows() {
List<Map<String, String>> data = [
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
];
return data.map((bus) {
return visaList.asMap().entries.map((entry){
int index = entry.key + 1; // To start index from 1
Map<String, dynamic> item = entry.value;
print(item);
return DataRow(cells: [
DataCell(Text(bus["tripType"]!)),
DataCell(Text(bus["class"]!)),
DataCell(Text(bus["from"]!)),
DataCell(Text(bus["to"]!)),
DataCell(Text(item["indx"]?.toString() ?? "N/A")),
DataCell(Text(item["type_of_visa"]!)),
DataCell(Text(item["country"]!)),
DataCell(Text(item["start_date"]!)),
DataCell(Row(
children: [
IconButton(
@ -67,13 +71,13 @@ class VisaListWidget extends StatelessWidget {
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
onPressed: () {
// Edit action
onOpen(true, item, "Visa");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
// Delete action
onDeleteMiscellaneous(item);
},
),
],

View File

@ -11,6 +11,7 @@ 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';
@ -90,36 +91,190 @@ class CreateNewPlan extends StatefulWidget {
}
class _CreateNewPlansState extends State<CreateNewPlan> {
final FocusNode _textFieldFocusNode = FocusNode(); // Declare FocusNode
bool _isTextFieldFocused = false;
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";
Map<String, dynamic>? storedData;
// late String? _selectedIsBillable = "Billable";
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>> trainList = [];
List<Map<String, dynamic>> busList = [];
List<Map<String, dynamic>> taxiList = [];
//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_on": "2025-02-10 14:38:21",
"created_by": selfId,
// "updated_on": null,
"updated_by": null,
"is_active": "1",
"flight":[],
"accommodation": [],
"bus": [],
"insurance": [],
"miscellaneous": miscellaneousList,
"taxi": [],
"train": [],
"visa": [],
};
// 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 "Train":
trainList = newList;
break;
case "Bus":
busList = newList;
break;
case "Taxi":
taxiList = newList;
break;
case "Forex":
taxiList = newList;
break;
case "Accommodation":
taxiList = newList;
break;
default:
print("Unknown itinerary type: $type");
}
});
print("Updated $type List: $newList");
}
@override
void initState() {
void initState(){
super.initState();
fetchPlans();
fetchUserDetails();
_textFieldFocusNode.addListener(() {
fetchPlans();
fetchCostCenter();
fetchCountryList();
// _tripTitleController.addListener(() {
// print("Current Value: ${_tripTitleController.text}");
// });
_tripTitleFocusNode.addListener(() {
setState(() {
_isTextFieldFocused = _textFieldFocusNode.hasFocus;
_isTripTitleFocused = _tripTitleFocusNode.hasFocus;
});
});
_descriptionFocusNode.addListener(() {
setState(() {
_isdescriptionFocused = _descriptionFocusNode.hasFocus;
});
});
}
@override
void dispose() {
_textFieldFocusNode.dispose();
_tripTitleFocusNode.dispose();
_descriptionFocusNode.dispose();
super.dispose();
}
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();
@ -132,7 +287,20 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
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 {
@ -167,7 +335,7 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
Map<String, dynamic> plansJson = data['data']; // 'data' is a Map, not a List
setState(() {
apiData = data['data']; // Store API response in state
apiData = plansJson; // Store API response in state
isLoading = false;
});
@ -180,22 +348,176 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
}
}
Future<void> fetchCostCenter() async {
final String apiUrldata = '$apiUrl/api/getCostCenterMaster';
final token = await getToken();
Future<void> getStoredData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? jsonString = prefs.getString('api_response');
// final userId = await getUserId();
if (jsonString != null) {
storedData = json.decode(jsonString);
print('Stored Data: $storedData');
} else {
storedData = null;
// 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'];
}
});
print('plansJSON');
} catch (e) {
throw Exception('Error parsing response: $e');
}
} else {
throw Exception('Failed to load plans');
}
return;
}
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);
}
});
}
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.');
}
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;
@ -210,14 +532,36 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Plan This Trip For", // Your label
style: TextStyle(
// 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)),
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: 5),
SizedBox(height: 7),
isMobile
? SingleChildScrollView(
scrollDirection: Axis.horizontal,
@ -254,12 +598,13 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: _isTextFieldFocused,
isFocused: _isTripTitleFocused,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
focusNode: _textFieldFocusNode,
focusNode: _tripTitleFocusNode,
controller: _tripTitleController,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Trip Title",
@ -285,7 +630,7 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Trip Type", // Your label
"Trip Type *", // Your label
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -302,6 +647,14 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
: 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),
),
),
],
),
),
@ -336,6 +689,8 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
_buildNonDescriptionColumn(),
SizedBox(height: 15),
_buildDescriptionColumn(isDesktop),
],
),
@ -349,10 +704,19 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
Row(
children: [
Expanded(child: DynamicItinerary(apiData: apiData)), // Wrap with Expanded if needed
Expanded(child: DynamicItinerary(apiData: apiData, apiCountryData: apiCountryData,
onItineraryUpdate: handleItineraryUpdate,
)), // Wrap with Expanded if needed
],
),
SizedBox(height: 15),
isDesktop
? Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _buildSubmit(isDesktop),)
:Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _buildSubmit(isDesktop),)
],
);
@ -362,6 +726,9 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
/// Extracted helper function
List<Widget> _buildCostIsBillable() {
List<dynamic> purposeList = apiData?['plan_is_billable'] ?? [];
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -380,72 +747,114 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
child: SizedBox(
height: 45, // Set appropriate height
child: DropdownButtonFormField<String>(
value: "Option 1",
value: selectedCostCenterId,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
),
onChanged: (newValue) {},
items: [
DropdownMenuItem(value: "Option 1", child: Text("Option 1")),
DropdownMenuItem(value: "Option 2", child: Text("Option 2")),
],
onChanged: (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),
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: [
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"),
],
),
],
),
],
)
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
groupValue: _selectedIsBillable,
activeColor: Colors.blueAccent,
onChanged: (value) {
setState(() {
_selectedIsBillable = value;
});
},
),
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"),
// ],
// ),
// ],
// ),
// ],
// )
];
}
@ -490,11 +899,12 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
List<Widget> _buildTripType(bool isMobile) {
return [
CustomTextFieldWrapper(
color: Color(0xFFF4F4FB),
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
width: 120,
isFocused: _selectedOption == "Option 1",
isFocused: _selectedTripType == "1",
isDesktop: widget.isDesktop,
child: SizedBox(
height: 35,
@ -506,11 +916,11 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
visualDensity: VisualDensity.compact,
dense: true,
title: Text("Domestic"),
value: "Option 1",
groupValue: _selectedOption,
value: "1",
groupValue: _selectedTripType,
onChanged: (value) {
setState(() {
_selectedOption = value!;
_selectedTripType = value!;
});
},
),
@ -522,22 +932,24 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
color: Color(0xFFF4F4FB),
width: 150,
padding: EdgeInsets.symmetric(horizontal: 5, vertical: 2),
isFocused: _selectedOption == "Option 2",
isFocused: _selectedTripType == "2",
isDesktop: widget.isDesktop,
child: RadioListTile<String>(
activeColor: Colors.blueAccent,
contentPadding: EdgeInsets.zero,
dense: true,
title: Text("International"),
value: "Option 2",
groupValue: _selectedOption,
value: "2",
groupValue: _selectedTripType,
onChanged: (value) {
setState(() {
_selectedOption = value!;
_selectedTripType = value!;
});
},
),
),
];
}
@ -546,11 +958,11 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
// 'plan_purpose_of_travel' Starts ------------------------------------------------------
List<dynamic> purposeList = apiData?['plan_purpose_of_travel'] ?? [];
List<dynamic> purposeList = apiData?['plan_purpose_of_travel'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
)).toList();
@ -564,7 +976,7 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
}
// Default selected value
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
selectedPurpose ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
// 'plan_functional_department' Starts ---------------------------------------------
@ -572,7 +984,7 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
List<DropdownMenuItem<String>> dropdownFuncDeptItems = funcDeptList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
)).toList();
@ -586,7 +998,7 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
}
// Default selected value
String? selectedFuncDept = dropdownFuncDeptItems.isNotEmpty ? dropdownFuncDeptItems.first.value : null;
selectedFuncDept ??= dropdownFuncDeptItems.isNotEmpty ? dropdownFuncDeptItems.first.value : null;
// 'plan_functional_department' End
return Column(
@ -661,7 +1073,7 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
contentPadding: EdgeInsets.symmetric(
horizontal: 10), // Proper padding
),
onChanged: purposeList.isNotEmpty
onChanged: funcDeptList.isNotEmpty
? (newValue) {
setState(() {
selectedFuncDept = newValue;
@ -701,12 +1113,13 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: false, // Dropdown doesn't use focus
isFocused: _isdescriptionFocused,
width: isDesktop? MediaQuery.of(context).size.width * 0.5 :
MediaQuery.of(context).size.width * 0.85 ,
isDesktop: widget.isDesktop,
child: TextField(
focusNode: _textFieldFocusNode,
focusNode: _descriptionFocusNode,
controller: _descriptionController,
maxLines: 6,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
@ -727,6 +1140,19 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
);
}
List<Widget> _buildSubmit(isDesktop){
return[
ElevatedButton(
onPressed: (){},
child: Text("Cancel")),
SizedBox(width: 20,),
ElevatedButton(
onPressed: (){
handleSubmit();
},
child: Text("Submit"))
];
}
void _showInputDialog(String title){
showDialog(
@ -734,8 +1160,14 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
builder: (BuildContext context){
return UserSelectionDialog(
title:title,
onSubmit: (input){
print("USer entered : $input");
onSubmit: (input,userId,isTraveller){
setState(() {
otherUserName = input;
selectedplanUserId = userId;
selectedIstravelUser = isTraveller;
});
print("USer entered : $otherUserName $userId $isTraveller");
getSelectedPlanFor();
}
);
}

View File

@ -23,7 +23,10 @@ import '../itnerary_list/visa_list.dart';
class DynamicItinerary extends StatefulWidget {
final Map<String, dynamic>? apiData;
const DynamicItinerary({super.key, required this.apiData});
final List<dynamic>? apiCountryData;
final Function(String, List<Map<String, dynamic>>) onItineraryUpdate; // Updated Signature
const DynamicItinerary({super.key, required this.apiData, required this.onItineraryUpdate, required this.apiCountryData});
@override
_DynamicItineraryState createState() => _DynamicItineraryState();
@ -35,6 +38,112 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
String selectedListOption = "";
bool isSelected = false;
Map<String, dynamic>? selectedItem;
int? selectedIndex;
// List<Map<String, dynamic>> miscellaneousList = [];
Map<String, List<Map<String, dynamic>>> itineraryData = {
"Train": [{
"train_id": "2",
"plan_id": "2",
"class": "1",
"train_no": "OJH65JHB87",
"from": "Madurai",
"to": "Chennai",
"date": "2025-02-02",
"comments": "1 st class AC",
"created_on": "2025-02-10 14:38:21",
"created_by": null,
"updated_on": null,
"updated_by": null,
"is_active": "1"
}],
"Bus": [{
"bus_id": "2",
"plan_id": "2",
"from": "Chennai - OMR",
"to": "Chennai - ECR",
"date": "2025-02-15",
"time": "12:00:00",
"comments": "i need ac bus",
"created_on": "2025-02-10 14:38:21",
"created_by": null,
"updated_on": null,
"updated_by": null,
"is_active": "1"
}],
"Taxi": [{
"taxi_id": "2",
"plan_id": "2",
"destination_city": "Madurai",
"date": "2025-02-15",
"time": "12:00:00",
"location_of_pickup": "chennai - ECR",
"car_required_for": "1",
"no_of_passengers": "2",
"car_type": "1",
"comments": "Come Sharply",
"created_by": "f",
"updated_by": "g",
"is_active": "1"
}],
"Miscellaneous": [{
"miscellaneous_id": "2",
"plan_id": "2",
"special_request": "2",
"comments": "Please arrange one guide for me ",
"created_by": null,
"updated_by": null,
"is_active": "1"
}],
"Flight": [],
"Accommodation": [{
"accomodation_id": "2",
"plan_id": "2",
"destination_city": "chennai",
"hotel_name": "The park",
"checkin_date": "2025-02-14",
"checkin_time": "03:00:00",
"checkout_date": "2025-02-15",
"checkout_time": "03:00:00",
"comments": "A/c is must",
"created_on": "2025-02-10 14:38:21",
"created_by": null,
"updated_on": null,
"updated_by": null,
"is_active": "0"
}],
"Insurance": [{
"insurance_id": "2",
"plan_id": "2",
"start_date": "2025-02-01",
"end_date": "2025-02-28",
"type_of_insurance": "1",
"comments": "Temp insurance",
"created_on": "2025-02-10 14:38:21",
"created_by": null,
"updated_on": null,
"updated_by": null,
"is_active": "1"
}],
"Visa": [{
"visa_id": "2",
"plan_id": "2",
"country": "2",
// "country_code": "2",
"type_of_visa": "2",
"start_date": "2025-02-01",
"comments": "visa registered",
"created_on": "2025-02-10 14:38:21",
"created_by": null,
"updated_on": null,
"updated_by": null,
"is_active": "1"
}],
"Forex": [],
};
// Store form values for each tab
final Map<String, Map<String, String>> formData = {
@ -51,8 +160,24 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
void handleClose(bool value) {
setState(() {
selectedOption = "";
isSelected = value;
selectedIndex = null;
selectedItem = null;
});
}
void handleEdit(bool value,selectedItem, title) {
setState(() {
selectedOption = title;
isSelected = value;
this.selectedItem = selectedItem;
});
print(selectedItem);
}
Widget build(BuildContext context) {
@ -77,30 +202,156 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
}
void handleItineraryUpdate(String type, Map<String, dynamic> newData) {
setState(() {
if (!itineraryData.containsKey(type)) {
itineraryData[type] = []; // Initialize if null
}
List<Map<String, dynamic>> itemList = itineraryData[type]!;
print("newData - $newData");
// int? existingId = newData["id"];
// String? existingId = newData["id"];
String? idKey = "${type.toLowerCase()}_id";
String? existingId = newData[idKey];
print("Looking for ID using key: $idKey, Found ID: $existingId");
int? existingIndex = newData["indx"];
print("existingIndex - $existingIndex, existingId - $existingId");
// CASE 2: Update using id if available
if (existingId != null && existingId != 0) {
// int itemId = itemList.indexWhere((item) => item["id"] == existingId);
int itemId = itemList.indexWhere((item) => item[idKey]?.toString() == existingId.toString());
if (itemId != -1) {
print(" Updating existing item with id: $existingId");
itemList[itemId] = newData;
return;
}
}
// CASE 1: Update if indx exists in list
if (existingIndex != null && existingIndex != 0) {
int itemIndex = itemList.indexWhere((item) => item["indx"] == existingIndex);
if (itemIndex != -1) {
print("Updating existing item with indx: $existingIndex");
itemList[itemIndex] = newData; // Update the item
return;
}
}
// CASE 3: New Entry (Assign new indx)
print(" Creating new entry");
newData["indx"] = itemList.length + 1; // Assign a new indx
itemList.add(newData);
print(" Updated $type List: ${itineraryData[type]}");
});
widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent
}
void handleItinerarydelete(String type, Map<String, dynamic> data){
setState(() {
if(!itineraryData.containsKey(type)){
return;
}
List<Map<String, dynamic>> itemList = itineraryData[type]!;
// int? existingId = data["id"];
String? existingId = data["id"];
int? existingIndex = data["indx"];
print("🗑️ Deleting item -> ID: $existingId, Index: $existingIndex");
// Delete by ID
if(existingId != null && existingId != 0){
itemList.removeWhere((item) => item["id"]?.toString() == existingId.toString());
print("Deleted by ID: $existingId");
}
//Delete by Index
else if(existingIndex != null && existingId !=0){
itemList.removeWhere((item) => item["indx"] == existingIndex);
print("Deleted by ID: $existingIndex");
}
else{
print("No Valid Deletion");
}
itineraryData[type]= List.from(itemList);
});
widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent
}
// void handleItineraryUpdate(String type, Map<String, dynamic> newData) {
// setState(() {
// if (!itineraryData.containsKey(type)) {
// itineraryData[type] = []; // Initialize if null
// }
//
// // Assign an index based on the current list length
// int newIndex = itineraryData[type]!.length + 1;
// newData["indx"] = newIndex; // Add an ID field
//
// itineraryData[type]!.add(newData); // Append new object
// });
//
// widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent
// print("Updated $type List: ${itineraryData[type]}");
// }
switch (selectedListOption) {
case "Train":
selectedListWidget = TrainListWidget();
selectedListWidget = TrainListWidget( trainList : itineraryData["Train"]!,
onOpen: handleEdit,
onDeleteTrain: (data)=> handleItinerarydelete("Train", data),
);
break;
case "Taxi":
selectedListWidget = TaxiListWidget();
selectedListWidget = TaxiListWidget( taxiList : itineraryData["Taxi"]! ,
onOpen: handleEdit,
onDeleteTaxi: (data)=> handleItinerarydelete("Taxi", data),);
break;
case "Bus":
selectedListWidget = BusListWidget();
selectedListWidget = BusListWidget(busList : itineraryData["Bus"]!,
onOpen: handleEdit,
onDeleteBus: (data) => handleItinerarydelete("Bus", data),);
break;
case "Insurance":
selectedListWidget = InsuranceListWidget();
selectedListWidget = InsuranceListWidget(insuranceList : itineraryData["Insurance"]!,
onOpen: handleEdit,
onDeleteInsurance:(data) => handleItinerarydelete("Insurance", data),);
break;
case "Visa":
selectedListWidget = VisaListWidget();
selectedListWidget = VisaListWidget(visaList : itineraryData["Visa"]!,
onOpen: handleEdit,
onDeleteMiscellaneous: (data) => handleItinerarydelete("Visa", data),);
break;
case "Forex":
selectedListWidget = ForexListWidget();
selectedListWidget = ForexListWidget(forexList: itineraryData["Forex"]!,
onOpen: handleEdit,
onDeleteForex: (data) => handleItinerarydelete("Forex", data),);
break;
case "Accommodation":
selectedListWidget = AccomodationListWidget();
selectedListWidget = AccomodationListWidget(accommodationList: itineraryData["Accommodation"]!,
onOpen: handleEdit,
onDeleteAccommodation:(data) => handleItinerarydelete("Accommodation", data));
break;
case "Miscellaneous":
selectedListWidget = MiscellaneousListWidget();
selectedListWidget = MiscellaneousListWidget(miscellaneousList: itineraryData["Miscellaneous"]!,
onOpen: handleEdit,
onDeleteMiscellaneous: (data) => handleItinerarydelete("Miscellaneous", data),
);
break;
case "Flight":
default:
@ -110,29 +361,46 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
switch (selectedOption) {
case "Train":
selectedWidget = TrainScreen(onClose: handleClose,formData: formData["Train"]!, updateFormData: updateFormData, apiData: widget.apiData,);
// selectedWidget = TrainScreen(formData: formData["Train"]!);
selectedWidget = TrainScreen(onClose: handleClose, apiData: widget.apiData,
onSavetrain :(data) => handleItineraryUpdate("Train", data),
selectedItem: selectedItem);
break;
case "Taxi":
selectedWidget = TaxiScreen(onClose: handleClose, formData: formData["Taxi"]!, updateFormData: updateFormData, apiData: widget.apiData,);
selectedWidget = TaxiScreen(onClose: handleClose,apiData: widget.apiData,
onSavetaxi: (data)=> handleItineraryUpdate("Taxi", data),
selectedItem: selectedItem);
break;
case "Bus":
selectedWidget = BusScreen(onClose: handleClose, formData: formData["Bus"]!, updateFormData: updateFormData, apiData: widget.apiData,);
selectedWidget = BusScreen(onClose: handleClose, apiData: widget.apiData,
onSaveBus: (data)=> handleItineraryUpdate("Bus", data),
selectedItem: selectedItem);
break;
case "Insurance":
selectedWidget = InsuranceScreen(onClose: handleClose, formData: formData["Insurance"]!, updateFormData: updateFormData, apiData: widget.apiData,);
selectedWidget = InsuranceScreen(onClose: handleClose, apiData: widget.apiData,
onSaveInsurance:(data) => handleItineraryUpdate("Insurance", data),
selectedItem: selectedItem);
break;
case "Visa":
selectedWidget = VisaScreen(onClose: handleClose, formData: formData["Insurance"]!, updateFormData: updateFormData, apiData: widget.apiData,);
break;
case "Visa":
selectedWidget = VisaScreen(onClose: handleClose,apiData: widget.apiData, apiCountryData : widget.apiCountryData,
onSaveVisa: (data) => handleItineraryUpdate("Visa", data),
selectedItem: selectedItem,);
break;
case "Miscellaneous":
selectedWidget =MiscellaneousScreen(formData: formData["Miscellaneous"]!, updateFormData: updateFormData, onClose: handleClose, apiData: widget.apiData);
break;
selectedWidget = MiscellaneousScreen(onClose: handleClose, apiData: widget.apiData,
onSaveMiscellaneous: (data) => handleItineraryUpdate("Miscellaneous", data),
selectedItem: selectedItem, selectedIndex: selectedIndex, );
break;
case "Accommodation":
selectedWidget = AccomodationScreen( onClose: handleClose,formData: formData["Accommodation"]!, updateFormData: updateFormData);
selectedWidget = AccomodationScreen( onClose: handleClose,
onSaveAccomadation: (data)=>handleItineraryUpdate("Accommodation", data),
selectedItem: selectedItem, );
break;
case "Forex":
selectedWidget = ForexScreen( onClose: handleClose,formData: formData["Forex"]!, updateFormData: updateFormData, apiData: widget.apiData);
selectedWidget = ForexScreen( onClose: handleClose,apiData: widget.apiData, apiCountryData : widget.apiCountryData,
onSaveForex: (data)=>handleItineraryUpdate("Forex", data),
selectedItem: selectedItem);
break;
case "Flight":
default:
@ -194,7 +462,6 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
],
),
],
);
@ -248,6 +515,7 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
setState(() {
selectedOption = title;
isSelected = true;
selectedItem = null;
});
},
child: Icon(

View File

@ -138,74 +138,78 @@ class _ListPlansState extends State<ListPlans>{
builder: (context, sizingInfo) {
bool isTabletOrDesktop = sizingInfo.isTablet || sizingInfo.isDesktop;
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
// scrollDirection: isTabletOrDesktop ? Axis.vertical : Axis.horizontal,
child: SizedBox(
// constraints: isTabletOrDesktop
// ? const BoxConstraints(maxWidth: double.infinity)
// : BoxConstraints.tightFor(width: 600),
width: MediaQuery.of(context).size.width ,
child: DataTable(
// columnSpacing: 50.0,
dividerThickness: 0.5, // Reduce the thickness of row dividers
border: TableBorder(
horizontalInside: BorderSide(width: 0.5, color: Colors.grey.shade200), // Reduce horizontal line thickness
),
columns: const [
DataColumn(label: Text('Plan ID', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Trip Title', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Trip Type', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Cost Center', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Functional Department', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Purpose Of Travel', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Description', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
//
DataColumn(label: Text('Is Billable', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Status', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Actions', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
],
rows: plans.map((plan) {
return DataRow(cells: [
DataCell(Text(plan.planId)),
// DataCell(Text(plan.tripTitle)),
DataCell(Row(
children: [
Flexible(
child: Text(
plan.tripTitle,
softWrap: true,
overflow: TextOverflow.ellipsis, // Adds "..." if text is too long
),
),
],
)),
DataCell(Text(plan.tripType)),
DataCell(Text(plan.costCenter)),
// DataCell(Text(plan.functionalDepartment)),
// DataCell(Text(plan.purposeOfTravel)),
// DataCell(Text(plan.description)),
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
// scrollDirection: isTabletOrDesktop ? Axis.vertical : Axis.horizontal,
child: SizedBox(
// constraints: isTabletOrDesktop
// ? const BoxConstraints(maxWidth: double.infinity)
// : BoxConstraints.tightFor(width: 600),
width: MediaQuery.of(context).size.width ,
child: DataTable(
// columnSpacing: 50.0,
dividerThickness: 0.5, // Reduce the thickness of row dividers
border: TableBorder(
horizontalInside: BorderSide(width: 0.5, color: Colors.grey.shade200), // Reduce horizontal line thickness
),
columns: const [
DataColumn(label: Text('Plan ID', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Trip Title', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Trip Type', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Cost Center', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Functional Department', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Purpose Of Travel', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Description', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
//
DataCell(Text(plan.isBillable)),
DataCell(Text(plan.status)),
DataCell(
TextButton(
onPressed: () {
print("View button clicked for ${plan.tripTitle}");
},
child: const Text('View',
style: TextStyle(color: Colors.blueAccent)),
),
),
]);
}).toList(),
),
),
DataColumn(label: Text('Is Billable', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Status', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Actions', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
],
rows: plans.map((plan) {
return DataRow(cells: [
DataCell(Text(plan.planId)),
// DataCell(Text(plan.tripTitle)),
DataCell(Row(
children: [
Flexible(
child: Text(
plan.tripTitle,
softWrap: true,
overflow: TextOverflow.ellipsis, // Adds "..." if text is too long
),
),
],
)),
);
},
DataCell(Text(plan.tripType)),
DataCell(Text(plan.costCenter)),
// DataCell(Text(plan.functionalDepartment)),
// DataCell(Text(plan.purposeOfTravel)),
// DataCell(Text(plan.description)),
//
DataCell(Text(plan.isBillable)),
DataCell(Text(plan.status)),
DataCell(
TextButton(
onPressed: () {
print("View button clicked for ${plan.tripTitle}");
},
child: const Text('View',
style: TextStyle(color: Colors.blueAccent)),
),
),
]);
}).toList(),
),
),
),
);
},
);
},
),

View File

@ -0,0 +1,31 @@
class SearchTraveler {
final String travellerId;
final String firstName;
final String lastName;
final String email;
final String mobileNo;
SearchTraveler({
required this.travellerId,
required this.firstName,
required this.lastName,
required this.email,
required this.mobileNo,
});
factory SearchTraveler.fromJson(Map<String, dynamic> json){
return SearchTraveler(
travellerId: json['traveller_id'].toString(),
firstName: json['first_name'].toString()?? '',
lastName: json['last_name'].toString()?? '',
email: json['email'].toString()?? '',
mobileNo: json['mobile_no'].toString()?? '',
);
}
}

View File

@ -1,6 +1,5 @@
class SearchUser{
final String userId;
final String travellerId;
final String firstName;
final String lastName;
final String email;
@ -9,7 +8,6 @@ class SearchUser{
SearchUser({
required this.userId,
required this.travellerId,
required this.firstName,
required this.lastName,
required this.email,
@ -21,7 +19,6 @@ class SearchUser{
factory SearchUser.fromJson(Map<String, dynamic> json){
return SearchUser(
userId: json['user_id'].toString(),
travellerId: json['traveller_id'].toString(),
firstName: json['first_name'].toString()?? '',
lastName: json['last_name'].toString()?? '',
email: json['email'].toString()?? '',

View File

@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
class CustomTextFieldForexWrapper extends StatefulWidget {
final Widget child;
final bool isFocused;
final bool isDesktop;
final double? width;
final Color? color;
final VoidCallback? onFocusChange; // Callback for focus handling
final EdgeInsetsGeometry padding;
const CustomTextFieldForexWrapper({
super.key,
required this.child,
required this.isFocused,
required this.isDesktop,
this.width,
this.color = Colors.white,
this.onFocusChange,
this.padding = const EdgeInsets.symmetric(horizontal: 12),
});
@override
_CustomTextFieldForexWrapperState createState() => _CustomTextFieldForexWrapperState();
}
class _CustomTextFieldForexWrapperState extends State<CustomTextFieldForexWrapper> {
@override
Widget build(BuildContext context) {
return Container(
width: widget.width ?? // Use custom width if provided, else default
(widget.isDesktop
? MediaQuery.of(context).size.width * 0.25
: MediaQuery.of(context).size.width * 0.8),
padding: widget.padding,
decoration: BoxDecoration(
color: widget.color,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: widget.isFocused ? Color(0xFF78B4FC) : Color(0xFFD6D5E6),
width: widget.isFocused ? 2.0 : 0.5,
),
boxShadow: widget.isFocused
? [
BoxShadow(
color: Color.fromRGBO(120, 180, 252, 0.3),
blurRadius: 10,
spreadRadius: 2,
offset: Offset(0, 4),
),
]
: [],
),
child: widget.child,
);
}
}

View File

@ -65,6 +65,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.8"
dropdown_search:
dependency: "direct main"
description:
name: dropdown_search
sha256: "55106e8290acaa97ed15bea1fdad82c3cf0c248dd410e651f5a8ac6870f783ab"
url: "https://pub.dev"
source: hosted
version: "5.0.6"
easy_stepper:
dependency: "direct main"
description:

View File

@ -40,6 +40,7 @@ dependencies:
shared_preferences: ^2.5.2
easy_stepper: ^0.8.5+1
intl: ^0.20.2
dropdown_search: ^5.0.6
dev_dependencies: