user mangement bugs

This commit is contained in:
venbaittech 2025-06-05 12:40:16 +05:30
parent 7c9968c159
commit b246c79789
15 changed files with 4932 additions and 3769 deletions

File diff suppressed because it is too large Load Diff

View File

@ -156,6 +156,8 @@ class _LoginWidgetState extends State<LoginWidget> {
backgroundColor: Colors.red,
),
);
_emailController.clear();
_passwordController.clear();
// Fluttertoast.showToast(
// msg: "Login Failed: $errorMessage",
// toastLength: Toast.LENGTH_LONG,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -15,8 +15,6 @@ import '../../utils/auth_utils.dart';
import '../../utils/pagination.dart';
import 'groupDetails.dart';
class GroupList extends StatefulWidget {
@override
_GroupListState createState() => _GroupListState();
@ -39,7 +37,6 @@ class _GroupListState extends State<GroupList> {
List filteredGroups = [];
TextEditingController searchController = TextEditingController();
int currentPage = 0;
int itemsPerPage = 10;
@ -71,14 +68,14 @@ class _GroupListState extends State<GroupList> {
setState(() {
layoutColor =
layoutString != null
? Color(int.parse(layoutString))
: Colors.redAccent;
layoutString != null
? Color(int.parse(layoutString))
: Colors.redAccent;
bodyColor =
bodyStringColor != null
? Color(int.parse(bodyStringColor))
: Colors.white;
bodyStringColor != null
? Color(int.parse(bodyStringColor))
: Colors.white;
});
}
@ -87,15 +84,11 @@ class _GroupListState extends State<GroupList> {
return prefs.getString('auth_token');
}
Future<List<dynamic>> fetchGroups() async {
final result = await apiService.fetchAllGroup();
return result; // Returning raw JSON list
}
Future<void> loadAllGroups() async {
try {
final result = await apiService.fetchAllGroup();
@ -116,14 +109,21 @@ class _GroupListState extends State<GroupList> {
filteredGroups =
allGroups.where((group) {
return (group['name']?.toLowerCase().contains(lowerQuery) ??
false) ||
(group['domestic_policy_name']?.toLowerCase().contains(lowerQuery) ??
false)
(group['international_policy_name']?.toLowerCase().contains(lowerQuery) ??
false) ||
(group['domestic_policy_name']?.toLowerCase().contains(
lowerQuery,
) ??
false)(
group['international_policy_name']?.toLowerCase().contains(
lowerQuery,
) ??
false,
) ||
(group['description']?.toLowerCase().contains(lowerQuery) ??
false) ||
(group['is_active']?.toLowerCase().contains(lowerQuery) ?? false);}).toList();
(group['is_active']?.toLowerCase().contains(lowerQuery) ??
false);
}).toList();
currentPage = 0;
});
@ -131,10 +131,10 @@ class _GroupListState extends State<GroupList> {
}
void handleActiveStatus(
Map<String, dynamic> groupData,
String groupId,
String currentStatus,
) async {
Map<String, dynamic> groupData,
String groupId,
String currentStatus,
) async {
print("Toggling group status - $groupId (Current: $currentStatus)");
final String apiUrlData =
@ -159,7 +159,7 @@ class _GroupListState extends State<GroupList> {
'Content-Type': 'application/json',
},
body: jsonEncode({
"is_active": newStatus // Set new status dynamically
"is_active": newStatus, // Set new status dynamically
}),
);
@ -189,7 +189,6 @@ class _GroupListState extends State<GroupList> {
loadAllGroups();
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
@ -203,21 +202,17 @@ class _GroupListState extends State<GroupList> {
drawer: CustomDrawer(isDesktop: false),
body: Padding(
padding:
isDesktop
? EdgeInsets.symmetric(
horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding
vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [
Expanded(child: buildGroupList(isDesktop)),
],
),
isDesktop
? EdgeInsets.symmetric(
horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding
vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(children: [Expanded(child: buildGroupList(isDesktop))]),
),
);
},
@ -250,9 +245,9 @@ class _GroupListState extends State<GroupList> {
// : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
// padding: const EdgeInsets.all(10),
height:
isDesktop
? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height,
isDesktop
? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height,
child: Padding(
padding: const EdgeInsets.all(10.0),
@ -342,23 +337,22 @@ class _GroupListState extends State<GroupList> {
),
),
onPressed: () async {
// List<dynamic> groups = await futureGroups;
// context.go('/CreateGroup');
showDialog(
// List<dynamic> groups = await futureGroups;
// context.go('/CreateGroup');
showDialog(
context: context,
builder: (context) => GroupData(
isDesktop: isDesktop,
groupId: null,
layoutColor: layoutColor!,
fetchGetGroup: refreshData
),
);
builder:
(context) => GroupData(
isDesktop: isDesktop,
groupId: null,
layoutColor: layoutColor!,
fetchGetGroup: refreshData,
),
);
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New Group",
@ -382,49 +376,49 @@ class _GroupListState extends State<GroupList> {
isDesktop
? SizedBox.shrink()
: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.8,
height: 35,
child: TextField(
controller: searchController,
onChanged: filterGroups,
decoration: InputDecoration(
hintText: "Search for a Group",
hintStyle: TextStyle(
fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon(
Icons.search,
color: Color(0xFF9E9DBD),
size: 18,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Colors.grey.shade200,
width: 0.5,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Colors.grey.shade300,
width: 1,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.8,
height: 35,
child: TextField(
controller: searchController,
onChanged: filterGroups,
decoration: InputDecoration(
hintText: "Search for a Group",
hintStyle: TextStyle(
fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon(
Icons.search,
color: Color(0xFF9E9DBD),
size: 18,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Colors.grey.shade200,
width: 0.5,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
),
),
style: GoogleFonts.poppins(fontSize: 12),
),
),
style: GoogleFonts.poppins(fontSize: 12),
),
// SizedBox(width: 16),
],
),
// SizedBox(width: 16),
],
),
const SizedBox(height: 10),
FutureBuilder<List<dynamic>>(
future: futureGroups,
@ -467,7 +461,7 @@ class _GroupListState extends State<GroupList> {
}
List<dynamic> groups =
filteredGroups.isNotEmpty ? filteredGroups : allGroups;
filteredGroups.isNotEmpty ? filteredGroups : allGroups;
groups.sort((a, b) {
DateTime dateA = DateTime.parse(a['created_on']);
@ -477,10 +471,10 @@ class _GroupListState extends State<GroupList> {
});
List paginatedGroup =
groups
.skip(currentPage * itemsPerPage)
.take(itemsPerPage)
.toList();
groups
.skip(currentPage * itemsPerPage)
.take(itemsPerPage)
.toList();
Widget table = LayoutBuilder(
builder: (context, constraints) {
@ -554,128 +548,146 @@ class _GroupListState extends State<GroupList> {
),
],
rows:
paginatedGroup.map((group) {
String groupId =
group['group_id'].toString(); // Get group ID
bool isSelected = selectedGroupId == groupId;
paginatedGroup.map((group) {
String groupId =
group['group_id']
.toString(); // Get group ID
bool isSelected = selectedGroupId == groupId;
return DataRow(
cells: [
DataCell(
Text(
"${group['name'] ?? ''}",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
return DataRow(
cells: [
DataCell(
Text(
"${group['name'] ?? ''}",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
),
),
),
),
),
DataCell(
Text("${group['domestic_policy_name'] ?? 'N/A'}",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
DataCell(
Text(
"${group['domestic_policy_name'] ?? 'N/A'}",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
),
),
),
),
),
DataCell(
Text(
"${group['international_policy_name'] ?? 'N/A'}",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
DataCell(
Text(
"${group['international_policy_name'] ?? 'N/A'}",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
),
),
),
),
),
DataCell(
Text(
"${group['description'] ?? 'N/A'}",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
DataCell(
Text(
"${group['description'] ?? 'N/A'}",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
),
),
),
),
),
DataCell(
Text(
group['is_active'] == "1"
? "Active"
: "Inactive",
style: TextStyle(
color:
DataCell(
Text(
group['is_active'] == "1"
? Colors.green
: Colors.grey,
fontFamily: "Inter",
fontWeight: FontWeight.w400,
? "Active"
: "Inactive",
style: TextStyle(
color:
group['is_active'] == "1"
? Colors.green
: Colors.grey,
fontFamily: "Inter",
fontWeight: FontWeight.w400,
),
),
),
),
DataCell(
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
GestureDetector(
onTap: () async {
if (group['group_id'] != null) {
final newGroupID = int.tryParse(
group['group_id'].toString());
if (newGroupID != null) {
final data = await apiService.getGroupDetailsFind(
newGroupID); // Always an int
showDialog(
context: context,
builder: (context) =>
GroupData(
isDesktop: isDesktop,
groupId: newGroupID,
// Pass the ID
groupData: data,
layoutColor: layoutColor!,
fetchGetGroup: refreshData
),
DataCell(
Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: [
GestureDetector(
onTap: () async {
if (group['group_id'] != null) {
final newGroupID = int.tryParse(
group['group_id'].toString(),
);
if (newGroupID != null) {
final data = await apiService
.getGroupDetailsFind(
newGroupID,
); // Always an int
showDialog(
context: context,
builder:
(context) => GroupData(
isDesktop: isDesktop,
groupId: newGroupID,
// Pass the ID
groupData: data,
layoutColor:
layoutColor!,
fetchGetGroup:
refreshData,
),
);
}
} else {
print(
"something went wrong check properly",
);
}
},
// onTap: () {
// context.go("/CreateGroup", extra: group);
// },
child: Tooltip(
message: 'Edit Group Details',
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
),
),
SizedBox(width: 5),
GestureDetector(
onTap: () {
final idStr = group['group_id'];
final id = int.tryParse(
idStr.toString(),
);
}
} else {
print("something went wrong check properly");
}
},
// onTap: () {
// context.go("/CreateGroup", extra: group);
// },
child: Tooltip(
message: 'Edit Group Details',
child: Image.asset('assets/images/IconsImg/edit.png',
width: 20, height: 15), ),
if (id == null) {
print("group_id is null");
return;
}
final status = group['is_active'];
// print("GroupId : ${group['group_id']} ");
deleteGroup(group, id, status);
},
child: Tooltip(
message: 'Delete Group Details',
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15,
),
),
),
],
),
SizedBox(
width: 5,
),
GestureDetector(
onTap: () {
final idStr = group['group_id'];
final id = int.tryParse(idStr.toString());
if (id == null) {
print("group_id is null");
return;
}
final status = group['is_active'];
// print("GroupId : ${group['group_id']} ");
deleteGroup(group, id, status);
},
child: Tooltip(
message: 'Delete Group Details',
child: Image.asset('assets/images/IconsImg/delete.png',
width: 20, height: 15),),
),
],
),
),
],
);
}).toList(),
),
],
);
}).toList(),
),
);
},
@ -688,7 +700,10 @@ class _GroupListState extends State<GroupList> {
return Card(
color: Colors.white,
margin: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
margin: EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
@ -700,7 +715,8 @@ class _GroupListState extends State<GroupList> {
children: [
// Row 1: Policy Name and Actions
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: RichText(
@ -718,11 +734,12 @@ class _GroupListState extends State<GroupList> {
),
),
TextSpan(
text: "${object['name'] ?? 'N/A'}",
text:
"${object['name'] ?? 'N/A'}",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.normal
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.normal,
),
),
],
@ -736,42 +753,54 @@ class _GroupListState extends State<GroupList> {
onTap: () async {
if (object['group_id'] != null) {
final newGroupID = int.tryParse(
object['group_id'].toString());
object['group_id'].toString(),
);
if (newGroupID != null) {
final data = await apiService.getGroupDetailsFind(
newGroupID); // Always an int
final data = await apiService
.getGroupDetailsFind(
newGroupID,
); // Always an int
showDialog(
context: context,
builder: (context) =>
GroupData(
isDesktop: isDesktop,
groupId: newGroupID,
// Pass the ID
groupData: data,
layoutColor: layoutColor!,
fetchGetGroup: refreshData
builder:
(context) => GroupData(
isDesktop: isDesktop,
groupId: newGroupID,
// Pass the ID
groupData: data,
layoutColor:
layoutColor!,
fetchGetGroup:
refreshData,
),
);
}
} else {
print("something went wrong check properly");
print(
"something went wrong check properly",
);
}
},
// onTap: () {
// context.go("/CreateGroup", extra: group);
// },
child: Tooltip( message: 'Edit Group Details',
child: Image.asset('assets/images/IconsImg/edit.png',
width: 20, height: 15),),
),
SizedBox(
width: 5,
child: Tooltip(
message: 'Edit Group Details',
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
),
),
SizedBox(width: 5),
GestureDetector(
onTap: () {
final idStr = object['group_id'];
final id = int.tryParse(idStr.toString());
final id = int.tryParse(
idStr.toString(),
);
if (id == null) {
print("group_id is null");
@ -783,8 +812,12 @@ class _GroupListState extends State<GroupList> {
},
child: Tooltip(
message: 'Edit Group Details',
child: Image.asset('assets/images/IconsImg/delete.png',
width: 20, height: 15),),
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15,
),
),
),
],
),
@ -792,7 +825,6 @@ class _GroupListState extends State<GroupList> {
),
SizedBox(height: 8), // Spacing
// Row 2:
RichText(
text: TextSpan(
@ -809,18 +841,19 @@ class _GroupListState extends State<GroupList> {
),
),
TextSpan(
text: "${object['domestic_policy_name'] ?? 'N/A'}",
text:
"${object['domestic_policy_name'] ?? 'N/A'}",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.normal
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.normal,
),
),
],
),
),
SizedBox(height: 8), // Spacing
// Row 3:
RichText(
text: TextSpan(
@ -837,18 +870,18 @@ class _GroupListState extends State<GroupList> {
),
),
TextSpan(
text: "${object['international_policy_name']}",
text:
"${object['international_policy_name'] ?? 'N/A'}",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.normal
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.normal,
),
),
],
),
),
SizedBox(height: 8), // Spacing
// Row 4:
RichText(
text: TextSpan(
@ -865,11 +898,12 @@ class _GroupListState extends State<GroupList> {
),
),
TextSpan(
text: "${object['description'] ?? 'N/A'}",
text:
"${object['description'] ?? 'N/A'}",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.normal
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.normal,
),
),
],
@ -889,12 +923,12 @@ class _GroupListState extends State<GroupList> {
children: [
Expanded(
child:
isDesktop
? SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table, // <-- your existing table
)
: buildMobileCardView(paginatedGroup),
isDesktop
? SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table, // <-- your existing table
)
: buildMobileCardView(paginatedGroup),
),
PaginationControls(
currentPage: currentPage,
@ -924,4 +958,4 @@ class _GroupListState extends State<GroupList> {
),
);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -14,12 +14,13 @@ class TaxiScreen extends StatefulWidget {
final Map<String, dynamic>? selectedItem;
final String? loginUser;
TaxiScreen(
{required this.onClose,
this.apiData,
required this.onSavetaxi,
required this.selectedItem,
required this.loginUser});
TaxiScreen({
required this.onClose,
this.apiData,
required this.onSavetaxi,
required this.selectedItem,
required this.loginUser,
});
@override
_TaxiScreenState createState() => _TaxiScreenState();
@ -97,13 +98,17 @@ class _TaxiScreenState extends State<TaxiScreen> {
super.initState();
_addFocusListener(
_destinationFocusNode, (focus) => _destinationFocus = focus);
_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);
_numPassengerFocusNode,
(focus) => _numPassengerFocus = focus,
);
_addFocusListener(_commentsFocusNode, (focus) => _commentsFocus = focus);
_destinationController = initController("destination_city");
@ -168,7 +173,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
"location_of_pickup",
"no_of_passengers",
"date",
"time"
"time",
];
// Check validation for each field
@ -199,30 +204,33 @@ class _TaxiScreenState extends State<TaxiScreen> {
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isMobile = sizingInfo.isMobile;
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isMobile = sizingInfo.isMobile;
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Container(
// color: Color(0xFFF4F4FB),
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(28.0),
child: Center(
child: Column(children: _buildAccomadtionForm(isDesktop)),
return Container(
// color: Color(0xFFF4F4FB),
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(28.0),
child: Center(
child: Column(children: _buildAccomadtionForm(isDesktop)),
),
),
)
],
],
),
),
),
),
);
});
);
},
);
}
List<Widget> _buildAccomadtionForm(bool isDesktop) {
@ -235,7 +243,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
List<List<Widget>> rowBuilders = [
// _builClassType(isDesktop),
_buildSecondRow(isDesktop)
_buildSecondRow(isDesktop),
];
return [
@ -253,19 +261,24 @@ class _TaxiScreenState extends State<TaxiScreen> {
List<Widget> _buildFirstRow(isDesktop) {
List<dynamic> purposeList = widget.apiData?['taxt_car_type'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item) => DropdownMenuItem<String>(
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
))
.toList();
List<DropdownMenuItem<String>> dropdownItems =
purposeList
.map(
(item) => DropdownMenuItem<String>(
value: item['dropdown_key'],
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)),
child: Text(
"No options available",
style: TextStyle(color: Colors.grey),
),
),
);
}
@ -281,31 +294,28 @@ class _TaxiScreenState extends State<TaxiScreen> {
Text(
"Taxi Required For",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
isDesktop
? Row(children: _buildTripType(isDesktop))
: Column(children: _buildTripType(isDesktop))
: Column(children: _buildTripType(isDesktop)),
],
),
if (isDesktop)
Spacer()
else
SizedBox(
height: 8,
),
if (isDesktop) Spacer() else SizedBox(height: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Number of Passenger",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
@ -319,8 +329,9 @@ class _TaxiScreenState extends State<TaxiScreen> {
style: const TextStyle(fontSize: 12),
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(
r'^\d*\.?\d*$')), // Allow only positive numbers with optional decimal
FilteringTextInputFormatter.allow(
RegExp(r'^\d*\.?\d*$'),
), // Allow only positive numbers with optional decimal
],
decoration: const InputDecoration(
labelText: "Number of Passenger",
@ -334,28 +345,21 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
if (errorMessages["no_of_passengers"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
],
],
),
if (isDesktop)
Spacer()
else
SizedBox(
height: 8,
),
if (isDesktop) Spacer() else SizedBox(height: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Car Type",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
@ -369,18 +373,21 @@ class _TaxiScreenState extends State<TaxiScreen> {
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
), // Proper padding
),
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedCarType = newValue;
});
print(
"Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
}
: null,
onChanged:
purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedCarType = newValue;
});
print(
"Updating form data: Flight -> trip_type -> ${newValue ?? ""}",
);
}
: null,
items: dropdownItems,
),
@ -388,31 +395,31 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
],
),
if (isDesktop)
SizedBox.shrink()
else
SizedBox(
height: 8,
),
if (isDesktop) SizedBox.shrink() else SizedBox(height: 8),
];
}
List<Widget> _buildTripType(bool isDesktop) {
List<dynamic> purposeList = widget.apiData?['taxi_car_required_for'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item) => DropdownMenuItem<String>(
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
))
.toList();
List<DropdownMenuItem<String>> dropdownItems =
purposeList
.map(
(item) => DropdownMenuItem<String>(
value: item['dropdown_key'],
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)),
child: Text(
"No options available",
style: TextStyle(color: Colors.grey),
),
),
);
}
@ -425,9 +432,10 @@ class _TaxiScreenState extends State<TaxiScreen> {
CustomTextFieldWrapper(
isFocused: _taxiReqFocused,
isDesktop: isDesktop,
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
width:
isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
@ -436,18 +444,21 @@ class _TaxiScreenState extends State<TaxiScreen> {
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
), // Proper padding
),
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedReqTaxi = newValue;
});
print(
"Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
}
: null,
onChanged:
purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedReqTaxi = newValue;
});
print(
"Updating form data: Flight -> trip_type -> ${newValue ?? ""}",
);
}
: null,
items: dropdownItems,
),
@ -466,10 +477,11 @@ class _TaxiScreenState extends State<TaxiScreen> {
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: _selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today)
? _selectedCheckOutDate!
: today,
initialDate:
_selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today)
? _selectedCheckOutDate!
: today,
firstDate: today,
lastDate: DateTime(2100),
);
@ -494,8 +506,13 @@ class _TaxiScreenState extends State<TaxiScreen> {
// Formatting time to HH:mm (24-hour format)
final now = DateTime.now();
final formattedTime = DateFormat('HH:mm').format(
DateTime(now.year, now.month, now.day, pickedTime.hour,
pickedTime.minute),
DateTime(
now.year,
now.month,
now.day,
pickedTime.hour,
pickedTime.minute,
),
);
_timeController.text = formattedTime;
});
@ -509,9 +526,10 @@ class _TaxiScreenState extends State<TaxiScreen> {
Text(
"City",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
@ -535,28 +553,21 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
if (errorMessages["destination_city"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
],
],
),
if (isDesktop)
Spacer()
else
SizedBox(
height: 8,
),
if (isDesktop) Spacer() else SizedBox(height: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Location of Pickup",
"Pickup Location",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
@ -569,7 +580,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
controller: _locationController,
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Location of Pickup",
labelText: "Pickup Location",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
@ -580,28 +591,21 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
if (errorMessages["location_of_pickup"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
],
],
),
if (isDesktop)
Spacer()
else
SizedBox(
height: 8,
),
if (isDesktop) Spacer() else SizedBox(height: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Date",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
@ -622,8 +626,11 @@ class _TaxiScreenState extends State<TaxiScreen> {
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(Icons.calendar_today,
size: 16, color: Colors.grey),
suffixIcon: Icon(
Icons.calendar_today,
size: 16,
color: Colors.grey,
),
),
),
),
@ -632,28 +639,21 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
if (errorMessages["date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
],
],
),
if (isDesktop)
Spacer()
else
SizedBox(
height: 8,
),
if (isDesktop) Spacer() else SizedBox(height: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Time",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
@ -674,8 +674,11 @@ class _TaxiScreenState extends State<TaxiScreen> {
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon:
Icon(Icons.access_time, size: 16, color: Colors.grey),
suffixIcon: Icon(
Icons.access_time,
size: 16,
color: Colors.grey,
),
),
),
),
@ -684,10 +687,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
if (errorMessages["time"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
],
],
),
@ -702,17 +702,19 @@ class _TaxiScreenState extends State<TaxiScreen> {
Text(
"Comments",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: _commentsFocus, // Dropdown doesn't use focus
isDesktop: isDesktop,
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
width:
isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
@ -733,9 +735,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
],
),
if (isDesktop) Spacer(),
SizedBox(
height: 5,
),
SizedBox(height: 5),
Column(
children: [
Row(
@ -756,9 +756,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
child: Text(
@ -767,7 +765,6 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
),
SizedBox(width: 10), // Space between buttons
// Save Changes Button
ElevatedButton(
onPressed: () {
@ -775,9 +772,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B), // Primary color for save
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
child: Text(

View File

@ -85,7 +85,8 @@ class _FlightListWidgetState extends State<FlightListWidget> {
return AlertDialog(
title: const Text('Select Trip Type'),
content: const Text(
'Please select a trip type before adding a flight.'),
'Please select a trip type before adding a flight.',
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
@ -118,16 +119,18 @@ class _FlightListWidgetState extends State<FlightListWidget> {
// style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
// ),
MouseRegion(
cursor: widget.isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
cursor:
widget.isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: GestureDetector(
onTap: widget.isViewMode
? null
: () {
checkClass();
print("New data");
},
onTap:
widget.isViewMode
? null
: () {
checkClass();
print("New data");
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
@ -141,7 +144,7 @@ class _FlightListWidgetState extends State<FlightListWidget> {
Icons.add_circle_sharp,
size: 30,
color: Color(0xFF114D8B),
)
),
],
),
),
@ -149,9 +152,9 @@ class _FlightListWidgetState extends State<FlightListWidget> {
],
),
const SizedBox(height: 16),
// Scroll behavior based on device
_buildData(context, isDesktop)
// Scroll behavior based on device
_buildData(context, isDesktop),
],
),
),
@ -224,8 +227,11 @@ class _FlightListWidgetState extends State<FlightListWidget> {
if (hour == 24 && minute == 0) {
// 24:00 is treated as 00:00 on the next day
dateTime = DateTime(now.year, now.month, now.day)
.add(const Duration(days: 1));
dateTime = DateTime(
now.year,
now.month,
now.day,
).add(const Duration(days: 1));
} else {
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
throw FormatException("Invalid hour or minute");
@ -245,7 +251,21 @@ class _FlightListWidgetState extends State<FlightListWidget> {
itemCount: filteredList.length,
itemBuilder: (context, index) {
final item = filteredList[index];
String? tripTypeName = '';
switch (item["trip_type"]?.toString()) {
case "Roundtrip":
tripTypeName = "Round Trip";
break;
case "Multitrip":
tripTypeName = "Multi-Trip";
break;
case "Oneway":
tripTypeName = "One-Way";
break;
default:
tripTypeName = item["trip_type"]?.toString();
break;
}
return Container(
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 6),
decoration: BoxDecoration(
@ -281,7 +301,7 @@ class _FlightListWidgetState extends State<FlightListWidget> {
Row(
children: [
Text(
item["trip_type"]?.toString() ?? "N/A",
tripTypeName ?? "N/A",
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w800,
@ -313,59 +333,53 @@ class _FlightListWidgetState extends State<FlightListWidget> {
),
],
),
Divider(
color: Colors.blueGrey.shade50,
),
Divider(color: Colors.blueGrey.shade50),
SizedBox(height: 4),
if (isDesktop)
Row(
children: [
Expanded(
flex: 2,
child: Text(
"Class",
style: GoogleFonts.poppins(
fontSize: 11,
),
)),
flex: 2,
child: Text(
"Class",
style: GoogleFonts.poppins(fontSize: 11),
),
),
Expanded(
flex: 4,
child: Text(
"Sector",
style: GoogleFonts.poppins(
fontSize: 11,
),
)),
flex: 4,
child: Text(
"Sector",
style: GoogleFonts.poppins(fontSize: 11),
),
),
Expanded(
flex: 2,
child: Text(
"Date",
style: GoogleFonts.poppins(
fontSize: 11,
),
)),
flex: 2,
child: Text(
"Date",
style: GoogleFonts.poppins(fontSize: 11),
),
),
Expanded(
flex: 2,
child: Text(
"Time",
style: GoogleFonts.poppins(
fontSize: 11,
),
)),
flex: 2,
child: Text(
"Time",
style: GoogleFonts.poppins(fontSize: 11),
),
),
],
),
SizedBox(height: 4),
// Trip Rows
// Trip Rows
if (isDesktop)
if (item["trips"] != null && item["trips"].isNotEmpty)
...item["trips"].map<Widget>((trip) {
String fromPlaceCountry =
countryMap[trip["from_place"]?.toString()] ??
"Unknown Country";
"Unknown Country";
String toPlaceCountry =
countryMap[trip["to_place"]?.toString()] ??
"Unknown Country";
"Unknown Country";
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
@ -385,7 +399,7 @@ class _FlightListWidgetState extends State<FlightListWidget> {
Expanded(
flex: 4,
child: Text(
"$fromPlaceCountry (from) - (to) $toPlaceCountry",
"$fromPlaceCountry (From) - (To) $toPlaceCountry",
// "${trip["from_place"]?.toString()} - ${trip["to_place"]?.toString()}",
style: GoogleFonts.poppins(
fontSize: 12,
@ -435,15 +449,22 @@ class _FlightListWidgetState extends State<FlightListWidget> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildKeyValueRow(
"Class",
getRequestForClass(trip["class"].toString()) ??
"N/A"),
_buildKeyValueRow("Sector",
"${trip["from_place"] ?? "N/A"} - ${trip["to_place"] ?? "N/A"}"),
"Class",
getRequestForClass(trip["class"].toString()) ??
"N/A",
),
_buildKeyValueRow(
"Date", formatDate(trip["date"] ?? "")),
"Sector",
"${trip["from_place"] ?? "N/A"} - ${trip["to_place"] ?? "N/A"}",
),
_buildKeyValueRow(
"Time", formatTime(trip["time"] ?? "")),
"Date",
formatDate(trip["date"] ?? ""),
),
_buildKeyValueRow(
"Time",
formatTime(trip["time"] ?? ""),
),
],
),
),
@ -474,12 +495,7 @@ class _FlightListWidgetState extends State<FlightListWidget> {
),
),
Expanded(
child: Text(
value,
style: GoogleFonts.poppins(
fontSize: 12,
),
),
child: Text(value, style: GoogleFonts.poppins(fontSize: 12)),
),
],
),

View File

@ -131,7 +131,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
"delegationEndDate",
// "dateOfIssue",
// "dateOfExpiry",
"changePassword"
"changePassword",
];
Color? layoutColor;
@ -202,7 +202,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
print("API Selected User Has Data - $apiselectedUser");
}
userIdApi = apiselectedUser?["user_id"] ?? "";
userIdApi = apiselectedUser?["user_id"] ?? "";
controllers["Fname"]?.text = apiselectedUser?["first_name"] ?? "";
controllers["Lname"]?.text = apiselectedUser?["last_name"] ?? "";
controllers["email"]?.text = apiselectedUser?["email"] ?? "";
@ -286,7 +286,8 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
if (apiselectedUser?["delegated_to_user_id"] != null) {
print(
"UPDADele- ${apiselectedUser?["delegated_to_user_id"]?.toString()}");
"UPDADele- ${apiselectedUser?["delegated_to_user_id"]?.toString()}",
);
selectedSubstituteApprover =
apiselectedUser?["delegated_to_user_id"]?.toString() ?? "";
@ -303,12 +304,11 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
List<dynamic> decodedList = jsonDecode(fixedJson);
selectedServiceIds = decodedList.map<Map<String, dynamic>>((item) {
final map = Map<String, dynamic>.from(item);
return {
"service_id": map['service_id'].toString(),
};
}).toList();
selectedServiceIds =
decodedList.map<Map<String, dynamic>>((item) {
final map = Map<String, dynamic>.from(item);
return {"service_id": map['service_id'].toString()};
}).toList();
} catch (e) {
print("❌ Error decoding fixed agent_supported_service_ids: $e");
selectedServiceIds = [];
@ -380,8 +380,9 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
// userIdsApi = userMap.keys.toList();
// Handle selectedUser as a Map (not a List)
apiselectedUser = extraData['selectedUser']
as Map<String, dynamic>?; // Cast it as a Map
apiselectedUser =
extraData['selectedUser']
as Map<String, dynamic>?; // Cast it as a Map
isViewMode = extraData['isViewMode'] ?? false;
isEditProfile = extraData['isEditProfile'] ?? false;
});
@ -443,7 +444,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
userMap = {
for (var user in userList)
user['user_id'].toString():
"${user['first_name']} ${user['last_name']}"
"${user['first_name']} ${user['last_name']}",
};
userIdsApi = userMap.keys.toList();
});
@ -481,13 +482,15 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
String? bodyStringColor = await getBodyColor();
setState(() {
layoutColor = layoutString != null
? Color(int.parse(layoutString))
: Colors.redAccent;
layoutColor =
layoutString != null
? Color(int.parse(layoutString))
: Colors.redAccent;
bodyColor = bodyStringColor != null
? Color(int.parse(bodyStringColor))
: Colors.white;
bodyColor =
bodyStringColor != null
? Color(int.parse(bodyStringColor))
: Colors.white;
});
}
@ -538,6 +541,11 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
}
}
void handleGoBack() async {
print("hello, please Go Back");
printFormData();
}
void handleNext() async {
print("USR Detail Next");
printFormData();
@ -552,35 +560,67 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
print("USERDETAILS : $data");
if (!isValidData(data)) {
print("USERDETAILS : $userDetials");
print("Validation Failed: Required fields are missing.");
setState(() {});
return; // Stop execution if validation fails
final tabs = {
"personal": "Personal Details",
"office": "Office Details",
"travel": "Travel Details",
};
final tabKeys = tabs.keys.toList(); // ["personal", "office", "travel"]
final currentIndex = tabKeys.indexOf(selectedTab ?? "personal");
print("currentIndex - $currentIndex");
bool isValid = false;
final currentTab = tabKeys[currentIndex];
if (currentTab == "personal") {
isValid = isValidData(userDetials);
} else if (currentTab == "office") {
isValid = isValidDataTwo(userDetials);
} else {
print("USERDETAILS : $userDetials");
final tabs = {
"personal": "Personal Details",
"office": "Office Details",
"travel": "Travel Details",
};
final tabKeys = tabs.keys.toList(); // ["personal", "office", "travel"]
final currentIndex = tabKeys.indexOf(selectedTab ?? "personal");
if (currentIndex < tabKeys.length - 1) {
// Move to next tab
setState(() {
selectedTab = tabKeys[currentIndex + 1];
});
} else {
// Already at last tab (travel), maybe submit form or show done message
print("All tabs completed!");
// You can trigger full form submit here
}
isValid = true; // Travel tab might not need validation at this point
}
if (!isValid) {
print("Validation Failed on $currentTab: $userDetials");
setState(() {}); // To trigger UI update showing errors
return;
}
if (currentIndex < tabKeys.length - 1) {
// Move to next tab
setState(() {
selectedTab = tabKeys[currentIndex + 1];
});
} else {
// Final step submit or show done
print("All tabs completed!");
// Submit the full form here
}
// if (!isValidData(data))
// {
// print("USERDETAILS : $userDetials");
// print("Validation Failed: Required fields are missing.");
// setState(() {});
// return; // Stop execution if validation fails
// } else
// {
// print("USERDETAILS : $userDetials");
//
// if (currentIndex < tabKeys.length - 1) {
// // Move to next tab
// setState(() {
// selectedTab = tabKeys[currentIndex + 1];
// });
// } else {
// // Already at last tab (travel), maybe submit form or show done message
// print("All tabs completed!");
// // You can trigger full form submit here
// }
// }
}
void handleSubmit() async {
@ -601,7 +641,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
// Map<String, dynamic> data = userDetials;
if (!isValidData(data)) {
if (!isValidData(data) && isValidDataTwo(data)) {
print("USERDETAILS : $userDetials");
print("Validation Failed: Required fields are missing.");
setState(() {});
@ -623,7 +663,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
"last_name",
"email",
"mobile_no",
// "employeeCode"
// "employeeCode",
];
if (apiselectedUser == null) {
@ -647,8 +687,9 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
if (data["alternate_mobile_no"] != null &&
data["alternate_mobile_no"].toString().isNotEmpty) {
if (!RegExp(r"^\d{10}$")
.hasMatch(data["alternate_mobile_no"].toString())) {
if (!RegExp(
r"^\d{10}$",
).hasMatch(data["alternate_mobile_no"].toString())) {
errorMessages["alternate_mobile_no"] =
"Enter 10 digits"; // Invalid mobile number format
}
@ -656,14 +697,31 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
// Email validation
if (data["email"] != null && data["email"].toString().isNotEmpty) {
if (!RegExp(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
.hasMatch(data["email"].toString())) {
if (!RegExp(
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
).hasMatch(data["email"].toString())) {
errorMessages["email"] = "Invalid email format"; // Invalid email format
}
}
return errorMessages.isEmpty; // Valid if there are no errors
}
bool isValidDataTwo(Map<String, dynamic> data) {
errorMessages.clear(); // Reset errors
// Required fields that must not be empty
List<String> requiredFields = ["employee_code"];
// Check validation for each field
for (String field in requiredFields) {
if (data[field] == null || data[field].toString().trim().isEmpty) {
errorMessages[field] = "Required";
}
}
return errorMessages.isEmpty; // Valid if there are no errors
}
void _clearError(String field) {
if (mounted && errorMessages.containsKey(field)) {
setState(() {
@ -814,8 +872,10 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
print("enteredPassword - $enteredPassword ");
if (hashedPassword != null && hashedPassword.isNotEmpty) {
bool isMatch =
BCrypt.checkpw(enteredPassword, hashedPassword); // Compare passwords
bool isMatch = BCrypt.checkpw(
enteredPassword,
hashedPassword,
); // Compare passwords
setState(() {
// Ensure UI updates
@ -825,8 +885,9 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
print(" Password match!");
} else {
print(" Password NOT match!");
errorMessages
.remove("password"); // Clear error if password is different
errorMessages.remove(
"password",
); // Clear error if password is different
}
});
} else {
@ -836,32 +897,36 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: Color(0xFFf5f5f5),
// appBar: isDesktop ? null : const CustomAppBar(title: 'Create User '),
// drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false),
body: Padding(
padding: isDesktop
? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [
Expanded(child: buildData(isDesktop, context)),
],
return Scaffold(
backgroundColor: Color(0xFFf5f5f5),
// appBar: isDesktop ? null : const CustomAppBar(title: 'Create User '),
// drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false),
body: Padding(
padding:
isDesktop
? EdgeInsets.symmetric(
horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding
vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [Expanded(child: buildData(isDesktop, context))],
),
),
),
);
});
);
},
);
}
Widget buildData(bool isDesktop, context) {
@ -875,9 +940,10 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
children: [
Expanded(
child: Container(
height: isDesktop
? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height,
height:
isDesktop
? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height,
child: Padding(
padding: EdgeInsets.all(0.0),
child: _buildUserDetails(isDesktop),
@ -887,25 +953,32 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: isDesktop
? Row(
padding: const EdgeInsets.all(8.0),
child:
isDesktop
? Row(
mainAxisAlignment: MainAxisAlignment.end,
children: (selectedTab == "travel" ||
selectedRole == "5" ||
setSelectesUserType == true)
? _buildSubmit(isDesktop, layoutColor!)
: _buildNext(isDesktop, layoutColor!),
children:
(selectedTab == "travel" ||
selectedRole == "5" ||
setSelectesUserType == true)
? _buildSubmit(isDesktop, layoutColor!)
: _buildNext(
isDesktop,
layoutColor!,
), // _buildGoBack(isDesktop, layoutColor!),
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: (selectedTab == "travel" ||
selectedRole == "5" ||
setSelectesUserType == true)
? _buildSubmit(isDesktop, layoutColor!)
: _buildNext(isDesktop, layoutColor!),
)),
)
: Row(
mainAxisAlignment: MainAxisAlignment.end,
children:
(selectedTab == "travel" ||
selectedRole == "5" ||
setSelectesUserType == true)
? _buildSubmit(isDesktop, layoutColor!)
: _buildNext(isDesktop, layoutColor!),
),
),
),
],
),
);
@ -944,25 +1017,22 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
// })
],
),
SizedBox(
height: 18,
),
SizedBox(height: 18),
isDesktop
? buildTabsForUser()
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: buildTabsForUser(),
),
scrollDirection: Axis.horizontal,
child: buildTabsForUser(),
),
Container(
// color: Colors.yellow.shade50,
height: MediaQuery.of(context).size.height * 0.64,
child: Row(
children: [
Expanded(child: buildTabContents(isDesktop, isViewMode)),
],
),
)
),
],
),
),
@ -977,7 +1047,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
personalDetailsKey: personalDetailsKey,
isDesktop: isDesktop, // pass isDesktop as a named argument
isViewMode: isViewMode,
userIdApi:userIdApi,
userIdApi: userIdApi,
controllers: controllers,
errorMessages: errorMessages,
selectedGender: selectedGender,
@ -1012,7 +1082,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
return OfficeDetails(
isDesktop: isDesktop, // pass isDesktop as a named argument
isViewMode: isViewMode,
userIdApi:userIdApi,
userIdApi: userIdApi,
controllers: controllers,
errorMessages: errorMessages,
selectedLevel: selectedLevel,
@ -1063,14 +1133,14 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
errorMessages: errorMessages,
travelDetails: travelDetailsDataFromAPI, // 👈 Pass this down
passportFileUrlFromApi: passportFileUrlFromApi,
userIdApi:userIdApi,
userIdApi: userIdApi,
);
default:
return PersonalDetails(
personalDetailsKey: personalDetailsKey,
isDesktop: isDesktop, // pass isDesktop as a named argument
isViewMode: isViewMode,
userIdApi:userIdApi,
userIdApi: userIdApi,
controllers: controllers,
errorMessages: errorMessages,
selectedGender: selectedGender,
@ -1101,9 +1171,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
Map<String, String> getTabs(bool setSelectesUserType) {
if (setSelectesUserType || selectedRole == "5") {
return {
"personal": "Personal Details",
};
return {"personal": "Personal Details"};
} else {
return allTabs;
}
@ -1113,50 +1181,102 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
return Row(
crossAxisAlignment: CrossAxisAlignment.end, // important
children: tabs.entries.map((entry) {
final isSelected = selectedTab == entry.key;
return GestureDetector(
onTap: () {
setState(() {
selectedTab = entry.key;
});
},
child: Padding(
padding: const EdgeInsets.only(right: 24.0), // space between tabs
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
entry.value,
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: isSelected ? Color(0xFF114D8B) : Color(0xFF475569),
),
children:
tabs.entries.map((entry) {
final isSelected = selectedTab == entry.key;
return GestureDetector(
onTap: () {
setState(() {
bool isValid = false;
final currentTab = selectedTab;
if (currentTab == "personal") {
isValid = isValidData(userDetials);
if (isValid) {
selectedTab = entry.key;
}
} else if (currentTab == "office") {
isValid = isValidDataTwo(userDetials);
if (isValid) {
selectedTab = entry.key;
}
} else {
isValid =
true; // Travel tab might not need validation at this point
selectedTab = entry.key;
}
});
},
child: Padding(
padding: const EdgeInsets.only(
right: 24.0,
), // space between tabs
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
entry.value,
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color:
isSelected ? Color(0xFF114D8B) : Color(0xFF475569),
),
),
const SizedBox(height: 8),
AnimatedContainer(
duration: Duration(milliseconds: 300),
height: 2,
width: isSelected ? 50 : 0, // small line
color: Color(0xFF114D8B),
),
],
),
const SizedBox(height: 8),
AnimatedContainer(
duration: Duration(milliseconds: 300),
height: 2,
width: isSelected ? 50 : 0, // small line
color: Color(0xFF114D8B),
),
],
),
),
);
}).toList(),
),
);
}).toList(),
);
}
// ---- Submit ---------------------------------------
// ---- Submit ---------------------------------------
List<Widget> _buildGoBack(isDesktop, Color layoutColor) {
return [
MouseRegion(
cursor:
isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
isViewMode ? layoutColor : layoutColor, // Keep original color
foregroundColor:
isViewMode ? Colors.white : Colors.white, // Keep original color
disabledBackgroundColor:
layoutColor, // Ensure color remains when disabled
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: layoutColor, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: handleGoBack,
child: Text("Back"),
),
),
];
}
List<Widget> _buildNext(isDesktop, Color layoutColor) {
return [
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
cursor:
isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
@ -1177,41 +1297,42 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
// isViewMode ? null : handleNext, // Disable when in view mode
child: Text("Next"),
),
)
),
];
}
List<Widget> _buildSubmit(isDesktop, Color layoutColor) {
return [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: layoutColor, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: layoutColor, width: 2),
),
onPressed: () {
isEditProfile ? context.go('/listPlan') : context.go('/listUser');
},
child: Text("Cancel")),
SizedBox(
width: 20,
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
isEditProfile ? context.go('/listPlan') : context.go('/listUser');
},
child: Text("Cancel"),
),
SizedBox(width: 20),
if (!isViewMode)
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
cursor:
isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
isViewMode ? layoutColor : layoutColor, // Keep original color
foregroundColor: isViewMode
? Colors.white
: Colors.white, // Keep original color
foregroundColor:
isViewMode
? Colors.white
: Colors.white, // Keep original color
disabledBackgroundColor:
layoutColor, // Ensure color remains when disabled
disabledForegroundColor: Colors.white,
@ -1225,7 +1346,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
isViewMode ? null : handleSubmit, // Disable when in view mode
child: Text("Submit"),
),
)
),
];
}
}

View File

@ -17,7 +17,6 @@ class OfficeDetails extends StatefulWidget {
final bool isViewMode;
final String? userIdApi;
final ValueChanged<String?>? onLevelChanged;
final ValueChanged<String?>? onDepartmentChanged;
final ValueChanged<String?>? onFirstApproverChanged;
@ -157,17 +156,25 @@ class _OfficeDetailsState extends State<OfficeDetails> {
fetchFindGroup();
}
Future<void> apiCheckDuplicate(String label, String field, String value, String? userId) async {
Future<void> apiCheckDuplicate(
String label,
String field,
String value,
String? userId,
) async {
try {
// Basic validation: Check mobile number length
if (field == "employeeCode") {
field = "employee_code";
}
final response = await apiService.CheckDuplicate(label, field, value, userId);
final response = await apiService.CheckDuplicate(
label,
field,
value,
userId,
);
if (response.isNotEmpty) {
_clearError(field);
widget.errorMessages[field] = response['message'] ?? "$label already exists";
widget.errorMessages[field] =
response['message'] ?? "$label Already Exists";
print("Duplicate found: ${response['message']}");
return;
} else {
@ -290,7 +297,7 @@ class _OfficeDetailsState extends State<OfficeDetails> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 10),
SizedBox(height: 20),
_buildFirstRow(widget.isDesktop),
if (widget.isDesktop) SizedBox(height: 10),
_buildSecondRow(widget.isDesktop),
@ -407,7 +414,7 @@ class _OfficeDetailsState extends State<OfficeDetails> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Employee Code",
"Employee Code *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -425,8 +432,13 @@ class _OfficeDetailsState extends State<OfficeDetails> {
controller: widget.controllers["employeeCode"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("employeeCode");
apiCheckDuplicate("Employee Code", "employeeCode",value,widget.userIdApi);
_clearError("employee_code");
apiCheckDuplicate(
"Employee Code",
"employee_code",
value,
widget.userIdApi,
);
},
decoration: InputDecoration(
labelText: "Employee Code",
@ -441,10 +453,10 @@ class _OfficeDetailsState extends State<OfficeDetails> {
),
),
),
if (widget.errorMessages["employeeCode"] != null) ...[
if (widget.errorMessages["employee_code"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["employeeCode"]!,
widget.errorMessages["employee_code"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
@ -452,7 +464,71 @@ class _OfficeDetailsState extends State<OfficeDetails> {
);
}
// Widget buildDepartmentFieldOld() {
// return Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// "Department",
// style: GoogleFonts.poppins(
// fontSize: 12,
// fontWeight: FontWeight.w600,
// color: Color(0xFF575A74),
// ),
// ),
// SizedBox(height: 5),
// CustomTextFieldUserWrapper(
// isFocused: false, // Dropdown doesn't use focus
// isDesktop: widget.isDesktop,
// child: SizedBox(
// height: 45, // Set appropriate height
// child: DropdownButtonFormField<String>(
// value: selectedDepartment,
// // value: widget.isViewMode ? null : selectedDepartment,
// style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
// decoration: InputDecoration(
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(
// horizontal: 10,
// ), // Proper padding
// ),
// onChanged:
// widget.isViewMode
// ? null
// : (newValue) {
// setState(() {
// selectedDepartment = newValue;
// });
// widget.onDepartmentChanged?.call(newValue);
// },
//
// items:
// apiCostData?.map<DropdownMenuItem<String>>((item) {
// return DropdownMenuItem(
//
// value: item['department_id'], // ID as value
// child: Text(item['name'] ?? "Unknown"),
// );
// }).toList(),
// hint: Text("Select"),
// disabledHint: Text(
// selectedDepartment ?? "Select Department",
// style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
// ),
// ),
// ),
// ),
// ],
// );
// }
Widget buildDepartmentField() {
// Map department_id to department_name
Map<String, String> departmentMap = {
for (var item in apiCostData ?? [])
item['department_id'] as String: item['name'] as String,
};
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -466,41 +542,74 @@ class _OfficeDetailsState extends State<OfficeDetails> {
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false, // Dropdown doesn't use focus
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 45, // Set appropriate height
child: DropdownButtonFormField<String>(
value: selectedDepartment,
// value: widget.isViewMode ? null : selectedDepartment,
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
), // Proper padding
height: 40,
child: DropdownSearch<String>(
selectedItem: departmentMap[selectedDepartment],
enabled: !widget.isViewMode,
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose,
menuProps: const MenuProps(backgroundColor: Colors.white),
itemBuilder:
(context, item, isSelected) => Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
constraints: BoxConstraints(maxHeight: 200),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search Department...",
contentPadding: EdgeInsets.symmetric(horizontal: 10),
),
),
),
items: departmentMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder:
(context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Department",
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black,
),
),
),
onChanged:
widget.isViewMode
? null
: (newValue) {
: (String? newValue) {
if (newValue == null) return;
final departmentId =
departmentMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
setState(() {
selectedDepartment = newValue;
selectedDepartment = departmentId;
});
widget.onDepartmentChanged?.call(newValue);
widget.onDepartmentChanged?.call(
departmentId,
); // Send department_id
},
items:
apiCostData?.map<DropdownMenuItem<String>>((item) {
return DropdownMenuItem(
value: item['department_id'], // ID as value
child: Text(item['name'] ?? "Unknown"),
);
}).toList(),
hint: Text("Select"),
disabledHint: Text(
selectedDepartment ?? "Select Department",
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
),
),
),
),
@ -585,6 +694,17 @@ class _OfficeDetailsState extends State<OfficeDetails> {
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose, // Allows flexible height
itemBuilder:
(context, item, isSelected) => Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
constraints: BoxConstraints(maxHeight: 250),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
@ -686,6 +806,22 @@ class _OfficeDetailsState extends State<OfficeDetails> {
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose, // Allows flexible height
menuProps: const MenuProps(
backgroundColor: Colors.white,
),
itemBuilder:
(context, item, isSelected) => Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(
fontSize: 11.5,
),
),
),
constraints: BoxConstraints(maxHeight: 250),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
@ -806,6 +942,22 @@ class _OfficeDetailsState extends State<OfficeDetails> {
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose, // Allows flexible height
menuProps: const MenuProps(
backgroundColor: Colors.white,
),
itemBuilder:
(context, item, isSelected) => Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(
fontSize: 11.5,
),
),
),
constraints: BoxConstraints(maxHeight: 250),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
@ -925,6 +1077,22 @@ class _OfficeDetailsState extends State<OfficeDetails> {
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose, // Allows flexible height
menuProps: const MenuProps(
backgroundColor: Colors.white,
),
itemBuilder:
(context, item, isSelected) => Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(
fontSize: 11.5,
),
),
),
constraints: BoxConstraints(maxHeight: 250),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
@ -1047,6 +1215,22 @@ class _OfficeDetailsState extends State<OfficeDetails> {
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose, // Allows flexible height
menuProps: const MenuProps(
backgroundColor: Colors.white,
),
itemBuilder:
(context, item, isSelected) => Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(
fontSize: 11.5,
),
),
),
constraints: BoxConstraints(maxHeight: 250),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(

View File

@ -157,7 +157,12 @@ class PersonalDetailsState extends State<PersonalDetails> {
});
}
Future<void> apiCheckDuplicate(String label, String field, String value, String? userId) async {
Future<void> apiCheckDuplicate(
String label,
String field,
String value,
String? userId,
) async {
try {
// Basic validation: Check mobile number length
if (field == "mobile_no" && value.length != 10) {
@ -167,10 +172,16 @@ class PersonalDetailsState extends State<PersonalDetails> {
return; // Skip the API call if input is invalid
}
final response = await apiService.CheckDuplicate(label, field, value, userId);
final response = await apiService.CheckDuplicate(
label,
field,
value,
userId,
);
if (response.isNotEmpty) {
_clearError(field);
widget.errorMessages[field] = response['message'] ?? "$label already exists";
widget.errorMessages[field] =
response['message'] ?? "$label Already Exists";
print("Duplicate found: ${response['message']}");
return;
} else {
@ -182,9 +193,6 @@ class PersonalDetailsState extends State<PersonalDetails> {
}
}
void _clearError(String field) {
setState(() {
widget.errorMessages.remove(field);
@ -305,11 +313,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 10),
SizedBox(height: 20),
_buildFirstRow(widget.isDesktop),
SizedBox(height: 10),
_buildSecondRow(widget.isDesktop),
// SizedBox(height: 10),
SizedBox(height: 10),
_buildThirdRow(widget.isDesktop),
SizedBox(height: 10),
_buildForthRow(widget.isDesktop),
@ -731,7 +739,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"First Name",
"First Name *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -781,7 +789,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Last Name",
"Last Name*",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -822,7 +830,107 @@ class PersonalDetailsState extends State<PersonalDetails> {
);
}
// Widget buildGenderFieldOld() {
// return Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// "Gender",
// style: GoogleFonts.poppins(
// fontSize: 12,
// fontWeight: FontWeight.w600,
// color: Color(0xFF575A74),
// ),
// ),
// SizedBox(height: 5),
// CustomTextFieldUserWrapper(
// width:
// widget.isDesktop
// ? MediaQuery.of(context).size.width * 0.12
// : null,
// isFocused: false,
// isDesktop: widget.isDesktop,
// child: SizedBox(
// height: 40,
// child: DropdownButtonFormField<String>(
// // value: selectedGender,
// value: widget.isViewMode ? null : selectedGender,
// onChanged:
// widget.isViewMode
// ? null
// : (String? newValue) {
// setState(() {
// selectedGender = newValue;
// print("selectedGender - $selectedGender");
// });
//
// widget.onGenderChanged?.call(newValue);
// },
// decoration: InputDecoration(
// border: InputBorder.none,
// enabled: !widget.isViewMode, // Disables input when in view mode
// ),
// style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
// items: [
// DropdownMenuItem(value: "Male", child: Text("Male")),
// DropdownMenuItem(value: "Female", child: Text("Female")),
// ],
// hint: Text(
// selectedGender ?? "Select Gender",
// style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
// ),
// disabledHint: Text(
// selectedGender ?? "Select Gender",
// style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
// ),
// ),
//
// // child: DropdownSearch<String>(
// // selectedItem: selectedGender,
// // // key: ValueKey(selectedGender),
// // popupProps: PopupProps.menu(
// // fit: FlexFit.loose, // Allows flexible height
// // constraints: BoxConstraints(maxHeight: 250),
// //
// // ),
// // items: ["Male", "Female", "Others"],
// // 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",
// // style: TextStyle(fontSize: 12),
// // ),
// // ),
// // onChanged: isViewMode
// // ? null
// // : (String? newValue) {
// // setState(() {
// // // Find the country_code based on selected country_name
// // selectedGender = newValue;
// // print("selectedGender - $selectedGender");
// // // if (selectedCountry!.isNotEmpty) {
// // // errorMessages.remove("country_code");
// // // }
// // });
// // },
// //
// //
// // ),
// ),
// ),
// ],
// );
// }
Widget buildGenderField() {
List<String> genderOptions = ["Male", "Female"];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -844,9 +952,45 @@ class PersonalDetailsState extends State<PersonalDetails> {
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
// value: selectedGender,
value: widget.isViewMode ? null : selectedGender,
child: DropdownSearch<String>(
selectedItem: selectedGender,
enabled: !widget.isViewMode,
popupProps: PopupProps.menu(
showSearchBox: false, // Set true if you want search
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 200),
itemBuilder:
(context, item, isSelected) => Container(
color: Colors.white,
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
),
items: genderOptions,
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder:
(context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Gender",
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black,
),
),
),
onChanged:
widget.isViewMode
? null
@ -855,65 +999,9 @@ class PersonalDetailsState extends State<PersonalDetails> {
selectedGender = newValue;
print("selectedGender - $selectedGender");
});
widget.onGenderChanged?.call(newValue);
},
decoration: InputDecoration(
border: InputBorder.none,
enabled: !widget.isViewMode, // Disables input when in view mode
),
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
items: [
DropdownMenuItem(value: "Male", child: Text("Male")),
DropdownMenuItem(value: "Female", child: Text("Female")),
],
hint: Text(
selectedGender ?? "Select Gender",
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
),
disabledHint: Text(
selectedGender ?? "Select Gender",
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
),
),
// child: DropdownSearch<String>(
// selectedItem: selectedGender,
// // key: ValueKey(selectedGender),
// popupProps: PopupProps.menu(
// fit: FlexFit.loose, // Allows flexible height
// constraints: BoxConstraints(maxHeight: 250),
//
// ),
// items: ["Male", "Female", "Others"],
// 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",
// style: TextStyle(fontSize: 12),
// ),
// ),
// onChanged: isViewMode
// ? null
// : (String? newValue) {
// setState(() {
// // Find the country_code based on selected country_name
// selectedGender = newValue;
// print("selectedGender - $selectedGender");
// // if (selectedCountry!.isNotEmpty) {
// // errorMessages.remove("country_code");
// // }
// });
// },
//
//
// ),
),
),
],
@ -1014,7 +1102,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Email",
"Email *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -1032,7 +1120,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
controller: widget.controllers["email"],
onChanged: (value) {
_clearError("email");
apiCheckDuplicate("Email", "email",value,widget.userIdApi);
apiCheckDuplicate("Email", "email", value, widget.userIdApi);
},
enabled: !widget.isViewMode,
decoration: InputDecoration(
@ -1064,7 +1152,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Mobile Number",
"Mobile Number *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -1083,7 +1171,12 @@ class PersonalDetailsState extends State<PersonalDetails> {
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("mobile_no");
apiCheckDuplicate("Mobile Number", "mobile_no",value,widget.userIdApi);
apiCheckDuplicate(
"Mobile Number",
"mobile_no",
value,
widget.userIdApi,
);
},
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
@ -1208,6 +1301,18 @@ class PersonalDetailsState extends State<PersonalDetails> {
popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality
fit: FlexFit.loose, // Allows flexible height
menuProps: const MenuProps(backgroundColor: Colors.white),
itemBuilder:
(context, item, isSelected) => Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
constraints: BoxConstraints(maxHeight: 200),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
@ -1323,12 +1428,79 @@ class PersonalDetailsState extends State<PersonalDetails> {
);
}
// Widget buildRoleOld() {
// return Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// "Role ",
// style: GoogleFonts.poppins(
// fontSize: 12,
// fontWeight: FontWeight.w600,
// color: Color(0xFF575A74),
// ),
// ),
// SizedBox(height: 5),
// CustomTextFieldUserWrapper(
// isFocused: false, // Dropdown doesn't use focus
// isDesktop: widget.isDesktop,
// child: SizedBox(
// height: 45, // Set appropriate height
// child: DropdownButtonFormField<String>(
// // value: widget.isViewMode ? null : selectedRole,
// value: selectedRole,
// style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
// decoration: InputDecoration(
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(
// horizontal: 10,
// ), // Proper padding
// ),
// onChanged:
// widget.isViewMode
// ? null
// : (newValue) {
// setState(() {
// selectedRole = newValue;
// isTravelAgent = selectedRole == "5";
// // Pass the result back to parent
// widget.onUserTypeChanged?.call(isTravelAgent);
// });
// widget.onRoleChanged?.call(newValue);
// },
// items:
// apiRoleData?.map<DropdownMenuItem<String>>((item) {
// return DropdownMenuItem(
// value: item['dropdown_key'], // ID as value
// child: Text(item['dropdown_value'] ?? "Select Role"),
// );
// }).toList(),
// hint: Text("Select Role"),
// disabledHint: Text(
// selectedRole ?? "Select Role",
// style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
// ),
// ),
// ),
// ),
// ],
// );
// }
Widget buildRole() {
// Map dropdown_key (ID) -> dropdown_value (Name)
Map<String, String> roleMap = {
for (var item in apiRoleData ?? [])
item['dropdown_key'] as String: item['dropdown_value'] as String,
};
List<String> roleNames = roleMap.values.toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Role ",
"Role",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -1337,44 +1509,73 @@ class PersonalDetailsState extends State<PersonalDetails> {
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false, // Dropdown doesn't use focus
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 45, // Set appropriate height
child: DropdownButtonFormField<String>(
// value: widget.isViewMode ? null : selectedRole,
value: selectedRole,
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
), // Proper padding
height: 40,
child: DropdownSearch<String>(
selectedItem: selectedRole != null ? roleMap[selectedRole] : null,
enabled: !widget.isViewMode,
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
menuProps: const MenuProps(backgroundColor: Colors.white),
itemBuilder:
(context, item, isSelected) => Container(
color: Colors.white,
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search Role...",
contentPadding: EdgeInsets.symmetric(horizontal: 10),
),
),
),
items: roleNames,
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder:
(context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Role",
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black,
),
),
),
onChanged:
widget.isViewMode
? null
: (newValue) {
: (String? newValue) {
if (newValue == null) return;
final selectedKey =
roleMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
setState(() {
selectedRole = newValue;
isTravelAgent = selectedRole == "5";
// Pass the result back to parent
widget.onUserTypeChanged?.call(isTravelAgent);
selectedRole = selectedKey;
isTravelAgent = selectedKey == "5";
});
widget.onRoleChanged?.call(newValue);
widget.onUserTypeChanged?.call(isTravelAgent);
widget.onRoleChanged?.call(selectedKey);
},
items:
apiRoleData?.map<DropdownMenuItem<String>>((item) {
return DropdownMenuItem(
value: item['dropdown_key'], // ID as value
child: Text(item['dropdown_value'] ?? "Select Role"),
);
}).toList(),
hint: Text("Select Role"),
disabledHint: Text(
selectedRole ?? "Select Role",
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
),
),
),
),
@ -1429,7 +1630,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Password",
"Password *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,

View File

@ -33,7 +33,6 @@ class TravellerDetails extends StatefulWidget {
final String? passportFileUrlFromApi;
final String? userIdApi;
const TravellerDetails({
Key? key,
required this.controllers,
@ -149,15 +148,24 @@ class TravellerDetailsState extends State<TravellerDetails> {
});
}
Future<void> apiCheckDuplicate(String label, String field, String value, String? userId) async {
Future<void> apiCheckDuplicate(
String label,
String field,
String value,
String? userId,
) async {
var newField = "";
try {
if(field == "forex_card_num"){
field = "forex_pre_paid_card_number";
}
final response = await apiService.CheckDuplicate(label, field, value, userId);
final response = await apiService.CheckDuplicate(
label,
newField,
value,
userId,
);
if (response.isNotEmpty) {
_clearError(field);
widget.errorMessages[field] = response['message'] ?? "$label already exists";
widget.errorMessages[field] =
response['message'] ?? "$label already exists";
print("Duplicate found: ${response['message']}");
return;
} else {
@ -705,6 +713,7 @@ class TravellerDetailsState extends State<TravellerDetails> {
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
padding: const EdgeInsets.only(top: 8),
decoration: BoxDecoration(
border: Border(
top: BorderSide(
@ -1107,7 +1116,13 @@ class TravellerDetailsState extends State<TravellerDetails> {
controller: controllers["passportNumber"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("last_name");
_clearError("passport_number");
apiCheckDuplicate(
"Passport Number",
"passport_number",
value,
widget.userIdApi,
);
},
decoration: InputDecoration(
labelText: "Passport Number",
@ -1122,6 +1137,13 @@ class TravellerDetailsState extends State<TravellerDetails> {
),
),
),
if (widget.errorMessages["passport_number"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["passport_number"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
);
}
@ -1181,7 +1203,8 @@ class TravellerDetailsState extends State<TravellerDetails> {
_selectedDateOfIssue != null && _selectedDateOfIssue!.isAfter(today)
? _selectedDateOfIssue!
: today,
firstDate: today,
// firstDate: today,
firstDate: DateTime(1900),
lastDate: DateTime(2100),
);
@ -1271,7 +1294,8 @@ class TravellerDetailsState extends State<TravellerDetails> {
_selectedDateOfExpiry!.isAfter(today)
? _selectedDateOfExpiry!
: today,
firstDate: today,
// firstDate: today,
firstDate: DateTime(1900),
lastDate: DateTime(2100),
);
@ -2330,8 +2354,13 @@ class TravellerDetailsState extends State<TravellerDetails> {
controller: controllers["forex_card_num"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("forex_card_num");
apiCheckDuplicate("Forex Pre-Paid Card Number", "forex_card_num",value,widget.userIdApi);
_clearError("forex_pre_paid_card_number");
apiCheckDuplicate(
"Forex Pre-Paid Card Number",
"forex_pre_paid_card_number",
value,
widget.userIdApi,
);
},
decoration: InputDecoration(
labelText: "Card Number",
@ -2467,9 +2496,16 @@ class TravellerDetailsState extends State<TravellerDetails> {
height: 60,
child: Center(
child: Padding(
padding: EdgeInsets.only(top: 20, left: 8), // 👈 Add top padding here
padding: EdgeInsets.only(
top: 20,
left: 8,
), // 👈 Add top padding here
child: IconButton(
icon: Icon(Icons.horizontal_rule, color: Colors.red, size: 15),
icon: Icon(
Icons.remove_circle_sharp,
color: Colors.red,
size: 15,
),
tooltip: 'Cancel the Details',
onPressed: () {
setState(() {
@ -2480,7 +2516,6 @@ class TravellerDetailsState extends State<TravellerDetails> {
),
),
),
],
)
: Column(
@ -2491,7 +2526,11 @@ class TravellerDetailsState extends State<TravellerDetails> {
buildFrequentFlierInformation(entry),
SizedBox(width: 15),
IconButton(
icon: Icon(Icons.horizontal_rule, color: Colors.red, size: 15),
icon: Icon(
Icons.remove_circle_sharp,
color: Colors.red,
size: 15,
),
tooltip: 'Cancel the Details',
onPressed: () {
setState(() {
@ -2773,30 +2812,37 @@ class TravellerDetailsState extends State<TravellerDetails> {
child:
widget.isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildHotel(entry),
SizedBox(width: 25),
buildHotelMembershipNum(entry),
SizedBox(
height: 60,
child: Center(
child: Padding(
padding: EdgeInsets.only(top: 20, left: 8), // 👈 Add top padding here
child: IconButton(
icon: Icon(Icons.horizontal_rule, color: Colors.red, size: 15),
tooltip: 'Cancel the Details',
onPressed: () {
setState(() {
removeHotelLoyaltyEntry(entry);
});
},
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildHotel(entry),
SizedBox(width: 25),
buildHotelMembershipNum(entry),
SizedBox(
height: 60,
child: Center(
child: Padding(
padding: EdgeInsets.only(
top: 20,
left: 8,
), // 👈 Add top padding here
child: IconButton(
icon: Icon(
Icons.remove_circle_sharp,
color: Colors.red,
size: 15,
),
tooltip: 'Cancel the Details',
onPressed: () {
setState(() {
removeHotelLoyaltyEntry(entry);
});
},
),
),
),
),
),
),
],
)
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -2804,7 +2850,11 @@ class TravellerDetailsState extends State<TravellerDetails> {
SizedBox(height: 8),
buildHotelMembershipNum(entry),
IconButton(
icon: Icon(Icons.horizontal_rule, color: Colors.red, size: 15),
icon: Icon(
Icons.remove_circle_sharp,
color: Colors.red,
size: 15,
),
tooltip: 'Cancel the Details',
onPressed: () {
setState(() {
@ -3035,9 +3085,16 @@ class TravellerDetailsState extends State<TravellerDetails> {
height: 60,
child: Center(
child: Padding(
padding: EdgeInsets.only(top: 20, left: 8), // 👈 Add top padding here
padding: EdgeInsets.only(
top: 20,
left: 8,
), // 👈 Add top padding here
child: IconButton(
icon: Icon(Icons.horizontal_rule, color: Colors.red, size: 15),
icon: Icon(
Icons.remove_circle_sharp,
color: Colors.red,
size: 15,
),
tooltip: 'Cancel the Details',
onPressed: () {
setState(() {
@ -3061,7 +3118,11 @@ class TravellerDetailsState extends State<TravellerDetails> {
SizedBox(height: 8),
buildVisaValidUpTo(entry),
IconButton(
icon: Icon(Icons.horizontal_rule, color: Colors.red, size: 15),
icon: Icon(
Icons.remove_circle_sharp,
color: Colors.red,
size: 15,
),
tooltip: 'Cancel the Details',
onPressed: () {
setState(() {
@ -3187,6 +3248,116 @@ class TravellerDetailsState extends State<TravellerDetails> {
);
}
List<dynamic> purposeList = apiData?['visa_type_of_visa'];
// Map for id => name
Map<String, String> visaTypeMap = {
for (var item in purposeList)
item['dropdown_key'].toString(): item['dropdown_value'].toString(),
};
// Selected value from entry
String? selectedPurpose = entry['visa_type_id']?.toString();
if (!visaTypeMap.containsKey(selectedPurpose)) {
selectedPurpose = null;
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Visa Type",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.17
: null,
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: DropdownSearch<String>(
selectedItem: visaTypeMap[selectedPurpose],
enabled: purposeList.isNotEmpty,
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose,
menuProps: const MenuProps(backgroundColor: Colors.white),
constraints: BoxConstraints(maxHeight: 200),
itemBuilder:
(context, item, isSelected) => Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search Visa Type...",
hintStyle: GoogleFonts.poppins(fontSize: 11.5),
contentPadding: EdgeInsets.symmetric(horizontal: 10),
),
),
),
items: visaTypeMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder:
(context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Visa Type",
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black,
),
),
),
onChanged: (newValue) {
if (newValue == null) return;
final selectedId =
visaTypeMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
setState(() {
entry['visa_type_id'] = selectedId;
});
print("Updating form data: visa_type_id -> $selectedId");
},
),
),
),
],
);
}
Widget buildVisaType3(entry) {
if (apiData == null || apiData?['visa_type_of_visa'] == null) {
return Center(
child: Transform.scale(scale: 0.5, child: CircularProgressIndicator()),
);
}
// List<dynamic> purposeList = apiData?['visa_type_of_visa'] ?? [];
// List<dynamic> purposeList = [];
List<dynamic> purposeList = apiData?['visa_type_of_visa'];
@ -3402,8 +3573,9 @@ class TravellerDetailsState extends State<TravellerDetails> {
final pickedDate = await showDatePicker(
context: context,
initialDate: initialDate,
firstDate: initialDate,
// initialDate: initialDate,
// firstDate: initialDate,
firstDate: DateTime(1900),
lastDate: DateTime(2100),
);
@ -3490,8 +3662,9 @@ class TravellerDetailsState extends State<TravellerDetails> {
final pickedDate = await showDatePicker(
context: context,
initialDate: initialDate,
firstDate: initialDate,
// initialDate: initialDate,
// firstDate: initialDate,
firstDate: DateTime(1900),
lastDate: DateTime(2100),
);

View File

@ -2,7 +2,7 @@ import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
// import 'package:flutter/rendering.dart';
import 'package:flutter/rendering.dart';
import 'dart:html' as html;
import 'package:frontend/config/apiUrl.dart'; // 1 newly added
import 'package:frontend/services/apiService.dart';
@ -31,7 +31,7 @@ class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
// SemanticsBinding.instance.ensureSemantics(); // Safe here
SemanticsBinding.instance.ensureSemantics(); // Safe here
if (kIsWeb) {
final uri = Uri.parse(html.window.location.href);
if (uri.path == '/authredirection' &&

View File

@ -1,3 +1,3 @@
//api url
const String apiUrl = 'http://apitest.tripapprovaltool.com';
const String apiUrl = 'http://apitest.tripapprovaltool.com/tstat_be';
// const String apiUrl = 'https://uat.tripapprovaltool.com';