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

View File

@ -472,6 +472,40 @@ class _LoginWidgetState extends State<LoginWidget> {
/// **Password Field** /// **Password Field**
_buildLabel("Password"), _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( TextFormField(
controller: _passwordController, controller: _passwordController,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
@ -479,6 +513,7 @@ class _LoginWidgetState extends State<LoginWidget> {
fontSize: 11, fontSize: 11,
), ),
obscureText: _obscureText, obscureText: _obscureText,
textInputAction: TextInputAction.done,
decoration: _inputDecoration( decoration: _inputDecoration(
"Enter your password", "Enter your password",
).copyWith( ).copyWith(
@ -497,13 +532,17 @@ class _LoginWidgetState extends State<LoginWidget> {
), ),
), ),
), ),
onFieldSubmitted: (_) {
if (_formKey.currentState!.validate()) {
_login(context);
}
},
validator: validator:
(value) => (value) =>
value == null || value.isEmpty value == null || value.isEmpty
? 'Required Password' ? 'Required Password'
: null, : null,
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
/// **Login Button** /// **Login Button**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,14 +17,15 @@ class TrainScreen extends StatefulWidget {
final String? loginUser; final String? loginUser;
final String? tripType; final String? tripType;
TrainScreen( TrainScreen({
{required this.onClose, required this.onClose,
this.apiData, this.apiData,
required this.onSavetrain, required this.onSavetrain,
required this.selectedItem, required this.selectedItem,
required this.loginUser, required this.loginUser,
this.apiDataForClass, this.apiDataForClass,
this.tripType}); this.tripType,
});
@override @override
_TrainScreenState createState() => _TrainScreenState(); _TrainScreenState createState() => _TrainScreenState();
@ -232,7 +233,7 @@ class _TrainScreenState extends State<TrainScreen> {
"from_station", "from_station",
"to_station", "to_station",
"date", "date",
"time" "time",
]; ];
// Check validation for each field // Check validation for each field
@ -287,51 +288,54 @@ class _TrainScreenState extends State<TrainScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
bool isMobile = sizingInfo.isMobile; builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; bool isMobile = sizingInfo.isMobile;
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Container( return Container(
// color: Color(0xFFF4F4FB), // color: Color(0xFFF4F4FB),
child: Form( child: Form(
key: _formKey, key: _formKey,
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
children: [ children: [
// Align( // Align(
// alignment: Alignment.centerRight, // alignment: Alignment.centerRight,
// child: InkWell( // child: InkWell(
// onTap: () { // onTap: () {
// widget.onClose(false); // widget.onClose(false);
// }, // },
// child: Icon( // child: Icon(
// Icons.close, // Icons.close,
// size: 18, // size: 18,
// color: Color(0xFF575A74), // color: Color(0xFF575A74),
// ), // ),
// ), // ),
// ), // ),
// Text("Train Booking List", // Text("Train Booking List",
// style: TextStyle( // style: TextStyle(
// fontSize: 18, // fontSize: 18,
// fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
// color: Color(0xFF575A74))), // color: Color(0xFF575A74))),
// SizedBox( // SizedBox(
// height: 6, // height: 6,
// ), // ),
Padding( Padding(
padding: const EdgeInsets.all(28.0), padding: const EdgeInsets.all(28.0),
child: Center( child: Center(
child: Column(children: _buildAccomadtionForm(isDesktop)), child: Column(children: _buildAccomadtionForm(isDesktop)),
),
), ),
) ],
], ),
), ),
), ),
), );
); },
}); );
} }
List<Widget> _buildAccomadtionForm(bool isDesktop) { List<Widget> _buildAccomadtionForm(bool isDesktop) {
@ -344,7 +348,7 @@ class _TrainScreenState extends State<TrainScreen> {
List<List<Widget>> rowBuilders = [ List<List<Widget>> rowBuilders = [
_builClassType(isDesktop), _builClassType(isDesktop),
_buildSecondRow(isDesktop) _buildSecondRow(isDesktop),
]; ];
return [ return [
@ -367,9 +371,10 @@ class _TrainScreenState extends State<TrainScreen> {
Text( Text(
"Train Number", "Train Number",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
isDesktop isDesktop
@ -377,38 +382,35 @@ class _TrainScreenState extends State<TrainScreen> {
: Column(children: _buildTripType(isDesktop)), : Column(children: _buildTripType(isDesktop)),
if (errorMessages["train_no"] != null) ...[ if (errorMessages["train_no"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
]; ];
} }
List<Widget> _buildTripType(bool isDesktop) { List<Widget> _buildTripType(bool isDesktop) {
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? []; List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList List<DropdownMenuItem<String>> dropdownItems =
.map((item) => DropdownMenuItem<String>( purposeList
value: item['dropdown_value'], .map(
child: Text(item['dropdown_value']), (item) => DropdownMenuItem<String>(
)) value: item['dropdown_value'],
.toList(); child: Text(item['dropdown_value']),
),
)
.toList();
if (dropdownItems.isEmpty) { if (dropdownItems.isEmpty) {
dropdownItems.add( dropdownItems.add(
DropdownMenuItem<String>( DropdownMenuItem<String>(
value: null, value: null,
child: Text("No options available", child: Text(
style: TextStyle(color: Colors.grey)), "No options available",
style: TextStyle(color: Colors.grey),
),
), ),
); );
} }
@ -455,10 +457,11 @@ class _TrainScreenState extends State<TrainScreen> {
DateTime? pickedDate = await showDatePicker( DateTime? pickedDate = await showDatePicker(
context: context, context: context,
initialDate: _selectedCheckOutDate != null && initialDate:
_selectedCheckOutDate!.isAfter(today) _selectedCheckOutDate != null &&
? _selectedCheckOutDate! _selectedCheckOutDate!.isAfter(today)
: today, ? _selectedCheckOutDate!
: today,
firstDate: today, firstDate: today,
lastDate: DateTime(2100), lastDate: DateTime(2100),
); );
@ -483,8 +486,13 @@ class _TrainScreenState extends State<TrainScreen> {
// Formatting time to HH:mm (24-hour format) // Formatting time to HH:mm (24-hour format)
final now = DateTime.now(); final now = DateTime.now();
final formattedTime = DateFormat('HH:mm').format( final formattedTime = DateFormat('HH:mm').format(
DateTime(now.year, now.month, now.day, pickedTime.hour, DateTime(
pickedTime.minute), now.year,
now.month,
now.day,
pickedTime.hour,
pickedTime.minute,
),
); );
_timeController.text = formattedTime; _timeController.text = formattedTime;
}); });
@ -495,19 +503,24 @@ class _TrainScreenState extends State<TrainScreen> {
List<dynamic> purposeList = widget.apiDataForClass?['train_class'] ?? []; List<dynamic> purposeList = widget.apiDataForClass?['train_class'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList List<DropdownMenuItem<String>> dropdownItems =
.map((item) => DropdownMenuItem<String>( purposeList
value: item['dropdown_key'], .map(
child: Text(item['dropdown_value']), (item) => DropdownMenuItem<String>(
)) value: item['dropdown_key'],
.toList(); child: Text(item['dropdown_value']),
),
)
.toList();
if (dropdownItems.isEmpty) { if (dropdownItems.isEmpty) {
dropdownItems.add( dropdownItems.add(
DropdownMenuItem<String>( DropdownMenuItem<String>(
value: null, value: null,
child: Text("No options available", child: Text(
style: TextStyle(color: Colors.grey)), "No options available",
style: TextStyle(color: Colors.grey),
),
), ),
); );
} }
@ -523,9 +536,10 @@ class _TrainScreenState extends State<TrainScreen> {
Text( Text(
"Class *", "Class *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -543,86 +557,91 @@ class _TrainScreenState extends State<TrainScreen> {
style: TextStyle(fontSize: 12), style: TextStyle(fontSize: 12),
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
contentPadding: contentPadding: EdgeInsets.symmetric(
EdgeInsets.symmetric(horizontal: 10), // Proper padding horizontal: 10,
), // Proper padding
), ),
onChanged: purposeList.isNotEmpty onChanged:
? (newValue) { purposeList.isNotEmpty
setState(() { ? (newValue) {
selectedClass = newValue; setState(() {
}); selectedClass = newValue;
} });
: null, }
: null,
items: dropdownItems, items: dropdownItems,
), ),
), ),
), ),
if (errorMessages["class"] != null) ...[ if (errorMessages["class"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"From", "From",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
isFocused: _fromFocus, isFocused: _fromFocus,
isDesktop: isDesktop, isDesktop: isDesktop,
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: isCountryLoading child:
? Center(child: CircularProgressIndicator()) isCountryLoading
: DropdownSearch<String>( ? Center(child: CircularProgressIndicator())
: DropdownSearch<String>(
// selectedItem: selectedFrom != null // selectedItem: selectedFrom != null
// ? countryMap[selectedFrom] // ? countryMap[selectedFrom]
// : null, // : null,
selectedItem:
selectedItem: selectedFrom != null selectedFrom != null
? countryMap[ ? countryMap[selectedFrom] // get the display value from code
selectedFrom] // get the display value from code : null,
: null,
popupProps: PopupProps.menu( popupProps: PopupProps.menu(
menuProps: MenuProps(backgroundColor: Colors.white),
constraints: BoxConstraints(maxHeight: 230),
showSearchBox: true, showSearchBox: true,
searchFieldProps: TextFieldProps( searchFieldProps: TextFieldProps(
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search ...", hintText: "Search ...",
contentPadding: contentPadding: EdgeInsets.symmetric(
EdgeInsets.symmetric(horizontal: 10), horizontal: 10,
),
), ),
), ),
), ),
items: countryMap.values.toList(), items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps( dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration( dropdownSearchDecoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
border: InputBorder.none, 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) { // onChanged: (String? newValue) {
// setState(() { // setState(() {
// // selectedFrom[index] = countryMap.entries // // selectedFrom[index] = countryMap.entries
@ -636,56 +655,51 @@ class _TrainScreenState extends State<TrainScreen> {
// print(selectedFrom); // print(selectedFrom);
// }); // });
// }, // },
onChanged: (String? newValue) { onChanged: (String? newValue) {
setState(() { setState(() {
selectedFrom = countryMap.entries selectedFrom =
.firstWhere((entry) => entry.value == newValue) countryMap.entries
.key; .firstWhere(
(entry) => entry.value == newValue,
)
.key;
}); });
}, },
), ),
) ),
// child: SizedBox( // child: SizedBox(
// height: 40, // height: 40,
// child: TextField( // child: TextField(
// focusNode: _fromFocusNode, // focusNode: _fromFocusNode,
// controller: _fromController, // controller: _fromController,
// style: const TextStyle(fontSize: 12), // style: const TextStyle(fontSize: 12),
// decoration: const InputDecoration( // decoration: const InputDecoration(
// labelText: "From", // labelText: "From",
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey), // labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never, // floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none, // border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16), // contentPadding: EdgeInsets.symmetric(vertical: 16),
// ), // ),
// ), // ),
// ), // ),
), ),
if (errorMessages["from_station"] != null) ...[ if (errorMessages["from_station"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"To", "To",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -693,71 +707,72 @@ class _TrainScreenState extends State<TrainScreen> {
isDesktop: isDesktop, isDesktop: isDesktop,
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: isCountryLoading child:
? Center(child: CircularProgressIndicator()) isCountryLoading
: DropdownSearch<String>( ? Center(child: CircularProgressIndicator())
selectedItem: selectedTo != null : DropdownSearch<String>(
? countryMap[ selectedItem:
selectedTo] // get the display value from code selectedTo != null
: null, ? countryMap[selectedTo] // get the display value from code
popupProps: PopupProps.menu( : null,
showSearchBox: true, popupProps: PopupProps.menu(
searchFieldProps: TextFieldProps( menuProps: MenuProps(backgroundColor: Colors.white),
decoration: InputDecoration( constraints: BoxConstraints(maxHeight: 230),
hintText: "Search ...", showSearchBox: true,
contentPadding: searchFieldProps: TextFieldProps(
EdgeInsets.symmetric(horizontal: 10), decoration: InputDecoration(
hintText: "Search ...",
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
),
),
), ),
), ),
), items: countryMap.values.toList(),
items: countryMap.values.toList(), dropdownDecoratorProps: DropDownDecoratorProps(
dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration(
dropdownSearchDecoration: InputDecoration( border: InputBorder.none,
border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 1),
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) ...[ if (errorMessages["to_station"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Date", "Date",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -779,8 +794,11 @@ class _TrainScreenState extends State<TrainScreen> {
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(Icons.calendar_today, suffixIcon: Icon(
size: 16, color: Colors.grey), Icons.calendar_today,
size: 16,
color: Colors.grey,
),
), ),
), ),
), ),
@ -789,28 +807,21 @@ class _TrainScreenState extends State<TrainScreen> {
), ),
if (errorMessages["date"] != null) ...[ if (errorMessages["date"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Time", "Time",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -832,8 +843,11 @@ class _TrainScreenState extends State<TrainScreen> {
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: suffixIcon: Icon(
Icon(Icons.access_time, size: 16, color: Colors.grey), Icons.access_time,
size: 16,
color: Colors.grey,
),
), ),
), ),
), ),
@ -842,10 +856,7 @@ class _TrainScreenState extends State<TrainScreen> {
), ),
if (errorMessages["time"] != null) ...[ if (errorMessages["time"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
@ -860,9 +871,10 @@ class _TrainScreenState extends State<TrainScreen> {
Text( Text(
"Comments", "Comments",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldWrapper( CustomTextFieldWrapper(
@ -890,9 +902,7 @@ class _TrainScreenState extends State<TrainScreen> {
], ],
), ),
if (isDesktop) Spacer(), if (isDesktop) Spacer(),
SizedBox( SizedBox(height: 5),
height: 5,
),
Column( Column(
children: [ children: [
Row( Row(
@ -900,7 +910,7 @@ class _TrainScreenState extends State<TrainScreen> {
children: _handleAction(isDesktop), children: _handleAction(isDesktop),
), ),
], ],
) ),
]; ];
} }
@ -913,9 +923,7 @@ class _TrainScreenState extends State<TrainScreen> {
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color backgroundColor: Colors.grey[400], // Light grey color
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
), ),
child: Text( child: Text(
@ -924,7 +932,6 @@ class _TrainScreenState extends State<TrainScreen> {
), ),
), ),
SizedBox(width: 10), // Space between buttons SizedBox(width: 10), // Space between buttons
// Save Changes Button // Save Changes Button
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
@ -932,9 +939,7 @@ class _TrainScreenState extends State<TrainScreen> {
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B), // Primary color for save backgroundColor: Color(0xFF114D8B), // Primary color for save
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
), ),
child: Text( child: Text(

View File

@ -23,6 +23,7 @@ import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart'; import '../../services/apiService.dart';
import '../../widgets/custom_radio_button.dart'; import '../../widgets/custom_radio_button.dart';
import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_field.dart';
import '../../widgets/saving_loader.dart';
import '../approvals/approval_dialogs.dart'; import '../approvals/approval_dialogs.dart';
import '../dialog/user_selection_dialog.dart'; import '../dialog/user_selection_dialog.dart';
import '../itnerary/flights.dart'; import '../itnerary/flights.dart';
@ -909,8 +910,8 @@ class CreateNewPlansState extends State<CreateNewPlan> {
costCenterIds = costCenterMap.keys.toList(); costCenterIds = costCenterMap.keys.toList();
// Optionally auto-select the first item if not already selected // Optionally auto-select the first item if not already selected
selectedCostCenterId ??= // selectedCostCenterId ??=
costCenterIds.isNotEmpty ? costCenterIds.first : null; // costCenterIds.isNotEmpty ? costCenterIds.first : null;
}); });
print('plansJSON'); print('plansJSON');
@ -1069,11 +1070,16 @@ class CreateNewPlansState extends State<CreateNewPlan> {
miscellaneousList, miscellaneousList,
]; ];
bool anyServiceSelected = serviceLists.any( // bool anyServiceSelected = serviceLists.any(
(list) => list != null && list.isNotEmpty, // (list) => list != null && list.isNotEmpty,
); // );
bool anyServiceSelected = serviceLists.any((list) {
return list != null &&
list.any((entry) => entry['is_active'].toString() == "1");
});
if (!anyServiceSelected) { if (!anyServiceSelected) {
// validationErrors["services"] = "Please select at least one service"; validationErrors["services"] = "Please select at least one service";
setState(() { setState(() {
temporaryMessage = "Please select at least one service"; temporaryMessage = "Please select at least one service";
@ -1172,7 +1178,7 @@ class CreateNewPlansState extends State<CreateNewPlan> {
} }
void handleSubmit() { void handleSubmit() {
setState(() { setState(() async {
if (validateForm() && temporaryMessage == null) { if (validateForm() && temporaryMessage == null) {
// if (selectedPlanId != null && selectedPlanId!.isNotEmpty) { // if (selectedPlanId != null && selectedPlanId!.isNotEmpty) {
// planData['plan_id'] = selectedPlanId; // Add plan_id for update // planData['plan_id'] = selectedPlanId; // Add plan_id for update
@ -1183,24 +1189,17 @@ class CreateNewPlansState extends State<CreateNewPlan> {
// "${planData['traveller_id']}," // "${planData['traveller_id']},"
// " ${selectedPlanId}, " // " ${selectedPlanId}, "
// " "); // " ");
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const SavingLoader(),
);
// postPlanData(planData);
postPlanData(planData); await postPlanData(planData);
final currentUri = // Close loading dialog (ONLY if still mounted)
GoRouterState.of( if (mounted) Navigator.of(context, rootNavigator: true).pop();
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');
}
} }
}); });
} }
@ -1233,6 +1232,22 @@ class CreateNewPlansState extends State<CreateNewPlan> {
if (response.statusCode == 200) { if (response.statusCode == 200) {
print("Plan submitted successfully!"); print("Plan submitted successfully!");
print("Response: ${response.body}"); 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 { } else {
print("Failed to submit plan. Status: ${response.statusCode}"); print("Failed to submit plan. Status: ${response.statusCode}");
print("Error: ${response.body}"); 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), 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( // CustomTextFieldWrapper(
// isFocused: false, // Dropdown doesn't use focus // 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 = [ List<Map<String, String>> options = [
{"title": "Self", "value": "Option 1"}, {"title": "Self", "value": "Option 1"},
{"title": "Other Employee", "value": "Option 2"}, {"title": "Other Employee", "value": "Option 2"},
{"title": "Others", "value": "Option 3"}, {"title": "Others (Non Employee)", "value": "Option 3"},
]; ];
return [ return [

View File

@ -33,25 +33,26 @@ class DynamicItinerary extends StatefulWidget {
final List<dynamic>? apiCountryData; final List<dynamic>? apiCountryData;
final String? loginUser; final String? loginUser;
final Function(String, List<Map<String, dynamic>>) final Function(String, List<Map<String, dynamic>>)
onItineraryUpdate; // Updated Signature onItineraryUpdate; // Updated Signature
final Map<String, dynamic> selectedPlanData; final Map<String, dynamic> selectedPlanData;
final bool isViewMode; final bool isViewMode;
final GlobalKey<FlightScreenState> flightScreenKey; final GlobalKey<FlightScreenState> flightScreenKey;
final ValueNotifier<String?> tripTypeNotifier; final ValueNotifier<String?> tripTypeNotifier;
const DynamicItinerary( const DynamicItinerary({
{super.key, super.key,
required this.apiData, required this.apiData,
required this.onItineraryUpdate, required this.onItineraryUpdate,
required this.apiCountryData, required this.apiCountryData,
required this.loginUser, required this.loginUser,
required this.selectedPlanData, required this.selectedPlanData,
required this.isViewMode, required this.isViewMode,
required this.hasAction, required this.hasAction,
this.tripType, this.tripType,
this.apiDataForClass, this.apiDataForClass,
required this.tripTypeNotifier, required this.tripTypeNotifier,
required this.flightScreenKey}); required this.flightScreenKey,
});
@override @override
DynamicItineraryState createState() => DynamicItineraryState(); DynamicItineraryState createState() => DynamicItineraryState();
@ -135,9 +136,10 @@ class DynamicItineraryState extends State<DynamicItinerary> {
if (rawServices != null && rawServices is String) { if (rawServices != null && rawServices is String) {
try { try {
List<dynamic> decoded = json.decode(rawServices); List<dynamic> decoded = json.decode(rawServices);
List<Map<String, String>> formatted = decoded List<Map<String, String>> formatted =
.map((e) => {"service_id": e['service_id'].toString()}) decoded
.toList(); .map((e) => {"service_id": e['service_id'].toString()})
.toList();
setState(() { setState(() {
selectedOrgServiceIds = formatted; selectedOrgServiceIds = formatted;
@ -168,7 +170,7 @@ class DynamicItineraryState extends State<DynamicItinerary> {
"insurance", "insurance",
"visa", "visa",
"miscellaneous", "miscellaneous",
"taxi" "taxi",
]; ];
} else { } else {
// tripType is null or not 1/2, allow everything // tripType is null or not 1/2, allow everything
@ -242,23 +244,27 @@ class DynamicItineraryState extends State<DynamicItinerary> {
final selectedIds = final selectedIds =
selectedOrgServiceIds.map((e) => e['service_id']).toSet(); selectedOrgServiceIds.map((e) => e['service_id']).toSet();
final additionalServices = selectedAllServices!.where((service) { final additionalServices =
final name = (service['name'] ?? "").toString().toLowerCase(); selectedAllServices!.where((service) {
final id = service['service_id'].toString(); final name = (service['name'] ?? "").toString().toLowerCase();
final isNameAllowed = final id = service['service_id'].toString();
allowedServiceNames.isEmpty || allowedServiceNames.contains(name); final isNameAllowed =
return filledItineraryKeys.contains(name) && allowedServiceNames.isEmpty ||
!selectedIds.contains(id) && allowedServiceNames.contains(name);
isNameAllowed; return filledItineraryKeys.contains(name) &&
}).toList(); !selectedIds.contains(id) &&
isNameAllowed;
}).toList();
final originalFiltered = selectedAllServices!.where((service) { final originalFiltered =
final name = (service['name'] ?? "").toString().toLowerCase(); selectedAllServices!.where((service) {
final id = service['service_id'].toString(); final name = (service['name'] ?? "").toString().toLowerCase();
final isNameAllowed = final id = service['service_id'].toString();
allowedServiceNames.isEmpty || allowedServiceNames.contains(name); final isNameAllowed =
return selectedIds.contains(id) && isNameAllowed; allowedServiceNames.isEmpty ||
}).toList(); allowedServiceNames.contains(name);
return selectedIds.contains(id) && isNameAllowed;
}).toList();
setState(() { setState(() {
ServicesChoosed = [...originalFiltered, ...additionalServices] ServicesChoosed = [...originalFiltered, ...additionalServices]
@ -266,22 +272,26 @@ class DynamicItineraryState extends State<DynamicItinerary> {
}); });
print( print(
"Services chosen based on filled keys + selected: $ServicesChoosed"); "Services chosen based on filled keys + selected: $ServicesChoosed",
);
} else { } else {
final selectedIds = final selectedIds =
selectedOrgServiceIds.map((e) => e['service_id']).toSet(); selectedOrgServiceIds.map((e) => e['service_id']).toSet();
final filtered = selectedAllServices!.where((service) { final filtered =
final name = (service['name'] ?? "").toString().toLowerCase(); selectedAllServices!.where((service) {
final isNameAllowed = final name = (service['name'] ?? "").toString().toLowerCase();
allowedServiceNames.isEmpty || allowedServiceNames.contains(name); final isNameAllowed =
return selectedIds.contains(service['service_id'].toString()) && allowedServiceNames.isEmpty ||
isNameAllowed; allowedServiceNames.contains(name);
}).toList(); return selectedIds.contains(service['service_id'].toString()) &&
isNameAllowed;
}).toList();
setState(() { setState(() {
ServicesChoosed = filtered ServicesChoosed =
..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0)); filtered
..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0));
}); });
print("Filtered Selected Services Chosen: $ServicesChoosed"); print("Filtered Selected Services Chosen: $ServicesChoosed");
@ -296,23 +306,32 @@ class DynamicItineraryState extends State<DynamicItinerary> {
setState(() { setState(() {
itineraryData = { itineraryData = {
"Train": List<Map<String, dynamic>>.from( "Train": List<Map<String, dynamic>>.from(
widget.selectedPlanData['train'] ?? []), widget.selectedPlanData['train'] ?? [],
),
"Bus": List<Map<String, dynamic>>.from( "Bus": List<Map<String, dynamic>>.from(
widget.selectedPlanData['bus'] ?? []), widget.selectedPlanData['bus'] ?? [],
),
"Taxi": List<Map<String, dynamic>>.from( "Taxi": List<Map<String, dynamic>>.from(
widget.selectedPlanData['taxi'] ?? []), widget.selectedPlanData['taxi'] ?? [],
),
"Miscellaneous": List<Map<String, dynamic>>.from( "Miscellaneous": List<Map<String, dynamic>>.from(
widget.selectedPlanData['miscellaneous'] ?? []), widget.selectedPlanData['miscellaneous'] ?? [],
),
"Flight": List<Map<String, dynamic>>.from( "Flight": List<Map<String, dynamic>>.from(
widget.selectedPlanData['flight'] ?? []), widget.selectedPlanData['flight'] ?? [],
),
"Accomodation": List<Map<String, dynamic>>.from( "Accomodation": List<Map<String, dynamic>>.from(
widget.selectedPlanData['accomodation'] ?? []), widget.selectedPlanData['accomodation'] ?? [],
),
"Insurance": List<Map<String, dynamic>>.from( "Insurance": List<Map<String, dynamic>>.from(
widget.selectedPlanData['insurance'] ?? []), widget.selectedPlanData['insurance'] ?? [],
),
"Visa": List<Map<String, dynamic>>.from( "Visa": List<Map<String, dynamic>>.from(
widget.selectedPlanData['visa'] ?? []), widget.selectedPlanData['visa'] ?? [],
),
"Forex": List<Map<String, dynamic>>.from( "Forex": List<Map<String, dynamic>>.from(
widget.selectedPlanData['forex'] ?? []), widget.selectedPlanData['forex'] ?? [],
),
}; };
}); });
} else { } else {
@ -330,7 +349,7 @@ class DynamicItineraryState extends State<DynamicItinerary> {
"accomodation", "accomodation",
"insurance", "insurance",
"visa", "visa",
"forex" "forex",
]; ];
// for (String key in keys) { // for (String key in keys) {
@ -416,7 +435,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
if (existingId != null && existingId != 0) { if (existingId != null && existingId != 0) {
// int itemId = itemList.indexWhere((item) => item["id"] == existingId); // int itemId = itemList.indexWhere((item) => item["id"] == existingId);
int itemId = itemList.indexWhere( int itemId = itemList.indexWhere(
(item) => item[idKey]?.toString() == existingId.toString()); (item) => item[idKey]?.toString() == existingId.toString(),
);
if (itemId != -1) { if (itemId != -1) {
print(" Updating existing item with id: $existingId"); print(" Updating existing item with id: $existingId");
@ -428,8 +448,9 @@ class DynamicItineraryState extends State<DynamicItinerary> {
// CASE 1: Update if indx exists in list // CASE 1: Update if indx exists in list
if (existingIndex != null && existingIndex != 0) { if (existingIndex != null && existingIndex != 0) {
int itemIndex = int itemIndex = itemList.indexWhere(
itemList.indexWhere((item) => item["indx"] == existingIndex); (item) => item["indx"] == existingIndex,
);
if (itemIndex != -1) { if (itemIndex != -1) {
print("Updating existing item with indx: $existingIndex"); print("Updating existing item with indx: $existingIndex");
newData["is_active"] = "1"; newData["is_active"] = "1";
@ -494,6 +515,7 @@ class DynamicItineraryState extends State<DynamicItinerary> {
}); });
print(" onItineraryUpdate - $type - ${itineraryData[type]!} "); print(" onItineraryUpdate - $type - ${itineraryData[type]!} ");
widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent
print("ItienreayDATE - $itineraryData");
} }
// void handleItineraryUpdate(String type, Map<String, dynamic> newData) { // void handleItineraryUpdate(String type, Map<String, dynamic> newData) {
@ -583,8 +605,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
onOpen: handleEdit, onOpen: handleEdit,
onAddNew: handlecreateNewPlan, onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode, isViewMode: widget.isViewMode,
onDeleteAccommodation: (data) => onDeleteAccommodation:
handleItinerarydelete("Accomodation", data), (data) => handleItinerarydelete("Accomodation", data),
); );
break; break;
case "Miscellaneous": case "Miscellaneous":
@ -594,50 +616,54 @@ class DynamicItineraryState extends State<DynamicItinerary> {
isViewMode: widget.isViewMode, isViewMode: widget.isViewMode,
onAddNew: handlecreateNewPlan, onAddNew: handlecreateNewPlan,
apiData: widget.apiData, apiData: widget.apiData,
onDeleteMiscellaneous: (data) => onDeleteMiscellaneous:
handleItinerarydelete("Miscellaneous", data), (data) => handleItinerarydelete("Miscellaneous", data),
); );
break; break;
case "Flight": case "Flight":
default: default:
selectedListWidget = FlightListWidget( selectedListWidget = FlightListWidget(
hasAction: widget.hasAction, hasAction: widget.hasAction,
tripType: widget.tripType, tripType: widget.tripType,
flightList: itineraryData["Flight"]!, flightList: itineraryData["Flight"]!,
onOpen: handleEdit, onOpen: handleEdit,
onAddNew: handlecreateNewPlan, onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode, isViewMode: widget.isViewMode,
apiData: widget.apiData, apiData: widget.apiData,
onDeleteFlight: (data) => handleItinerarydelete("Flight", data)); onDeleteFlight: (data) => handleItinerarydelete("Flight", data),
);
break; break;
} }
switch (selectedOption) { switch (selectedOption) {
case "Train": case "Train":
selectedWidget = TrainScreen( selectedWidget = TrainScreen(
onClose: handleClose, onClose: handleClose,
apiData: widget.apiData, apiData: widget.apiData,
apiDataForClass: widget.apiDataForClass, apiDataForClass: widget.apiDataForClass,
loginUser: widget.loginUser, loginUser: widget.loginUser,
onSavetrain: (data) => handleItineraryUpdate("Train", data), onSavetrain: (data) => handleItineraryUpdate("Train", data),
tripType: widget.tripType, tripType: widget.tripType,
selectedItem: selectedItem); selectedItem: selectedItem,
);
break; break;
case "Taxi": case "Taxi":
selectedWidget = TaxiScreen( selectedWidget = TaxiScreen(
onClose: handleClose, onClose: handleClose,
apiData: widget.apiData, apiData: widget.apiData,
loginUser: widget.loginUser, loginUser: widget.loginUser,
onSavetaxi: (data) => handleItineraryUpdate("Taxi", data), onSavetaxi: (data) => handleItineraryUpdate("Taxi", data),
selectedItem: selectedItem); selectedItem: selectedItem,
);
break; break;
case "Bus": case "Bus":
selectedWidget = BusScreen( selectedWidget = BusScreen(
onClose: handleClose, onClose: handleClose,
apiData: widget.apiData, apiData: widget.apiData,
loginUser: widget.loginUser, loginUser: widget.loginUser,
onSaveBus: (data) => handleItineraryUpdate("Bus", data), onSaveBus: (data) => handleItineraryUpdate("Bus", data),
selectedItem: selectedItem); selectedItem: selectedItem,
);
break; break;
case "Insurance": case "Insurance":
selectedWidget = InsuranceScreen( selectedWidget = InsuranceScreen(
@ -666,8 +692,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
onClose: handleClose, onClose: handleClose,
apiData: widget.apiData, apiData: widget.apiData,
loginUser: widget.loginUser, loginUser: widget.loginUser,
onSaveMiscellaneous: (data) => onSaveMiscellaneous:
handleItineraryUpdate("Miscellaneous", data), (data) => handleItineraryUpdate("Miscellaneous", data),
selectedItem: selectedItem, selectedItem: selectedItem,
selectedIndex: selectedIndex, selectedIndex: selectedIndex,
); );
@ -676,8 +702,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
selectedWidget = AccomodationScreen( selectedWidget = AccomodationScreen(
onClose: handleClose, onClose: handleClose,
loginUser: widget.loginUser, loginUser: widget.loginUser,
onSaveAccomadation: (data) => onSaveAccomadation:
handleItineraryUpdate("Accomodation", data), (data) => handleItineraryUpdate("Accomodation", data),
selectedItem: selectedItem, selectedItem: selectedItem,
flightData: itineraryData["Flight"]!, flightData: itineraryData["Flight"]!,
); );
@ -776,85 +802,106 @@ class DynamicItineraryState extends State<DynamicItinerary> {
// ); // );
// }); // });
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
bool isMobile = sizingInfo.isMobile; builder: (context, sizingInfo) {
bool isMobile = sizingInfo.isMobile;
return Stack( return Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
// Second container (yellow box) // Second container (yellow box)
Container( Container(
margin: EdgeInsets.only( margin: EdgeInsets.only(
top: 40), // Push it down to make room for the tab bar top: 40,
padding: EdgeInsets.all(12), ), // Push it down to make room for the tab bar
decoration: BoxDecoration( padding: EdgeInsets.all(12),
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),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, // Card background color: Colors.white, // Card background
borderRadius: BorderRadius.circular(8), // color: Colors.yellow.shade50, // Card background
// color: Color(0xFFF9F9F9), // Slightly lighter than white
// color: Color(0xFFE6E7F5) // border: Border.all(color: Color(0xFFE6E7F5), width: 1.3),
// border: Border.all(color: Colors.black12, width: 1.3),
border: Border.all(color: Color(0xFFE6E7F5), width: 1.2), border: Border.all(color: Color(0xFFE6E7F5), width: 1.2),
borderRadius: BorderRadius.circular(8),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black12,
// color: Color(0x0D000000), // 5% opacity black // color: Color(0x0D000000), // 5% opacity black
blurRadius: 10, color: Colors.black12, // 5% opacity black
blurRadius: 5,
offset: Offset(0, 0.2), offset: Offset(0, 0.2),
), ),
], ],
), ),
child: isMobile child: Column(
? SingleChildScrollView( crossAxisAlignment: CrossAxisAlignment.stretch,
scrollDirection: Axis.horizontal, children: [
child: Row( SizedBox(height: 2),
children: _buildOptions(), isSelected ? selectedWidget : selectedListWidget,
), ],
) ),
: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: _buildOptions(),
),
), ),
),
], // 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() { List<Widget> _buildOptions() {
if (ServicesChoosed == null) return []; if (ServicesChoosed == null && !hasValidItineraryEntries()) return [];
if (ServicesChoosed != null && if (ServicesChoosed != null &&
ServicesChoosed!.isNotEmpty && ServicesChoosed!.isNotEmpty &&
@ -875,18 +922,28 @@ class DynamicItineraryState extends State<DynamicItinerary> {
// } // }
return ServicesChoosed!.map((service) { 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( return Padding(
padding: const EdgeInsets.only(right: 20.0), padding: const EdgeInsets.only(right: 20.0),
child: _buildOption( child: _buildOption(
service, itineraryData[service['name']]?.isNotEmpty ?? false), service,
hasActive,
// itineraryData[service['name']]?.isNotEmpty ?? false,
),
); );
}).toList(); }).toList();
} }
Widget _buildOption( Widget _buildOption(Map<String, dynamic> service, bool hasData) {
Map<String, dynamic> service,
bool hasData,
) {
String name = service['name']; String name = service['name'];
String iconUrl = service['icon']; // Can be empty string String iconUrl = service['icon']; // Can be empty string
IconData fallbackIcon = _getLocalIconForService(name); IconData fallbackIcon = _getLocalIconForService(name);
@ -914,26 +971,26 @@ class DynamicItineraryState extends State<DynamicItinerary> {
children: [ children: [
iconUrl.isNotEmpty iconUrl.isNotEmpty
? Image.network( ? Image.network(
iconUrl, iconUrl,
width: 18, width: 18,
height: 18, height: 18,
errorBuilder: (context, error, stackTrace) { errorBuilder: (context, error, stackTrace) {
return Icon( return Icon(
fallbackIcon, fallbackIcon,
size: 25, size: 25,
color: isOptionSelected color:
? Color(0xFF114D8B) isOptionSelected
: Color(0xFF475569), ? Color(0xFF114D8B)
); : Color(0xFF475569),
}, );
) },
)
: Icon( : Icon(
fallbackIcon, fallbackIcon,
size: 25, size: 25,
color: isOptionSelected color:
? Color(0xFF114D8B) isOptionSelected ? Color(0xFF114D8B) : Color(0xFF475569),
: Color(0xFF475569), ),
),
SizedBox(height: 2), SizedBox(height: 2),
Row( Row(
children: [ children: [
@ -943,12 +1000,12 @@ class DynamicItineraryState extends State<DynamicItinerary> {
// style: GoogleFonts.poppins( fontSize: 12, // style: GoogleFonts.poppins( fontSize: 12,
// fontWeight: FontWeight.w600, // fontWeight: FontWeight.w600,
// color: Color(0xFF575A74)) // color: Color(0xFF575A74))
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: isOptionSelected color:
? Color(0xFF114D8B) isOptionSelected
: Color(0xFF475569), ? Color(0xFF114D8B)
: Color(0xFF475569),
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: fontWeight:
isOptionSelected ? FontWeight.bold : FontWeight.w500, isOptionSelected ? FontWeight.bold : FontWeight.w500,
@ -964,86 +1021,87 @@ class DynamicItineraryState extends State<DynamicItinerary> {
); );
} }
Widget _buildOption1( // Widget _buildOption1(
Map<String, dynamic> service, // Map<String, dynamic> service,
bool hasData, // bool hasData,
) { // )
String name = service['name']; // {
String iconUrl = service['icon']; // Can be empty string // String name = service['name'];
// Optional: define local icon fallback if iconUrl is empty // String iconUrl = service['icon']; // Can be empty string
IconData fallbackIcon = _getLocalIconForService(name); // // Optional: define local icon fallback if iconUrl is empty
// final idMap = {"service_id": service['service_id'].toString()}; // IconData fallbackIcon = _getLocalIconForService(name);
// final isSelected = selectedServiceIds.contains(idMap); // // final idMap = {"service_id": service['service_id'].toString()};
// // final isSelected = selectedServiceIds.contains(idMap);
String serviceId = service['service_id'].toString(); //
// bool isSelected = selectedServiceIds.contains(serviceId); // String serviceId = service['service_id'].toString();
// bool isSelected = // // bool isSelected = selectedServiceIds.contains(serviceId);
// selectedServiceIds.any((item) => item["service_id"] == serviceId); // // bool isSelected =
// // selectedServiceIds.any((item) => item["service_id"] == serviceId);
return GestureDetector( //
onTap: () { // return GestureDetector(
setState(() { // onTap: () {
selectedListOption = name; // setState(() {
isSelected = false; // selectedListOption = name;
}); // isSelected = false;
}, // });
child: Row(children: [ // },
iconUrl.isNotEmpty // child: Row(children: [
? Image.network( // iconUrl.isNotEmpty
iconUrl, // ? Image.network(
width: 18, // iconUrl,
height: 18, // width: 18,
errorBuilder: (context, error, stackTrace) { // height: 18,
return Icon( // errorBuilder: (context, error, stackTrace) {
fallbackIcon, // return Icon(
size: 18, // fallbackIcon,
color: selectedListOption == name // size: 18,
? Color(0xFF114D8B) // color: selectedListOption == name
: Color(0xFF475569), // ? Color(0xFF114D8B)
); // : Color(0xFF475569),
}, // );
) // },
: Icon( // )
fallbackIcon, // : Icon(
size: 18, // fallbackIcon,
color: selectedListOption == name // size: 18,
? Color(0xFF114D8B) // color: selectedListOption == name
: Color(0xFF475569), // ? Color(0xFF114D8B)
), // : Color(0xFF475569),
// ),
SizedBox(width: 2), //
Text( // SizedBox(width: 2),
name, // Text(
style: TextStyle( // name,
fontSize: 14, // style: TextStyle(
// color: selectedListOption == title ? Colors.blueAccent : Color(0xFF575A74), // fontSize: 14,
color: selectedListOption == name // // color: selectedListOption == title ? Colors.blueAccent : Color(0xFF575A74),
? Color(0xFF114D8B) // color: selectedListOption == name
: Color(0xFF475569), // ? Color(0xFF114D8B)
fontFamily: "Archivo", // : Color(0xFF475569),
fontWeight: selectedListOption == name // fontFamily: "Archivo",
? FontWeight.bold // fontWeight: selectedListOption == name
: FontWeight.w500), // ? FontWeight.bold
// fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)), // : FontWeight.w500),
), // // fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)),
// ),
SizedBox(width: 2), //
// if (selectedListOption == title && widget.isViewMode == false) // SizedBox(width: 2),
if (hasData) // // if (selectedListOption == title && widget.isViewMode == false)
Icon(Icons.circle, size: 8, color: Colors.green // if (hasData)
// color: Colors.grey, // Icon(Icons.circle, size: 8, color: Colors.green
) // // color: Colors.grey,
// Container( // )
// height: 10, // // Container(
// width: 10, // // height: 10,
// // decoration: BoxDecoration( // // width: 10,
// // shape: BoxShape.circle, // // // decoration: BoxDecoration(
// // border: Border.all(color: Colors.green, width: 1.5), // // // shape: BoxShape.circle,
// // ), // // // border: Border.all(color: Colors.green, width: 1.5),
// child:), // // // ),
]), // // child:),
); // ]),
} // );
// }
IconData _getLocalIconForService(String name) { IconData _getLocalIconForService(String name) {
switch (name.toLowerCase()) { 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/auth_utils.dart';
import '../../utils/pagination.dart'; import '../../utils/pagination.dart';
class PolicyList extends StatefulWidget { class PolicyList extends StatefulWidget {
@override @override
_PolicyListState createState() => _PolicyListState(); _PolicyListState createState() => _PolicyListState();
} }
class _PolicyListState extends State<PolicyList> { class _PolicyListState extends State<PolicyList> {
final ApiService apiService = ApiService(); final ApiService apiService = ApiService();
late Future<List<dynamic>> futurePolicy; late Future<List<dynamic>> futurePolicy;
@ -37,7 +35,6 @@ class _PolicyListState extends State<PolicyList> {
List filteredPolicy = []; List filteredPolicy = [];
TextEditingController searchController = TextEditingController(); TextEditingController searchController = TextEditingController();
int currentPage = 0; int currentPage = 0;
int itemsPerPage = 10; int itemsPerPage = 10;
@ -68,14 +65,14 @@ class _PolicyListState extends State<PolicyList> {
setState(() { setState(() {
layoutColor = layoutColor =
layoutString != null layoutString != null
? Color(int.parse(layoutString)) ? Color(int.parse(layoutString))
: Colors.redAccent; : Colors.redAccent;
bodyColor = bodyColor =
bodyStringColor != null bodyStringColor != null
? Color(int.parse(bodyStringColor)) ? Color(int.parse(bodyStringColor))
: Colors.white; : Colors.white;
}); });
} }
@ -84,44 +81,49 @@ class _PolicyListState extends State<PolicyList> {
return prefs.getString('auth_token'); return prefs.getString('auth_token');
} }
Future<List<dynamic>> fetchPolicy() async { Future<List<dynamic>> fetchPolicy() async {
final data = await apiService.fetchAllPolicy(); final data = await apiService.fetchAllPolicy();
return data; // Returning raw JSON list return data; // Returning raw JSON list
} }
// Refresh user list after update // Refresh user list after update
void refreshPolicyList() { void refreshPolicyList() {
setState(() { setState(() {
futurePolicy = fetchPolicy(); // Re-fetch users after status update futurePolicy = fetchPolicy(); // Re-fetch users after status update
futurePolicy.then((object) {
setState(() {
allPolicy = object;
});
});
// Wait for futurePlans to be fetched and update allPlans // Wait for futurePlans to be fetched and update allPlans
}); });
} }
void filterPolicy(String query) { void filterPolicy(String query) {
print("allPolicy before filtering: $query");
final lowerQuery = query.toLowerCase(); final lowerQuery = query.toLowerCase();
setState(() { setState(() {
filteredPolicy = filteredPolicy =
allPolicy.where((object) { allPolicy.where((object) {
return (object['name']?.toLowerCase().contains(lowerQuery) ?? final isActiveStatus =
false) || 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) ?? (object['policy_type']?.toLowerCase().contains(lowerQuery) ??
false) || false) ||
(object['is_active']?.toLowerCase().contains(lowerQuery) ?? false);}).toList(); (isActiveStatus.contains(lowerQuery));
}).toList();
currentPage = 0; currentPage = 0;
}); });
print("filtered: $filteredPolicy"); print("filteredPolicy: $filteredPolicy");
} }
void handleActiveStatus( void handleActiveStatus(
Map<String, dynamic> policyData, Map<String, dynamic> policyData,
String policyId, String policyId,
String currentStatus, String currentStatus,
) async { ) async {
print("Toggling user status - $policyId (Current: $currentStatus)"); print("Toggling user status - $policyId (Current: $currentStatus)");
final String apiUrlData = final String apiUrlData =
@ -159,7 +161,6 @@ class _PolicyListState extends State<PolicyList> {
if (response.statusCode == 200) { if (response.statusCode == 200) {
print("policyData submitted successfully!"); print("policyData submitted successfully!");
print("Response: ${response.body}"); print("Response: ${response.body}");
loadAllGroups(); loadAllGroups();
} else { } else {
print("Failed to submit policyData. Status: ${response.statusCode}"); print("Failed to submit policyData. Status: ${response.statusCode}");
@ -175,31 +176,45 @@ class _PolicyListState extends State<PolicyList> {
print("policystatus: $status"); print("policystatus: $status");
print("policysData: $policydata"); print("policysData: $policydata");
// handleActiveStatus(groupdata, groupId, status);
print("Calling handleActiveStatus with: id=$policyId, status=$status");
handleActiveStatus(policydata, policyId.toString(), status.toString()); handleActiveStatus(policydata, policyId.toString(), status.toString());
} }
Future<void> refreshData() async {
loadAllGroups();
}
Future<void> loadAllGroups() async { Future<void> loadAllGroups() async {
try { try {
final result = await apiService.fetchAllPolicy(); 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(() { setState(() {
futurePolicy = result as Future<List>; allPolicy = result;
}); });
print("Fetched services: $futurePolicy"); print("Fetched services: $allPolicy");
} catch (e) { } catch (e) {
print('Error fetching role list: $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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -216,16 +231,16 @@ class _PolicyListState extends State<PolicyList> {
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: padding:
isDesktop isDesktop
? EdgeInsets.symmetric( ? EdgeInsets.symmetric(
horizontal: horizontal:
MediaQuery.of(context).size.width * MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding 0.1, // 30% of screen width as horizontal padding
vertical: vertical:
MediaQuery.of(context).size.height * MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding 0, // 5% of screen height as vertical padding
) )
: EdgeInsets.all(0), : EdgeInsets.all(0),
child: Row( child: Row(
children: [ children: [
// if (isDesktop) CustomDrawer(isDesktop: true), // 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), // : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
// padding: const EdgeInsets.all(10), // padding: const EdgeInsets.all(10),
height: height:
isDesktop isDesktop
? MediaQuery.of(context).size.height * 0.98 ? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height, : MediaQuery.of(context).size.height,
child: Padding( child: Padding(
padding: const EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
@ -362,11 +377,10 @@ class _PolicyListState extends State<PolicyList> {
// Print the resolved value // Print the resolved value
print("CREATELIAS - $policyData"); print("CREATELIAS - $policyData");
context.go('/Policy'); context.go('/Policy');
}, },
child: Row( child: Row(
mainAxisSize: mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely MainAxisSize.min, // Ensures content fits nicely
children: [ children: [
Text( Text(
"Add New Policy", "Add New Policy",
@ -390,49 +404,49 @@ class _PolicyListState extends State<PolicyList> {
isDesktop isDesktop
? SizedBox.shrink() ? SizedBox.shrink()
: Row( : Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Container( Container(
width: MediaQuery.of(context).size.width * 0.8, width: MediaQuery.of(context).size.width * 0.8,
height: 35, height: 35,
child: TextField( child: TextField(
controller: searchController, controller: searchController,
onChanged: filterPolicy, onChanged: filterPolicy,
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search for a Policy", hintText: "Search for a Policy",
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 12, fontSize: 12,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
), ),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
size: 18, size: 18,
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade200, color: Colors.grey.shade200,
width: 0.5, width: 0.5,
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade300, color: Colors.grey.shade300,
width: 1, width: 1,
),
),
), ),
style: GoogleFonts.poppins(fontSize: 12),
), ),
), ),
style: GoogleFonts.poppins(fontSize: 12), // SizedBox(width: 16),
), ],
), ),
// SizedBox(width: 16),
],
),
const SizedBox(height: 10), const SizedBox(height: 10),
FutureBuilder<List<dynamic>>( FutureBuilder<List<dynamic>>(
future: futurePolicy, future: futurePolicy,
@ -475,7 +489,7 @@ class _PolicyListState extends State<PolicyList> {
} }
List<dynamic> policyData = List<dynamic> policyData =
filteredPolicy.isNotEmpty ? filteredPolicy : allPolicy; filteredPolicy.isNotEmpty ? filteredPolicy : allPolicy;
policyData.sort((a, b) { policyData.sort((a, b) {
DateTime dateA = DateTime.parse(a['created_on']); DateTime dateA = DateTime.parse(a['created_on']);
@ -485,10 +499,10 @@ class _PolicyListState extends State<PolicyList> {
}); });
List paginatedUser = List paginatedUser =
policyData policyData
.skip(currentPage * itemsPerPage) .skip(currentPage * itemsPerPage)
.take(itemsPerPage) .take(itemsPerPage)
.toList(); .toList();
Widget table = LayoutBuilder( Widget table = LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
@ -544,104 +558,119 @@ class _PolicyListState extends State<PolicyList> {
), ),
], ],
rows: rows:
paginatedUser.map((policy) { paginatedUser.map((policy) {
String policyId = String policyId =
policy['policy_id'].toString(); // Get policy ID policy['policy_id']
bool isSelected = selectedPolicyId == policyId; .toString(); // Get policy ID
bool isSelected = selectedPolicyId == policyId;
return DataRow( return DataRow(
cells: [ cells: [
DataCell( DataCell(
Text( Text(
"${policy['name'] ?? ''}", "${policy['name'] ?? ''}",
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
),
),
), ),
), DataCell(
), Text(
DataCell( policy['domestic'] == "1"
Text( ? "Domestic"
policy['domestic'] == "1" : "International",
? "Domestic" style: TextStyle(
: "International", fontSize: 13,
style: TextStyle( fontFamily: "Inter",
fontSize: 13, ),
fontFamily: "Inter", ),
), ),
), DataCell(
), Text(
DataCell(
Text(
policy['is_active'] == "1"
? "Active"
: "Inactive",
style: TextStyle(
color:
policy['is_active'] == "1" policy['is_active'] == "1"
? Colors.green ? "Active"
: Colors.grey, : "Inactive",
fontFamily: "Inter", style: TextStyle(
fontWeight: FontWeight.w400, color:
policy['is_active'] == "1"
? Colors.green
: Colors.grey,
fontFamily: "Inter",
fontWeight: FontWeight.w400,
),
), ),
), ),
), DataCell(
DataCell( Row(
Row( mainAxisAlignment:
mainAxisAlignment: MainAxisAlignment.start, MainAxisAlignment.start,
children: [ children: [
GestureDetector( GestureDetector(
onTap: () async { onTap: () async {
final rawId = policy['policy_id']; final rawId = policy['policy_id'];
final intPolicyId = final intPolicyId =
rawId is int rawId is int
? rawId ? rawId
: int.tryParse(rawId.toString()) ?? 0; : int.tryParse(
rawId.toString(),
) ??
0;
Map<String, dynamic> policyData = await apiService Map<String, dynamic> policyData =
.getSinglePolicy(intPolicyId); await apiService
.getSinglePolicy(
intPolicyId,
);
print("PolicyDATa: $policyData"); print("PolicyDATa: $policyData");
context.go("/Policy", extra: policyData); context.go(
}, "/Policy",
child: Tooltip( extra: policyData,
message: 'Edit Policy Details', );
child: Image.asset( },
'assets/images/IconsImg/edit.png', child: Tooltip(
width: 20, message: 'Edit Policy Details',
height: 15, 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']; }).toList(),
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(),
), ),
); );
}, },
@ -654,7 +683,10 @@ class _PolicyListState extends State<PolicyList> {
return Card( return Card(
color: Colors.white, color: Colors.white,
margin: EdgeInsets.symmetric(horizontal: 12, vertical: 6), margin: EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
@ -666,7 +698,8 @@ class _PolicyListState extends State<PolicyList> {
children: [ children: [
// Row 1: Policy Name and Actions // Row 1: Policy Name and Actions
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: RichText( child: RichText(
@ -684,11 +717,12 @@ class _PolicyListState extends State<PolicyList> {
), ),
), ),
TextSpan( TextSpan(
text: "${object['name'] ?? 'N/A'}", text:
"${object['name'] ?? 'N/A'}",
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: FontWeight.normal fontWeight: FontWeight.normal,
), ),
), ),
], ],
@ -701,16 +735,26 @@ class _PolicyListState extends State<PolicyList> {
GestureDetector( GestureDetector(
onTap: () async { onTap: () async {
final rawId = object['policy_id']; final rawId = object['policy_id'];
final intPolicyId = rawId is int final intPolicyId =
? rawId rawId is int
: int.tryParse(rawId.toString()) ?? 0; ? rawId
: int.tryParse(
rawId.toString(),
) ??
0;
Map<String, dynamic> policyData = Map<String, dynamic> policyData =
await apiService.getSinglePolicy(intPolicyId); await apiService
.getSinglePolicy(
intPolicyId,
);
print("PolicyDATa: $policyData"); print("PolicyDATa: $policyData");
context.go("/Policy", extra: policyData); context.go(
"/Policy",
extra: policyData,
);
}, },
child: Tooltip( child: Tooltip(
message: 'Edit Policy Details', message: 'Edit Policy Details',
@ -725,7 +769,9 @@ class _PolicyListState extends State<PolicyList> {
GestureDetector( GestureDetector(
onTap: () { onTap: () {
final idStr = object['policy_id']; final idStr = object['policy_id'];
final id = int.tryParse(idStr.toString()); final id = int.tryParse(
idStr.toString(),
);
if (id == null) { if (id == null) {
print("policy_id is null"); print("policy_id is null");
@ -750,7 +796,6 @@ class _PolicyListState extends State<PolicyList> {
), ),
SizedBox(height: 8), // Spacing SizedBox(height: 8), // Spacing
// Row 2: Policy Type // Row 2: Policy Type
RichText( RichText(
text: TextSpan( text: TextSpan(
@ -767,13 +812,14 @@ class _PolicyListState extends State<PolicyList> {
), ),
), ),
TextSpan( TextSpan(
text: object['domestic'] == "1" text:
? "Domestic" object['domestic'] == "1"
: "International", ? "Domestic"
: "International",
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: FontWeight.normal fontWeight: FontWeight.normal,
), ),
), ),
], ],
@ -787,7 +833,6 @@ class _PolicyListState extends State<PolicyList> {
); );
} }
Widget buildMobileCardView2(List<dynamic> paginatedUser) { Widget buildMobileCardView2(List<dynamic> paginatedUser) {
return ListView.builder( return ListView.builder(
itemCount: paginatedUser.length, itemCount: paginatedUser.length,
@ -810,7 +855,6 @@ class _PolicyListState extends State<PolicyList> {
children: [ children: [
Column( Column(
children: [ children: [
Expanded( Expanded(
flex: 1, flex: 1,
child: Text( child: Text(
@ -859,30 +903,41 @@ class _PolicyListState extends State<PolicyList> {
onTap: () async { onTap: () async {
final rawId = object['policy_id']; final rawId = object['policy_id'];
final intPolicyId = final intPolicyId =
rawId is int rawId is int
? rawId ? rawId
: int.tryParse(rawId.toString()) ?? 0; : int.tryParse(
rawId.toString(),
) ??
0;
Map<String, dynamic> policyData = await apiService Map<String, dynamic> policyData =
.getSinglePolicy(intPolicyId); await apiService.getSinglePolicy(
intPolicyId,
);
print("PolicyDATa: $policyData"); print("PolicyDATa: $policyData");
context.go("/Policy", extra: policyData); context.go(
"/Policy",
extra: policyData,
);
}, },
child: Tooltip( child: Tooltip(
message: 'Edit Policy Details', message: 'Edit Policy Details',
child: Image.asset( child: Image.asset(
'assets/images/IconsImg/edit.png', 'assets/images/IconsImg/edit.png',
width: 20, width: 20,
height: 15, height: 15,
),), ),
),
), ),
SizedBox(width: 5), SizedBox(width: 5),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
final idStr = object['policy_id']; final idStr = object['policy_id'];
final id = int.tryParse(idStr.toString()); final id = int.tryParse(
idStr.toString(),
);
if (id == null) { if (id == null) {
print("group_id is null"); print("group_id is null");
@ -894,11 +949,12 @@ class _PolicyListState extends State<PolicyList> {
}, },
child: Tooltip( child: Tooltip(
message: 'Delete Policy Details', message: 'Delete Policy Details',
child: Image.asset( child: Image.asset(
'assets/images/IconsImg/delete.png', 'assets/images/IconsImg/delete.png',
width: 20, width: 20,
height: 15, height: 15,
),), ),
),
), ),
], ],
), ),
@ -916,12 +972,12 @@ class _PolicyListState extends State<PolicyList> {
children: [ children: [
Expanded( Expanded(
child: child:
isDesktop isDesktop
? SingleChildScrollView( ? SingleChildScrollView(
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
child: table, // <-- your existing table child: table, // <-- your existing table
) )
: buildMobileCardView(paginatedUser), : buildMobileCardView(paginatedUser),
), ),
PaginationControls( PaginationControls(
currentPage: currentPage, 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 int? travellerId; // <-- Add this
final Map<String, dynamic>? travellerData; final Map<String, dynamic>? travellerData;
const TravellerData( const TravellerData({
{super.key, super.key,
required this.isDesktop, required this.isDesktop,
this.layoutColor, this.layoutColor,
required this.fetchGetTraveller, required this.fetchGetTraveller,
this.travellerId, this.travellerId,
this.travellerData}); this.travellerData,
});
@override @override
TravellerDataState createState() => TravellerDataState(); TravellerDataState createState() => TravellerDataState();
@ -49,12 +50,7 @@ class TravellerDataState extends State<TravellerData> {
int? travellerDataId; int? travellerDataId;
late String isActive = "1"; late String isActive = "1";
List<String> dataHeader = [ List<String> dataHeader = ["first_name", "last_name", "email", "mobile"];
"first_name",
"last_name",
"email",
"mobile",
];
Map<String, dynamic> travellerDetails() { Map<String, dynamic> travellerDetails() {
final data = { final data = {
@ -72,7 +68,6 @@ class TravellerDataState extends State<TravellerData> {
void initState() { void initState() {
super.initState(); super.initState();
apiData = null; apiData = null;
for (var field in dataHeader) { for (var field in dataHeader) {
controllers[field] = TextEditingController(); controllers[field] = TextEditingController();
@ -115,7 +110,6 @@ class TravellerDataState extends State<TravellerData> {
}); });
} }
void toggleStatus() { void toggleStatus() {
setState(() { setState(() {
isActive = isActive == "1" ? "0" : "1"; isActive = isActive == "1" ? "0" : "1";
@ -132,7 +126,7 @@ class TravellerDataState extends State<TravellerData> {
"mobile": controllers["mobile"]?.text, "mobile": controllers["mobile"]?.text,
}; };
final requiredFields = ["first_name","last_name","email","mobile"]; final requiredFields = ["first_name", "last_name", "email", "mobile"];
bool hasFocused = false; bool hasFocused = false;
// Check validation for each field // Check validation for each field
@ -150,13 +144,14 @@ class TravellerDataState extends State<TravellerData> {
if (data["mobile"] != null && data["mobile"].toString().isNotEmpty) { if (data["mobile"] != null && data["mobile"].toString().isNotEmpty) {
if (!RegExp(r"^\d{10}$").hasMatch(data["mobile"].toString())) { if (!RegExp(r"^\d{10}$").hasMatch(data["mobile"].toString())) {
errorMessages["mobile"] = 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 (data["email"] != null && data["email"].toString().isNotEmpty) {
if (!RegExp(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") if (!RegExp(
.hasMatch(data["email"].toString())) { 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 errorMessages["email"] = "Invalid email format"; // Invalid email format
} }
} }
@ -195,7 +190,9 @@ class TravellerDataState extends State<TravellerData> {
apiUrldata = '$apiUrl/api/travellers/update/$travellerDataId'; apiUrldata = '$apiUrl/api/travellers/update/$travellerDataId';
travellerData["traveller_id"] = travellerDataId.toString(); travellerData["traveller_id"] = travellerDataId.toString();
travellerData["updated_by"] = userId; travellerData["updated_by"] = userId;
(travellerData.containsKey("created_by")) ? travellerData.remove("created_by") : '' ; (travellerData.containsKey("created_by"))
? travellerData.remove("created_by")
: '';
} else { } else {
print("for add Traveller id - null"); print("for add Traveller id - null");
apiUrldata = '$apiUrl/api/travellers/create'; apiUrldata = '$apiUrl/api/travellers/create';
@ -217,10 +214,10 @@ class TravellerDataState extends State<TravellerData> {
}; };
final body = jsonEncode(travellerData); final body = jsonEncode(travellerData);
final response = travellerDataId != null final response =
? await http.put(uri, headers: headers, body: body) travellerDataId != null
: await http.post(uri, headers: headers, body: body); ? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body);
switch (response.statusCode) { switch (response.statusCode) {
case 200: case 200:
@ -241,7 +238,6 @@ class TravellerDataState extends State<TravellerData> {
print("Failed to submit traveller. Status: ${response.statusCode}"); print("Failed to submit traveller. Status: ${response.statusCode}");
print("Error: ${response.body}"); print("Error: ${response.body}");
} }
} catch (e) { } catch (e) {
print(" Error submitting plan: $e"); print(" Error submitting plan: $e");
} }
@ -249,7 +245,6 @@ class TravellerDataState extends State<TravellerData> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30), contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30),
@ -264,27 +259,30 @@ class TravellerDataState extends State<TravellerData> {
Row( Row(
children: [ children: [
Text( Text(
(travellerDataId != null) ? 'Edit Traveller' : 'Create Traveller', (travellerDataId != null)
style: GoogleFonts.poppins(fontSize: 15, color: Colors.black), ? 'Edit Traveller'
: 'Create Traveller',
style: GoogleFonts.poppins(
fontSize: 15,
color: Colors.black,
),
), ),
const Spacer(), const Spacer(),
], ],
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Divider( Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
thickness: 0.2,
color: Colors.blueGrey.shade100,
),
const SizedBox(height: 5), const SizedBox(height: 5),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"First Name", "First Name *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -292,19 +290,23 @@ class TravellerDataState extends State<TravellerData> {
isDesktop: widget.isDesktop, isDesktop: widget.isDesktop,
color: Colors.transparent, color: Colors.transparent,
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: TextField( child: TextField(
controller: controllers["first_name"], controller: controllers["first_name"],
focusNode: focusNodes["first_name"], focusNode: focusNodes["first_name"],
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: "First Name", labelText: "First Name",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey), labelStyle: TextStyle(
floatingLabelBehavior: FloatingLabelBehavior.never, fontSize: 11,
border: InputBorder.none, color: Colors.grey,
contentPadding: EdgeInsets.symmetric(vertical: 16),
), ),
)), floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
), ),
if (errorMessages["first_name"] != null) ...[ if (errorMessages["first_name"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
@ -315,18 +317,17 @@ class TravellerDataState extends State<TravellerData> {
], ],
], ],
), ),
SizedBox( SizedBox(height: 15),
height: 15,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Last Name", "Last Name *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -334,19 +335,23 @@ class TravellerDataState extends State<TravellerData> {
isDesktop: widget.isDesktop, isDesktop: widget.isDesktop,
color: Colors.transparent, color: Colors.transparent,
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: TextField( child: TextField(
controller: controllers["last_name"], controller: controllers["last_name"],
focusNode: focusNodes["last_name"], focusNode: focusNodes["last_name"],
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: "Last Name", labelText: "Last Name",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey), labelStyle: TextStyle(
floatingLabelBehavior: FloatingLabelBehavior.never, fontSize: 11,
border: InputBorder.none, color: Colors.grey,
contentPadding: EdgeInsets.symmetric(vertical: 16),
), ),
)), floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
), ),
if (errorMessages["last_name"] != null) ...[ if (errorMessages["last_name"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
@ -357,18 +362,17 @@ class TravellerDataState extends State<TravellerData> {
], ],
], ],
), ),
SizedBox( SizedBox(height: 15),
height: 15,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Email", "Email *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -376,19 +380,23 @@ class TravellerDataState extends State<TravellerData> {
isDesktop: widget.isDesktop, isDesktop: widget.isDesktop,
color: Colors.transparent, color: Colors.transparent,
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: TextField( child: TextField(
controller: controllers["email"], controller: controllers["email"],
focusNode: focusNodes["email"], focusNode: focusNodes["email"],
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: "Email", labelText: "Email",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey), labelStyle: TextStyle(
floatingLabelBehavior: FloatingLabelBehavior.never, fontSize: 11,
border: InputBorder.none, color: Colors.grey,
contentPadding: EdgeInsets.symmetric(vertical: 16),
), ),
)), floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
), ),
if (errorMessages["email"] != null) ...[ if (errorMessages["email"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
@ -399,18 +407,17 @@ class TravellerDataState extends State<TravellerData> {
], ],
], ],
), ),
SizedBox( SizedBox(height: 15),
height: 15,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Mobile", "Mobile *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -418,19 +425,23 @@ class TravellerDataState extends State<TravellerData> {
isDesktop: widget.isDesktop, isDesktop: widget.isDesktop,
color: Colors.transparent, color: Colors.transparent,
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: TextField( child: TextField(
controller: controllers["mobile"], controller: controllers["mobile"],
focusNode: focusNodes["mobile"], focusNode: focusNodes["mobile"],
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: "Mobile", labelText: "Mobile",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey), labelStyle: TextStyle(
floatingLabelBehavior: FloatingLabelBehavior.never, fontSize: 11,
border: InputBorder.none, color: Colors.grey,
contentPadding: EdgeInsets.symmetric(vertical: 16),
), ),
)), floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
), ),
if (errorMessages["mobile"] != null) ...[ if (errorMessages["mobile"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
@ -441,9 +452,7 @@ class TravellerDataState extends State<TravellerData> {
], ],
], ],
), ),
SizedBox( SizedBox(height: 15),
height: 15,
),
if (travellerDataId != null) if (travellerDataId != null)
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -451,13 +460,16 @@ class TravellerDataState extends State<TravellerData> {
Text( Text(
"Change Status ", "Change Status ",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
Tooltip( Tooltip(
message: message:
isActive == "1" ? "Tap to deactivate" : "Tap to activate", isActive == "1"
? "Tap to deactivate"
: "Tap to activate",
child: GestureDetector( child: GestureDetector(
onTap: toggleStatus, onTap: toggleStatus,
child: Text( child: Text(
@ -469,13 +481,10 @@ class TravellerDataState extends State<TravellerData> {
), ),
), ),
), ),
) ),
], ],
), ),
if (travellerDataId != null) if (travellerDataId != null) SizedBox(height: 15),
SizedBox(
height: 15,
),
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
@ -512,18 +521,22 @@ class TravellerDataState extends State<TravellerData> {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
), ),
child: Text('Save', child: Text(
style: GoogleFonts.poppins( 'Save',
fontSize: 11, color: Colors.white)), style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.white,
),
),
), ),
), ),
], ],
) ),
// : SizedBox.shrink(), // : 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", "last_name",
"email", "email",
"mobile_no", "mobile_no",
"role_id",
// "employeeCode", // "employeeCode",
]; ];
@ -1125,6 +1126,9 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
}, },
); );
case "travel": case "travel":
final fullName =
"${controllers["Fname"]?.text ?? ""} ${controllers["Lname"]?.text ?? ""}"
.trim();
return TravellerDetails( return TravellerDetails(
key: travellerDetailsKey, key: travellerDetailsKey,
isDesktop: isDesktop, isDesktop: isDesktop,
@ -1134,6 +1138,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
travelDetails: travelDetailsDataFromAPI, // 👈 Pass this down travelDetails: travelDetailsDataFromAPI, // 👈 Pass this down
passportFileUrlFromApi: passportFileUrlFromApi, passportFileUrlFromApi: passportFileUrlFromApi,
userIdApi: userIdApi, userIdApi: userIdApi,
fullName: fullName,
); );
default: default:
return PersonalDetails( return PersonalDetails(

View File

@ -314,6 +314,8 @@ class PersonalDetailsState extends State<PersonalDetails> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 20), SizedBox(height: 20),
_buildPassportDataRow1(widget.isDesktop),
SizedBox(height: 10),
_buildFirstRow(widget.isDesktop), _buildFirstRow(widget.isDesktop),
SizedBox(height: 10), SizedBox(height: 10),
_buildSecondRow(widget.isDesktop), _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 { Future<void> loadAllServices() async {
try { try {
final result = await apiService.fetchAllServices(); final result = await apiService.fetchAllServices();
@ -1500,7 +1521,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Role", "Role*",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, 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 Map<String, dynamic>? travelDetails;
final String? passportFileUrlFromApi; final String? passportFileUrlFromApi;
final String? userIdApi; final String? userIdApi;
final String fullName;
const TravellerDetails({ const TravellerDetails({
Key? key, Key? key,
@ -42,6 +43,7 @@ class TravellerDetails extends StatefulWidget {
this.travelDetails, this.travelDetails,
this.passportFileUrlFromApi, this.passportFileUrlFromApi,
this.userIdApi, this.userIdApi,
required this.fullName,
}) : super(key: key); }) : super(key: key);
@override @override
TravellerDetailsState createState() => TravellerDetailsState(); TravellerDetailsState createState() => TravellerDetailsState();
@ -54,6 +56,8 @@ class TravellerDetailsState extends State<TravellerDetails> {
int? expandedIndex; int? expandedIndex;
bool isCountryLoading = true; bool isCountryLoading = true;
Map<String, String> errorMessages = {};
late final userId; late final userId;
String? selectedFileNames; String? selectedFileNames;
Uint8List? passportDocumentBytes; Uint8List? passportDocumentBytes;
@ -154,18 +158,16 @@ class TravellerDetailsState extends State<TravellerDetails> {
String value, String value,
String? userId, String? userId,
) async { ) async {
var newField = "";
try { try {
final response = await apiService.CheckDuplicate( final response = await apiService.CheckDuplicate(
label, label,
newField, field,
value, value,
userId, userId,
); );
if (response.isNotEmpty) { if (response.isNotEmpty) {
_clearError(field); _clearError(field);
widget.errorMessages[field] = errorMessages[field] = response['message'] ?? "$label Already Exists";
response['message'] ?? "$label already exists";
print("Duplicate found: ${response['message']}"); print("Duplicate found: ${response['message']}");
return; return;
} else { } 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) { void _clearError(String field) {
setState(() { setState(() {
widget.errorMessages.remove(field); errorMessages.remove(field);
}); });
} }
@ -858,7 +888,7 @@ class TravellerDetailsState extends State<TravellerDetails> {
// SizedBox( // SizedBox(
// height: 10, // height: 10,
// ), // ),
// _buildPassportDataRow1(widget.isDesktop), _buildPassportDataRow1(widget.isDesktop),
SizedBox(height: 10), SizedBox(height: 10),
_buildPassportDataRow2(widget.isDesktop), _buildPassportDataRow2(widget.isDesktop),
SizedBox(height: 10), SizedBox(height: 10),
@ -870,28 +900,49 @@ class TravellerDetailsState extends State<TravellerDetails> {
Widget _buildPassportDataRow1(bool isDesktop) { Widget _buildPassportDataRow1(bool isDesktop) {
return Container( return Container(
color: Colors.white, color: Colors.white,
child: child: Row(
widget.isDesktop children: [
? Row( Text(
crossAxisAlignment: CrossAxisAlignment.start, "Name as per passport : ",
children: [ style: GoogleFonts.poppins(
buildFirstNameField(), fontSize: 11,
Spacer(), fontStyle: FontStyle.italic,
buildLastNameField(), letterSpacing: 0.5,
Spacer(), // Space after Last Name ),
buildNationality(), ),
], Text(
) "${widget.fullName} ",
: Column( style: GoogleFonts.poppins(
crossAxisAlignment: CrossAxisAlignment.start, fontSize: 10,
children: [ fontWeight: FontWeight.w600,
buildFirstNameField(), letterSpacing: 0.5,
SizedBox(height: 8), // Vertical space color: Colors.black87,
buildLastNameField(), // fontStyle: FontStyle.italic,
SizedBox(height: 8), ),
buildNationality(), ),
], ],
), ),
// 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 SizedBox(height: 5), // Space before error message
Text( Text(
widget.errorMessages["passport_number"]!, errorMessages["passport_number"]!,
style: TextStyle(color: Colors.red, fontSize: 12), 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 //api url
const String apiUrl = 'http://apitest.tripapprovaltool.com/tstat_be'; 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', 'value': '/traveller',
'icon': Icons.travel_explore, 'icon': Icons.travel_explore,
'label': 'Traveller', 'label': 'Traveller (Non Employee)',
'description': 'Create and Edit Traveller', 'description': 'Create and Edit Traveller',
}, },
]; ];

View File

@ -318,7 +318,7 @@ class ApiService {
Future<List<dynamic>> fetchAllGroup() async { Future<List<dynamic>> fetchAllGroup() async {
String? orgId = await getOrgId(); 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(); final token = await getToken();
@ -392,7 +392,7 @@ class ApiService {
Future<List<dynamic>> fetchAllPolicy() async { Future<List<dynamic>> fetchAllPolicy() async {
String? orgId = await getOrgId(); 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(); 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),
),
],
),
),
);
}
}