This commit is contained in:
venbaittech 2025-06-05 20:12:05 +05:30
parent cf15771518
commit 335fbacede
29 changed files with 3730 additions and 3179 deletions

View File

@ -287,7 +287,9 @@ class _ApprovalListState extends State<ApprovalList> {
}
void viewPlanforApprover(
String planId, {
String planId,
String? approverId,
String? delegaterId, {
bool isViewMode = false,
bool isApprover = true,
}) async {
@ -295,14 +297,26 @@ class _ApprovalListState extends State<ApprovalList> {
Map<String, dynamic> planData = await getViewPlan(planId);
print("ViewAAA - $planData");
context.go(
'/createPlan',
context.replace(
'/approver/plans',
extra: {
'planData': planData,
'approverId': approverId,
'delegaterId': delegaterId,
'isViewMode': isViewMode,
'isApprover': isApprover,
},
);
// context.go(
// '/createPlan',
// extra: {
// 'planData': planData,
// 'isViewMode': isViewMode,
// 'isApprover': isApprover,
// },
// );
} catch (e) {
print("Error fetching plan: $e");
}
@ -854,6 +868,8 @@ class _ApprovalListState extends State<ApprovalList> {
); // Close popup manually
viewPlanforApprover(
plan.planId,
plan.approverId,
plan.delegaterId,
isViewMode:
true,
isApprover:
@ -1108,6 +1124,8 @@ class _ApprovalListState extends State<ApprovalList> {
); // Close popup manually
viewPlanforApprover(
plan.planId,
plan.approverId,
plan.delegaterId,
isViewMode: true,
isApprover: true,
);

View File

@ -472,6 +472,40 @@ class _LoginWidgetState extends State<LoginWidget> {
/// **Password Field**
_buildLabel("Password"),
// TextFormField(
// controller: _passwordController,
// style: GoogleFonts.poppins(
// fontWeight: FontWeight.w600,
// fontSize: 11,
// ),
// obscureText: _obscureText,
//
// decoration: _inputDecoration(
// "Enter your password",
// ).copyWith(
// prefixIcon: Icon(Icons.key, size: 16),
// suffixIcon: IconButton(
// icon: Icon(
// _obscureText
// ? Icons.visibility_off
// : Icons.visibility,
// color: Color(0xFF12B24B),
// size: 16,
// ),
//
// onPressed:
// () => setState(
// () => _obscureText = !_obscureText,
// ),
// ),
// ),
//
// validator:
// (value) =>
// value == null || value.isEmpty
// ? 'Required Password'
// : null,
// ),
TextFormField(
controller: _passwordController,
style: GoogleFonts.poppins(
@ -479,6 +513,7 @@ class _LoginWidgetState extends State<LoginWidget> {
fontSize: 11,
),
obscureText: _obscureText,
textInputAction: TextInputAction.done,
decoration: _inputDecoration(
"Enter your password",
).copyWith(
@ -497,13 +532,17 @@ class _LoginWidgetState extends State<LoginWidget> {
),
),
),
onFieldSubmitted: (_) {
if (_formKey.currentState!.validate()) {
_login(context);
}
},
validator:
(value) =>
value == null || value.isEmpty
? 'Required Password'
: null,
),
const SizedBox(height: 10),
/// **Login Button**

View File

@ -19,13 +19,14 @@ class CostCenterData extends StatefulWidget {
final int? costcenterId; // <-- Add this
final Map<String, dynamic>? costcenterData;
const CostCenterData(
{super.key,
required this.isDesktop,
this.layoutColor,
required this.fetchGetCostCenter,
this.costcenterId,
this.costcenterData});
const CostCenterData({
super.key,
required this.isDesktop,
this.layoutColor,
required this.fetchGetCostCenter,
this.costcenterId,
this.costcenterData,
});
@override
CostCenterDataState createState() => CostCenterDataState();
@ -49,10 +50,7 @@ class CostCenterDataState extends State<CostCenterData> {
int? costcenterDataId;
late String isActive = "1";
List<String> dataHeader = [
"name",
"description",
];
List<String> dataHeader = ["name", "description"];
Map<String, dynamic> costcenterDetails() {
final data = {
@ -69,7 +67,6 @@ class CostCenterDataState extends State<CostCenterData> {
void initState() {
super.initState();
apiData = null;
for (var field in dataHeader) {
controllers[field] = TextEditingController();
@ -110,7 +107,6 @@ class CostCenterDataState extends State<CostCenterData> {
});
}
void toggleStatus() {
setState(() {
isActive = isActive == "1" ? "0" : "1";
@ -171,7 +167,9 @@ class CostCenterDataState extends State<CostCenterData> {
apiUrldata = '$apiUrl/api/updateCostCenter/$costcenterDataId';
costcenterData["cost_center_id"] = costcenterDataId.toString();
costcenterData["updated_by"] = userId;
(costcenterData.containsKey("created_by")) ? costcenterData.remove("created_by") : '' ;
(costcenterData.containsKey("created_by"))
? costcenterData.remove("created_by")
: '';
} else {
print("for add CostCenter id - null");
apiUrldata = '$apiUrl/api/createCostCenter';
@ -193,10 +191,10 @@ class CostCenterDataState extends State<CostCenterData> {
};
final body = jsonEncode(costcenterData);
final response = costcenterDataId != null
? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body);
final response =
costcenterDataId != null
? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body);
switch (response.statusCode) {
case 200:
@ -217,7 +215,6 @@ class CostCenterDataState extends State<CostCenterData> {
print("Failed to submit costcenter. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print(" Error submitting plan: $e");
}
@ -225,7 +222,6 @@ class CostCenterDataState extends State<CostCenterData> {
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: Colors.white,
contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30),
@ -238,27 +234,27 @@ class CostCenterDataState extends State<CostCenterData> {
Row(
children: [
Text(
(costcenterDataId != null) ? 'Edit CostCenter' : 'Create CostCenter',
(costcenterDataId != null)
? 'Edit CostCenter'
: 'Create CostCenter',
style: GoogleFonts.poppins(fontSize: 15, color: Colors.black),
),
const Spacer(),
],
),
const SizedBox(height: 2),
Divider(
thickness: 0.2,
color: Colors.blueGrey.shade100,
),
Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
const SizedBox(height: 5),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Name",
"Name *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -266,19 +262,20 @@ class CostCenterDataState extends State<CostCenterData> {
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["name"],
focusNode: focusNodes["name"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Name",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
)),
height: 40,
child: TextField(
controller: controllers["name"],
focusNode: focusNodes["name"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Name",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["name"] != null) ...[
SizedBox(height: 5), // Space before error message
@ -289,18 +286,17 @@ class CostCenterDataState extends State<CostCenterData> {
],
],
),
SizedBox(
height: 15,
),
SizedBox(height: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Description",
"Description *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -334,41 +330,37 @@ class CostCenterDataState extends State<CostCenterData> {
],
],
),
SizedBox(
height: 15,
),
SizedBox(height: 15),
if (costcenterDataId != null)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Change Status ",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Change Status ",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
Tooltip(
message:
isActive == "1" ? "Tap to deactivate" : "Tap to activate",
child: GestureDetector(
onTap: toggleStatus,
child: Text(
isActive == "1" ? "Active" : "Inactive",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
color: isActive == "1" ? Colors.green : Colors.red,
),
),
Tooltip(
message:
isActive == "1" ? "Tap to deactivate" : "Tap to activate",
child: GestureDetector(
onTap: toggleStatus,
child: Text(
isActive == "1" ? "Active" : "Inactive",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
color: isActive == "1" ? Colors.green : Colors.grey,
),
),
)
],
),
if (costcenterDataId != null)
SizedBox(
height: 15,
),
),
],
),
if (costcenterDataId != null) SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@ -405,16 +397,20 @@ class CostCenterDataState extends State<CostCenterData> {
borderRadius: BorderRadius.circular(8),
),
),
child: Text('Save',
style: GoogleFonts.poppins(
fontSize: 11, color: Colors.white)),
child: Text(
'Save',
style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.white,
),
),
),
),
],
)
),
// : SizedBox.shrink(),
],
),
);
}
}
}

View File

@ -99,7 +99,7 @@ class CostCenterListState extends State<CostCenterList> {
}
Future<List<dynamic>> fetchGetCostCenter() async {
final String apiUrlData = '$apiUrl/api/getCostCenterMaster';
final String apiUrlData = '$apiUrl/api/getCostCenterMaster?for=table_view';
final String? token = await getToken();
@ -566,7 +566,7 @@ class CostCenterListState extends State<CostCenterList> {
color:
tableObject['is_active'] == "1"
? Colors.green
: Colors.red,
: Colors.grey,
),
softWrap: true,
overflow: TextOverflow.ellipsis,

View File

@ -19,13 +19,14 @@ class DepartmentData extends StatefulWidget {
final int? departmentId; // <-- Add this
final Map<String, dynamic>? departmentData;
const DepartmentData(
{super.key,
required this.isDesktop,
this.layoutColor,
required this.fetchGetDepartment,
this.departmentId,
this.departmentData});
const DepartmentData({
super.key,
required this.isDesktop,
this.layoutColor,
required this.fetchGetDepartment,
this.departmentId,
this.departmentData,
});
@override
DepartmentDataState createState() => DepartmentDataState();
@ -49,10 +50,7 @@ class DepartmentDataState extends State<DepartmentData> {
int? departmentDataId;
late String isActive = "1";
List<String> dataHeader = [
"name",
"description",
];
List<String> dataHeader = ["name", "description"];
Map<String, dynamic> departmentDetails() {
final data = {
@ -69,7 +67,6 @@ class DepartmentDataState extends State<DepartmentData> {
void initState() {
super.initState();
apiData = null;
for (var field in dataHeader) {
controllers[field] = TextEditingController();
@ -110,7 +107,6 @@ class DepartmentDataState extends State<DepartmentData> {
});
}
void toggleStatus() {
setState(() {
isActive = isActive == "1" ? "0" : "1";
@ -171,7 +167,9 @@ class DepartmentDataState extends State<DepartmentData> {
apiUrldata = '$apiUrl/api/updateDepartment/$departmentDataId';
departmentData["department_id"] = departmentDataId.toString();
departmentData["updated_by"] = userId;
(departmentData.containsKey("created_by")) ? departmentData.remove("created_by") : '' ;
(departmentData.containsKey("created_by"))
? departmentData.remove("created_by")
: '';
} else {
print("for add Department id - null");
apiUrldata = '$apiUrl/api/createDepartment';
@ -193,10 +191,10 @@ class DepartmentDataState extends State<DepartmentData> {
};
final body = jsonEncode(departmentData);
final response = departmentDataId != null
? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body);
final response =
departmentDataId != null
? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body);
switch (response.statusCode) {
case 200:
@ -217,7 +215,6 @@ class DepartmentDataState extends State<DepartmentData> {
print("Failed to submit department. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print(" Error submitting plan: $e");
}
@ -225,7 +222,6 @@ class DepartmentDataState extends State<DepartmentData> {
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: Colors.white,
contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30),
@ -238,27 +234,27 @@ class DepartmentDataState extends State<DepartmentData> {
Row(
children: [
Text(
(departmentDataId != null) ? 'Edit Department' : 'Create Department',
(departmentDataId != null)
? 'Edit Department'
: 'Create Department',
style: GoogleFonts.poppins(fontSize: 15, color: Colors.black),
),
const Spacer(),
],
),
const SizedBox(height: 2),
Divider(
thickness: 0.2,
color: Colors.blueGrey.shade100,
),
Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
const SizedBox(height: 5),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Name",
"Name *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -266,19 +262,20 @@ class DepartmentDataState extends State<DepartmentData> {
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["name"],
focusNode: focusNodes["name"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Name",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
)),
height: 40,
child: TextField(
controller: controllers["name"],
focusNode: focusNodes["name"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Name",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["name"] != null) ...[
SizedBox(height: 5), // Space before error message
@ -289,18 +286,17 @@ class DepartmentDataState extends State<DepartmentData> {
],
],
),
SizedBox(
height: 15,
),
SizedBox(height: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Description",
"Description *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -334,41 +330,37 @@ class DepartmentDataState extends State<DepartmentData> {
],
],
),
SizedBox(
height: 15,
),
SizedBox(height: 15),
if (departmentDataId != null)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Change Status ",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Change Status ",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
Tooltip(
message:
isActive == "1" ? "Tap to deactivate" : "Tap to activate",
child: GestureDetector(
onTap: toggleStatus,
child: Text(
isActive == "1" ? "Active" : "Inactive",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
color: isActive == "1" ? Colors.green : Colors.red,
),
),
Tooltip(
message:
isActive == "1" ? "Tap to deactivate" : "Tap to activate",
child: GestureDetector(
onTap: toggleStatus,
child: Text(
isActive == "1" ? "Active" : "Inactive",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
color: isActive == "1" ? Colors.green : Colors.red,
),
),
)
],
),
if (departmentDataId != null)
SizedBox(
height: 15,
),
),
],
),
if (departmentDataId != null) SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@ -405,16 +397,20 @@ class DepartmentDataState extends State<DepartmentData> {
borderRadius: BorderRadius.circular(8),
),
),
child: Text('Save',
style: GoogleFonts.poppins(
fontSize: 11, color: Colors.white)),
child: Text(
'Save',
style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.white,
),
),
),
),
],
)
),
// : SizedBox.shrink(),
],
),
);
}
}
}

View File

@ -99,7 +99,7 @@ class DepartmentListState extends State<DepartmentList> {
}
Future<List<dynamic>> fetchGetDepartment() async {
final String apiUrlData = '$apiUrl/api/getDepartmentList';
final String apiUrlData = '$apiUrl/api/getDepartmentList?for=table_view';
final String? token = await getToken();
@ -565,7 +565,7 @@ class DepartmentListState extends State<DepartmentList> {
color:
tableObject['is_active'] == "1"
? Colors.green
: Colors.red,
: Colors.grey,
),
softWrap: true,
overflow: TextOverflow.ellipsis,

View File

@ -100,11 +100,13 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
}
} else {
throw Exception(
"Unexpected response format: Expected a List but got ${responseBody.runtimeType}");
"Unexpected response format: Expected a List but got ${responseBody.runtimeType}",
);
}
} else {
throw Exception(
'Failed to load users. Status Code: ${response.statusCode}');
'Failed to load users. Status Code: ${response.statusCode}',
);
}
} catch (e) {
print("Error fetching users: $e");
@ -138,56 +140,63 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
List<dynamic> travellerList = responseBody['data'];
setState(() {
_traveller = travellerList
.map((user) => SearchTraveler.fromJson(user))
.toList();
_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} ${travvelr.mobileNo}");
"${travvelr.firstName} ${travvelr.lastName} ${travvelr.mobileNo}",
);
}
} else {
throw Exception(
"Unexpected response format: Expected a List but got ${responseBody.runtimeType}");
"Unexpected response format: Expected a List but got ${responseBody.runtimeType}",
);
}
} else {
throw Exception(
'Failed to load users. Status Code: ${response.statusCode}');
'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 _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.empCode?.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...");
@ -200,19 +209,23 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
];
} else {
_filteredList = [
..._users.where((user) {
print("usersLLL : ${user}");
..._users
.where((user) {
print("usersLLL : ${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}),
List<String> searchFields = [
"${user.firstName} ${user.lastName}".toLowerCase(),
user.email.toLowerCase() ?? "",
user.userId.toLowerCase() ?? "",
user.mobileNo ?? "",
user.alternateMobileNo ?? "",
user.empCode?.toLowerCase() ?? "",
];
return searchFields.any(
(field) => field.contains(query.toLowerCase()),
);
})
.map((user) => {"type": "user", "data": user}),
];
}
});
@ -221,7 +234,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
for (var item in _filteredList) {
var user = item["data"];
print(
"${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}");
"${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}",
);
}
}
@ -236,16 +250,19 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
];
} else {
_filteredList = [
..._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}),
..._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}),
];
}
});
@ -254,7 +271,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
for (var item in _filteredList) {
var user = item["data"];
print(
"${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}");
"${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}",
);
}
}
@ -266,32 +284,39 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
if (query.isEmpty) {
_filteredList = [
..._users.map((user) => {"type": "user", "data": user}),
..._traveller
.map((traveller) => {"type": "traveller", "data": traveller}),
..._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}),
..._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}),
];
}
});
@ -300,7 +325,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
for (var item in _filteredList) {
var user = item["data"];
print(
"${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}");
"${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}",
);
}
}
@ -324,10 +350,14 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
MainAxisSize.min, // Ensures content doesn't expand unnecessarily
children: [
widget.title == "Others"
? Text("Please Select Other User",
style: GoogleFonts.poppins(fontSize: 14))
: Text("Please Select Other Employee",
style: GoogleFonts.poppins(fontSize: 14)),
? Text(
"Please Select Other User",
style: GoogleFonts.poppins(fontSize: 14),
)
: Text(
"Please Select Other Employee",
style: GoogleFonts.poppins(fontSize: 14),
),
SizedBox(height: 10),
// Search Field
@ -344,11 +374,14 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
style: GoogleFonts.poppins(fontSize: 12),
decoration: InputDecoration(
hintText: "Search for a user",
hintStyle:
GoogleFonts.poppins(fontSize: 14, color: Colors.grey),
hintStyle: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey,
),
prefixIcon: Icon(Icons.search),
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey.shade200, width: 1),
// borderSide: BorderSide(color: Color(0xFFF5F5F5), width: 2),
@ -366,9 +399,13 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("or create a new traveler",
style: GoogleFonts.poppins(
fontSize: 14, color: Color(0xFF575A74))),
Text(
"or create a new traveler",
style: GoogleFonts.poppins(
fontSize: 14,
color: Color(0xFF575A74),
),
),
TextButton(
onPressed: () {
setState(() {
@ -376,9 +413,13 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
_searchController.clear();
});
},
child: Text("Create",
style: GoogleFonts.poppins(
fontSize: 14, color: widget.layoutColorForUser)),
child: Text(
"Create",
style: GoogleFonts.poppins(
fontSize: 14,
color: widget.layoutColorForUser,
),
),
),
],
),
@ -389,17 +430,20 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
// User List or Message
_searchController.text.isNotEmpty
? SizedBox(
height: 300, // Limit height to avoid overflow
// child: _filteredUsers.isEmpty
child: _filteredList.isEmpty
? Center(
height: 300, // Limit height to avoid overflow
// child: _filteredUsers.isEmpty
child:
_filteredList.isEmpty
? Center(
child: Text(
"No users found",
style: GoogleFonts.poppins(
fontSize: 14, color: Colors.grey),
fontSize: 14,
color: Colors.grey,
),
),
)
: ListView.builder(
: ListView.builder(
// itemCount: _filteredUsers.length,
itemCount: _filteredList.length,
itemBuilder: (context, index) {
@ -411,7 +455,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
item["type"]; // "user" or "traveller"
if (user is Map<String, dynamic>) {
print(
"userLsirer - ${jsonEncode(user)}"); // pretty JSON-like string
"userLsirer - ${jsonEncode(user)}",
); // pretty JSON-like string
} else {
print("userLsirer - $user"); // fallback
}
@ -420,37 +465,42 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
"${user.firstName ?? "Unknown"} ${user.lastName ?? ""}",
style: GoogleFonts.poppins(fontSize: 11),
),
subtitle: userType == "user"
? Text(
"Employee ID: ${user.empCode ?? ""} ",
// "Employee ID: ${userType == "user" ? user.userId : user.travellerId}",
style:
GoogleFonts.poppins(fontSize: 10),
)
: Text(
"Mobile : ${user.mobileNo ?? ""} ",
// "Employee ID: ${userType == "user" ? user.userId : user.travellerId}",
style:
GoogleFonts.poppins(fontSize: 10),
),
subtitle:
userType == "user"
? Text(
"Employee ID: ${user.empCode ?? ""} ",
// "Employee ID: ${userType == "user" ? user.userId : user.travellerId}",
style: GoogleFonts.poppins(
fontSize: 10,
),
)
: Text(
"Mobile : ${user.mobileNo ?? ""} ",
// "Employee ID: ${userType == "user" ? user.userId : user.travellerId}",
style: GoogleFonts.poppins(
fontSize: 10,
),
),
onTap: () {
String selectedUser =
"${user.firstName ?? "Unknown"} ${user.lastName ?? ""}";
setState(() {
_searchController.text = selectedUser;
userIdSelected = userType == "user"
? user.userId
: user.travellerId;
userIdSelected =
userType == "user"
? user.userId
: user.travellerId;
isTraveller = userType == "traveller";
});
print(
"Selected: $selectedUser, ID: ${userType == "user" ? user.userId : user.travellerId},"
" isTraveller: $userIdSelected");
"Selected: $selectedUser, ID: ${userType == "user" ? user.userId : user.travellerId},"
" isTraveller: $userIdSelected",
);
},
);
},
),
)
)
: SizedBox.shrink(),
// Traveler Form
@ -462,10 +512,16 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
padding: const EdgeInsets.all(16.0),
child: TravelerForm(
formKey: _formKey,
onSubmit: (String fullName, String travellerId,
bool isTraveller) {
widget.onSubmit(fullName, travellerId,
isTraveller); // Pass the data up
onSubmit: (
String fullName,
String travellerId,
bool isTraveller,
) {
widget.onSubmit(
fullName,
travellerId,
isTraveller,
); // Pass the data up
},
firstNameController: TextEditingController(),
lastNameController: TextEditingController(),
@ -487,7 +543,9 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(
color: widget.layoutColorForUser, width: 2),
color: widget.layoutColorForUser,
width: 2,
),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
@ -508,15 +566,21 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(
color: widget.layoutColorForUser, width: 2),
color: widget.layoutColorForUser,
width: 2,
),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
print(
"Submitting: ${_searchController.text}, ID: $userIdSelected");
"Submitting: ${_searchController.text}, ID: $userIdSelected",
);
widget.onSubmit(
_searchController.text, userIdSelected, isTraveller);
_searchController.text,
userIdSelected,
isTraveller,
);
Navigator.pop(context);
},
child: Text(
@ -542,14 +606,15 @@ class TravelerForm extends StatefulWidget {
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});
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();
@ -582,8 +647,9 @@ class _TravelerFormState extends State<TravelerForm> {
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)) {
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;
@ -642,7 +708,8 @@ class _TravelerFormState extends State<TravelerForm> {
String lastName = travellerData["last_name"];
print(
"Traveller Added: ID: $travellerId, Name: $firstName $lastName");
"Traveller Added: ID: $travellerId, Name: $firstName $lastName",
);
// // Pass data to callback
// widget.onSubmit("$firstName $lastName", travellerId, true);
@ -658,21 +725,22 @@ class _TravelerFormState extends State<TravelerForm> {
SnackBar(
content: Text(
"Traveller added successfully!",
style:
GoogleFonts.poppins(color: Colors.white), // Set text color
style: GoogleFonts.poppins(
color: Colors.white,
), // Set text color
),
backgroundColor: Colors.green,
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error: ${response.body}")),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text("Error: ${response.body}")));
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Failed to connect to server.")),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text("Failed to connect to server.")));
}
}
@ -697,8 +765,10 @@ class _TravelerFormState extends State<TravelerForm> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text("Create Traveler",
style: GoogleFonts.poppins(color: Colors.black54)),
Text(
"Create Traveler",
style: GoogleFonts.poppins(color: Colors.black54),
),
SizedBox(height: 7),
Expanded(
child: SingleChildScrollView(
@ -714,13 +784,17 @@ class _TravelerFormState extends State<TravelerForm> {
onPressed: () {
widget.formKey.currentState?.reset();
},
child: Text("Clear",
style: GoogleFonts.poppins(color: Colors.grey)),
child: Text(
"Clear",
style: GoogleFonts.poppins(color: Colors.grey),
),
),
TextButton(
onPressed: () => _onSubmit(context),
child: Text("Add",
style: GoogleFonts.poppins(color: Color(0xFF114D8B))),
child: Text(
"Add",
style: GoogleFonts.poppins(color: Color(0xFF114D8B)),
),
),
],
),

View File

@ -21,13 +21,14 @@ class ForexData extends StatefulWidget {
final int? forexId; // <-- Add this
final Map<String, dynamic>? forexData;
const ForexData(
{super.key,
required this.isDesktop,
this.layoutColor,
required this.fetchGetForex,
this.forexId,
this.forexData});
const ForexData({
super.key,
required this.isDesktop,
this.layoutColor,
required this.fetchGetForex,
this.forexId,
this.forexData,
});
@override
ForexDataState createState() => ForexDataState();
@ -61,7 +62,7 @@ class ForexDataState extends State<ForexData> {
"currency",
"perdiemAmount",
"cash",
"card"
"card",
];
Map<String, dynamic> forex_Detials() {
@ -197,7 +198,7 @@ class ForexDataState extends State<ForexData> {
"currency",
"perdiemAmount",
"cash_percentage",
"card_percentage"
"card_percentage",
];
// Check validation for each field
@ -273,9 +274,10 @@ class ForexDataState extends State<ForexData> {
};
final body = jsonEncode(forexData);
final response = forexDataId != null
? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body);
final response =
forexDataId != null
? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body);
// final response = await http.post(
// Uri.parse(apiUrldata),
@ -326,7 +328,7 @@ class ForexDataState extends State<ForexData> {
// Map country codes to country names
countryMap = {
for (var item in countryList)
item['country_code'] as String: item['country_name'] as String
item['country_code'] as String: item['country_name'] as String,
};
// Extract only country codes for processing
@ -355,21 +357,19 @@ class ForexDataState extends State<ForexData> {
],
),
const SizedBox(height: 2),
Divider(
thickness: 0.2,
color: Colors.blueGrey.shade100,
),
Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
const SizedBox(height: 5),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Country",
"Country *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -381,18 +381,19 @@ class ForexDataState extends State<ForexData> {
selectedItem: countryMap[selectedCountry],
popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality
menuProps: const MenuProps(
backgroundColor: Colors.white,
),
menuProps: const MenuProps(backgroundColor: Colors.white),
constraints: BoxConstraints(maxHeight: 250),
itemBuilder: (context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
itemBuilder:
(context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 6.0,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search Country...",
@ -405,25 +406,25 @@ class ForexDataState extends State<ForexData> {
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 1,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder:
(context, selectedItem) => Align(
// Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Country",
style: GoogleFonts.poppins(fontSize: 11),
),
),
),
),
dropdownBuilder: (context, selectedItem) => Align(
// Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Country",
style: GoogleFonts.poppins(fontSize: 11),
),
),
onChanged: (String? newValue) {
setState(() {
// Find the country_code based on selected country_name
selectedCountry = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
selectedCountry =
countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
selectedCountryName = newValue;
});
},
@ -444,11 +445,12 @@ class ForexDataState extends State<ForexData> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Currency",
"Currency *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -459,18 +461,19 @@ class ForexDataState extends State<ForexData> {
// ? MediaQuery.of(context).size.width * 0.330
// : MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["currency"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Currency",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
)),
height: 40,
child: TextField(
controller: controllers["currency"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Currency",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["currency"] != null) ...[
SizedBox(height: 5), // Space before error message
@ -481,11 +484,9 @@ class ForexDataState extends State<ForexData> {
],
],
),
// if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,),
SizedBox(
height: 10,
),
// if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,),
SizedBox(height: 10),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -493,38 +494,43 @@ class ForexDataState extends State<ForexData> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Cash (%)",
"Cash (%) *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
color: Colors.transparent,
width: widget.isDesktop
? MediaQuery.of(context).size.width * 0.09
: MediaQuery.of(context).size.width * 0.66,
width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.09
: MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["cash"],
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Cash",
labelStyle:
TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
height: 40,
child: TextField(
controller: controllers["cash"],
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Cash",
labelStyle: TextStyle(
fontSize: 11,
color: Colors.grey,
),
)),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["cash_percentage"] != null) ...[
SizedBox(height: 5), // Space before error message
@ -540,38 +546,43 @@ class ForexDataState extends State<ForexData> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Card (%)",
"Card (%) *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
color: Colors.transparent,
width: widget.isDesktop
? MediaQuery.of(context).size.width * 0.09
: MediaQuery.of(context).size.width * 0.66,
width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.09
: MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["card"],
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Card",
labelStyle:
TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
height: 40,
child: TextField(
controller: controllers["card"],
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Card",
labelStyle: TextStyle(
fontSize: 11,
color: Colors.grey,
),
)),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["card_percentage"] != null) ...[
SizedBox(height: 5), // Space before error message
@ -581,21 +592,20 @@ class ForexDataState extends State<ForexData> {
),
],
],
)
),
],
),
SizedBox(
height: 10,
),
SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Perdiem Amount",
"Perdiem Amount *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -603,18 +613,19 @@ class ForexDataState extends State<ForexData> {
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["perdiemAmount"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Perdiem Amount",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
)),
height: 40,
child: TextField(
controller: controllers["perdiemAmount"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Perdiem Amount",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["perdiemAmount"] != null) ...[
SizedBox(height: 5), // Space before error message
@ -625,9 +636,7 @@ class ForexDataState extends State<ForexData> {
],
],
),
SizedBox(
height: 15,
),
SizedBox(height: 15),
if (forexDataId != null)
Row(
@ -636,9 +645,10 @@ class ForexDataState extends State<ForexData> {
Text(
"Change Status ",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
Tooltip(
message:
@ -654,13 +664,10 @@ class ForexDataState extends State<ForexData> {
),
),
),
)
),
],
),
if (forexDataId != null)
SizedBox(
height: 15,
),
if (forexDataId != null) SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@ -697,13 +704,17 @@ class ForexDataState extends State<ForexData> {
borderRadius: BorderRadius.circular(8),
),
),
child: Text('Save',
style: GoogleFonts.poppins(
fontSize: 11, color: Colors.white)),
child: Text(
'Save',
style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.white,
),
),
),
),
],
)
),
// : SizedBox.shrink(),
],
),

View File

@ -102,7 +102,7 @@ class ForexDataListState extends State<ForexDataList> {
Future<List<dynamic>> fetchGetForex() async {
orgId = await getOrgId();
final String apiUrlData = '$apiUrl/api/getForexPerdiemList';
final String apiUrlData = '$apiUrl/api/getForexPerdiemList?for=table_view';
final String? token = await getToken();

View File

@ -21,13 +21,14 @@ class GroupData extends StatefulWidget {
final int? groupId; // <-- Add this
final Map<String, dynamic>? groupData;
const GroupData(
{super.key,
required this.isDesktop,
this.layoutColor,
required this.fetchGetGroup,
this.groupId,
this.groupData});
const GroupData({
super.key,
required this.isDesktop,
this.layoutColor,
required this.fetchGetGroup,
this.groupId,
this.groupData,
});
@override
GroupDataState createState() => GroupDataState();
@ -46,7 +47,6 @@ class GroupDataState extends State<GroupData> {
final Map<String, TextEditingController> controllers = {};
Map<String, String> errorMessages = {};
List<dynamic> domesticList = [];
List<dynamic> internationalList = [];
@ -75,12 +75,12 @@ class GroupDataState extends State<GroupData> {
Map<String, dynamic> group_Detials() {
final data = {
"name":controllers["name"]?.text,
"description":controllers["description"]?.text,
"domestic_policy_id":selectedDomesticPolicyID,
"international_policy_id":selectedInternationalPolicyID,
"domestic_policy_name":selectedDomesticPolicyName,
"international_policy_name":selectedInternationalPolicyName,
"name": controllers["name"]?.text,
"description": controllers["description"]?.text,
"domestic_policy_id": selectedDomesticPolicyID,
"international_policy_id": selectedInternationalPolicyID,
"domestic_policy_name": selectedDomesticPolicyName,
"international_policy_name": selectedInternationalPolicyName,
"is_active": isActive,
};
return data;
@ -166,23 +166,19 @@ class GroupDataState extends State<GroupData> {
});
}
bool validateData() {
errorMessages.clear();
final data = {
"name":controllers["name"]?.text,
"description":controllers["description"]?.text,
"domestic_policy_id":selectedDomesticPolicyID,
"international_policy_id":selectedInternationalPolicyID,
"domestic_policy_name":selectedDomesticPolicyName,
"international_policy_name":selectedInternationalPolicyName,
"name": controllers["name"]?.text,
"description": controllers["description"]?.text,
"domestic_policy_id": selectedDomesticPolicyID,
"international_policy_id": selectedInternationalPolicyID,
"domestic_policy_name": selectedDomesticPolicyName,
"international_policy_name": selectedInternationalPolicyName,
};
final requiredFields = [
"name",
"description",
];
final requiredFields = ["name", "description"];
// Check validation for each field
for (String field in requiredFields) {
@ -240,9 +236,10 @@ class GroupDataState extends State<GroupData> {
};
final body = jsonEncode(groupData);
final response = groupDataId != null
? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body);
final response =
groupDataId != null
? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body);
if (response.statusCode == 200 || response.statusCode == 201) {
print("Group Details Created successfully!");
@ -288,8 +285,8 @@ class GroupDataState extends State<GroupData> {
// Map id to names
DomesticMap = {
for (var object in domesticList)
object['policy_id'] as String: object['name'] as String
};
object['policy_id'] as String: object['name'] as String,
};
// print("domestic -- map--$DomesticMap");
@ -302,7 +299,7 @@ class GroupDataState extends State<GroupData> {
InternationalMap = {
for (var item in internationalList)
item['policy_id'] as String: item['name'] as String
item['policy_id'] as String: item['name'] as String,
};
// Extract only id for processing
@ -330,20 +327,18 @@ class GroupDataState extends State<GroupData> {
],
),
const SizedBox(height: 2),
Divider(
thickness: 0.2,
color: Colors.blueGrey.shade100,
),
Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
const SizedBox(height: 5),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Name",
"Name *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -351,18 +346,19 @@ class GroupDataState extends State<GroupData> {
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["name"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Name",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
)),
height: 40,
child: TextField(
controller: controllers["name"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Name",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["name"] != null) ...[
SizedBox(height: 5), // Space before error message
@ -380,9 +376,10 @@ class GroupDataState extends State<GroupData> {
Text(
"Select Policy For International",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -391,21 +388,23 @@ class GroupDataState extends State<GroupData> {
child: SizedBox(
height: 40,
child: DropdownSearch<String>(
selectedItem: InternationalMap[selectedInternationalPolicyID],
selectedItem:
InternationalMap[selectedInternationalPolicyID],
popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality
menuProps: const MenuProps(
backgroundColor: Colors.white,
),
menuProps: const MenuProps(backgroundColor: Colors.white),
constraints: BoxConstraints(maxHeight: 250),
itemBuilder: (context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
itemBuilder:
(context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 6.0,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Select Policy For International",
@ -418,25 +417,25 @@ class GroupDataState extends State<GroupData> {
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 1,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder:
(context, selectedItem) => Align(
// Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Policy For International",
style: GoogleFonts.poppins(fontSize: 11),
),
),
),
),
dropdownBuilder: (context, selectedItem) => Align(
// Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Policy For International",
style: GoogleFonts.poppins(fontSize: 11),
),
),
onChanged: (String? newValue) {
setState(() {
// Find the country_code based on selected country_name
selectedInternationalPolicyID = InternationalMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
selectedInternationalPolicyID =
InternationalMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
selectedInternationalPolicyName = newValue;
});
},
@ -452,9 +451,10 @@ class GroupDataState extends State<GroupData> {
Text(
"Select Policy For Domestic",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -466,18 +466,19 @@ class GroupDataState extends State<GroupData> {
selectedItem: DomesticMap[selectedDomesticPolicyID],
popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality
menuProps: const MenuProps(
backgroundColor: Colors.white,
),
menuProps: const MenuProps(backgroundColor: Colors.white),
constraints: BoxConstraints(maxHeight: 250),
itemBuilder: (context, object, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0),
child: Text(
object,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
itemBuilder:
(context, object, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 6.0,
),
child: Text(
object,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Select Policy For Domestic...",
@ -490,25 +491,25 @@ class GroupDataState extends State<GroupData> {
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 1,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder:
(context, selectedItem) => Align(
// Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Policy For Domestic",
style: GoogleFonts.poppins(fontSize: 11),
),
),
),
),
dropdownBuilder: (context, selectedItem) => Align(
// Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Policy For Domestic",
style: GoogleFonts.poppins(fontSize: 11),
),
),
onChanged: (String? newValue) {
setState(() {
// Find the country_code based on selected country_name
selectedDomesticPolicyID = DomesticMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
selectedDomesticPolicyID =
DomesticMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
selectedDomesticPolicyName = newValue;
});
},
@ -522,11 +523,12 @@ class GroupDataState extends State<GroupData> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Description",
"Description *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -559,9 +561,7 @@ class GroupDataState extends State<GroupData> {
],
],
),
SizedBox(
height: 15,
),
SizedBox(height: 15),
if (groupDataId != null)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -569,13 +569,14 @@ class GroupDataState extends State<GroupData> {
Text(
"Change Status ",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
Tooltip(
message:
isActive == "1" ? "Tap to deactivate" : "Tap to activate",
isActive == "1" ? "Tap to deactivate" : "Tap to activate",
child: GestureDetector(
onTap: toggleStatus,
child: Text(
@ -587,13 +588,10 @@ class GroupDataState extends State<GroupData> {
),
),
),
)
),
],
),
if (groupDataId != null)
SizedBox(
height: 15,
),
if (groupDataId != null) SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@ -630,16 +628,20 @@ class GroupDataState extends State<GroupData> {
borderRadius: BorderRadius.circular(8),
),
),
child: Text('Save',
style: GoogleFonts.poppins(
fontSize: 11, color: Colors.white)),
child: Text(
'Save',
style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.white,
),
),
),
),
],
)
),
// : SizedBox.shrink(),
],
),
);
}
}
}

View File

@ -102,32 +102,30 @@ class _GroupListState extends State<GroupList> {
}
void filterGroups(String query) {
print("allGroups before filtering: $query");
final lowerQuery = query.toLowerCase();
setState(() {
filteredGroups =
allGroups.where((group) {
return (group['name']?.toLowerCase().contains(lowerQuery) ??
allGroups.where((object) {
final isActiveStatus =
object['is_active'] == "1" ? "active" : "inactive";
return (object['group_id']?.toLowerCase().contains(lowerQuery) ??
false) ||
(group['domestic_policy_name']?.toLowerCase().contains(
(object['name']?.toLowerCase().contains(lowerQuery) ?? false) ||
(object['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);
(object['international_policy_name']?.toLowerCase().contains(
lowerQuery,
) ??
false) ||
(object['description']?.toLowerCase().contains(lowerQuery) ??
false) ||
(isActiveStatus.contains(lowerQuery));
}).toList();
currentPage = 0;
});
print("filtered: $filteredGroups");
print("filteredGroups: $filteredGroups");
}
void handleActiveStatus(
@ -287,7 +285,7 @@ class _GroupListState extends State<GroupList> {
controller: searchController,
onChanged: filterGroups,
decoration: InputDecoration(
hintText: "Search for a Group",
hintText: "Search ...",
hintStyle: TextStyle(
fontSize: 12,
color: Color(0xFF9E9DBD),
@ -385,7 +383,7 @@ class _GroupListState extends State<GroupList> {
controller: searchController,
onChanged: filterGroups,
decoration: InputDecoration(
hintText: "Search for a Group",
hintText: "Search ...",
hintStyle: TextStyle(
fontSize: 12,
color: Color(0xFF9E9DBD),
@ -924,12 +922,35 @@ class _GroupListState extends State<GroupList> {
Expanded(
child:
isDesktop
? SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table, // <-- your existing table
)
: buildMobileCardView(paginatedGroup),
? (searchController.text.isNotEmpty &&
filteredGroups.isEmpty
? Center(
child: Text(
"No matches found",
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey,
),
),
)
: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
))
: (searchController.text.isNotEmpty &&
paginatedGroup.isEmpty
? Center(
child: Text(
"No matches found",
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey,
),
),
)
: buildMobileCardView(paginatedGroup)),
),
PaginationControls(
currentPage: currentPage,
itemsPerPage: itemsPerPage,

View File

@ -20,13 +20,14 @@ class HotelsData extends StatefulWidget {
final int? hotelsId; // <-- Add this
final Map<String, dynamic>? hotelsData;
const HotelsData(
{super.key,
required this.isDesktop,
this.layoutColor,
required this.fetchGetHotels,
this.hotelsId,
this.hotelsData});
const HotelsData({
super.key,
required this.isDesktop,
this.layoutColor,
required this.fetchGetHotels,
this.hotelsId,
this.hotelsData,
});
@override
HotelsDataState createState() => HotelsDataState();
@ -110,7 +111,8 @@ class HotelsDataState extends State<HotelsData> {
if (data == null) return;
setState(() {
selectedCountry = data['country_code']; // For dropdown
selectedCountryName = data['country_name']; // For dropdown label or display
selectedCountryName =
data['country_name']; // For dropdown label or display
controllers['city']?.text = data['city'] ?? '';
controllers['hotel_chain']?.text = data['hotel_chain'] ?? '';
controllers['hotel_name']?.text = data['hotel_name'] ?? '';
@ -148,7 +150,12 @@ class HotelsDataState extends State<HotelsData> {
"city": controllers["city"]?.text,
};
final requiredFields = ["hotel_name","hotel_chain","country_code","city"];
final requiredFields = [
"hotel_name",
"hotel_chain",
"country_code",
"city",
];
// Check validation for each field
for (String field in requiredFields) {
@ -174,7 +181,6 @@ class HotelsDataState extends State<HotelsData> {
}
Future<void> postHotelsData({int isActive = 1}) async {
final hotelsData = hotels_Details();
final String apiUrldata;
@ -184,16 +190,20 @@ class HotelsDataState extends State<HotelsData> {
apiUrldata = '$apiUrl/api/updateHotels/$hotelsDataId';
hotelsData["hotel_id"] = hotelsDataId.toString();
hotelsData["updated_by"] = userId;
(hotelsData.containsKey("created_by")) ? hotelsData.remove("created_by") : '' ;
(hotelsData.containsKey("country_name")) ? hotelsData.remove("country_name") : '' ;
(hotelsData.containsKey("created_by"))
? hotelsData.remove("created_by")
: '';
(hotelsData.containsKey("country_name"))
? hotelsData.remove("country_name")
: '';
} else {
print("for add Hotel id - null");
apiUrldata = '$apiUrl/api/createHotels';
print("called apiUrl - $apiUrldata");
hotelsData["created_by"] = userId;
(hotelsData.containsKey("country_name")) ? hotelsData.remove("country_name") : '' ;
(hotelsData.containsKey("country_name"))
? hotelsData.remove("country_name")
: '';
}
final token = await getToken(); // Fetch token
@ -210,9 +220,10 @@ class HotelsDataState extends State<HotelsData> {
};
final body = jsonEncode(hotelsData);
final response = hotelsDataId != null
? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body);
final response =
hotelsDataId != null
? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body);
if (response.statusCode == 200 || response.statusCode == 201) {
print("Hotels Details Created successfully!");
@ -254,7 +265,7 @@ class HotelsDataState extends State<HotelsData> {
// Map country codes to country names
countryMap = {
for (var item in countryList)
item['country_code'] as String: item['country_name'] as String
item['country_code'] as String: item['country_name'] as String,
};
// Extract only country codes for processing
@ -281,20 +292,18 @@ class HotelsDataState extends State<HotelsData> {
],
),
const SizedBox(height: 2),
Divider(
thickness: 0.2,
color: Colors.blueGrey.shade100,
),
Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
const SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Hotel Name",
"Hotel Name *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -302,18 +311,19 @@ class HotelsDataState extends State<HotelsData> {
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["hotel_name"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Hotel Name",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
)),
height: 40,
child: TextField(
controller: controllers["hotel_name"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Hotel Name",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["hotel_name"] != null) ...[
SizedBox(height: 5), // Space before error message
@ -329,11 +339,12 @@ class HotelsDataState extends State<HotelsData> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Hotel Chain",
"Hotel Chain *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -341,18 +352,19 @@ class HotelsDataState extends State<HotelsData> {
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["hotel_chain"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Hotel Chain",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
)),
height: 40,
child: TextField(
controller: controllers["hotel_chain"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Hotel Chain",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["hotel_chain"] != null) ...[
SizedBox(height: 5), // Space before error message
@ -363,55 +375,23 @@ class HotelsDataState extends State<HotelsData> {
],
],
),
SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"City",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["city"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "City",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
)),
),
if (errorMessages["city"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["city"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
// - It has been observed that many of the dropdowns have overlapping issues, causing label names to be hidden - just copied searchable dropdown - still not completed (user mangement screen only ) master page except policy - my trips - flight taxi train insurance, visa misscenllo color white size padding data
// - Delete option is not working in the policy list page - completed
// - Label Names for all the modules should be set bold as it is looking like normal text in user management compared to trips page - completed
// - In the masters org mangement search option not working for traveller - particular 4 master page - - issues occur - commpleted - email master working fine, traveller master working fine, amount master working fine, group - completed
// - QC- Authentication - - completed - ask to check
const SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Country",
"Country *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -423,18 +403,19 @@ class HotelsDataState extends State<HotelsData> {
selectedItem: countryMap[selectedCountry],
popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality
menuProps: const MenuProps(
backgroundColor: Colors.white,
),
menuProps: const MenuProps(backgroundColor: Colors.white),
constraints: BoxConstraints(maxHeight: 250),
itemBuilder: (context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
itemBuilder:
(context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 6.0,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search Country...",
@ -447,25 +428,25 @@ class HotelsDataState extends State<HotelsData> {
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 1,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder:
(context, selectedItem) => Align(
// Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Country",
style: GoogleFonts.poppins(fontSize: 11),
),
),
),
),
dropdownBuilder: (context, selectedItem) => Align(
// Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Country",
style: GoogleFonts.poppins(fontSize: 11),
),
),
onChanged: (String? newValue) {
setState(() {
// Find the country_code based on selected country_name
selectedCountry = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
selectedCountry =
countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
selectedCountryName = newValue;
});
},
@ -481,7 +462,48 @@ class HotelsDataState extends State<HotelsData> {
],
],
),
SizedBox( height: 15 ),
const SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"City *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["city"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "City",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["city"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["city"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
SizedBox(height: 10),
// if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,),
if (hotelsDataId != null)
Row(
@ -490,13 +512,14 @@ class HotelsDataState extends State<HotelsData> {
Text(
"Change Status ",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
Tooltip(
message:
isActive == "1" ? "Tap to deactivate" : "Tap to activate",
isActive == "1" ? "Tap to deactivate" : "Tap to activate",
child: GestureDetector(
onTap: toggleStatus,
child: Text(
@ -508,13 +531,10 @@ class HotelsDataState extends State<HotelsData> {
),
),
),
)
),
],
),
if (hotelsDataId != null)
SizedBox(
height: 15,
),
if (hotelsDataId != null) SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@ -551,13 +571,17 @@ class HotelsDataState extends State<HotelsData> {
borderRadius: BorderRadius.circular(8),
),
),
child: Text('Save',
style: GoogleFonts.poppins(
fontSize: 11, color: Colors.white)),
child: Text(
'Save',
style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.white,
),
),
),
),
],
)
),
// : SizedBox.shrink(),
],
),

View File

@ -102,7 +102,7 @@ class HotelsDataListState extends State<HotelsDataList> {
Future<List<dynamic>> fetchGetHotels() async {
orgId = await getOrgId();
final String apiUrlData = '$apiUrl/api/getHotels';
final String apiUrlData = '$apiUrl/api/getHotels?for=table_view';
final String? token = await getToken();
@ -646,7 +646,7 @@ class HotelsDataListState extends State<HotelsDataList> {
color:
hotels['is_active'] == "1"
? Colors.green
: Colors.red,
: Colors.grey,
),
softWrap: true,
overflow: TextOverflow.ellipsis,

File diff suppressed because it is too large Load Diff

View File

@ -223,8 +223,6 @@ class _ForexScreenState extends State<ForexScreen> {
}
bool isValidForexData(Map<String, dynamic> data) {
errorMessages.clear(); // Reset errors
// Required fields that must not be empty
List<String> requiredFields = [
"start_date",
@ -232,7 +230,7 @@ class _ForexScreenState extends State<ForexScreen> {
"country_code",
"deposit_on_card",
"deposit_on_cash",
// "card_number"
// "card_number",
];
// If have_card is "1", then delivery_location is required
@ -246,11 +244,17 @@ class _ForexScreenState extends State<ForexScreen> {
// Check validation for each field
for (String field in requiredFields) {
if (data[field] == null || data[field].toString().trim().isEmpty) {
errorMessages[field] = "This field is required";
errorMessages[field] = "Required";
}
}
return errorMessages.isEmpty; // Valid if there are no errors
return errorMessages.values.every((msg) => msg.trim().isEmpty);
// if (hasErrors) {
// print("At least one error message is present.");
// }
//
// return errorMessages.isEmpty; // Valid if there are no errors
}
Map<String, String?> getFlightTripDateRange(
@ -286,7 +290,7 @@ class _ForexScreenState extends State<ForexScreen> {
Map<String, dynamic> data = forexData;
if (!isValidForexData(data)) {
if (!isValidForexData(data) && errorMessages.isNotEmpty) {
print("Validation Failed: Required fields are missing.");
setState(() {});
return; // Stop execution if validation fails
@ -613,7 +617,7 @@ class _ForexScreenState extends State<ForexScreen> {
errorMessages["deposit_on_card"] =
"Enter Valid Amount"; // Clear error if valid
} else {
errorMessages["deposit_on_card"] = ""; // Clear error if valid
errorMessages.remove("deposit_on_card"); // Clear error if valid
}
// Refresh UI if using StatefulWidget
@ -621,7 +625,8 @@ class _ForexScreenState extends State<ForexScreen> {
}
void _validateCashAmount(String value) {
errorMessages["deposit_on_card"] = " ";
// errorMessages["deposit_on_card"] = " ";
errorMessages.remove("deposit_on_cash");
print("_validateCashAmount - $value - $fifteenPercent");
int? enteredAmount = int.tryParse(value);
@ -642,11 +647,18 @@ class _ForexScreenState extends State<ForexScreen> {
textControllers["_card"]?.text = difference.toString();
if (enteredAmount > fifteenPercent) {
if (checkValidAmount > 0) {
textControllers["_card"]?.text = "0";
errorMessages["deposit_on_card"] =
"Enter Valid Amount"; // Clear error if valid
}
errorMessages["deposit_on_cash"] = "Amount cannot exceed $fifteenPercent";
} else if (checkValidAmount == quotedAmount) {
errorMessages["deposit_on_card"] = " "; // Clear error if valid
errorMessages.remove("deposit_on_cash");
// errorMessages["deposit_on_card"] = " "; // Clear error if valid
} else {
errorMessages["deposit_on_cash"] = ""; // Clear error if valid
// errorMessages["deposit_on_cash"] = ""; // Clear error if valid
errorMessages.remove("deposit_on_cash");
}
// Refresh UI if using StatefulWidget
@ -887,7 +899,7 @@ class _ForexScreenState extends State<ForexScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Start Date",
"Start Date *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -958,7 +970,7 @@ class _ForexScreenState extends State<ForexScreen> {
if (errorMessages["start_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Select Start Date",
errorMessages["start_date"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
@ -969,7 +981,7 @@ class _ForexScreenState extends State<ForexScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"End Date",
"End Date *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -1123,7 +1135,7 @@ class _ForexScreenState extends State<ForexScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Country",
"Country*",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -1179,7 +1191,7 @@ class _ForexScreenState extends State<ForexScreen> {
if (errorMessages["country_code"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Select Country",
errorMessages["country_code"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
@ -1600,8 +1612,12 @@ class _ForexScreenState extends State<ForexScreen> {
child: TextField(
focusNode: focusNodes["_cash"],
controller: textControllers["_cash"],
style: const TextStyle(fontSize: 12),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly, // Only allow digits
],
style: const TextStyle(fontSize: 12),
// keyboardType: TextInputType.number,
onChanged: (value) {
// errorMessages["deposit_on_cash"] = "";
_validateCashAmount(
@ -1653,6 +1669,9 @@ class _ForexScreenState extends State<ForexScreen> {
controller: textControllers["_card"],
style: const TextStyle(fontSize: 12),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly, // Only allow digits
],
onChanged: (value) {
_validateCardAmount(
value,
@ -1884,7 +1903,7 @@ class _ForexScreenState extends State<ForexScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Card Number",
"Card Number*",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,

View File

@ -17,14 +17,15 @@ class TrainScreen extends StatefulWidget {
final String? loginUser;
final String? tripType;
TrainScreen(
{required this.onClose,
this.apiData,
required this.onSavetrain,
required this.selectedItem,
required this.loginUser,
this.apiDataForClass,
this.tripType});
TrainScreen({
required this.onClose,
this.apiData,
required this.onSavetrain,
required this.selectedItem,
required this.loginUser,
this.apiDataForClass,
this.tripType,
});
@override
_TrainScreenState createState() => _TrainScreenState();
@ -232,7 +233,7 @@ class _TrainScreenState extends State<TrainScreen> {
"from_station",
"to_station",
"date",
"time"
"time",
];
// Check validation for each field
@ -287,51 +288,54 @@ class _TrainScreenState extends State<TrainScreen> {
@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: [
// Align(
// alignment: Alignment.centerRight,
// child: InkWell(
// onTap: () {
// widget.onClose(false);
// },
// child: Icon(
// Icons.close,
// size: 18,
// color: Color(0xFF575A74),
// ),
// ),
// ),
// Text("Train Booking List",
// style: TextStyle(
// fontSize: 18,
// fontWeight: FontWeight.bold,
// color: Color(0xFF575A74))),
// SizedBox(
// height: 6,
// ),
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: [
// Align(
// alignment: Alignment.centerRight,
// child: InkWell(
// onTap: () {
// widget.onClose(false);
// },
// child: Icon(
// Icons.close,
// size: 18,
// color: Color(0xFF575A74),
// ),
// ),
// ),
// Text("Train Booking List",
// style: TextStyle(
// fontSize: 18,
// fontWeight: FontWeight.bold,
// color: Color(0xFF575A74))),
// SizedBox(
// height: 6,
// ),
Padding(
padding: const EdgeInsets.all(28.0),
child: Center(
child: Column(children: _buildAccomadtionForm(isDesktop)),
),
),
)
],
],
),
),
),
),
);
});
);
},
);
}
List<Widget> _buildAccomadtionForm(bool isDesktop) {
@ -344,7 +348,7 @@ class _TrainScreenState extends State<TrainScreen> {
List<List<Widget>> rowBuilders = [
_builClassType(isDesktop),
_buildSecondRow(isDesktop)
_buildSecondRow(isDesktop),
];
return [
@ -367,9 +371,10 @@ class _TrainScreenState extends State<TrainScreen> {
Text(
"Train Number",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
isDesktop
@ -377,38 +382,35 @@ class _TrainScreenState extends State<TrainScreen> {
: Column(children: _buildTripType(isDesktop)),
if (errorMessages["train_no"] != 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),
];
}
List<Widget> _buildTripType(bool isDesktop) {
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item) => DropdownMenuItem<String>(
value: item['dropdown_value'],
child: Text(item['dropdown_value']),
))
.toList();
List<DropdownMenuItem<String>> dropdownItems =
purposeList
.map(
(item) => DropdownMenuItem<String>(
value: item['dropdown_value'],
child: Text(item['dropdown_value']),
),
)
.toList();
if (dropdownItems.isEmpty) {
dropdownItems.add(
DropdownMenuItem<String>(
value: null,
child: Text("No options available",
style: TextStyle(color: Colors.grey)),
child: Text(
"No options available",
style: TextStyle(color: Colors.grey),
),
),
);
}
@ -455,10 +457,11 @@ class _TrainScreenState extends State<TrainScreen> {
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),
);
@ -483,8 +486,13 @@ class _TrainScreenState extends State<TrainScreen> {
// 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;
});
@ -495,19 +503,24 @@ class _TrainScreenState extends State<TrainScreen> {
List<dynamic> purposeList = widget.apiDataForClass?['train_class'] ?? [];
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),
),
),
);
}
@ -523,9 +536,10 @@ class _TrainScreenState extends State<TrainScreen> {
Text(
"Class *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
@ -543,86 +557,91 @@ class _TrainScreenState extends State<TrainScreen> {
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(() {
selectedClass = newValue;
});
}
: null,
onChanged:
purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedClass = newValue;
});
}
: null,
items: dropdownItems,
),
),
),
if (errorMessages["class"] != 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(
"From",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
isFocused: _fromFocus,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: isCountryLoading
? Center(child: CircularProgressIndicator())
: DropdownSearch<String>(
isFocused: _fromFocus,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child:
isCountryLoading
? Center(child: CircularProgressIndicator())
: DropdownSearch<String>(
// selectedItem: selectedFrom != null
// ? countryMap[selectedFrom]
// : null,
selectedItem: selectedFrom != null
? countryMap[
selectedFrom] // get the display value from code
: null,
selectedItem:
selectedFrom != null
? countryMap[selectedFrom] // get the display value from code
: null,
popupProps: PopupProps.menu(
menuProps: MenuProps(backgroundColor: Colors.white),
constraints: BoxConstraints(maxHeight: 230),
showSearchBox: true,
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search ...",
contentPadding:
EdgeInsets.symmetric(horizontal: 10),
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
),
),
),
),
items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select",
style: TextStyle(fontSize: 12),
),
),
dropdownBuilder:
(context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select",
style: TextStyle(fontSize: 12),
),
),
// onChanged: (String? newValue) {
// setState(() {
// // selectedFrom[index] = countryMap.entries
@ -636,56 +655,51 @@ class _TrainScreenState extends State<TrainScreen> {
// print(selectedFrom);
// });
// },
onChanged: (String? newValue) {
setState(() {
selectedFrom = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
selectedFrom =
countryMap.entries
.firstWhere(
(entry) => entry.value == newValue,
)
.key;
});
},
),
)
// child: SizedBox(
// height: 40,
// child: TextField(
// focusNode: _fromFocusNode,
// controller: _fromController,
// style: const TextStyle(fontSize: 12),
// decoration: const InputDecoration(
// labelText: "From",
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
// ),
// ),
// ),
),
),
// child: SizedBox(
// height: 40,
// child: TextField(
// focusNode: _fromFocusNode,
// controller: _fromController,
// style: const TextStyle(fontSize: 12),
// decoration: const InputDecoration(
// labelText: "From",
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
// ),
// ),
// ),
),
if (errorMessages["from_station"] != 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(
"To",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
@ -693,71 +707,72 @@ class _TrainScreenState extends State<TrainScreen> {
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: isCountryLoading
? Center(child: CircularProgressIndicator())
: DropdownSearch<String>(
selectedItem: selectedTo != null
? countryMap[
selectedTo] // get the display value from code
: null,
popupProps: PopupProps.menu(
showSearchBox: true,
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search ...",
contentPadding:
EdgeInsets.symmetric(horizontal: 10),
child:
isCountryLoading
? Center(child: CircularProgressIndicator())
: DropdownSearch<String>(
selectedItem:
selectedTo != null
? countryMap[selectedTo] // get the display value from code
: null,
popupProps: PopupProps.menu(
menuProps: MenuProps(backgroundColor: Colors.white),
constraints: BoxConstraints(maxHeight: 230),
showSearchBox: true,
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search ...",
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
),
),
),
),
),
items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
items: countryMap.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",
style: TextStyle(fontSize: 12),
),
),
onChanged: (String? newValue) {
setState(() {
selectedTo =
countryMap.entries
.firstWhere(
(entry) => entry.value == newValue,
)
.key;
});
},
),
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select",
style: TextStyle(fontSize: 12),
),
),
onChanged: (String? newValue) {
setState(() {
selectedTo = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
});
},
),
),
),
if (errorMessages["to_station"] != 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(
@ -779,8 +794,11 @@ class _TrainScreenState extends State<TrainScreen> {
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,
),
),
),
),
@ -789,28 +807,21 @@ class _TrainScreenState extends State<TrainScreen> {
),
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(
@ -832,8 +843,11 @@ class _TrainScreenState extends State<TrainScreen> {
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,
),
),
),
),
@ -842,10 +856,7 @@ class _TrainScreenState extends State<TrainScreen> {
),
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)),
],
],
),
@ -860,9 +871,10 @@ class _TrainScreenState extends State<TrainScreen> {
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(
@ -890,9 +902,7 @@ class _TrainScreenState extends State<TrainScreen> {
],
),
if (isDesktop) Spacer(),
SizedBox(
height: 5,
),
SizedBox(height: 5),
Column(
children: [
Row(
@ -900,7 +910,7 @@ class _TrainScreenState extends State<TrainScreen> {
children: _handleAction(isDesktop),
),
],
)
),
];
}
@ -913,9 +923,7 @@ class _TrainScreenState extends State<TrainScreen> {
},
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(
@ -924,7 +932,6 @@ class _TrainScreenState extends State<TrainScreen> {
),
),
SizedBox(width: 10), // Space between buttons
// Save Changes Button
ElevatedButton(
onPressed: () {
@ -932,9 +939,7 @@ class _TrainScreenState extends State<TrainScreen> {
},
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

@ -23,6 +23,7 @@ import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
import '../../widgets/custom_radio_button.dart';
import '../../widgets/custom_text_field.dart';
import '../../widgets/saving_loader.dart';
import '../approvals/approval_dialogs.dart';
import '../dialog/user_selection_dialog.dart';
import '../itnerary/flights.dart';
@ -909,8 +910,8 @@ class CreateNewPlansState extends State<CreateNewPlan> {
costCenterIds = costCenterMap.keys.toList();
// Optionally auto-select the first item if not already selected
selectedCostCenterId ??=
costCenterIds.isNotEmpty ? costCenterIds.first : null;
// selectedCostCenterId ??=
// costCenterIds.isNotEmpty ? costCenterIds.first : null;
});
print('plansJSON');
@ -1069,11 +1070,16 @@ class CreateNewPlansState extends State<CreateNewPlan> {
miscellaneousList,
];
bool anyServiceSelected = serviceLists.any(
(list) => list != null && list.isNotEmpty,
);
// bool anyServiceSelected = serviceLists.any(
// (list) => list != null && list.isNotEmpty,
// );
bool anyServiceSelected = serviceLists.any((list) {
return list != null &&
list.any((entry) => entry['is_active'].toString() == "1");
});
if (!anyServiceSelected) {
// validationErrors["services"] = "Please select at least one service";
validationErrors["services"] = "Please select at least one service";
setState(() {
temporaryMessage = "Please select at least one service";
@ -1172,7 +1178,7 @@ class CreateNewPlansState extends State<CreateNewPlan> {
}
void handleSubmit() {
setState(() {
setState(() async {
if (validateForm() && temporaryMessage == null) {
// if (selectedPlanId != null && selectedPlanId!.isNotEmpty) {
// planData['plan_id'] = selectedPlanId; // Add plan_id for update
@ -1183,24 +1189,17 @@ class CreateNewPlansState extends State<CreateNewPlan> {
// "${planData['traveller_id']},"
// " ${selectedPlanId}, "
// " ");
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const SavingLoader(),
);
// postPlanData(planData);
postPlanData(planData);
await postPlanData(planData);
final currentUri =
GoRouterState.of(
context,
).uri.toString(); // safer than `.location`
print("currentUri - $currentUri");
if (currentUri == "/allTrips/trips") {
context.go('/listAllPlan');
} else if (currentUri == "/createPlan") {
context.go('/listPlan');
} else {
widget.isApprover
? context.go('/approvallist')
: context.go('/listPlan');
}
// Close loading dialog (ONLY if still mounted)
if (mounted) Navigator.of(context, rootNavigator: true).pop();
}
});
}
@ -1233,6 +1232,22 @@ class CreateNewPlansState extends State<CreateNewPlan> {
if (response.statusCode == 200) {
print("Plan submitted successfully!");
print("Response: ${response.body}");
final currentUri =
GoRouterState.of(
context,
).uri.toString(); // safer than `.location`
print("currentUri - $currentUri");
if (currentUri == "/allTrips/trips") {
context.go('/listAllPlan');
} else if (currentUri == "/createPlan") {
context.go('/listPlan');
} else {
widget.isApprover
? context.go('/approvallist')
: context.go('/listPlan');
}
} else {
print("Failed to submit plan. Status: ${response.statusCode}");
print("Error: ${response.body}");
@ -2184,6 +2199,14 @@ class CreateNewPlansState extends State<CreateNewPlan> {
// ),
// ),
),
if (validationErrors["cost_center_id"] != null)
Padding(
padding: EdgeInsets.only(top: 4),
child: Text(
validationErrors["cost_center_id"]!,
style: GoogleFonts.poppins(fontSize: 12, color: Colors.red),
),
),
],
),
SizedBox(width: 25, height: 5),
@ -2307,6 +2330,14 @@ class CreateNewPlansState extends State<CreateNewPlan> {
),
),
),
if (validationErrors["purpose_of_travel"] != null)
Padding(
padding: EdgeInsets.only(top: 4),
child: Text(
validationErrors["purpose_of_travel"]!,
style: GoogleFonts.poppins(fontSize: 12, color: Colors.red),
),
),
// CustomTextFieldWrapper(
// isFocused: false, // Dropdown doesn't use focus
@ -2466,6 +2497,14 @@ class CreateNewPlansState extends State<CreateNewPlan> {
),
),
),
if (validationErrors["functional_department"] != null)
Padding(
padding: EdgeInsets.only(top: 4),
child: Text(
validationErrors["functional_department"]!,
style: GoogleFonts.poppins(fontSize: 12, color: Colors.red),
),
),
],
),
];
@ -2475,7 +2514,7 @@ class CreateNewPlansState extends State<CreateNewPlan> {
List<Map<String, String>> options = [
{"title": "Self", "value": "Option 1"},
{"title": "Other Employee", "value": "Option 2"},
{"title": "Others", "value": "Option 3"},
{"title": "Others (Non Employee)", "value": "Option 3"},
];
return [

View File

@ -33,25 +33,26 @@ class DynamicItinerary extends StatefulWidget {
final List<dynamic>? apiCountryData;
final String? loginUser;
final Function(String, List<Map<String, dynamic>>)
onItineraryUpdate; // Updated Signature
onItineraryUpdate; // Updated Signature
final Map<String, dynamic> selectedPlanData;
final bool isViewMode;
final GlobalKey<FlightScreenState> flightScreenKey;
final ValueNotifier<String?> tripTypeNotifier;
const DynamicItinerary(
{super.key,
required this.apiData,
required this.onItineraryUpdate,
required this.apiCountryData,
required this.loginUser,
required this.selectedPlanData,
required this.isViewMode,
required this.hasAction,
this.tripType,
this.apiDataForClass,
required this.tripTypeNotifier,
required this.flightScreenKey});
const DynamicItinerary({
super.key,
required this.apiData,
required this.onItineraryUpdate,
required this.apiCountryData,
required this.loginUser,
required this.selectedPlanData,
required this.isViewMode,
required this.hasAction,
this.tripType,
this.apiDataForClass,
required this.tripTypeNotifier,
required this.flightScreenKey,
});
@override
DynamicItineraryState createState() => DynamicItineraryState();
@ -135,9 +136,10 @@ class DynamicItineraryState extends State<DynamicItinerary> {
if (rawServices != null && rawServices is String) {
try {
List<dynamic> decoded = json.decode(rawServices);
List<Map<String, String>> formatted = decoded
.map((e) => {"service_id": e['service_id'].toString()})
.toList();
List<Map<String, String>> formatted =
decoded
.map((e) => {"service_id": e['service_id'].toString()})
.toList();
setState(() {
selectedOrgServiceIds = formatted;
@ -168,7 +170,7 @@ class DynamicItineraryState extends State<DynamicItinerary> {
"insurance",
"visa",
"miscellaneous",
"taxi"
"taxi",
];
} else {
// tripType is null or not 1/2, allow everything
@ -242,23 +244,27 @@ class DynamicItineraryState extends State<DynamicItinerary> {
final selectedIds =
selectedOrgServiceIds.map((e) => e['service_id']).toSet();
final additionalServices = selectedAllServices!.where((service) {
final name = (service['name'] ?? "").toString().toLowerCase();
final id = service['service_id'].toString();
final isNameAllowed =
allowedServiceNames.isEmpty || allowedServiceNames.contains(name);
return filledItineraryKeys.contains(name) &&
!selectedIds.contains(id) &&
isNameAllowed;
}).toList();
final additionalServices =
selectedAllServices!.where((service) {
final name = (service['name'] ?? "").toString().toLowerCase();
final id = service['service_id'].toString();
final isNameAllowed =
allowedServiceNames.isEmpty ||
allowedServiceNames.contains(name);
return filledItineraryKeys.contains(name) &&
!selectedIds.contains(id) &&
isNameAllowed;
}).toList();
final originalFiltered = selectedAllServices!.where((service) {
final name = (service['name'] ?? "").toString().toLowerCase();
final id = service['service_id'].toString();
final isNameAllowed =
allowedServiceNames.isEmpty || allowedServiceNames.contains(name);
return selectedIds.contains(id) && isNameAllowed;
}).toList();
final originalFiltered =
selectedAllServices!.where((service) {
final name = (service['name'] ?? "").toString().toLowerCase();
final id = service['service_id'].toString();
final isNameAllowed =
allowedServiceNames.isEmpty ||
allowedServiceNames.contains(name);
return selectedIds.contains(id) && isNameAllowed;
}).toList();
setState(() {
ServicesChoosed = [...originalFiltered, ...additionalServices]
@ -266,22 +272,26 @@ class DynamicItineraryState extends State<DynamicItinerary> {
});
print(
"Services chosen based on filled keys + selected: $ServicesChoosed");
"Services chosen based on filled keys + selected: $ServicesChoosed",
);
} else {
final selectedIds =
selectedOrgServiceIds.map((e) => e['service_id']).toSet();
final filtered = selectedAllServices!.where((service) {
final name = (service['name'] ?? "").toString().toLowerCase();
final isNameAllowed =
allowedServiceNames.isEmpty || allowedServiceNames.contains(name);
return selectedIds.contains(service['service_id'].toString()) &&
isNameAllowed;
}).toList();
final filtered =
selectedAllServices!.where((service) {
final name = (service['name'] ?? "").toString().toLowerCase();
final isNameAllowed =
allowedServiceNames.isEmpty ||
allowedServiceNames.contains(name);
return selectedIds.contains(service['service_id'].toString()) &&
isNameAllowed;
}).toList();
setState(() {
ServicesChoosed = filtered
..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0));
ServicesChoosed =
filtered
..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0));
});
print("Filtered Selected Services Chosen: $ServicesChoosed");
@ -296,23 +306,32 @@ class DynamicItineraryState extends State<DynamicItinerary> {
setState(() {
itineraryData = {
"Train": List<Map<String, dynamic>>.from(
widget.selectedPlanData['train'] ?? []),
widget.selectedPlanData['train'] ?? [],
),
"Bus": List<Map<String, dynamic>>.from(
widget.selectedPlanData['bus'] ?? []),
widget.selectedPlanData['bus'] ?? [],
),
"Taxi": List<Map<String, dynamic>>.from(
widget.selectedPlanData['taxi'] ?? []),
widget.selectedPlanData['taxi'] ?? [],
),
"Miscellaneous": List<Map<String, dynamic>>.from(
widget.selectedPlanData['miscellaneous'] ?? []),
widget.selectedPlanData['miscellaneous'] ?? [],
),
"Flight": List<Map<String, dynamic>>.from(
widget.selectedPlanData['flight'] ?? []),
widget.selectedPlanData['flight'] ?? [],
),
"Accomodation": List<Map<String, dynamic>>.from(
widget.selectedPlanData['accomodation'] ?? []),
widget.selectedPlanData['accomodation'] ?? [],
),
"Insurance": List<Map<String, dynamic>>.from(
widget.selectedPlanData['insurance'] ?? []),
widget.selectedPlanData['insurance'] ?? [],
),
"Visa": List<Map<String, dynamic>>.from(
widget.selectedPlanData['visa'] ?? []),
widget.selectedPlanData['visa'] ?? [],
),
"Forex": List<Map<String, dynamic>>.from(
widget.selectedPlanData['forex'] ?? []),
widget.selectedPlanData['forex'] ?? [],
),
};
});
} else {
@ -330,7 +349,7 @@ class DynamicItineraryState extends State<DynamicItinerary> {
"accomodation",
"insurance",
"visa",
"forex"
"forex",
];
// for (String key in keys) {
@ -416,7 +435,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
if (existingId != null && existingId != 0) {
// int itemId = itemList.indexWhere((item) => item["id"] == existingId);
int itemId = itemList.indexWhere(
(item) => item[idKey]?.toString() == existingId.toString());
(item) => item[idKey]?.toString() == existingId.toString(),
);
if (itemId != -1) {
print(" Updating existing item with id: $existingId");
@ -428,8 +448,9 @@ class DynamicItineraryState extends State<DynamicItinerary> {
// CASE 1: Update if indx exists in list
if (existingIndex != null && existingIndex != 0) {
int itemIndex =
itemList.indexWhere((item) => item["indx"] == existingIndex);
int itemIndex = itemList.indexWhere(
(item) => item["indx"] == existingIndex,
);
if (itemIndex != -1) {
print("Updating existing item with indx: $existingIndex");
newData["is_active"] = "1";
@ -494,6 +515,7 @@ class DynamicItineraryState extends State<DynamicItinerary> {
});
print(" onItineraryUpdate - $type - ${itineraryData[type]!} ");
widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent
print("ItienreayDATE - $itineraryData");
}
// void handleItineraryUpdate(String type, Map<String, dynamic> newData) {
@ -583,8 +605,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
onOpen: handleEdit,
onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode,
onDeleteAccommodation: (data) =>
handleItinerarydelete("Accomodation", data),
onDeleteAccommodation:
(data) => handleItinerarydelete("Accomodation", data),
);
break;
case "Miscellaneous":
@ -594,50 +616,54 @@ class DynamicItineraryState extends State<DynamicItinerary> {
isViewMode: widget.isViewMode,
onAddNew: handlecreateNewPlan,
apiData: widget.apiData,
onDeleteMiscellaneous: (data) =>
handleItinerarydelete("Miscellaneous", data),
onDeleteMiscellaneous:
(data) => handleItinerarydelete("Miscellaneous", data),
);
break;
case "Flight":
default:
selectedListWidget = FlightListWidget(
hasAction: widget.hasAction,
tripType: widget.tripType,
flightList: itineraryData["Flight"]!,
onOpen: handleEdit,
onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode,
apiData: widget.apiData,
onDeleteFlight: (data) => handleItinerarydelete("Flight", data));
hasAction: widget.hasAction,
tripType: widget.tripType,
flightList: itineraryData["Flight"]!,
onOpen: handleEdit,
onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode,
apiData: widget.apiData,
onDeleteFlight: (data) => handleItinerarydelete("Flight", data),
);
break;
}
switch (selectedOption) {
case "Train":
selectedWidget = TrainScreen(
onClose: handleClose,
apiData: widget.apiData,
apiDataForClass: widget.apiDataForClass,
loginUser: widget.loginUser,
onSavetrain: (data) => handleItineraryUpdate("Train", data),
tripType: widget.tripType,
selectedItem: selectedItem);
onClose: handleClose,
apiData: widget.apiData,
apiDataForClass: widget.apiDataForClass,
loginUser: widget.loginUser,
onSavetrain: (data) => handleItineraryUpdate("Train", data),
tripType: widget.tripType,
selectedItem: selectedItem,
);
break;
case "Taxi":
selectedWidget = TaxiScreen(
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
onSavetaxi: (data) => handleItineraryUpdate("Taxi", data),
selectedItem: selectedItem);
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
onSavetaxi: (data) => handleItineraryUpdate("Taxi", data),
selectedItem: selectedItem,
);
break;
case "Bus":
selectedWidget = BusScreen(
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
onSaveBus: (data) => handleItineraryUpdate("Bus", data),
selectedItem: selectedItem);
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
onSaveBus: (data) => handleItineraryUpdate("Bus", data),
selectedItem: selectedItem,
);
break;
case "Insurance":
selectedWidget = InsuranceScreen(
@ -666,8 +692,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
onSaveMiscellaneous: (data) =>
handleItineraryUpdate("Miscellaneous", data),
onSaveMiscellaneous:
(data) => handleItineraryUpdate("Miscellaneous", data),
selectedItem: selectedItem,
selectedIndex: selectedIndex,
);
@ -676,8 +702,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
selectedWidget = AccomodationScreen(
onClose: handleClose,
loginUser: widget.loginUser,
onSaveAccomadation: (data) =>
handleItineraryUpdate("Accomodation", data),
onSaveAccomadation:
(data) => handleItineraryUpdate("Accomodation", data),
selectedItem: selectedItem,
flightData: itineraryData["Flight"]!,
);
@ -776,85 +802,106 @@ class DynamicItineraryState extends State<DynamicItinerary> {
// );
// });
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isMobile = sizingInfo.isMobile;
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isMobile = sizingInfo.isMobile;
return Stack(
clipBehavior: Clip.none,
children: [
// Second container (yellow box)
Container(
margin: EdgeInsets.only(
top: 40), // Push it down to make room for the tab bar
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white, // Card background
// color: Colors.yellow.shade50, // Card background
// color: Color(0xFFF9F9F9), // Slightly lighter than white
// border: Border.all(color: Color(0xFFE6E7F5), width: 1.3),
border: Border.all(color: Color(0xFFE6E7F5), width: 1.2),
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
// color: Color(0x0D000000), // 5% opacity black
color: Colors.black12, // 5% opacity black
blurRadius: 5,
offset: Offset(0, 0.2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 2),
isSelected ? selectedWidget : selectedListWidget,
],
),
),
// First container (tab bar) positioned above
Positioned(
top: 0,
left: MediaQuery.of(context).size.width * 0.05,
right: MediaQuery.of(context).size.width * 0.05,
child: Container(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 12),
return Stack(
clipBehavior: Clip.none,
children: [
// Second container (yellow box)
Container(
margin: EdgeInsets.only(
top: 40,
), // Push it down to make room for the tab bar
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white, // Card background
borderRadius: BorderRadius.circular(8),
// color: Color(0xFFE6E7F5)
// border: Border.all(color: Colors.black12, width: 1.3),
// color: Colors.yellow.shade50, // Card background
// color: Color(0xFFF9F9F9), // Slightly lighter than white
// border: Border.all(color: Color(0xFFE6E7F5), width: 1.3),
border: Border.all(color: Color(0xFFE6E7F5), width: 1.2),
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black12,
// color: Color(0x0D000000), // 5% opacity black
blurRadius: 10,
color: Colors.black12, // 5% opacity black
blurRadius: 5,
offset: Offset(0, 0.2),
),
],
),
child: isMobile
? SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: _buildOptions(),
),
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: _buildOptions(),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 2),
isSelected ? selectedWidget : selectedListWidget,
],
),
),
),
],
);
});
// First container (tab bar) positioned above
Positioned(
top: 0,
left: MediaQuery.of(context).size.width * 0.05,
right: MediaQuery.of(context).size.width * 0.05,
child: Container(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 12),
decoration: BoxDecoration(
color: Colors.white, // Card background
borderRadius: BorderRadius.circular(8),
// color: Color(0xFFE6E7F5)
// border: Border.all(color: Colors.black12, width: 1.3),
border: Border.all(color: Color(0xFFE6E7F5), width: 1.2),
boxShadow: [
BoxShadow(
color: Colors.black12,
// color: Color(0x0D000000), // 5% opacity black
blurRadius: 10,
offset: Offset(0, 0.2),
),
],
),
child:
isMobile
? SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(children: _buildOptions()),
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: _buildOptions(),
),
),
),
],
);
},
);
}
bool hasValidItineraryEntries() {
if (ServicesChoosed == null || ServicesChoosed!.isEmpty) return false;
for (var service in ServicesChoosed!) {
final serviceName = service['name'];
final entries = itineraryData[serviceName];
// Check if there is at least one active entry (is_active == 1)
final hasActive =
entries?.any((entry) => entry['is_active'] == 1) ?? false;
if (!hasActive) {
return false; // Fail fast if any one service has no active entries
}
}
return true; // All selected services have at least one active entry
}
List<Widget> _buildOptions() {
if (ServicesChoosed == null) return [];
if (ServicesChoosed == null && !hasValidItineraryEntries()) return [];
if (ServicesChoosed != null &&
ServicesChoosed!.isNotEmpty &&
@ -875,18 +922,28 @@ class DynamicItineraryState extends State<DynamicItinerary> {
// }
return ServicesChoosed!.map((service) {
final serviceName = service['name'];
final serviceEntries = itineraryData[serviceName];
final hasActive =
serviceEntries?.any((entry) => entry['is_active'] == "1") ?? false;
print("Service1: $serviceName");
print("Entries1: $serviceEntries");
print("Has Active1: $hasActive");
return Padding(
padding: const EdgeInsets.only(right: 20.0),
child: _buildOption(
service, itineraryData[service['name']]?.isNotEmpty ?? false),
service,
hasActive,
// itineraryData[service['name']]?.isNotEmpty ?? false,
),
);
}).toList();
}
Widget _buildOption(
Map<String, dynamic> service,
bool hasData,
) {
Widget _buildOption(Map<String, dynamic> service, bool hasData) {
String name = service['name'];
String iconUrl = service['icon']; // Can be empty string
IconData fallbackIcon = _getLocalIconForService(name);
@ -914,26 +971,26 @@ class DynamicItineraryState extends State<DynamicItinerary> {
children: [
iconUrl.isNotEmpty
? Image.network(
iconUrl,
width: 18,
height: 18,
errorBuilder: (context, error, stackTrace) {
return Icon(
fallbackIcon,
size: 25,
color: isOptionSelected
? Color(0xFF114D8B)
: Color(0xFF475569),
);
},
)
iconUrl,
width: 18,
height: 18,
errorBuilder: (context, error, stackTrace) {
return Icon(
fallbackIcon,
size: 25,
color:
isOptionSelected
? Color(0xFF114D8B)
: Color(0xFF475569),
);
},
)
: Icon(
fallbackIcon,
size: 25,
color: isOptionSelected
? Color(0xFF114D8B)
: Color(0xFF475569),
),
fallbackIcon,
size: 25,
color:
isOptionSelected ? Color(0xFF114D8B) : Color(0xFF475569),
),
SizedBox(height: 2),
Row(
children: [
@ -943,12 +1000,12 @@ class DynamicItineraryState extends State<DynamicItinerary> {
// style: GoogleFonts.poppins( fontSize: 12,
// fontWeight: FontWeight.w600,
// color: Color(0xFF575A74))
style: TextStyle(
fontSize: 14,
color: isOptionSelected
? Color(0xFF114D8B)
: Color(0xFF475569),
color:
isOptionSelected
? Color(0xFF114D8B)
: Color(0xFF475569),
fontFamily: "Inter",
fontWeight:
isOptionSelected ? FontWeight.bold : FontWeight.w500,
@ -964,86 +1021,87 @@ class DynamicItineraryState extends State<DynamicItinerary> {
);
}
Widget _buildOption1(
Map<String, dynamic> service,
bool hasData,
) {
String name = service['name'];
String iconUrl = service['icon']; // Can be empty string
// Optional: define local icon fallback if iconUrl is empty
IconData fallbackIcon = _getLocalIconForService(name);
// final idMap = {"service_id": service['service_id'].toString()};
// final isSelected = selectedServiceIds.contains(idMap);
String serviceId = service['service_id'].toString();
// bool isSelected = selectedServiceIds.contains(serviceId);
// bool isSelected =
// selectedServiceIds.any((item) => item["service_id"] == serviceId);
return GestureDetector(
onTap: () {
setState(() {
selectedListOption = name;
isSelected = false;
});
},
child: Row(children: [
iconUrl.isNotEmpty
? Image.network(
iconUrl,
width: 18,
height: 18,
errorBuilder: (context, error, stackTrace) {
return Icon(
fallbackIcon,
size: 18,
color: selectedListOption == name
? Color(0xFF114D8B)
: Color(0xFF475569),
);
},
)
: Icon(
fallbackIcon,
size: 18,
color: selectedListOption == name
? Color(0xFF114D8B)
: Color(0xFF475569),
),
SizedBox(width: 2),
Text(
name,
style: TextStyle(
fontSize: 14,
// color: selectedListOption == title ? Colors.blueAccent : Color(0xFF575A74),
color: selectedListOption == name
? Color(0xFF114D8B)
: Color(0xFF475569),
fontFamily: "Archivo",
fontWeight: selectedListOption == name
? FontWeight.bold
: FontWeight.w500),
// fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)),
),
SizedBox(width: 2),
// if (selectedListOption == title && widget.isViewMode == false)
if (hasData)
Icon(Icons.circle, size: 8, color: Colors.green
// color: Colors.grey,
)
// Container(
// height: 10,
// width: 10,
// // decoration: BoxDecoration(
// // shape: BoxShape.circle,
// // border: Border.all(color: Colors.green, width: 1.5),
// // ),
// child:),
]),
);
}
// Widget _buildOption1(
// Map<String, dynamic> service,
// bool hasData,
// )
// {
// String name = service['name'];
// String iconUrl = service['icon']; // Can be empty string
// // Optional: define local icon fallback if iconUrl is empty
// IconData fallbackIcon = _getLocalIconForService(name);
// // final idMap = {"service_id": service['service_id'].toString()};
// // final isSelected = selectedServiceIds.contains(idMap);
//
// String serviceId = service['service_id'].toString();
// // bool isSelected = selectedServiceIds.contains(serviceId);
// // bool isSelected =
// // selectedServiceIds.any((item) => item["service_id"] == serviceId);
//
// return GestureDetector(
// onTap: () {
// setState(() {
// selectedListOption = name;
// isSelected = false;
// });
// },
// child: Row(children: [
// iconUrl.isNotEmpty
// ? Image.network(
// iconUrl,
// width: 18,
// height: 18,
// errorBuilder: (context, error, stackTrace) {
// return Icon(
// fallbackIcon,
// size: 18,
// color: selectedListOption == name
// ? Color(0xFF114D8B)
// : Color(0xFF475569),
// );
// },
// )
// : Icon(
// fallbackIcon,
// size: 18,
// color: selectedListOption == name
// ? Color(0xFF114D8B)
// : Color(0xFF475569),
// ),
//
// SizedBox(width: 2),
// Text(
// name,
// style: TextStyle(
// fontSize: 14,
// // color: selectedListOption == title ? Colors.blueAccent : Color(0xFF575A74),
// color: selectedListOption == name
// ? Color(0xFF114D8B)
// : Color(0xFF475569),
// fontFamily: "Archivo",
// fontWeight: selectedListOption == name
// ? FontWeight.bold
// : FontWeight.w500),
// // fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)),
// ),
//
// SizedBox(width: 2),
// // if (selectedListOption == title && widget.isViewMode == false)
// if (hasData)
// Icon(Icons.circle, size: 8, color: Colors.green
// // color: Colors.grey,
// )
// // Container(
// // height: 10,
// // width: 10,
// // // decoration: BoxDecoration(
// // // shape: BoxShape.circle,
// // // border: Border.all(color: Colors.green, width: 1.5),
// // // ),
// // child:),
// ]),
// );
// }
IconData _getLocalIconForService(String name) {
switch (name.toLowerCase()) {

File diff suppressed because it is too large Load Diff

View File

@ -14,14 +14,12 @@ import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../utils/pagination.dart';
class PolicyList extends StatefulWidget {
@override
_PolicyListState createState() => _PolicyListState();
}
class _PolicyListState extends State<PolicyList> {
final ApiService apiService = ApiService();
late Future<List<dynamic>> futurePolicy;
@ -37,7 +35,6 @@ class _PolicyListState extends State<PolicyList> {
List filteredPolicy = [];
TextEditingController searchController = TextEditingController();
int currentPage = 0;
int itemsPerPage = 10;
@ -68,14 +65,14 @@ class _PolicyListState extends State<PolicyList> {
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;
});
}
@ -84,44 +81,49 @@ class _PolicyListState extends State<PolicyList> {
return prefs.getString('auth_token');
}
Future<List<dynamic>> fetchPolicy() async {
final data = await apiService.fetchAllPolicy();
return data; // Returning raw JSON list
final data = await apiService.fetchAllPolicy();
return data; // Returning raw JSON list
}
// Refresh user list after update
void refreshPolicyList() {
setState(() {
futurePolicy = fetchPolicy(); // Re-fetch users after status update
futurePolicy.then((object) {
setState(() {
allPolicy = object;
});
});
// Wait for futurePlans to be fetched and update allPlans
});
}
void filterPolicy(String query) {
print("allPolicy before filtering: $query");
final lowerQuery = query.toLowerCase();
setState(() {
filteredPolicy =
allPolicy.where((object) {
return (object['name']?.toLowerCase().contains(lowerQuery) ??
false) ||
final isActiveStatus =
object['is_active'] == "1" ? "active" : "inactive";
return (object['policy_id']?.toLowerCase().contains(lowerQuery) ??
false) ||
(object['name']?.toLowerCase().contains(lowerQuery) ?? false) ||
(object['policy_type']?.toLowerCase().contains(lowerQuery) ??
false) ||
(object['is_active']?.toLowerCase().contains(lowerQuery) ?? false);}).toList();
(isActiveStatus.contains(lowerQuery));
}).toList();
currentPage = 0;
});
print("filtered: $filteredPolicy");
print("filteredPolicy: $filteredPolicy");
}
void handleActiveStatus(
Map<String, dynamic> policyData,
String policyId,
String currentStatus,
) async {
Map<String, dynamic> policyData,
String policyId,
String currentStatus,
) async {
print("Toggling user status - $policyId (Current: $currentStatus)");
final String apiUrlData =
@ -159,7 +161,6 @@ class _PolicyListState extends State<PolicyList> {
if (response.statusCode == 200) {
print("policyData submitted successfully!");
print("Response: ${response.body}");
loadAllGroups();
} else {
print("Failed to submit policyData. Status: ${response.statusCode}");
@ -175,31 +176,45 @@ class _PolicyListState extends State<PolicyList> {
print("policystatus: $status");
print("policysData: $policydata");
// handleActiveStatus(groupdata, groupId, status);
print("Calling handleActiveStatus with: id=$policyId, status=$status");
handleActiveStatus(policydata, policyId.toString(), status.toString());
}
Future<void> refreshData() async {
loadAllGroups();
}
Future<void> loadAllGroups() async {
try {
final result = await apiService.fetchAllPolicy();
// Sort by policy_id descending (latest first)
result.sort((a, b) {
int idA = int.tryParse(a['policy_id'].toString()) ?? 0;
int idB = int.tryParse(b['policy_id'].toString()) ?? 0;
return idB.compareTo(idA); // latest first
});
setState(() {
futurePolicy = result as Future<List>;
allPolicy = result;
});
print("Fetched services: $futurePolicy");
print("Fetched services: $allPolicy");
} catch (e) {
print('Error fetching role list: $e');
}
}
// Future<void> loadAllGroups_old() async {
// try {
// final result = await apiService.fetchAllPolicy();
//
// // Sort by policy_id descending (latest first)
// result.sort((a, b) {
// int idA = int.tryParse(a['policy_id'].toString()) ?? 0;
// int idB = int.tryParse(b['policy_id'].toString()) ?? 0;
// return idB.compareTo(idA); // latest first
// });
//
// setState(() {
// futurePolicy = result as Future<List>;
// });
// print("Fetched services: $futurePolicy");
// refreshPolicyList();
// } catch (e) {
// print('Error fetching role list: $e');
// }
// }
@override
Widget build(BuildContext context) {
@ -216,16 +231,16 @@ class _PolicyListState extends State<PolicyList> {
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),
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: [
// if (isDesktop) CustomDrawer(isDesktop: true),
@ -265,9 +280,9 @@ class _PolicyListState extends State<PolicyList> {
// : 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),
@ -362,11 +377,10 @@ class _PolicyListState extends State<PolicyList> {
// Print the resolved value
print("CREATELIAS - $policyData");
context.go('/Policy');
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New Policy",
@ -390,49 +404,49 @@ class _PolicyListState extends State<PolicyList> {
isDesktop
? SizedBox.shrink()
: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.8,
height: 35,
child: TextField(
controller: searchController,
onChanged: filterPolicy,
decoration: InputDecoration(
hintText: "Search for a Policy",
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: filterPolicy,
decoration: InputDecoration(
hintText: "Search for a Policy",
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: futurePolicy,
@ -475,7 +489,7 @@ class _PolicyListState extends State<PolicyList> {
}
List<dynamic> policyData =
filteredPolicy.isNotEmpty ? filteredPolicy : allPolicy;
filteredPolicy.isNotEmpty ? filteredPolicy : allPolicy;
policyData.sort((a, b) {
DateTime dateA = DateTime.parse(a['created_on']);
@ -485,10 +499,10 @@ class _PolicyListState extends State<PolicyList> {
});
List paginatedUser =
policyData
.skip(currentPage * itemsPerPage)
.take(itemsPerPage)
.toList();
policyData
.skip(currentPage * itemsPerPage)
.take(itemsPerPage)
.toList();
Widget table = LayoutBuilder(
builder: (context, constraints) {
@ -544,104 +558,119 @@ class _PolicyListState extends State<PolicyList> {
),
],
rows:
paginatedUser.map((policy) {
String policyId =
policy['policy_id'].toString(); // Get policy ID
bool isSelected = selectedPolicyId == policyId;
paginatedUser.map((policy) {
String policyId =
policy['policy_id']
.toString(); // Get policy ID
bool isSelected = selectedPolicyId == policyId;
return DataRow(
cells: [
DataCell(
Text(
"${policy['name'] ?? ''}",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
return DataRow(
cells: [
DataCell(
Text(
"${policy['name'] ?? ''}",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
),
),
),
),
),
DataCell(
Text(
policy['domestic'] == "1"
? "Domestic"
: "International",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
DataCell(
Text(
policy['domestic'] == "1"
? "Domestic"
: "International",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
),
),
),
),
),
DataCell(
Text(
policy['is_active'] == "1"
? "Active"
: "Inactive",
style: TextStyle(
color:
DataCell(
Text(
policy['is_active'] == "1"
? Colors.green
: Colors.grey,
fontFamily: "Inter",
fontWeight: FontWeight.w400,
? "Active"
: "Inactive",
style: TextStyle(
color:
policy['is_active'] == "1"
? Colors.green
: Colors.grey,
fontFamily: "Inter",
fontWeight: FontWeight.w400,
),
),
),
),
DataCell(
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
GestureDetector(
onTap: () async {
final rawId = policy['policy_id'];
final intPolicyId =
rawId is int
? rawId
: int.tryParse(rawId.toString()) ?? 0;
DataCell(
Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: [
GestureDetector(
onTap: () async {
final rawId = policy['policy_id'];
final intPolicyId =
rawId is int
? rawId
: int.tryParse(
rawId.toString(),
) ??
0;
Map<String, dynamic> policyData = await apiService
.getSinglePolicy(intPolicyId);
Map<String, dynamic> policyData =
await apiService
.getSinglePolicy(
intPolicyId,
);
print("PolicyDATa: $policyData");
print("PolicyDATa: $policyData");
context.go("/Policy", extra: policyData);
},
child: Tooltip(
message: 'Edit Policy Details',
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
context.go(
"/Policy",
extra: policyData,
);
},
child: Tooltip(
message: 'Edit Policy Details',
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
),
),
),
SizedBox(width: 5),
GestureDetector(
onTap: () {
final idStr = policy['policy_id'];
final id = int.tryParse(
idStr.toString(),
);
if (id == null) {
print("group_id is null");
return;
}
final status =
policy['is_active'];
deletePolicy(policy, id, status);
},
child: Tooltip(
message: 'Delete Policy Details',
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15,
),
),
),
],
),
SizedBox(width: 5),
GestureDetector(
onTap: () {
final idStr = policy['policy_id'];
final id = int.tryParse(idStr.toString());
if (id == null) {
print("group_id is null");
return;
}
final status = policy['is_active'];
deletePolicy(policy, id, status);
},
child: Tooltip(
message: 'Delete Policy Details',
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15,
),
),),
],
),
),
],
);
}).toList(),
),
],
);
}).toList(),
),
);
},
@ -654,7 +683,10 @@ class _PolicyListState extends State<PolicyList> {
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),
),
@ -666,7 +698,8 @@ class _PolicyListState extends State<PolicyList> {
children: [
// Row 1: Policy Name and Actions
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: RichText(
@ -684,11 +717,12 @@ class _PolicyListState extends State<PolicyList> {
),
),
TextSpan(
text: "${object['name'] ?? 'N/A'}",
text:
"${object['name'] ?? 'N/A'}",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.normal
fontWeight: FontWeight.normal,
),
),
],
@ -701,16 +735,26 @@ class _PolicyListState extends State<PolicyList> {
GestureDetector(
onTap: () async {
final rawId = object['policy_id'];
final intPolicyId = rawId is int
? rawId
: int.tryParse(rawId.toString()) ?? 0;
final intPolicyId =
rawId is int
? rawId
: int.tryParse(
rawId.toString(),
) ??
0;
Map<String, dynamic> policyData =
await apiService.getSinglePolicy(intPolicyId);
await apiService
.getSinglePolicy(
intPolicyId,
);
print("PolicyDATa: $policyData");
context.go("/Policy", extra: policyData);
context.go(
"/Policy",
extra: policyData,
);
},
child: Tooltip(
message: 'Edit Policy Details',
@ -725,7 +769,9 @@ class _PolicyListState extends State<PolicyList> {
GestureDetector(
onTap: () {
final idStr = object['policy_id'];
final id = int.tryParse(idStr.toString());
final id = int.tryParse(
idStr.toString(),
);
if (id == null) {
print("policy_id is null");
@ -750,7 +796,6 @@ class _PolicyListState extends State<PolicyList> {
),
SizedBox(height: 8), // Spacing
// Row 2: Policy Type
RichText(
text: TextSpan(
@ -767,13 +812,14 @@ class _PolicyListState extends State<PolicyList> {
),
),
TextSpan(
text: object['domestic'] == "1"
? "Domestic"
: "International",
text:
object['domestic'] == "1"
? "Domestic"
: "International",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.normal
fontWeight: FontWeight.normal,
),
),
],
@ -787,7 +833,6 @@ class _PolicyListState extends State<PolicyList> {
);
}
Widget buildMobileCardView2(List<dynamic> paginatedUser) {
return ListView.builder(
itemCount: paginatedUser.length,
@ -810,7 +855,6 @@ class _PolicyListState extends State<PolicyList> {
children: [
Column(
children: [
Expanded(
flex: 1,
child: Text(
@ -859,30 +903,41 @@ class _PolicyListState extends State<PolicyList> {
onTap: () async {
final rawId = object['policy_id'];
final intPolicyId =
rawId is int
? rawId
: int.tryParse(rawId.toString()) ?? 0;
rawId is int
? rawId
: int.tryParse(
rawId.toString(),
) ??
0;
Map<String, dynamic> policyData = await apiService
.getSinglePolicy(intPolicyId);
Map<String, dynamic> policyData =
await apiService.getSinglePolicy(
intPolicyId,
);
print("PolicyDATa: $policyData");
context.go("/Policy", extra: policyData);
context.go(
"/Policy",
extra: policyData,
);
},
child: Tooltip(
message: 'Edit Policy Details',
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),),
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
),
),
SizedBox(width: 5),
GestureDetector(
onTap: () {
final idStr = object['policy_id'];
final id = int.tryParse(idStr.toString());
final id = int.tryParse(
idStr.toString(),
);
if (id == null) {
print("group_id is null");
@ -894,11 +949,12 @@ class _PolicyListState extends State<PolicyList> {
},
child: Tooltip(
message: 'Delete Policy 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,
),
),
),
],
),
@ -916,12 +972,12 @@ class _PolicyListState extends State<PolicyList> {
children: [
Expanded(
child:
isDesktop
? SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table, // <-- your existing table
)
: buildMobileCardView(paginatedUser),
isDesktop
? SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table, // <-- your existing table
)
: buildMobileCardView(paginatedUser),
),
PaginationControls(
currentPage: currentPage,
@ -951,4 +1007,4 @@ class _PolicyListState extends State<PolicyList> {
),
);
}
}
}

View File

@ -19,13 +19,14 @@ class TravellerData extends StatefulWidget {
final int? travellerId; // <-- Add this
final Map<String, dynamic>? travellerData;
const TravellerData(
{super.key,
required this.isDesktop,
this.layoutColor,
required this.fetchGetTraveller,
this.travellerId,
this.travellerData});
const TravellerData({
super.key,
required this.isDesktop,
this.layoutColor,
required this.fetchGetTraveller,
this.travellerId,
this.travellerData,
});
@override
TravellerDataState createState() => TravellerDataState();
@ -49,12 +50,7 @@ class TravellerDataState extends State<TravellerData> {
int? travellerDataId;
late String isActive = "1";
List<String> dataHeader = [
"first_name",
"last_name",
"email",
"mobile",
];
List<String> dataHeader = ["first_name", "last_name", "email", "mobile"];
Map<String, dynamic> travellerDetails() {
final data = {
@ -72,7 +68,6 @@ class TravellerDataState extends State<TravellerData> {
void initState() {
super.initState();
apiData = null;
for (var field in dataHeader) {
controllers[field] = TextEditingController();
@ -115,7 +110,6 @@ class TravellerDataState extends State<TravellerData> {
});
}
void toggleStatus() {
setState(() {
isActive = isActive == "1" ? "0" : "1";
@ -132,7 +126,7 @@ class TravellerDataState extends State<TravellerData> {
"mobile": controllers["mobile"]?.text,
};
final requiredFields = ["first_name","last_name","email","mobile"];
final requiredFields = ["first_name", "last_name", "email", "mobile"];
bool hasFocused = false;
// Check validation for each field
@ -150,13 +144,14 @@ class TravellerDataState extends State<TravellerData> {
if (data["mobile"] != null && data["mobile"].toString().isNotEmpty) {
if (!RegExp(r"^\d{10}$").hasMatch(data["mobile"].toString())) {
errorMessages["mobile"] =
"Enter 10 digits"; // Invalid mobile number format
"Enter 10 digits"; // Invalid mobile number format
}
}
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
}
}
@ -195,7 +190,9 @@ class TravellerDataState extends State<TravellerData> {
apiUrldata = '$apiUrl/api/travellers/update/$travellerDataId';
travellerData["traveller_id"] = travellerDataId.toString();
travellerData["updated_by"] = userId;
(travellerData.containsKey("created_by")) ? travellerData.remove("created_by") : '' ;
(travellerData.containsKey("created_by"))
? travellerData.remove("created_by")
: '';
} else {
print("for add Traveller id - null");
apiUrldata = '$apiUrl/api/travellers/create';
@ -217,10 +214,10 @@ class TravellerDataState extends State<TravellerData> {
};
final body = jsonEncode(travellerData);
final response = travellerDataId != null
? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body);
final response =
travellerDataId != null
? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body);
switch (response.statusCode) {
case 200:
@ -241,7 +238,6 @@ class TravellerDataState extends State<TravellerData> {
print("Failed to submit traveller. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print(" Error submitting plan: $e");
}
@ -249,7 +245,6 @@ class TravellerDataState extends State<TravellerData> {
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: Colors.white,
contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30),
@ -264,27 +259,30 @@ class TravellerDataState extends State<TravellerData> {
Row(
children: [
Text(
(travellerDataId != null) ? 'Edit Traveller' : 'Create Traveller',
style: GoogleFonts.poppins(fontSize: 15, color: Colors.black),
(travellerDataId != null)
? 'Edit Traveller'
: 'Create Traveller',
style: GoogleFonts.poppins(
fontSize: 15,
color: Colors.black,
),
),
const Spacer(),
],
),
const SizedBox(height: 2),
Divider(
thickness: 0.2,
color: Colors.blueGrey.shade100,
),
Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
const SizedBox(height: 5),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"First Name",
"First Name *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -292,19 +290,23 @@ class TravellerDataState extends State<TravellerData> {
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["first_name"],
focusNode: focusNodes["first_name"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "First Name",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
height: 40,
child: TextField(
controller: controllers["first_name"],
focusNode: focusNodes["first_name"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "First Name",
labelStyle: TextStyle(
fontSize: 11,
color: Colors.grey,
),
)),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["first_name"] != null) ...[
SizedBox(height: 5), // Space before error message
@ -315,18 +317,17 @@ class TravellerDataState extends State<TravellerData> {
],
],
),
SizedBox(
height: 15,
),
SizedBox(height: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Last Name",
"Last Name *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -334,19 +335,23 @@ class TravellerDataState extends State<TravellerData> {
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["last_name"],
focusNode: focusNodes["last_name"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Last Name",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
height: 40,
child: TextField(
controller: controllers["last_name"],
focusNode: focusNodes["last_name"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Last Name",
labelStyle: TextStyle(
fontSize: 11,
color: Colors.grey,
),
)),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["last_name"] != null) ...[
SizedBox(height: 5), // Space before error message
@ -357,18 +362,17 @@ class TravellerDataState extends State<TravellerData> {
],
],
),
SizedBox(
height: 15,
),
SizedBox(height: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Email",
"Email *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -376,19 +380,23 @@ class TravellerDataState extends State<TravellerData> {
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["email"],
focusNode: focusNodes["email"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Email",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
height: 40,
child: TextField(
controller: controllers["email"],
focusNode: focusNodes["email"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Email",
labelStyle: TextStyle(
fontSize: 11,
color: Colors.grey,
),
)),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["email"] != null) ...[
SizedBox(height: 5), // Space before error message
@ -399,18 +407,17 @@ class TravellerDataState extends State<TravellerData> {
],
],
),
SizedBox(
height: 15,
),
SizedBox(height: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Mobile",
"Mobile *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
@ -418,19 +425,23 @@ class TravellerDataState extends State<TravellerData> {
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["mobile"],
focusNode: focusNodes["mobile"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Mobile",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
height: 40,
child: TextField(
controller: controllers["mobile"],
focusNode: focusNodes["mobile"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Mobile",
labelStyle: TextStyle(
fontSize: 11,
color: Colors.grey,
),
)),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["mobile"] != null) ...[
SizedBox(height: 5), // Space before error message
@ -441,9 +452,7 @@ class TravellerDataState extends State<TravellerData> {
],
],
),
SizedBox(
height: 15,
),
SizedBox(height: 15),
if (travellerDataId != null)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -451,13 +460,16 @@ class TravellerDataState extends State<TravellerData> {
Text(
"Change Status ",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
Tooltip(
message:
isActive == "1" ? "Tap to deactivate" : "Tap to activate",
isActive == "1"
? "Tap to deactivate"
: "Tap to activate",
child: GestureDetector(
onTap: toggleStatus,
child: Text(
@ -469,13 +481,10 @@ class TravellerDataState extends State<TravellerData> {
),
),
),
)
),
],
),
if (travellerDataId != null)
SizedBox(
height: 15,
),
if (travellerDataId != null) SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@ -512,18 +521,22 @@ class TravellerDataState extends State<TravellerData> {
borderRadius: BorderRadius.circular(8),
),
),
child: Text('Save',
style: GoogleFonts.poppins(
fontSize: 11, color: Colors.white)),
child: Text(
'Save',
style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.white,
),
),
),
),
],
)
),
// : SizedBox.shrink(),
],
),
)
)
),
),
);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -663,6 +663,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
"last_name",
"email",
"mobile_no",
"role_id",
// "employeeCode",
];
@ -1125,6 +1126,9 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
},
);
case "travel":
final fullName =
"${controllers["Fname"]?.text ?? ""} ${controllers["Lname"]?.text ?? ""}"
.trim();
return TravellerDetails(
key: travellerDetailsKey,
isDesktop: isDesktop,
@ -1134,6 +1138,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
travelDetails: travelDetailsDataFromAPI, // 👈 Pass this down
passportFileUrlFromApi: passportFileUrlFromApi,
userIdApi: userIdApi,
fullName: fullName,
);
default:
return PersonalDetails(

View File

@ -314,6 +314,8 @@ class PersonalDetailsState extends State<PersonalDetails> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 20),
_buildPassportDataRow1(widget.isDesktop),
SizedBox(height: 10),
_buildFirstRow(widget.isDesktop),
SizedBox(height: 10),
_buildSecondRow(widget.isDesktop),
@ -329,6 +331,25 @@ class PersonalDetailsState extends State<PersonalDetails> {
);
}
Widget _buildPassportDataRow1(bool isDesktop) {
return Container(
color: Colors.white,
child: Row(
children: [
Text(
"* Please fill name details as per in passport *",
style: GoogleFonts.poppins(
fontSize: 10,
color: Colors.black87,
fontStyle: FontStyle.italic,
letterSpacing: 0.5,
),
),
],
),
);
}
Future<void> loadAllServices() async {
try {
final result = await apiService.fetchAllServices();
@ -1500,7 +1521,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Role",
"Role*",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -1579,6 +1600,13 @@ class PersonalDetailsState extends State<PersonalDetails> {
),
),
),
if (widget.errorMessages["role_id"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["role_id"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
);
}

View File

@ -32,6 +32,7 @@ class TravellerDetails extends StatefulWidget {
final Map<String, dynamic>? travelDetails;
final String? passportFileUrlFromApi;
final String? userIdApi;
final String fullName;
const TravellerDetails({
Key? key,
@ -42,6 +43,7 @@ class TravellerDetails extends StatefulWidget {
this.travelDetails,
this.passportFileUrlFromApi,
this.userIdApi,
required this.fullName,
}) : super(key: key);
@override
TravellerDetailsState createState() => TravellerDetailsState();
@ -54,6 +56,8 @@ class TravellerDetailsState extends State<TravellerDetails> {
int? expandedIndex;
bool isCountryLoading = true;
Map<String, String> errorMessages = {};
late final userId;
String? selectedFileNames;
Uint8List? passportDocumentBytes;
@ -154,18 +158,16 @@ class TravellerDetailsState extends State<TravellerDetails> {
String value,
String? userId,
) async {
var newField = "";
try {
final response = await apiService.CheckDuplicate(
label,
newField,
field,
value,
userId,
);
if (response.isNotEmpty) {
_clearError(field);
widget.errorMessages[field] =
response['message'] ?? "$label already exists";
errorMessages[field] = response['message'] ?? "$label Already Exists";
print("Duplicate found: ${response['message']}");
return;
} else {
@ -177,9 +179,37 @@ class TravellerDetailsState extends State<TravellerDetails> {
}
}
// Future<void> apiCheckDuplicate(
// String label,
// String field,
// String value,
// String? userId,
// ) async {
// var newField = "";
// try {
// final response = await apiService.CheckDuplicate(
// label,
// newField,
// value,
// userId,
// );
// if (response.isNotEmpty) {
// _clearError(field);
// errorMessages[field] = response['message'] ?? "$label already exists";
// print("Duplicate found: ${response['message']}");
// return;
// } else {
// _clearError(field);
// print("No duplicates found.");
// }
// } catch (e) {
// print("Error in checkDuplicate: $e");
// }
// }
void _clearError(String field) {
setState(() {
widget.errorMessages.remove(field);
errorMessages.remove(field);
});
}
@ -858,7 +888,7 @@ class TravellerDetailsState extends State<TravellerDetails> {
// SizedBox(
// height: 10,
// ),
// _buildPassportDataRow1(widget.isDesktop),
_buildPassportDataRow1(widget.isDesktop),
SizedBox(height: 10),
_buildPassportDataRow2(widget.isDesktop),
SizedBox(height: 10),
@ -870,28 +900,49 @@ class TravellerDetailsState extends State<TravellerDetails> {
Widget _buildPassportDataRow1(bool isDesktop) {
return Container(
color: Colors.white,
child:
widget.isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildFirstNameField(),
Spacer(),
buildLastNameField(),
Spacer(), // Space after Last Name
buildNationality(),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildFirstNameField(),
SizedBox(height: 8), // Vertical space
buildLastNameField(),
SizedBox(height: 8),
buildNationality(),
],
),
child: Row(
children: [
Text(
"Name as per passport : ",
style: GoogleFonts.poppins(
fontSize: 11,
fontStyle: FontStyle.italic,
letterSpacing: 0.5,
),
),
Text(
"${widget.fullName} ",
style: GoogleFonts.poppins(
fontSize: 10,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
color: Colors.black87,
// fontStyle: FontStyle.italic,
),
),
],
),
// child: widget.isDesktop
// ? Row(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// buildFirstNameField(),
// Spacer(),
// buildLastNameField(),
// Spacer(), // Space after Last Name
// buildNationality(),
// ],
// )
// : Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// buildFirstNameField(),
// SizedBox(height: 8), // Vertical space
// buildLastNameField(),
// SizedBox(height: 8),
// buildNationality(),
// ],
// ),
);
}
@ -984,13 +1035,6 @@ class TravellerDetailsState extends State<TravellerDetails> {
),
),
),
if (widget.errorMessages["first_name"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["first_name"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
);
}
@ -1033,13 +1077,6 @@ class TravellerDetailsState extends State<TravellerDetails> {
),
),
),
if (widget.errorMessages["last_name"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["last_name"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
);
}
@ -1082,13 +1119,6 @@ class TravellerDetailsState extends State<TravellerDetails> {
),
),
),
if (widget.errorMessages["last_name"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["last_name"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
);
}
@ -1137,10 +1167,10 @@ class TravellerDetailsState extends State<TravellerDetails> {
),
),
),
if (widget.errorMessages["passport_number"] != null) ...[
if (errorMessages["passport_number"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["passport_number"]!,
errorMessages["passport_number"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
@ -2375,6 +2405,13 @@ class TravellerDetailsState extends State<TravellerDetails> {
),
),
),
if (errorMessages["forex_pre_paid_card_number"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["forex_pre_paid_card_number"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
);
}

View File

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

View File

@ -143,7 +143,7 @@ class OrganizationSettingState extends State<OrganizationSetting> {
{
'value': '/traveller',
'icon': Icons.travel_explore,
'label': 'Traveller',
'label': 'Traveller (Non Employee)',
'description': 'Create and Edit Traveller',
},
];

View File

@ -318,7 +318,7 @@ class ApiService {
Future<List<dynamic>> fetchAllGroup() async {
String? orgId = await getOrgId();
final String apiUrldata = '$apiUrl/api/groups?org_id=$orgId';
final String apiUrldata = '$apiUrl/api/groups?for=table_view&org_id=$orgId';
final token = await getToken();
@ -392,7 +392,7 @@ class ApiService {
Future<List<dynamic>> fetchAllPolicy() async {
String? orgId = await getOrgId();
final String apiUrldata = '$apiUrl/api/policy?org_id=$orgId';
final String apiUrldata = '$apiUrl/api/policy?for=table_view&org_id=$orgId';
final token = await getToken();

View File

@ -0,0 +1,35 @@
// saving_loader.dart
import 'package:flutter/material.dart';
class SavingLoader extends StatelessWidget {
const SavingLoader({super.key});
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(
width: 80,
height: 10,
child: LinearProgressIndicator(
backgroundColor: Colors.green,
color: Colors.white,
),
),
const SizedBox(height: 12),
const Text(
"Your changes are being saved.",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
],
),
),
);
}
}