687 lines
23 KiB
Dart
687 lines
23 KiB
Dart
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/Searchtraveller.dart';
|
|
import '../../data/models/searchUser.dart';
|
|
import '../../utils/auth_utils.dart';
|
|
import '../../widgets/custom_text_traveller.dart';
|
|
|
|
class UserSelectionDialog extends StatefulWidget {
|
|
final String title;
|
|
final Color layoutColorForUser;
|
|
final void Function(String, String, bool) onSubmit;
|
|
|
|
UserSelectionDialog({
|
|
Key? key,
|
|
required this.title,
|
|
required this.layoutColorForUser,
|
|
required this.onSubmit,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
_UserSelectionDialogState createState() => _UserSelectionDialogState();
|
|
}
|
|
|
|
class _UserSelectionDialogState extends State<UserSelectionDialog> {
|
|
TextEditingController _controller = TextEditingController();
|
|
TextEditingController _searchController = TextEditingController();
|
|
// 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;
|
|
|
|
String? orgId;
|
|
|
|
final _formKey = GlobalKey<FormState>();
|
|
|
|
Future<String?> getToken() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
return prefs.getString('auth_token');
|
|
}
|
|
|
|
Future<void> fetchUsers() async {
|
|
orgId = await getOrgId();
|
|
// final String apiUrlData = '$apiUrl/api/users?org_id=$orgId';
|
|
final String apiUrldata = '$apiUrl/api/users?org_id=$orgId';
|
|
|
|
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> userList = responseBody['data'];
|
|
|
|
// Extract first name and last name correctly
|
|
// setState(() {
|
|
// _users = userList
|
|
// .where((user) => user['first_name'] != null && user['last_name'] != null)
|
|
// .map((user) => "${user['first_name']} ${user['last_name']}")
|
|
// .toList();
|
|
// _filteredUsers = List.from(_users);
|
|
// });
|
|
|
|
setState(() {
|
|
_users = userList.map((user) => SearchUser.fromJson(user)).toList();
|
|
_filteredUsers = List.from(_users);
|
|
});
|
|
|
|
print("Users fetched: ${_users.length}");
|
|
for (var user in _users) {
|
|
print("${user.firstName} ${user.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 users: $e");
|
|
}
|
|
}
|
|
|
|
Future<void> fetchTraveller() async {
|
|
orgId = await getOrgId();
|
|
final String apiUrldata = '$apiUrl/api/travellers?org_id=$orgId';
|
|
|
|
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");
|
|
}
|
|
}
|
|
|
|
void _filterUsers1(String query) {
|
|
print("Filtering users...");
|
|
setState(() {
|
|
if (query.isEmpty) {
|
|
_filteredUsers = List.from(_users);
|
|
} else {
|
|
_filteredUsers = _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()));
|
|
}).toList();
|
|
}
|
|
});
|
|
|
|
print("Filtered Users:");
|
|
for (var user in _filteredUsers) {
|
|
print("${user.firstName} ${user.lastName}");
|
|
}
|
|
}
|
|
|
|
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}");
|
|
}
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
fetchUsers();
|
|
fetchTraveller();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Dialog(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
|
child: Container(
|
|
color: Colors.white,
|
|
width: 400, // Adjust width as needed
|
|
padding: EdgeInsets.all(16),
|
|
child: Column(
|
|
mainAxisSize:
|
|
MainAxisSize.min, // Ensures content doesn't expand unnecessarily
|
|
children: [
|
|
Text("Please Select User", style: TextStyle(fontSize: 14)),
|
|
SizedBox(height: 10),
|
|
|
|
// Search Field
|
|
TextField(
|
|
controller: _searchController,
|
|
onChanged: (query) {
|
|
setState(() {
|
|
_showTravellerForm = false;
|
|
});
|
|
widget.title == "Others"
|
|
? _filterUsersTravellers(query)
|
|
: _filterUsers(query);
|
|
},
|
|
decoration: InputDecoration(
|
|
hintText: "Search for a user",
|
|
hintStyle: TextStyle(fontSize: 14),
|
|
prefixIcon: Icon(Icons.search),
|
|
border:
|
|
OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(color: Colors.grey, width: 1),
|
|
// borderSide: BorderSide(color: Color(0xFFF5F5F5), width: 2),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(color: Colors.blueAccent, width: 2),
|
|
),
|
|
),
|
|
),
|
|
|
|
SizedBox(height: 10),
|
|
|
|
if (widget.title == "Others") ...[
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text("or create a new traveler",
|
|
style: TextStyle(fontSize: 14, color: Color(0xFF575A74))),
|
|
TextButton(
|
|
onPressed: () {
|
|
setState(() {
|
|
_showTravellerForm = true;
|
|
_searchController.clear();
|
|
});
|
|
},
|
|
child: Text("Create",
|
|
style: TextStyle(
|
|
fontSize: 14, color: widget.layoutColorForUser)),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
|
|
SizedBox(height: 5),
|
|
|
|
// User List or Message
|
|
_searchController.text.isNotEmpty
|
|
? SizedBox(
|
|
height: 300, // Limit height to avoid overflow
|
|
// child: _filteredUsers.isEmpty
|
|
child: _filteredList.isEmpty
|
|
? Center(
|
|
child: Text(
|
|
"No users found",
|
|
style:
|
|
TextStyle(fontSize: 14, color: Colors.grey),
|
|
),
|
|
)
|
|
: ListView.builder(
|
|
// itemCount: _filteredUsers.length,
|
|
itemCount: _filteredList.length,
|
|
itemBuilder: (context, 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: ${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: ${userType == "user" ? user.userId : user.travellerId},"
|
|
" isTraveller: $userIdSelected");
|
|
},
|
|
);
|
|
},
|
|
),
|
|
)
|
|
: SizedBox.shrink(),
|
|
|
|
// Traveler Form
|
|
if (_showTravellerForm)
|
|
Container(
|
|
height: 300, // Set a defined height
|
|
color: Color(0xFFF4F4FB),
|
|
child: Padding(
|
|
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(),
|
|
mobileController: TextEditingController(),
|
|
orgId: orgId,
|
|
),
|
|
),
|
|
),
|
|
|
|
// Actions
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.white,
|
|
foregroundColor: widget.layoutColorForUser,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
side: BorderSide(
|
|
color: widget.layoutColorForUser, width: 2),
|
|
),
|
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
),
|
|
onPressed: () => Navigator.pop(context),
|
|
child: Text(
|
|
"Cancel",
|
|
),
|
|
),
|
|
SizedBox(width: 10),
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: widget.layoutColorForUser,
|
|
foregroundColor: Colors.white,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
side: BorderSide(
|
|
color: widget.layoutColorForUser, width: 2),
|
|
),
|
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
),
|
|
onPressed: () {
|
|
print(
|
|
"Submitting: ${_searchController.text}, ID: $userIdSelected");
|
|
widget.onSubmit(
|
|
_searchController.text, userIdSelected, isTraveller);
|
|
Navigator.pop(context);
|
|
},
|
|
child: Text("Submit"),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class TravelerForm extends StatefulWidget {
|
|
final TextEditingController firstNameController;
|
|
final TextEditingController lastNameController;
|
|
final TextEditingController emailController;
|
|
final TextEditingController mobileController;
|
|
final String? orgId;
|
|
final GlobalKey<FormState> formKey;
|
|
final void Function(String, String, bool) onSubmit;
|
|
|
|
TravelerForm(
|
|
{required this.formKey,
|
|
required this.orgId,
|
|
required this.firstNameController,
|
|
required this.lastNameController,
|
|
required this.emailController,
|
|
required this.mobileController,
|
|
required this.onSubmit});
|
|
|
|
@override
|
|
_TravelerFormState createState() => _TravelerFormState();
|
|
}
|
|
|
|
class _TravelerFormState extends State<TravelerForm> {
|
|
Future<String?> getToken() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
return prefs.getString('auth_token');
|
|
}
|
|
|
|
bool _validateForm() {
|
|
if (widget.formKey.currentState != null) {
|
|
return widget.formKey.currentState!.validate();
|
|
}
|
|
return false; // Return false when form is not initialized
|
|
}
|
|
|
|
String? validateMobile(String? value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Mobile number is required';
|
|
}
|
|
if (!RegExp(r'^[0-9]{10}$').hasMatch(value)) {
|
|
return 'Enter a valid 10-digit mobile number';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
String? validateEmail(String? value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Email is required';
|
|
}
|
|
if (!RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
|
|
.hasMatch(value)) {
|
|
return 'Enter a valid email address';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Future<void> _onSubmit(BuildContext context) async {
|
|
bool isValid = _validateForm();
|
|
print("Form Validation Result: $isValid");
|
|
|
|
if (isValid) {
|
|
print("Validation Success");
|
|
|
|
_submitForm(context);
|
|
} else {
|
|
print("Validation Failed"); // This should now print if validation fails
|
|
}
|
|
}
|
|
|
|
Future<void> _submitForm(BuildContext context) async {
|
|
Map<String, String> requestBody = {
|
|
"org_id": widget.orgId!,
|
|
"first_name": widget.firstNameController.text,
|
|
"last_name": widget.lastNameController.text,
|
|
"email": widget.emailController.text,
|
|
"mobile": widget.mobileController.text,
|
|
};
|
|
|
|
String apiUrlData = '$apiUrl/api/travellers/create';
|
|
|
|
try {
|
|
final token = await getToken();
|
|
if (token == null) {
|
|
throw Exception('Token not found. Please log in.');
|
|
}
|
|
print("response1");
|
|
var response = await http.post(
|
|
Uri.parse(apiUrlData),
|
|
headers: {
|
|
'Authorization': 'Bearer $token',
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: jsonEncode(requestBody),
|
|
);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(
|
|
"Traveller added successfully!",
|
|
style: TextStyle(color: Colors.white), // ✅ Set text color
|
|
),
|
|
backgroundColor: Colors.green,
|
|
),
|
|
);
|
|
} else {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text("Error: ${response.body}")),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text("Failed to connect to server.")),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ResponsiveBuilder(
|
|
builder: (context, sizingInfo) {
|
|
double widthFactor;
|
|
bool isDesktop =
|
|
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
|
|
|
if (sizingInfo.deviceScreenType == DeviceScreenType.desktop) {
|
|
widthFactor = 0.23;
|
|
} else if (sizingInfo.deviceScreenType == DeviceScreenType.tablet) {
|
|
widthFactor = 0.8;
|
|
} else {
|
|
widthFactor = 1.0;
|
|
}
|
|
|
|
return Form(
|
|
key: widget.formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Text("Create Traveler", style: TextStyle(color: Colors.black54)),
|
|
SizedBox(height: 7),
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
scrollDirection: Axis.vertical,
|
|
child: Column(children: _buildFormFirstRow(isDesktop)),
|
|
),
|
|
),
|
|
SizedBox(height: 4),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
TextButton(
|
|
onPressed: () {
|
|
widget.formKey.currentState?.reset();
|
|
},
|
|
child: Text("Clear", style: TextStyle(color: Colors.grey)),
|
|
),
|
|
TextButton(
|
|
onPressed: () => _onSubmit(context),
|
|
child:
|
|
Text("Add", style: TextStyle(color: Colors.blueAccent)),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
List<Widget> _buildFormFirstRow(bool isDesktop) {
|
|
return [
|
|
CustomTextField(
|
|
controller: widget.firstNameController,
|
|
labelText: "First Name",
|
|
validator: (value) => value!.isEmpty ? "Enter first name" : null,
|
|
),
|
|
SizedBox(height: 15),
|
|
CustomTextField(
|
|
controller: widget.lastNameController,
|
|
labelText: "Last Name",
|
|
validator: (value) => value!.isEmpty ? "Enter last name" : null,
|
|
),
|
|
SizedBox(height: 15),
|
|
CustomTextField(
|
|
controller: widget.emailController,
|
|
labelText: "Email",
|
|
keyboardType: TextInputType.emailAddress,
|
|
validator: validateEmail,
|
|
),
|
|
SizedBox(height: 15),
|
|
CustomTextField(
|
|
controller: widget.mobileController,
|
|
labelText: "Mobile Number",
|
|
keyboardType: TextInputType.phone,
|
|
validator: validateMobile,
|
|
),
|
|
];
|
|
}
|
|
}
|