From 335fbacede7dda69ba4d8fd7fe13935a83a00e5f Mon Sep 17 00:00:00 2001 From: venbaittech Date: Thu, 5 Jun 2025 20:12:05 +0530 Subject: [PATCH] merge --- lib/Screens/approvals/approval_list.dart | 24 +- .../authentication/login/login_widget.dart | 41 +- lib/Screens/costCenter/costCenterDetails.dart | 164 ++- lib/Screens/costCenter/costCenter_list.dart | 4 +- lib/Screens/department/departmentDetails.dart | 164 ++- lib/Screens/department/department_list.dart | 4 +- lib/Screens/dialog/user_selection_dialog.dart | 384 +++--- lib/Screens/forex/forexDetails.dart | 307 ++--- lib/Screens/forex/forex_list.dart | 2 +- lib/Screens/group/groupDetails.dart | 264 ++-- lib/Screens/group/groupList.dart | 67 +- lib/Screens/hotels/hotelsDetails.dart | 300 +++-- lib/Screens/hotels/hotels_list.dart | 4 +- lib/Screens/itnerary/flights.dart | 822 ++++++------ lib/Screens/itnerary/forex.dart | 53 +- lib/Screens/itnerary/train.dart | 505 ++++---- lib/Screens/plans/create_plans.dart | 87 +- .../plans/dynamic_itinerary_stepper.dart | 582 +++++---- lib/Screens/policy/policy.dart | 993 +++++++------- lib/Screens/policy/policy_list.dart | 506 ++++---- lib/Screens/traveller/travellerDetails.dart | 261 ++-- lib/Screens/traveller/travellerList.dart | 1154 ++++++++--------- .../create_user/create_user.dart | 5 + .../create_user/personal_details.dart | 30 +- .../create_user/traveller_details.dart | 139 +- lib/config/apiUrl.dart | 2 +- lib/routes/organizationSetting.dart | 2 +- lib/services/apiService.dart | 4 +- lib/widgets/saving_loader.dart | 35 + 29 files changed, 3730 insertions(+), 3179 deletions(-) create mode 100644 lib/widgets/saving_loader.dart diff --git a/lib/Screens/approvals/approval_list.dart b/lib/Screens/approvals/approval_list.dart index b2138e0..98c217a 100644 --- a/lib/Screens/approvals/approval_list.dart +++ b/lib/Screens/approvals/approval_list.dart @@ -287,7 +287,9 @@ class _ApprovalListState extends State { } void viewPlanforApprover( - String planId, { + String planId, + String? approverId, + String? delegaterId, { bool isViewMode = false, bool isApprover = true, }) async { @@ -295,14 +297,26 @@ class _ApprovalListState extends State { Map planData = await getViewPlan(planId); print("ViewAAA - $planData"); - context.go( - '/createPlan', + + context.replace( + '/approver/plans', extra: { 'planData': planData, + 'approverId': approverId, + 'delegaterId': delegaterId, 'isViewMode': isViewMode, 'isApprover': isApprover, }, ); + + // context.go( + // '/createPlan', + // extra: { + // 'planData': planData, + // 'isViewMode': isViewMode, + // 'isApprover': isApprover, + // }, + // ); } catch (e) { print("Error fetching plan: $e"); } @@ -854,6 +868,8 @@ class _ApprovalListState extends State { ); // Close popup manually viewPlanforApprover( plan.planId, + plan.approverId, + plan.delegaterId, isViewMode: true, isApprover: @@ -1108,6 +1124,8 @@ class _ApprovalListState extends State { ); // Close popup manually viewPlanforApprover( plan.planId, + plan.approverId, + plan.delegaterId, isViewMode: true, isApprover: true, ); diff --git a/lib/Screens/authentication/login/login_widget.dart b/lib/Screens/authentication/login/login_widget.dart index f704dda..e8a72fd 100644 --- a/lib/Screens/authentication/login/login_widget.dart +++ b/lib/Screens/authentication/login/login_widget.dart @@ -472,6 +472,40 @@ class _LoginWidgetState extends State { /// **Password Field** _buildLabel("Password"), + // TextFormField( + // controller: _passwordController, + // style: GoogleFonts.poppins( + // fontWeight: FontWeight.w600, + // fontSize: 11, + // ), + // obscureText: _obscureText, + // + // decoration: _inputDecoration( + // "Enter your password", + // ).copyWith( + // prefixIcon: Icon(Icons.key, size: 16), + // suffixIcon: IconButton( + // icon: Icon( + // _obscureText + // ? Icons.visibility_off + // : Icons.visibility, + // color: Color(0xFF12B24B), + // size: 16, + // ), + // + // onPressed: + // () => setState( + // () => _obscureText = !_obscureText, + // ), + // ), + // ), + // + // validator: + // (value) => + // value == null || value.isEmpty + // ? 'Required Password' + // : null, + // ), TextFormField( controller: _passwordController, style: GoogleFonts.poppins( @@ -479,6 +513,7 @@ class _LoginWidgetState extends State { fontSize: 11, ), obscureText: _obscureText, + textInputAction: TextInputAction.done, decoration: _inputDecoration( "Enter your password", ).copyWith( @@ -497,13 +532,17 @@ class _LoginWidgetState extends State { ), ), ), + onFieldSubmitted: (_) { + if (_formKey.currentState!.validate()) { + _login(context); + } + }, validator: (value) => value == null || value.isEmpty ? 'Required Password' : null, ), - const SizedBox(height: 10), /// **Login Button** diff --git a/lib/Screens/costCenter/costCenterDetails.dart b/lib/Screens/costCenter/costCenterDetails.dart index 0068d63..12d24d3 100644 --- a/lib/Screens/costCenter/costCenterDetails.dart +++ b/lib/Screens/costCenter/costCenterDetails.dart @@ -19,13 +19,14 @@ class CostCenterData extends StatefulWidget { final int? costcenterId; // <-- Add this final Map? costcenterData; - const CostCenterData( - {super.key, - required this.isDesktop, - this.layoutColor, - required this.fetchGetCostCenter, - this.costcenterId, - this.costcenterData}); + const CostCenterData({ + super.key, + required this.isDesktop, + this.layoutColor, + required this.fetchGetCostCenter, + this.costcenterId, + this.costcenterData, + }); @override CostCenterDataState createState() => CostCenterDataState(); @@ -49,10 +50,7 @@ class CostCenterDataState extends State { int? costcenterDataId; late String isActive = "1"; - List dataHeader = [ - "name", - "description", - ]; + List dataHeader = ["name", "description"]; Map costcenterDetails() { final data = { @@ -69,7 +67,6 @@ class CostCenterDataState extends State { void initState() { super.initState(); - apiData = null; for (var field in dataHeader) { controllers[field] = TextEditingController(); @@ -110,7 +107,6 @@ class CostCenterDataState extends State { }); } - void toggleStatus() { setState(() { isActive = isActive == "1" ? "0" : "1"; @@ -171,7 +167,9 @@ class CostCenterDataState extends State { apiUrldata = '$apiUrl/api/updateCostCenter/$costcenterDataId'; costcenterData["cost_center_id"] = costcenterDataId.toString(); costcenterData["updated_by"] = userId; - (costcenterData.containsKey("created_by")) ? costcenterData.remove("created_by") : '' ; + (costcenterData.containsKey("created_by")) + ? costcenterData.remove("created_by") + : ''; } else { print("for add CostCenter id - null"); apiUrldata = '$apiUrl/api/createCostCenter'; @@ -193,10 +191,10 @@ class CostCenterDataState extends State { }; final body = jsonEncode(costcenterData); - final response = costcenterDataId != null - ? await http.put(uri, headers: headers, body: body) - : await http.post(uri, headers: headers, body: body); - + final response = + costcenterDataId != null + ? await http.put(uri, headers: headers, body: body) + : await http.post(uri, headers: headers, body: body); switch (response.statusCode) { case 200: @@ -217,7 +215,6 @@ class CostCenterDataState extends State { print("Failed to submit costcenter. Status: ${response.statusCode}"); print("Error: ${response.body}"); } - } catch (e) { print(" Error submitting plan: $e"); } @@ -225,7 +222,6 @@ class CostCenterDataState extends State { @override Widget build(BuildContext context) { - return AlertDialog( backgroundColor: Colors.white, contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30), @@ -238,27 +234,27 @@ class CostCenterDataState extends State { Row( children: [ Text( - (costcenterDataId != null) ? 'Edit CostCenter' : 'Create CostCenter', + (costcenterDataId != null) + ? 'Edit CostCenter' + : 'Create CostCenter', style: GoogleFonts.poppins(fontSize: 15, color: Colors.black), ), const Spacer(), ], ), const SizedBox(height: 2), - Divider( - thickness: 0.2, - color: Colors.blueGrey.shade100, - ), + Divider(thickness: 0.2, color: Colors.blueGrey.shade100), const SizedBox(height: 5), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Name", + "Name *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -266,19 +262,20 @@ class CostCenterDataState extends State { isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( - height: 40, - child: TextField( - controller: controllers["name"], - focusNode: focusNodes["name"], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "Name", - labelStyle: TextStyle(fontSize: 11, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - ), - )), + height: 40, + child: TextField( + controller: controllers["name"], + focusNode: focusNodes["name"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Name", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), ), if (errorMessages["name"] != null) ...[ SizedBox(height: 5), // Space before error message @@ -289,18 +286,17 @@ class CostCenterDataState extends State { ], ], ), - SizedBox( - height: 15, - ), + SizedBox(height: 15), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Description", + "Description *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -334,41 +330,37 @@ class CostCenterDataState extends State { ], ], ), - SizedBox( - height: 15, - ), + SizedBox(height: 15), if (costcenterDataId != null) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Change Status ", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Change Status ", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), - Tooltip( - message: - isActive == "1" ? "Tap to deactivate" : "Tap to activate", - child: GestureDetector( - onTap: toggleStatus, - child: Text( - isActive == "1" ? "Active" : "Inactive", - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", - color: isActive == "1" ? Colors.green : Colors.red, - ), + ), + Tooltip( + message: + isActive == "1" ? "Tap to deactivate" : "Tap to activate", + child: GestureDetector( + onTap: toggleStatus, + child: Text( + isActive == "1" ? "Active" : "Inactive", + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + color: isActive == "1" ? Colors.green : Colors.grey, ), ), - ) - ], - ), - if (costcenterDataId != null) - SizedBox( - height: 15, + ), + ), + ], ), + if (costcenterDataId != null) SizedBox(height: 15), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -405,16 +397,20 @@ class CostCenterDataState extends State { borderRadius: BorderRadius.circular(8), ), ), - child: Text('Save', - style: GoogleFonts.poppins( - fontSize: 11, color: Colors.white)), + child: Text( + 'Save', + style: GoogleFonts.poppins( + fontSize: 11, + color: Colors.white, + ), + ), ), ), ], - ) + ), // : SizedBox.shrink(), ], ), ); } -} \ No newline at end of file +} diff --git a/lib/Screens/costCenter/costCenter_list.dart b/lib/Screens/costCenter/costCenter_list.dart index 43cf86a..af92cdb 100644 --- a/lib/Screens/costCenter/costCenter_list.dart +++ b/lib/Screens/costCenter/costCenter_list.dart @@ -99,7 +99,7 @@ class CostCenterListState extends State { } Future> fetchGetCostCenter() async { - final String apiUrlData = '$apiUrl/api/getCostCenterMaster'; + final String apiUrlData = '$apiUrl/api/getCostCenterMaster?for=table_view'; final String? token = await getToken(); @@ -566,7 +566,7 @@ class CostCenterListState extends State { color: tableObject['is_active'] == "1" ? Colors.green - : Colors.red, + : Colors.grey, ), softWrap: true, overflow: TextOverflow.ellipsis, diff --git a/lib/Screens/department/departmentDetails.dart b/lib/Screens/department/departmentDetails.dart index 078e395..faa0ca3 100644 --- a/lib/Screens/department/departmentDetails.dart +++ b/lib/Screens/department/departmentDetails.dart @@ -19,13 +19,14 @@ class DepartmentData extends StatefulWidget { final int? departmentId; // <-- Add this final Map? departmentData; - const DepartmentData( - {super.key, - required this.isDesktop, - this.layoutColor, - required this.fetchGetDepartment, - this.departmentId, - this.departmentData}); + const DepartmentData({ + super.key, + required this.isDesktop, + this.layoutColor, + required this.fetchGetDepartment, + this.departmentId, + this.departmentData, + }); @override DepartmentDataState createState() => DepartmentDataState(); @@ -49,10 +50,7 @@ class DepartmentDataState extends State { int? departmentDataId; late String isActive = "1"; - List dataHeader = [ - "name", - "description", - ]; + List dataHeader = ["name", "description"]; Map departmentDetails() { final data = { @@ -69,7 +67,6 @@ class DepartmentDataState extends State { void initState() { super.initState(); - apiData = null; for (var field in dataHeader) { controllers[field] = TextEditingController(); @@ -110,7 +107,6 @@ class DepartmentDataState extends State { }); } - void toggleStatus() { setState(() { isActive = isActive == "1" ? "0" : "1"; @@ -171,7 +167,9 @@ class DepartmentDataState extends State { apiUrldata = '$apiUrl/api/updateDepartment/$departmentDataId'; departmentData["department_id"] = departmentDataId.toString(); departmentData["updated_by"] = userId; - (departmentData.containsKey("created_by")) ? departmentData.remove("created_by") : '' ; + (departmentData.containsKey("created_by")) + ? departmentData.remove("created_by") + : ''; } else { print("for add Department id - null"); apiUrldata = '$apiUrl/api/createDepartment'; @@ -193,10 +191,10 @@ class DepartmentDataState extends State { }; final body = jsonEncode(departmentData); - final response = departmentDataId != null - ? await http.put(uri, headers: headers, body: body) - : await http.post(uri, headers: headers, body: body); - + final response = + departmentDataId != null + ? await http.put(uri, headers: headers, body: body) + : await http.post(uri, headers: headers, body: body); switch (response.statusCode) { case 200: @@ -217,7 +215,6 @@ class DepartmentDataState extends State { print("Failed to submit department. Status: ${response.statusCode}"); print("Error: ${response.body}"); } - } catch (e) { print(" Error submitting plan: $e"); } @@ -225,7 +222,6 @@ class DepartmentDataState extends State { @override Widget build(BuildContext context) { - return AlertDialog( backgroundColor: Colors.white, contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30), @@ -238,27 +234,27 @@ class DepartmentDataState extends State { Row( children: [ Text( - (departmentDataId != null) ? 'Edit Department' : 'Create Department', + (departmentDataId != null) + ? 'Edit Department' + : 'Create Department', style: GoogleFonts.poppins(fontSize: 15, color: Colors.black), ), const Spacer(), ], ), const SizedBox(height: 2), - Divider( - thickness: 0.2, - color: Colors.blueGrey.shade100, - ), + Divider(thickness: 0.2, color: Colors.blueGrey.shade100), const SizedBox(height: 5), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Name", + "Name *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -266,19 +262,20 @@ class DepartmentDataState extends State { isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( - height: 40, - child: TextField( - controller: controllers["name"], - focusNode: focusNodes["name"], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "Name", - labelStyle: TextStyle(fontSize: 11, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - ), - )), + height: 40, + child: TextField( + controller: controllers["name"], + focusNode: focusNodes["name"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Name", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), ), if (errorMessages["name"] != null) ...[ SizedBox(height: 5), // Space before error message @@ -289,18 +286,17 @@ class DepartmentDataState extends State { ], ], ), - SizedBox( - height: 15, - ), + SizedBox(height: 15), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Description", + "Description *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -334,41 +330,37 @@ class DepartmentDataState extends State { ], ], ), - SizedBox( - height: 15, - ), + SizedBox(height: 15), if (departmentDataId != null) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Change Status ", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Change Status ", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), - Tooltip( - message: - isActive == "1" ? "Tap to deactivate" : "Tap to activate", - child: GestureDetector( - onTap: toggleStatus, - child: Text( - isActive == "1" ? "Active" : "Inactive", - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", - color: isActive == "1" ? Colors.green : Colors.red, - ), + ), + Tooltip( + message: + isActive == "1" ? "Tap to deactivate" : "Tap to activate", + child: GestureDetector( + onTap: toggleStatus, + child: Text( + isActive == "1" ? "Active" : "Inactive", + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + color: isActive == "1" ? Colors.green : Colors.red, ), ), - ) - ], - ), - if (departmentDataId != null) - SizedBox( - height: 15, + ), + ), + ], ), + if (departmentDataId != null) SizedBox(height: 15), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -405,16 +397,20 @@ class DepartmentDataState extends State { borderRadius: BorderRadius.circular(8), ), ), - child: Text('Save', - style: GoogleFonts.poppins( - fontSize: 11, color: Colors.white)), + child: Text( + 'Save', + style: GoogleFonts.poppins( + fontSize: 11, + color: Colors.white, + ), + ), ), ), ], - ) + ), // : SizedBox.shrink(), ], ), ); } -} \ No newline at end of file +} diff --git a/lib/Screens/department/department_list.dart b/lib/Screens/department/department_list.dart index bdc821d..f7b63b1 100644 --- a/lib/Screens/department/department_list.dart +++ b/lib/Screens/department/department_list.dart @@ -99,7 +99,7 @@ class DepartmentListState extends State { } Future> fetchGetDepartment() async { - final String apiUrlData = '$apiUrl/api/getDepartmentList'; + final String apiUrlData = '$apiUrl/api/getDepartmentList?for=table_view'; final String? token = await getToken(); @@ -565,7 +565,7 @@ class DepartmentListState extends State { color: tableObject['is_active'] == "1" ? Colors.green - : Colors.red, + : Colors.grey, ), softWrap: true, overflow: TextOverflow.ellipsis, diff --git a/lib/Screens/dialog/user_selection_dialog.dart b/lib/Screens/dialog/user_selection_dialog.dart index 9427b0c..5dc4955 100644 --- a/lib/Screens/dialog/user_selection_dialog.dart +++ b/lib/Screens/dialog/user_selection_dialog.dart @@ -100,11 +100,13 @@ class _UserSelectionDialogState extends State { } } else { throw Exception( - "Unexpected response format: Expected a List but got ${responseBody.runtimeType}"); + "Unexpected response format: Expected a List but got ${responseBody.runtimeType}", + ); } } else { throw Exception( - 'Failed to load users. Status Code: ${response.statusCode}'); + 'Failed to load users. Status Code: ${response.statusCode}', + ); } } catch (e) { print("Error fetching users: $e"); @@ -138,56 +140,63 @@ class _UserSelectionDialogState extends State { List travellerList = responseBody['data']; setState(() { - _traveller = travellerList - .map((user) => SearchTraveler.fromJson(user)) - .toList(); + _traveller = + travellerList + .map((user) => SearchTraveler.fromJson(user)) + .toList(); _filteredTraveller = List.from(_traveller); }); print("Users fetched: ${_users.length}"); for (var travvelr in _traveller) { print( - "${travvelr.firstName} ${travvelr.lastName} ${travvelr.mobileNo}"); + "${travvelr.firstName} ${travvelr.lastName} ${travvelr.mobileNo}", + ); } } else { throw Exception( - "Unexpected response format: Expected a List but got ${responseBody.runtimeType}"); + "Unexpected response format: Expected a List but got ${responseBody.runtimeType}", + ); } } else { throw Exception( - 'Failed to load users. Status Code: ${response.statusCode}'); + 'Failed to load users. Status Code: ${response.statusCode}', + ); } } catch (e) { print("Error fetching traveller: $e"); } } - void _filterUsers1(String query) { - print("Filtering users..."); - setState(() { - if (query.isEmpty) { - _filteredUsers = List.from(_users); - } else { - _filteredUsers = _users.where((user) { - List searchFields = [ - "${user.firstName} ${user.lastName}".toLowerCase(), - user.email.toLowerCase() ?? "", - user.userId.toLowerCase() ?? "", - user.mobileNo ?? "", - user.alternateMobileNo ?? "" - ]; - - return searchFields - .any((field) => field.contains(query.toLowerCase())); - }).toList(); - } - }); - - print("Filtered Users:"); - for (var user in _filteredUsers) { - print("${user.firstName} ${user.lastName}"); - } - } + // void _filterUsers1(String query) { + // print("Filtering users..."); + // setState(() { + // if (query.isEmpty) { + // _filteredUsers = List.from(_users); + // } else { + // _filteredUsers = + // _users.where((user) { + // List searchFields = [ + // "${user.firstName} ${user.lastName}".toLowerCase(), + // user.email.toLowerCase() ?? "", + // user.empCode?.toLowerCase() ?? "", + // user.userId.toLowerCase() ?? "", + // user.mobileNo ?? "", + // user.alternateMobileNo ?? "", + // ]; + // + // return searchFields.any( + // (field) => field.contains(query.toLowerCase()), + // ); + // }).toList(); + // } + // }); + // + // print("Filtered Users:"); + // for (var user in _filteredUsers) { + // print("${user.firstName} ${user.lastName}"); + // } + // } void _filterUsers(String query) { print("Filtering _filterUsersTravellers..."); @@ -200,19 +209,23 @@ class _UserSelectionDialogState extends State { ]; } else { _filteredList = [ - ..._users.where((user) { - print("usersLLL : ${user}"); + ..._users + .where((user) { + print("usersLLL : ${user}"); - List searchFields = [ - "${user.firstName} ${user.lastName}".toLowerCase(), - user.email.toLowerCase() ?? "", - user.userId.toLowerCase() ?? "", - user.mobileNo ?? "", - user.alternateMobileNo ?? "" - ]; - return searchFields - .any((field) => field.contains(query.toLowerCase())); - }).map((user) => {"type": "user", "data": user}), + List searchFields = [ + "${user.firstName} ${user.lastName}".toLowerCase(), + user.email.toLowerCase() ?? "", + user.userId.toLowerCase() ?? "", + user.mobileNo ?? "", + user.alternateMobileNo ?? "", + user.empCode?.toLowerCase() ?? "", + ]; + return searchFields.any( + (field) => field.contains(query.toLowerCase()), + ); + }) + .map((user) => {"type": "user", "data": user}), ]; } }); @@ -221,7 +234,8 @@ class _UserSelectionDialogState extends State { for (var item in _filteredList) { var user = item["data"]; print( - "${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}"); + "${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}", + ); } } @@ -236,16 +250,19 @@ class _UserSelectionDialogState extends State { ]; } else { _filteredList = [ - ..._traveller.where((traveller) { - List searchFields = [ - "${traveller.firstName} ${traveller.lastName}".toLowerCase(), - traveller.email.toLowerCase() ?? "", - traveller.travellerId.toLowerCase() ?? "", - traveller.mobileNo ?? "", - ]; - return searchFields - .any((field) => field.contains(query.toLowerCase())); - }).map((traveller) => {"type": "traveller", "data": traveller}), + ..._traveller + .where((traveller) { + List searchFields = [ + "${traveller.firstName} ${traveller.lastName}".toLowerCase(), + traveller.email.toLowerCase() ?? "", + traveller.travellerId.toLowerCase() ?? "", + traveller.mobileNo ?? "", + ]; + return searchFields.any( + (field) => field.contains(query.toLowerCase()), + ); + }) + .map((traveller) => {"type": "traveller", "data": traveller}), ]; } }); @@ -254,7 +271,8 @@ class _UserSelectionDialogState extends State { for (var item in _filteredList) { var user = item["data"]; print( - "${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}"); + "${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}", + ); } } @@ -266,32 +284,39 @@ class _UserSelectionDialogState extends State { if (query.isEmpty) { _filteredList = [ ..._users.map((user) => {"type": "user", "data": user}), - ..._traveller - .map((traveller) => {"type": "traveller", "data": traveller}), + ..._traveller.map( + (traveller) => {"type": "traveller", "data": traveller}, + ), ]; } else { _filteredList = [ - ..._users.where((user) { - List searchFields = [ - "${user.firstName} ${user.lastName}".toLowerCase(), - user.email.toLowerCase() ?? "", - user.userId.toLowerCase() ?? "", - user.mobileNo ?? "", - user.alternateMobileNo ?? "" - ]; - return searchFields - .any((field) => field.contains(query.toLowerCase())); - }).map((user) => {"type": "user", "data": user}), - ..._traveller.where((traveller) { - List searchFields = [ - "${traveller.firstName} ${traveller.lastName}".toLowerCase(), - traveller.email.toLowerCase() ?? "", - traveller.travellerId.toLowerCase() ?? "", - traveller.mobileNo ?? "", - ]; - return searchFields - .any((field) => field.contains(query.toLowerCase())); - }).map((traveller) => {"type": "traveller", "data": traveller}), + ..._users + .where((user) { + List searchFields = [ + "${user.firstName} ${user.lastName}".toLowerCase(), + user.email.toLowerCase() ?? "", + user.userId.toLowerCase() ?? "", + user.mobileNo ?? "", + user.alternateMobileNo ?? "", + ]; + return searchFields.any( + (field) => field.contains(query.toLowerCase()), + ); + }) + .map((user) => {"type": "user", "data": user}), + ..._traveller + .where((traveller) { + List searchFields = [ + "${traveller.firstName} ${traveller.lastName}".toLowerCase(), + traveller.email.toLowerCase() ?? "", + traveller.travellerId.toLowerCase() ?? "", + traveller.mobileNo ?? "", + ]; + return searchFields.any( + (field) => field.contains(query.toLowerCase()), + ); + }) + .map((traveller) => {"type": "traveller", "data": traveller}), ]; } }); @@ -300,7 +325,8 @@ class _UserSelectionDialogState extends State { for (var item in _filteredList) { var user = item["data"]; print( - "${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}"); + "${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}", + ); } } @@ -324,10 +350,14 @@ class _UserSelectionDialogState extends State { MainAxisSize.min, // Ensures content doesn't expand unnecessarily children: [ widget.title == "Others" - ? Text("Please Select Other User", - style: GoogleFonts.poppins(fontSize: 14)) - : Text("Please Select Other Employee", - style: GoogleFonts.poppins(fontSize: 14)), + ? Text( + "Please Select Other User", + style: GoogleFonts.poppins(fontSize: 14), + ) + : Text( + "Please Select Other Employee", + style: GoogleFonts.poppins(fontSize: 14), + ), SizedBox(height: 10), // Search Field @@ -344,11 +374,14 @@ class _UserSelectionDialogState extends State { style: GoogleFonts.poppins(fontSize: 12), decoration: InputDecoration( hintText: "Search for a user", - hintStyle: - GoogleFonts.poppins(fontSize: 14, color: Colors.grey), + hintStyle: GoogleFonts.poppins( + fontSize: 14, + color: Colors.grey, + ), prefixIcon: Icon(Icons.search), - border: - OutlineInputBorder(borderRadius: BorderRadius.circular(8)), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), enabledBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.grey.shade200, width: 1), // borderSide: BorderSide(color: Color(0xFFF5F5F5), width: 2), @@ -366,9 +399,13 @@ class _UserSelectionDialogState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text("or create a new traveler", - style: GoogleFonts.poppins( - fontSize: 14, color: Color(0xFF575A74))), + Text( + "or create a new traveler", + style: GoogleFonts.poppins( + fontSize: 14, + color: Color(0xFF575A74), + ), + ), TextButton( onPressed: () { setState(() { @@ -376,9 +413,13 @@ class _UserSelectionDialogState extends State { _searchController.clear(); }); }, - child: Text("Create", - style: GoogleFonts.poppins( - fontSize: 14, color: widget.layoutColorForUser)), + child: Text( + "Create", + style: GoogleFonts.poppins( + fontSize: 14, + color: widget.layoutColorForUser, + ), + ), ), ], ), @@ -389,17 +430,20 @@ class _UserSelectionDialogState extends State { // User List or Message _searchController.text.isNotEmpty ? SizedBox( - height: 300, // Limit height to avoid overflow - // child: _filteredUsers.isEmpty - child: _filteredList.isEmpty - ? Center( + height: 300, // Limit height to avoid overflow + // child: _filteredUsers.isEmpty + child: + _filteredList.isEmpty + ? Center( child: Text( "No users found", style: GoogleFonts.poppins( - fontSize: 14, color: Colors.grey), + fontSize: 14, + color: Colors.grey, + ), ), ) - : ListView.builder( + : ListView.builder( // itemCount: _filteredUsers.length, itemCount: _filteredList.length, itemBuilder: (context, index) { @@ -411,7 +455,8 @@ class _UserSelectionDialogState extends State { item["type"]; // "user" or "traveller" if (user is Map) { print( - "userLsirer - ${jsonEncode(user)}"); // pretty JSON-like string + "userLsirer - ${jsonEncode(user)}", + ); // pretty JSON-like string } else { print("userLsirer - $user"); // fallback } @@ -420,37 +465,42 @@ class _UserSelectionDialogState extends State { "${user.firstName ?? "Unknown"} ${user.lastName ?? ""}", style: GoogleFonts.poppins(fontSize: 11), ), - subtitle: userType == "user" - ? Text( - "Employee ID: ${user.empCode ?? ""} ", - // "Employee ID: ${userType == "user" ? user.userId : user.travellerId}", - style: - GoogleFonts.poppins(fontSize: 10), - ) - : Text( - "Mobile : ${user.mobileNo ?? ""} ", - // "Employee ID: ${userType == "user" ? user.userId : user.travellerId}", - style: - GoogleFonts.poppins(fontSize: 10), - ), + subtitle: + userType == "user" + ? Text( + "Employee ID: ${user.empCode ?? ""} ", + // "Employee ID: ${userType == "user" ? user.userId : user.travellerId}", + style: GoogleFonts.poppins( + fontSize: 10, + ), + ) + : Text( + "Mobile : ${user.mobileNo ?? ""} ", + // "Employee ID: ${userType == "user" ? user.userId : user.travellerId}", + style: GoogleFonts.poppins( + fontSize: 10, + ), + ), onTap: () { String selectedUser = "${user.firstName ?? "Unknown"} ${user.lastName ?? ""}"; setState(() { _searchController.text = selectedUser; - userIdSelected = userType == "user" - ? user.userId - : user.travellerId; + userIdSelected = + userType == "user" + ? user.userId + : user.travellerId; isTraveller = userType == "traveller"; }); print( - "Selected: $selectedUser, ID: ${userType == "user" ? user.userId : user.travellerId}," - " isTraveller: $userIdSelected"); + "Selected: $selectedUser, ID: ${userType == "user" ? user.userId : user.travellerId}," + " isTraveller: $userIdSelected", + ); }, ); }, ), - ) + ) : SizedBox.shrink(), // Traveler Form @@ -462,10 +512,16 @@ class _UserSelectionDialogState extends State { padding: const EdgeInsets.all(16.0), child: TravelerForm( formKey: _formKey, - onSubmit: (String fullName, String travellerId, - bool isTraveller) { - widget.onSubmit(fullName, travellerId, - isTraveller); // Pass the data up + onSubmit: ( + String fullName, + String travellerId, + bool isTraveller, + ) { + widget.onSubmit( + fullName, + travellerId, + isTraveller, + ); // Pass the data up }, firstNameController: TextEditingController(), lastNameController: TextEditingController(), @@ -487,7 +543,9 @@ class _UserSelectionDialogState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide( - color: widget.layoutColorForUser, width: 2), + color: widget.layoutColorForUser, + width: 2, + ), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), @@ -508,15 +566,21 @@ class _UserSelectionDialogState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide( - color: widget.layoutColorForUser, width: 2), + color: widget.layoutColorForUser, + width: 2, + ), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: () { print( - "Submitting: ${_searchController.text}, ID: $userIdSelected"); + "Submitting: ${_searchController.text}, ID: $userIdSelected", + ); widget.onSubmit( - _searchController.text, userIdSelected, isTraveller); + _searchController.text, + userIdSelected, + isTraveller, + ); Navigator.pop(context); }, child: Text( @@ -542,14 +606,15 @@ class TravelerForm extends StatefulWidget { final GlobalKey formKey; final void Function(String, String, bool) onSubmit; - TravelerForm( - {required this.formKey, - required this.orgId, - required this.firstNameController, - required this.lastNameController, - required this.emailController, - required this.mobileController, - required this.onSubmit}); + TravelerForm({ + required this.formKey, + required this.orgId, + required this.firstNameController, + required this.lastNameController, + required this.emailController, + required this.mobileController, + required this.onSubmit, + }); @override _TravelerFormState createState() => _TravelerFormState(); @@ -582,8 +647,9 @@ class _TravelerFormState extends State { if (value == null || value.isEmpty) { return 'Email is required'; } - if (!RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$') - .hasMatch(value)) { + if (!RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + ).hasMatch(value)) { return 'Enter a valid email address'; } return null; @@ -642,7 +708,8 @@ class _TravelerFormState extends State { String lastName = travellerData["last_name"]; print( - "Traveller Added: ID: $travellerId, Name: $firstName $lastName"); + "Traveller Added: ID: $travellerId, Name: $firstName $lastName", + ); // // Pass data to callback // widget.onSubmit("$firstName $lastName", travellerId, true); @@ -658,21 +725,22 @@ class _TravelerFormState extends State { SnackBar( content: Text( "Traveller added successfully!", - style: - GoogleFonts.poppins(color: Colors.white), // ✅ Set text color + style: GoogleFonts.poppins( + color: Colors.white, + ), // ✅ Set text color ), backgroundColor: Colors.green, ), ); } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Error: ${response.body}")), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text("Error: ${response.body}"))); } } catch (e) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Failed to connect to server.")), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text("Failed to connect to server."))); } } @@ -697,8 +765,10 @@ class _TravelerFormState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text("Create Traveler", - style: GoogleFonts.poppins(color: Colors.black54)), + Text( + "Create Traveler", + style: GoogleFonts.poppins(color: Colors.black54), + ), SizedBox(height: 7), Expanded( child: SingleChildScrollView( @@ -714,13 +784,17 @@ class _TravelerFormState extends State { onPressed: () { widget.formKey.currentState?.reset(); }, - child: Text("Clear", - style: GoogleFonts.poppins(color: Colors.grey)), + child: Text( + "Clear", + style: GoogleFonts.poppins(color: Colors.grey), + ), ), TextButton( onPressed: () => _onSubmit(context), - child: Text("Add", - style: GoogleFonts.poppins(color: Color(0xFF114D8B))), + child: Text( + "Add", + style: GoogleFonts.poppins(color: Color(0xFF114D8B)), + ), ), ], ), diff --git a/lib/Screens/forex/forexDetails.dart b/lib/Screens/forex/forexDetails.dart index 9839ec7..44c60bc 100644 --- a/lib/Screens/forex/forexDetails.dart +++ b/lib/Screens/forex/forexDetails.dart @@ -21,13 +21,14 @@ class ForexData extends StatefulWidget { final int? forexId; // <-- Add this final Map? forexData; - const ForexData( - {super.key, - required this.isDesktop, - this.layoutColor, - required this.fetchGetForex, - this.forexId, - this.forexData}); + const ForexData({ + super.key, + required this.isDesktop, + this.layoutColor, + required this.fetchGetForex, + this.forexId, + this.forexData, + }); @override ForexDataState createState() => ForexDataState(); @@ -61,7 +62,7 @@ class ForexDataState extends State { "currency", "perdiemAmount", "cash", - "card" + "card", ]; Map forex_Detials() { @@ -197,7 +198,7 @@ class ForexDataState extends State { "currency", "perdiemAmount", "cash_percentage", - "card_percentage" + "card_percentage", ]; // Check validation for each field @@ -273,9 +274,10 @@ class ForexDataState extends State { }; final body = jsonEncode(forexData); - final response = forexDataId != null - ? await http.put(uri, headers: headers, body: body) - : await http.post(uri, headers: headers, body: body); + final response = + forexDataId != null + ? await http.put(uri, headers: headers, body: body) + : await http.post(uri, headers: headers, body: body); // final response = await http.post( // Uri.parse(apiUrldata), @@ -326,7 +328,7 @@ class ForexDataState extends State { // Map country codes to country names countryMap = { for (var item in countryList) - item['country_code'] as String: item['country_name'] as String + item['country_code'] as String: item['country_name'] as String, }; // Extract only country codes for processing @@ -355,21 +357,19 @@ class ForexDataState extends State { ], ), const SizedBox(height: 2), - Divider( - thickness: 0.2, - color: Colors.blueGrey.shade100, - ), + Divider(thickness: 0.2, color: Colors.blueGrey.shade100), const SizedBox(height: 5), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Country", + "Country *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -381,18 +381,19 @@ class ForexDataState extends State { selectedItem: countryMap[selectedCountry], popupProps: PopupProps.menu( showSearchBox: true, // Enables search functionality - menuProps: const MenuProps( - backgroundColor: Colors.white, - ), + menuProps: const MenuProps(backgroundColor: Colors.white), constraints: BoxConstraints(maxHeight: 250), - itemBuilder: (context, item, isSelected) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 6.0), - child: Text( - item, - style: GoogleFonts.poppins(fontSize: 11.5), - ), - ), + itemBuilder: + (context, item, isSelected) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 6.0, + ), + child: Text( + item, + style: GoogleFonts.poppins(fontSize: 11.5), + ), + ), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search Country...", @@ -405,25 +406,25 @@ class ForexDataState extends State { dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 1, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), + ), + dropdownBuilder: + (context, selectedItem) => Align( + // Center-align selected item + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select Country", + style: GoogleFonts.poppins(fontSize: 11), + ), ), - ), - ), - dropdownBuilder: (context, selectedItem) => Align( - // Center-align selected item - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select Country", - style: GoogleFonts.poppins(fontSize: 11), - ), - ), onChanged: (String? newValue) { setState(() { // Find the country_code based on selected country_name - selectedCountry = countryMap.entries - .firstWhere((entry) => entry.value == newValue) - .key; + selectedCountry = + countryMap.entries + .firstWhere((entry) => entry.value == newValue) + .key; selectedCountryName = newValue; }); }, @@ -444,11 +445,12 @@ class ForexDataState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Currency", + "Currency *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -459,18 +461,19 @@ class ForexDataState extends State { // ? MediaQuery.of(context).size.width * 0.330 // : MediaQuery.of(context).size.width * 0.66, child: SizedBox( - height: 40, - child: TextField( - controller: controllers["currency"], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "Currency", - labelStyle: TextStyle(fontSize: 11, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - ), - )), + height: 40, + child: TextField( + controller: controllers["currency"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Currency", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), ), if (errorMessages["currency"] != null) ...[ SizedBox(height: 5), // Space before error message @@ -481,11 +484,9 @@ class ForexDataState extends State { ], ], ), - // if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,), - SizedBox( - height: 10, - ), + // if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,), + SizedBox(height: 10), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -493,38 +494,43 @@ class ForexDataState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Cash (%)", + "Cash (%) *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: false, isDesktop: widget.isDesktop, color: Colors.transparent, - width: widget.isDesktop - ? MediaQuery.of(context).size.width * 0.09 - : MediaQuery.of(context).size.width * 0.66, + width: + widget.isDesktop + ? MediaQuery.of(context).size.width * 0.09 + : MediaQuery.of(context).size.width * 0.66, child: SizedBox( - height: 40, - child: TextField( - controller: controllers["cash"], - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "Cash", - labelStyle: - TextStyle(fontSize: 11, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), + height: 40, + child: TextField( + controller: controllers["cash"], + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Cash", + labelStyle: TextStyle( + fontSize: 11, + color: Colors.grey, ), - )), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), ), if (errorMessages["cash_percentage"] != null) ...[ SizedBox(height: 5), // Space before error message @@ -540,38 +546,43 @@ class ForexDataState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Card (%)", + "Card (%) *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: false, isDesktop: widget.isDesktop, color: Colors.transparent, - width: widget.isDesktop - ? MediaQuery.of(context).size.width * 0.09 - : MediaQuery.of(context).size.width * 0.66, + width: + widget.isDesktop + ? MediaQuery.of(context).size.width * 0.09 + : MediaQuery.of(context).size.width * 0.66, child: SizedBox( - height: 40, - child: TextField( - controller: controllers["card"], - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "Card", - labelStyle: - TextStyle(fontSize: 11, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), + height: 40, + child: TextField( + controller: controllers["card"], + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Card", + labelStyle: TextStyle( + fontSize: 11, + color: Colors.grey, ), - )), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), ), if (errorMessages["card_percentage"] != null) ...[ SizedBox(height: 5), // Space before error message @@ -581,21 +592,20 @@ class ForexDataState extends State { ), ], ], - ) + ), ], ), - SizedBox( - height: 10, - ), + SizedBox(height: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Perdiem Amount", + "Perdiem Amount *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -603,18 +613,19 @@ class ForexDataState extends State { isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( - height: 40, - child: TextField( - controller: controllers["perdiemAmount"], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "Perdiem Amount", - labelStyle: TextStyle(fontSize: 11, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - ), - )), + height: 40, + child: TextField( + controller: controllers["perdiemAmount"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Perdiem Amount", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), ), if (errorMessages["perdiemAmount"] != null) ...[ SizedBox(height: 5), // Space before error message @@ -625,9 +636,7 @@ class ForexDataState extends State { ], ], ), - SizedBox( - height: 15, - ), + SizedBox(height: 15), if (forexDataId != null) Row( @@ -636,9 +645,10 @@ class ForexDataState extends State { Text( "Change Status ", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), Tooltip( message: @@ -654,13 +664,10 @@ class ForexDataState extends State { ), ), ), - ) + ), ], ), - if (forexDataId != null) - SizedBox( - height: 15, - ), + if (forexDataId != null) SizedBox(height: 15), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -697,13 +704,17 @@ class ForexDataState extends State { borderRadius: BorderRadius.circular(8), ), ), - child: Text('Save', - style: GoogleFonts.poppins( - fontSize: 11, color: Colors.white)), + child: Text( + 'Save', + style: GoogleFonts.poppins( + fontSize: 11, + color: Colors.white, + ), + ), ), ), ], - ) + ), // : SizedBox.shrink(), ], ), diff --git a/lib/Screens/forex/forex_list.dart b/lib/Screens/forex/forex_list.dart index 49052a7..64daa91 100644 --- a/lib/Screens/forex/forex_list.dart +++ b/lib/Screens/forex/forex_list.dart @@ -102,7 +102,7 @@ class ForexDataListState extends State { Future> fetchGetForex() async { orgId = await getOrgId(); - final String apiUrlData = '$apiUrl/api/getForexPerdiemList'; + final String apiUrlData = '$apiUrl/api/getForexPerdiemList?for=table_view'; final String? token = await getToken(); diff --git a/lib/Screens/group/groupDetails.dart b/lib/Screens/group/groupDetails.dart index f2ebb58..fc3156b 100644 --- a/lib/Screens/group/groupDetails.dart +++ b/lib/Screens/group/groupDetails.dart @@ -21,13 +21,14 @@ class GroupData extends StatefulWidget { final int? groupId; // <-- Add this final Map? groupData; - const GroupData( - {super.key, - required this.isDesktop, - this.layoutColor, - required this.fetchGetGroup, - this.groupId, - this.groupData}); + const GroupData({ + super.key, + required this.isDesktop, + this.layoutColor, + required this.fetchGetGroup, + this.groupId, + this.groupData, + }); @override GroupDataState createState() => GroupDataState(); @@ -46,7 +47,6 @@ class GroupDataState extends State { final Map controllers = {}; Map errorMessages = {}; - List domesticList = []; List internationalList = []; @@ -75,12 +75,12 @@ class GroupDataState extends State { Map group_Detials() { final data = { - "name":controllers["name"]?.text, - "description":controllers["description"]?.text, - "domestic_policy_id":selectedDomesticPolicyID, - "international_policy_id":selectedInternationalPolicyID, - "domestic_policy_name":selectedDomesticPolicyName, - "international_policy_name":selectedInternationalPolicyName, + "name": controllers["name"]?.text, + "description": controllers["description"]?.text, + "domestic_policy_id": selectedDomesticPolicyID, + "international_policy_id": selectedInternationalPolicyID, + "domestic_policy_name": selectedDomesticPolicyName, + "international_policy_name": selectedInternationalPolicyName, "is_active": isActive, }; return data; @@ -166,23 +166,19 @@ class GroupDataState extends State { }); } - bool validateData() { errorMessages.clear(); final data = { - "name":controllers["name"]?.text, - "description":controllers["description"]?.text, - "domestic_policy_id":selectedDomesticPolicyID, - "international_policy_id":selectedInternationalPolicyID, - "domestic_policy_name":selectedDomesticPolicyName, - "international_policy_name":selectedInternationalPolicyName, + "name": controllers["name"]?.text, + "description": controllers["description"]?.text, + "domestic_policy_id": selectedDomesticPolicyID, + "international_policy_id": selectedInternationalPolicyID, + "domestic_policy_name": selectedDomesticPolicyName, + "international_policy_name": selectedInternationalPolicyName, }; - final requiredFields = [ - "name", - "description", - ]; + final requiredFields = ["name", "description"]; // Check validation for each field for (String field in requiredFields) { @@ -240,9 +236,10 @@ class GroupDataState extends State { }; final body = jsonEncode(groupData); - final response = groupDataId != null - ? await http.put(uri, headers: headers, body: body) - : await http.post(uri, headers: headers, body: body); + final response = + groupDataId != null + ? await http.put(uri, headers: headers, body: body) + : await http.post(uri, headers: headers, body: body); if (response.statusCode == 200 || response.statusCode == 201) { print("Group Details Created successfully!"); @@ -288,8 +285,8 @@ class GroupDataState extends State { // Map id to names DomesticMap = { for (var object in domesticList) - object['policy_id'] as String: object['name'] as String - }; + object['policy_id'] as String: object['name'] as String, + }; // print("domestic -- map--$DomesticMap"); @@ -302,7 +299,7 @@ class GroupDataState extends State { InternationalMap = { for (var item in internationalList) - item['policy_id'] as String: item['name'] as String + item['policy_id'] as String: item['name'] as String, }; // Extract only id for processing @@ -330,20 +327,18 @@ class GroupDataState extends State { ], ), const SizedBox(height: 2), - Divider( - thickness: 0.2, - color: Colors.blueGrey.shade100, - ), + Divider(thickness: 0.2, color: Colors.blueGrey.shade100), const SizedBox(height: 5), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Name", + "Name *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -351,18 +346,19 @@ class GroupDataState extends State { isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( - height: 40, - child: TextField( - controller: controllers["name"], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "Name", - labelStyle: TextStyle(fontSize: 11, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - ), - )), + height: 40, + child: TextField( + controller: controllers["name"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Name", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), ), if (errorMessages["name"] != null) ...[ SizedBox(height: 5), // Space before error message @@ -380,9 +376,10 @@ class GroupDataState extends State { Text( "Select Policy For International", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -391,21 +388,23 @@ class GroupDataState extends State { child: SizedBox( height: 40, child: DropdownSearch( - selectedItem: InternationalMap[selectedInternationalPolicyID], + selectedItem: + InternationalMap[selectedInternationalPolicyID], popupProps: PopupProps.menu( showSearchBox: true, // Enables search functionality - menuProps: const MenuProps( - backgroundColor: Colors.white, - ), + menuProps: const MenuProps(backgroundColor: Colors.white), constraints: BoxConstraints(maxHeight: 250), - itemBuilder: (context, item, isSelected) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 6.0), - child: Text( - item, - style: GoogleFonts.poppins(fontSize: 11.5), - ), - ), + itemBuilder: + (context, item, isSelected) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 6.0, + ), + child: Text( + item, + style: GoogleFonts.poppins(fontSize: 11.5), + ), + ), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Select Policy For International", @@ -418,25 +417,25 @@ class GroupDataState extends State { dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 1, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), + ), + dropdownBuilder: + (context, selectedItem) => Align( + // Center-align selected item + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select Policy For International", + style: GoogleFonts.poppins(fontSize: 11), + ), ), - ), - ), - dropdownBuilder: (context, selectedItem) => Align( - // Center-align selected item - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select Policy For International", - style: GoogleFonts.poppins(fontSize: 11), - ), - ), onChanged: (String? newValue) { setState(() { // Find the country_code based on selected country_name - selectedInternationalPolicyID = InternationalMap.entries - .firstWhere((entry) => entry.value == newValue) - .key; + selectedInternationalPolicyID = + InternationalMap.entries + .firstWhere((entry) => entry.value == newValue) + .key; selectedInternationalPolicyName = newValue; }); }, @@ -452,9 +451,10 @@ class GroupDataState extends State { Text( "Select Policy For Domestic", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -466,18 +466,19 @@ class GroupDataState extends State { selectedItem: DomesticMap[selectedDomesticPolicyID], popupProps: PopupProps.menu( showSearchBox: true, // Enables search functionality - menuProps: const MenuProps( - backgroundColor: Colors.white, - ), + menuProps: const MenuProps(backgroundColor: Colors.white), constraints: BoxConstraints(maxHeight: 250), - itemBuilder: (context, object, isSelected) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 6.0), - child: Text( - object, - style: GoogleFonts.poppins(fontSize: 11.5), - ), - ), + itemBuilder: + (context, object, isSelected) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 6.0, + ), + child: Text( + object, + style: GoogleFonts.poppins(fontSize: 11.5), + ), + ), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Select Policy For Domestic...", @@ -490,25 +491,25 @@ class GroupDataState extends State { dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 1, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), + ), + dropdownBuilder: + (context, selectedItem) => Align( + // Center-align selected item + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select Policy For Domestic", + style: GoogleFonts.poppins(fontSize: 11), + ), ), - ), - ), - dropdownBuilder: (context, selectedItem) => Align( - // Center-align selected item - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select Policy For Domestic", - style: GoogleFonts.poppins(fontSize: 11), - ), - ), onChanged: (String? newValue) { setState(() { // Find the country_code based on selected country_name - selectedDomesticPolicyID = DomesticMap.entries - .firstWhere((entry) => entry.value == newValue) - .key; + selectedDomesticPolicyID = + DomesticMap.entries + .firstWhere((entry) => entry.value == newValue) + .key; selectedDomesticPolicyName = newValue; }); }, @@ -522,11 +523,12 @@ class GroupDataState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Description", + "Description *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -559,9 +561,7 @@ class GroupDataState extends State { ], ], ), - SizedBox( - height: 15, - ), + SizedBox(height: 15), if (groupDataId != null) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -569,13 +569,14 @@ class GroupDataState extends State { Text( "Change Status ", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), Tooltip( message: - isActive == "1" ? "Tap to deactivate" : "Tap to activate", + isActive == "1" ? "Tap to deactivate" : "Tap to activate", child: GestureDetector( onTap: toggleStatus, child: Text( @@ -587,13 +588,10 @@ class GroupDataState extends State { ), ), ), - ) + ), ], ), - if (groupDataId != null) - SizedBox( - height: 15, - ), + if (groupDataId != null) SizedBox(height: 15), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -630,16 +628,20 @@ class GroupDataState extends State { borderRadius: BorderRadius.circular(8), ), ), - child: Text('Save', - style: GoogleFonts.poppins( - fontSize: 11, color: Colors.white)), + child: Text( + 'Save', + style: GoogleFonts.poppins( + fontSize: 11, + color: Colors.white, + ), + ), ), ), ], - ) + ), // : SizedBox.shrink(), ], ), ); } -} \ No newline at end of file +} diff --git a/lib/Screens/group/groupList.dart b/lib/Screens/group/groupList.dart index 919b0b0..d653297 100644 --- a/lib/Screens/group/groupList.dart +++ b/lib/Screens/group/groupList.dart @@ -102,32 +102,30 @@ class _GroupListState extends State { } void filterGroups(String query) { - print("allGroups before filtering: $query"); final lowerQuery = query.toLowerCase(); - setState(() { filteredGroups = - allGroups.where((group) { - return (group['name']?.toLowerCase().contains(lowerQuery) ?? + allGroups.where((object) { + final isActiveStatus = + object['is_active'] == "1" ? "active" : "inactive"; + return (object['group_id']?.toLowerCase().contains(lowerQuery) ?? false) || - (group['domestic_policy_name']?.toLowerCase().contains( + (object['name']?.toLowerCase().contains(lowerQuery) ?? false) || + (object['domestic_policy_name']?.toLowerCase().contains( lowerQuery, ) ?? - false)( - group['international_policy_name']?.toLowerCase().contains( - lowerQuery, - ) ?? - false, - ) || - (group['description']?.toLowerCase().contains(lowerQuery) ?? false) || - (group['is_active']?.toLowerCase().contains(lowerQuery) ?? - false); + (object['international_policy_name']?.toLowerCase().contains( + lowerQuery, + ) ?? + false) || + (object['description']?.toLowerCase().contains(lowerQuery) ?? + false) || + (isActiveStatus.contains(lowerQuery)); }).toList(); currentPage = 0; }); - - print("filtered: $filteredGroups"); + print("filteredGroups: $filteredGroups"); } void handleActiveStatus( @@ -287,7 +285,7 @@ class _GroupListState extends State { controller: searchController, onChanged: filterGroups, decoration: InputDecoration( - hintText: "Search for a Group", + hintText: "Search ...", hintStyle: TextStyle( fontSize: 12, color: Color(0xFF9E9DBD), @@ -385,7 +383,7 @@ class _GroupListState extends State { controller: searchController, onChanged: filterGroups, decoration: InputDecoration( - hintText: "Search for a Group", + hintText: "Search ...", hintStyle: TextStyle( fontSize: 12, color: Color(0xFF9E9DBD), @@ -924,12 +922,35 @@ class _GroupListState extends State { Expanded( child: isDesktop - ? SingleChildScrollView( - scrollDirection: Axis.vertical, - child: table, // <-- your existing table - ) - : buildMobileCardView(paginatedGroup), + ? (searchController.text.isNotEmpty && + filteredGroups.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, + color: Colors.grey, + ), + ), + ) + : SingleChildScrollView( + scrollDirection: Axis.vertical, + child: table, + )) + : (searchController.text.isNotEmpty && + paginatedGroup.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, + color: Colors.grey, + ), + ), + ) + : buildMobileCardView(paginatedGroup)), ), + PaginationControls( currentPage: currentPage, itemsPerPage: itemsPerPage, diff --git a/lib/Screens/hotels/hotelsDetails.dart b/lib/Screens/hotels/hotelsDetails.dart index 9c3e4de..e759d09 100644 --- a/lib/Screens/hotels/hotelsDetails.dart +++ b/lib/Screens/hotels/hotelsDetails.dart @@ -20,13 +20,14 @@ class HotelsData extends StatefulWidget { final int? hotelsId; // <-- Add this final Map? hotelsData; - const HotelsData( - {super.key, - required this.isDesktop, - this.layoutColor, - required this.fetchGetHotels, - this.hotelsId, - this.hotelsData}); + const HotelsData({ + super.key, + required this.isDesktop, + this.layoutColor, + required this.fetchGetHotels, + this.hotelsId, + this.hotelsData, + }); @override HotelsDataState createState() => HotelsDataState(); @@ -110,7 +111,8 @@ class HotelsDataState extends State { if (data == null) return; setState(() { selectedCountry = data['country_code']; // For dropdown - selectedCountryName = data['country_name']; // For dropdown label or display + selectedCountryName = + data['country_name']; // For dropdown label or display controllers['city']?.text = data['city'] ?? ''; controllers['hotel_chain']?.text = data['hotel_chain'] ?? ''; controllers['hotel_name']?.text = data['hotel_name'] ?? ''; @@ -148,7 +150,12 @@ class HotelsDataState extends State { "city": controllers["city"]?.text, }; - final requiredFields = ["hotel_name","hotel_chain","country_code","city"]; + final requiredFields = [ + "hotel_name", + "hotel_chain", + "country_code", + "city", + ]; // Check validation for each field for (String field in requiredFields) { @@ -174,7 +181,6 @@ class HotelsDataState extends State { } Future postHotelsData({int isActive = 1}) async { - final hotelsData = hotels_Details(); final String apiUrldata; @@ -184,16 +190,20 @@ class HotelsDataState extends State { apiUrldata = '$apiUrl/api/updateHotels/$hotelsDataId'; hotelsData["hotel_id"] = hotelsDataId.toString(); hotelsData["updated_by"] = userId; - (hotelsData.containsKey("created_by")) ? hotelsData.remove("created_by") : '' ; - (hotelsData.containsKey("country_name")) ? hotelsData.remove("country_name") : '' ; - - + (hotelsData.containsKey("created_by")) + ? hotelsData.remove("created_by") + : ''; + (hotelsData.containsKey("country_name")) + ? hotelsData.remove("country_name") + : ''; } else { print("for add Hotel id - null"); apiUrldata = '$apiUrl/api/createHotels'; print("called apiUrl - $apiUrldata"); hotelsData["created_by"] = userId; - (hotelsData.containsKey("country_name")) ? hotelsData.remove("country_name") : '' ; + (hotelsData.containsKey("country_name")) + ? hotelsData.remove("country_name") + : ''; } final token = await getToken(); // Fetch token @@ -210,9 +220,10 @@ class HotelsDataState extends State { }; final body = jsonEncode(hotelsData); - final response = hotelsDataId != null - ? await http.put(uri, headers: headers, body: body) - : await http.post(uri, headers: headers, body: body); + final response = + hotelsDataId != null + ? await http.put(uri, headers: headers, body: body) + : await http.post(uri, headers: headers, body: body); if (response.statusCode == 200 || response.statusCode == 201) { print("Hotels Details Created successfully!"); @@ -254,7 +265,7 @@ class HotelsDataState extends State { // Map country codes to country names countryMap = { for (var item in countryList) - item['country_code'] as String: item['country_name'] as String + item['country_code'] as String: item['country_name'] as String, }; // Extract only country codes for processing @@ -281,20 +292,18 @@ class HotelsDataState extends State { ], ), const SizedBox(height: 2), - Divider( - thickness: 0.2, - color: Colors.blueGrey.shade100, - ), + Divider(thickness: 0.2, color: Colors.blueGrey.shade100), const SizedBox(height: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Hotel Name", + "Hotel Name *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -302,18 +311,19 @@ class HotelsDataState extends State { isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( - height: 40, - child: TextField( - controller: controllers["hotel_name"], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "Hotel Name", - labelStyle: TextStyle(fontSize: 11, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - ), - )), + height: 40, + child: TextField( + controller: controllers["hotel_name"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Hotel Name", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), ), if (errorMessages["hotel_name"] != null) ...[ SizedBox(height: 5), // Space before error message @@ -329,11 +339,12 @@ class HotelsDataState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Hotel Chain", + "Hotel Chain *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -341,18 +352,19 @@ class HotelsDataState extends State { isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( - height: 40, - child: TextField( - controller: controllers["hotel_chain"], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "Hotel Chain", - labelStyle: TextStyle(fontSize: 11, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - ), - )), + height: 40, + child: TextField( + controller: controllers["hotel_chain"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Hotel Chain", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), ), if (errorMessages["hotel_chain"] != null) ...[ SizedBox(height: 5), // Space before error message @@ -363,55 +375,23 @@ class HotelsDataState extends State { ], ], ), - SizedBox(height: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "City", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), - ), - SizedBox(height: 5), - CustomTextFieldForexWrapper( - isFocused: false, - isDesktop: widget.isDesktop, - color: Colors.transparent, - child: SizedBox( - height: 40, - child: TextField( - controller: controllers["city"], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "City", - labelStyle: TextStyle(fontSize: 11, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - ), - )), - ), - if (errorMessages["city"] != null) ...[ - SizedBox(height: 5), // Space before error message - Text( - errorMessages["city"]!, - style: TextStyle(color: Colors.red, fontSize: 12), - ), - ], - ], - ), + + // - It has been observed that many of the dropdowns have overlapping issues, causing label names to be hidden - just copied searchable dropdown - still not completed (user mangement screen only ) master page except policy - my trips - flight taxi train insurance, visa misscenllo color white size padding data + // - Delete option is not working in the policy list page - completed + // - Label Names for all the modules should be set bold as it is looking like normal text in user management compared to trips page - completed + // - In the masters org mangement search option not working for traveller - particular 4 master page - - issues occur - commpleted - email master working fine, traveller master working fine, amount master working fine, group - completed + // - QC- Authentication - - completed - ask to check const SizedBox(height: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Country", + "Country *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -423,18 +403,19 @@ class HotelsDataState extends State { selectedItem: countryMap[selectedCountry], popupProps: PopupProps.menu( showSearchBox: true, // Enables search functionality - menuProps: const MenuProps( - backgroundColor: Colors.white, - ), + menuProps: const MenuProps(backgroundColor: Colors.white), constraints: BoxConstraints(maxHeight: 250), - itemBuilder: (context, item, isSelected) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 6.0), - child: Text( - item, - style: GoogleFonts.poppins(fontSize: 11.5), - ), - ), + itemBuilder: + (context, item, isSelected) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 6.0, + ), + child: Text( + item, + style: GoogleFonts.poppins(fontSize: 11.5), + ), + ), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search Country...", @@ -447,25 +428,25 @@ class HotelsDataState extends State { dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 1, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), + ), + dropdownBuilder: + (context, selectedItem) => Align( + // Center-align selected item + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select Country", + style: GoogleFonts.poppins(fontSize: 11), + ), ), - ), - ), - dropdownBuilder: (context, selectedItem) => Align( - // Center-align selected item - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select Country", - style: GoogleFonts.poppins(fontSize: 11), - ), - ), onChanged: (String? newValue) { setState(() { // Find the country_code based on selected country_name - selectedCountry = countryMap.entries - .firstWhere((entry) => entry.value == newValue) - .key; + selectedCountry = + countryMap.entries + .firstWhere((entry) => entry.value == newValue) + .key; selectedCountryName = newValue; }); }, @@ -481,7 +462,48 @@ class HotelsDataState extends State { ], ], ), - SizedBox( height: 15 ), + const SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "City *", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), + ), + SizedBox(height: 5), + CustomTextFieldForexWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + color: Colors.transparent, + child: SizedBox( + height: 40, + child: TextField( + controller: controllers["city"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "City", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + if (errorMessages["city"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["city"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + SizedBox(height: 10), // if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,), if (hotelsDataId != null) Row( @@ -490,13 +512,14 @@ class HotelsDataState extends State { Text( "Change Status ", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), Tooltip( message: - isActive == "1" ? "Tap to deactivate" : "Tap to activate", + isActive == "1" ? "Tap to deactivate" : "Tap to activate", child: GestureDetector( onTap: toggleStatus, child: Text( @@ -508,13 +531,10 @@ class HotelsDataState extends State { ), ), ), - ) + ), ], ), - if (hotelsDataId != null) - SizedBox( - height: 15, - ), + if (hotelsDataId != null) SizedBox(height: 15), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -551,13 +571,17 @@ class HotelsDataState extends State { borderRadius: BorderRadius.circular(8), ), ), - child: Text('Save', - style: GoogleFonts.poppins( - fontSize: 11, color: Colors.white)), + child: Text( + 'Save', + style: GoogleFonts.poppins( + fontSize: 11, + color: Colors.white, + ), + ), ), ), ], - ) + ), // : SizedBox.shrink(), ], ), diff --git a/lib/Screens/hotels/hotels_list.dart b/lib/Screens/hotels/hotels_list.dart index 7db65d8..b3ffcce 100644 --- a/lib/Screens/hotels/hotels_list.dart +++ b/lib/Screens/hotels/hotels_list.dart @@ -102,7 +102,7 @@ class HotelsDataListState extends State { Future> fetchGetHotels() async { orgId = await getOrgId(); - final String apiUrlData = '$apiUrl/api/getHotels'; + final String apiUrlData = '$apiUrl/api/getHotels?for=table_view'; final String? token = await getToken(); @@ -646,7 +646,7 @@ class HotelsDataListState extends State { color: hotels['is_active'] == "1" ? Colors.green - : Colors.red, + : Colors.grey, ), softWrap: true, overflow: TextOverflow.ellipsis, diff --git a/lib/Screens/itnerary/flights.dart b/lib/Screens/itnerary/flights.dart index b4ca591..df04dd9 100644 --- a/lib/Screens/itnerary/flights.dart +++ b/lib/Screens/itnerary/flights.dart @@ -21,19 +21,19 @@ class FlightScreen extends StatefulWidget { final Map? selectedItem; final ValueNotifier tripTypeNotifier; - FlightScreen( - {Key? key, - required this.apiData, - required this.loginUser, - required this.onClose, - required this.onSaveFlight, - required this.selectedItem, - required this.flightData, - required this.hasAction, - this.tripType, - required this.tripTypeNotifier, - this.apiDataForClass}) - : super(key: key); + FlightScreen({ + Key? key, + required this.apiData, + required this.loginUser, + required this.onClose, + required this.onSaveFlight, + required this.selectedItem, + required this.flightData, + required this.hasAction, + this.tripType, + required this.tripTypeNotifier, + this.apiDataForClass, + }) : super(key: key); @override FlightScreenState createState() => FlightScreenState(); @@ -70,7 +70,7 @@ class FlightScreenState extends State { "_date", "_visa", "_time", - "_comments" + "_comments", ]; Map focusNodes = {}; @@ -148,14 +148,18 @@ class FlightScreenState extends State { // Loop through each row and add listeners to clear errors for (int i = 1; i <= rowCount; i++) { - textControllers["_from${i}Controller"] - ?.addListener(() => _clearError("from_place_$i")); - textControllers["_to${i}Controller"] - ?.addListener(() => _clearError("to_place_$i")); - textControllers["_date${i}Controller"] - ?.addListener(() => _clearError("date_$i")); - textControllers["_time${i}Controller"] - ?.addListener(() => _clearError("time_$i")); + textControllers["_from${i}Controller"]?.addListener( + () => _clearError("from_place_$i"), + ); + textControllers["_to${i}Controller"]?.addListener( + () => _clearError("to_place_$i"), + ); + textControllers["_date${i}Controller"]?.addListener( + () => _clearError("date_$i"), + ); + textControllers["_time${i}Controller"]?.addListener( + () => _clearError("time_$i"), + ); } // loadCountryList(); @@ -178,17 +182,16 @@ class FlightScreenState extends State { } Map getFlightTripDateRange( - List> flightData) { - final allTrips = flightData - .expand((flight) => flight['trips'] ?? []) - .whereType>() - .toList(); + List> flightData, + ) { + final allTrips = + flightData + .expand((flight) => flight['trips'] ?? []) + .whereType>() + .toList(); if (allTrips.isEmpty) { - return { - 'firstTripDate': null, - 'lastTripDate': null, - }; + return {'firstTripDate': null, 'lastTripDate': null}; } allTrips.sort((a, b) { @@ -293,9 +296,10 @@ class FlightScreenState extends State { print("Text Controllers KeysII: ${textControllers.keys.toList()}"); // Determine the row count based on selectedTripType - int rowCount = selectedTripType == "Roundtrip" - ? 2 - : selectedTripType == "Multitrip" + int rowCount = + selectedTripType == "Roundtrip" + ? 2 + : selectedTripType == "Multitrip" ? multiTripRowCount : 1; @@ -476,10 +480,12 @@ class FlightScreenState extends State { // TextEditingController(text: trip["from_place"]); // textControllers["_to${index}Controller"] = // TextEditingController(text: trip["to_place"]); - textControllers["_date${index}Controller"] = - TextEditingController(text: trip["date"]); - textControllers["_time${index}Controller"] = - TextEditingController(text: trip["time"]); + textControllers["_date${index}Controller"] = TextEditingController( + text: trip["date"], + ); + textControllers["_time${index}Controller"] = TextEditingController( + text: trip["time"], + ); // Check if editing and flight_trip_id exists for this trip if (widget.selectedItem != null && @@ -541,10 +547,9 @@ class FlightScreenState extends State { final currDateTime = format.parse("$currDateStr $currTimeStr"); if (!currDateTime.isAfter(prevDateTime)) { - errorMessages["time_$index"] = "Must be after previous time"; + errorMessages["time_$index"] = "30 mins gap required"; } else if (currDateTime.difference(prevDateTime).inMinutes < 30) { - errorMessages["time_$index"] = - "Must be least 30 mins after previous time"; + errorMessages["time_$index"] = "30 mins gap required"; } else { errorMessages.remove("time_$index"); } @@ -665,32 +670,34 @@ class FlightScreenState extends State { @override Widget build(BuildContext context) { - return ResponsiveBuilder(builder: (context, sizingInfo) { - bool isMobile = sizingInfo.isMobile; - bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + return ResponsiveBuilder( + builder: (context, sizingInfo) { + bool isMobile = sizingInfo.isMobile; + bool isDesktop = + sizingInfo.deviceScreenType == DeviceScreenType.desktop; - return Container( - // color: Color(0xFFF4F4FB), - // color: Color(0xFFF9F9F9), // Slightly lighter than white - - child: Form( - key: _formKey, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(20.0), - child: Center( - child: Column(children: _buildAccomadtionForm(isDesktop)), + return Container( + // color: Color(0xFFF4F4FB), + // color: Color(0xFFF9F9F9), // Slightly lighter than white + child: Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(20.0), + child: Center( + child: Column(children: _buildAccomadtionForm(isDesktop)), + ), ), - ) - ], + ], + ), ), ), - ), - ); - }); + ); + }, + ); } List _buildAccomadtionForm(bool isDesktop) { @@ -703,14 +710,14 @@ class FlightScreenState extends State { List> rowBuilders = [ // _builClassType(isDesktop, 1), - _buildSecondRow(isDesktop, 1) + _buildSecondRow(isDesktop, 1), ]; List> rowRoundBuilders = [ // _builClassType(isDesktop, 1), _buildSecondRow(isDesktop, 1), // _builClassType(isDesktop, 2), - _buildSecondRow(isDesktop, 2) + _buildSecondRow(isDesktop, 2), ]; print("Trip Type Selected: $selectedTripType"); @@ -740,7 +747,6 @@ class FlightScreenState extends State { // ...List.generate(multiTripRowCount, (index) => // buildResponsiveRow(_builClassType(isDesktop, index + 1) + _buildSecondRow(isDesktop, index + 1)) // ).expand((row) => row), - if (selectedTripType == "Multitrip") Align( alignment: Alignment.centerRight, @@ -809,41 +815,42 @@ class FlightScreenState extends State { Text( "Trip Type", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), isDesktop ? Row(children: _buildTripType(isDesktop)) - : Column(children: _buildTripType(isDesktop)) + : Column(children: _buildTripType(isDesktop)), ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), ]; } List _buildTripType(bool isDesktop) { List purposeList = widget.apiData?['flight_trip_type'] ?? []; - List> dropdownItems = purposeList - .map((item) => DropdownMenuItem( - value: item['dropdown_value'], - child: Text(item['dropdown_value']), - )) - .toList(); + List> dropdownItems = + purposeList + .map( + (item) => DropdownMenuItem( + value: item['dropdown_value'], + child: Text(item['dropdown_value']), + ), + ) + .toList(); if (dropdownItems.isEmpty) { dropdownItems.add( DropdownMenuItem( value: null, - child: Text("No options available", - style: TextStyle(color: Colors.grey)), + child: Text( + "No options available", + style: TextStyle(color: Colors.grey), + ), ), ); } @@ -858,9 +865,10 @@ class FlightScreenState extends State { height: 40, width: double.infinity, child: DropdownSearch( - items: purposeList - .map((item) => item['dropdown_value'] as String) - .toList(), + items: + purposeList + .map((item) => item['dropdown_value'] as String) + .toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, @@ -876,29 +884,35 @@ class FlightScreenState extends State { errorMessages.clear(); }); print( - "Updating form data: Flight -> trip_type -> $selectedTripType"); + "Updating form data: Flight -> trip_type -> $selectedTripType", + ); _initializeFields(); }, selectedItem: selectedTripType, - 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), + ), + ), popupProps: PopupProps.menu( constraints: BoxConstraints(maxHeight: 100), menuProps: MenuProps(backgroundColor: Colors.white), - itemBuilder: (context, item, isSelected) => Padding( - padding: - const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0), - child: Text( - item, - style: TextStyle( - fontSize: 13), // Custom text size for dropdown items - ), - ), + itemBuilder: + (context, item, isSelected) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 6.0, + ), + child: Text( + item, + style: TextStyle( + fontSize: 13, + ), // Custom text size for dropdown items + ), + ), ), ), @@ -999,9 +1013,9 @@ class FlightScreenState extends State { return [ Container( padding: const EdgeInsets.all(10), + // padding: const EdgeInsets.only(left: 10, right: 10), // color: Colors.white, - child: Text( "Trip ${index}", style: TextStyle( @@ -1012,17 +1026,14 @@ class FlightScreenState extends State { ), ), SizedBox( - width: isDesktop - ? MediaQuery.of(context).size.width * 0.58 - : 80, // Ensure full width + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.58 + : 80, // Ensure full width child: Stack( alignment: Alignment.center, // Centers the icon children: [ - Divider( - color: Color(0xFF8B8FB2), - thickness: 0.5, - height: 20, - ), + Divider(color: Color(0xFF8B8FB2), thickness: 0.5, height: 20), Container( // padding: EdgeInsets.all(4), color: Colors.white, // Background to avoid overlapping @@ -1064,7 +1075,6 @@ class FlightScreenState extends State { // ], // ), // ), - Container( // color: Colors.white, // padding: const EdgeInsets.only(left: 10, right: 10), @@ -1076,7 +1086,7 @@ class FlightScreenState extends State { color: Colors.blueAccent, iconSize: 20, ), - ) + ), ]; } @@ -1085,19 +1095,24 @@ class FlightScreenState extends State { List purposeList = widget.apiDataForClass?['flight_class'] ?? []; - List> dropdownItems = purposeList - .map((item) => DropdownMenuItem( - value: item['dropdown_key'], - child: Text(item['dropdown_value']), - )) - .toList(); + List> dropdownItems = + purposeList + .map( + (item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + ), + ) + .toList(); if (dropdownItems.isEmpty) { dropdownItems.add( DropdownMenuItem( value: null, - child: Text("No options available", - style: TextStyle(color: Colors.grey)), + child: Text( + "No options available", + style: TextStyle(color: Colors.grey), + ), ), ); } @@ -1120,8 +1135,9 @@ class FlightScreenState extends State { flightLastTripDateNotifier.value != null && flightLastTripDateNotifier.value!.isNotEmpty) { try { - final tripDate = DateFormat('dd-MM-yyyy') - .parseStrict(flightLastTripDateNotifier.value!); + final tripDate = DateFormat( + 'dd-MM-yyyy', + ).parseStrict(flightLastTripDateNotifier.value!); if (tripDate.isAfter(today)) { firstDate = tripDate; } @@ -1133,8 +1149,9 @@ class FlightScreenState extends State { textControllers["_date${index - 1}Controller"]?.text; if (previousDateString != null && previousDateString.isNotEmpty) { try { - final previousDate = - DateFormat('dd-MM-yyyy').parseStrict(previousDateString); + final previousDate = DateFormat( + 'dd-MM-yyyy', + ).parseStrict(previousDateString); if (previousDate.isAfter(today)) { firstDate = previousDate; } @@ -1144,10 +1161,11 @@ class FlightScreenState extends State { } } - DateTime initialDate = _selectedCheckOutDate != null && - _selectedCheckOutDate!.isAfter(firstDate) - ? _selectedCheckOutDate! - : firstDate; + DateTime initialDate = + _selectedCheckOutDate != null && + _selectedCheckOutDate!.isAfter(firstDate) + ? _selectedCheckOutDate! + : firstDate; DateTime? pickedDate = await showDatePicker( context: context, @@ -1160,14 +1178,18 @@ class FlightScreenState extends State { setState(() { _selectedCheckOutDate = pickedDate; // _dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate); - textControllers["_date${index}Controller"]?.text = - DateFormat('dd-MM-yyyy').format(pickedDate); + textControllers["_date${index}Controller"]?.text = DateFormat( + 'dd-MM-yyyy', + ).format(pickedDate); }); } } Future _selectCheckOutTime( - BuildContext context, int index, VoidCallback onPicked) async { + BuildContext context, + int index, + VoidCallback onPicked, + ) async { TimeOfDay? pickedTime = await showTimePicker( context: context, initialTime: _selectedCheckOutTime ?? TimeOfDay.now(), @@ -1179,8 +1201,13 @@ class FlightScreenState extends State { // Formatting time to HH:mm (24-hour format) final now = DateTime.now(); final formattedTime = DateFormat('HH:mm').format( - DateTime(now.year, now.month, now.day, pickedTime.hour, - pickedTime.minute), + DateTime( + now.year, + now.month, + now.day, + pickedTime.hour, + pickedTime.minute, + ), ); // _timeController.text = formattedTime; textControllers["_time${index}Controller"]?.text = formattedTime; @@ -1208,50 +1235,57 @@ class FlightScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "From", + "From*", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( - // isFocused: _fromFocus, - isFocused: focusStates["_from${index}Focused"] ?? false, - // isFocused: focusStates["_from${fieldIndex}Focused"] ?? false, - isDesktop: isDesktop, - child: SizedBox( - height: 40, - child: isCountryLoading - ? Center(child: CircularProgressIndicator()) - : DropdownSearch( - selectedItem: selectedFrom[index] != null - ? countryMap[selectedFrom[index]] - : null, + // isFocused: _fromFocus, + isFocused: focusStates["_from${index}Focused"] ?? false, + // isFocused: focusStates["_from${fieldIndex}Focused"] ?? false, + isDesktop: isDesktop, + child: SizedBox( + height: 40, + child: + isCountryLoading + ? Center(child: CircularProgressIndicator()) + : DropdownSearch( + selectedItem: + selectedFrom[index] != null + ? countryMap[selectedFrom[index]] + : null, popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 220), showSearchBox: true, // Enables search functionality searchFieldProps: TextFieldProps( - decoration: InputDecoration( - hintText: "Search...", - contentPadding: EdgeInsets.symmetric( - horizontal: 10, vertical: 1), + decoration: InputDecoration( + hintText: "Search...", + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 1, ), - style: TextStyle(fontSize: 12)), - menuProps: MenuProps( - backgroundColor: Colors.white, - ), - itemBuilder: (context, item, isSelected) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 6.0), - child: Text( - item, - style: TextStyle( - fontSize: - 13), // 👈 Set your desired text size here ), + style: TextStyle(fontSize: 12), ), + menuProps: MenuProps(backgroundColor: Colors.white), + itemBuilder: + (context, item, isSelected) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 6.0, + ), + child: Text( + item, + style: TextStyle( + fontSize: 13, + ), // 👈 Set your desired text size here + ), + ), ), items: countryMap.values.toList(), dropdownDecoratorProps: DropDownDecoratorProps( @@ -1260,109 +1294,110 @@ class FlightScreenState extends State { contentPadding: EdgeInsets.symmetric(horizontal: 1), ), ), - dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select", - style: TextStyle(fontSize: 12), - ), - ), + dropdownBuilder: + (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select", + style: TextStyle(fontSize: 12), + ), + ), onChanged: (String? newValue) { setState(() { // selectedFrom[index] = countryMap.entries // .firstWhere((entry) => entry.value == newValue) // .key; - selectedFrom[index] = countryMap.entries - .firstWhere((entry) => entry.value == newValue) - .key; + selectedFrom[index] = + countryMap.entries + .firstWhere( + (entry) => entry.value == newValue, + ) + .key; print(selectedFrom[index]); }); }, ), - ) + ), - // child: SizedBox( - // height: 40, - // child: TextField( - // // focusNode: _fromFocusNode, - // focusNode: focusNodes["_from${index}FocusNode"], - // controller: textControllers["_from${index}Controller"], - // style: const TextStyle(fontSize: 12), - // decoration: const InputDecoration( - // labelText: "From", - // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), - // floatingLabelBehavior: FloatingLabelBehavior.never, - // border: InputBorder.none, - // contentPadding: EdgeInsets.symmetric(vertical: 16), - // ), - // ), - // ), - ), + // child: SizedBox( + // height: 40, + // child: TextField( + // // focusNode: _fromFocusNode, + // focusNode: focusNodes["_from${index}FocusNode"], + // controller: textControllers["_from${index}Controller"], + // style: const TextStyle(fontSize: 12), + // decoration: const InputDecoration( + // labelText: "From", + // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + // floatingLabelBehavior: FloatingLabelBehavior.never, + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric(vertical: 16), + // ), + // ), + // ), + ), if (errorMessages["from_place_$index"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), - if (isDesktop) - SizedBox( - width: 20, - ) - else - SizedBox( - height: 8, - ), + if (isDesktop) SizedBox(width: 20) else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "To", + "To*", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( - isFocused: focusStates["_to${index}Focused"] ?? false, - isDesktop: isDesktop, - child: SizedBox( - height: 40, - child: isCountryLoading - ? Center(child: CircularProgressIndicator()) - : DropdownSearch( - selectedItem: selectedTo[index] != null - ? countryMap[selectedTo[index]] - : null, + isFocused: focusStates["_to${index}Focused"] ?? false, + isDesktop: isDesktop, + child: SizedBox( + height: 40, + child: + isCountryLoading + ? Center(child: CircularProgressIndicator()) + : DropdownSearch( + selectedItem: + selectedTo[index] != null + ? countryMap[selectedTo[index]] + : null, popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 220), showSearchBox: true, // Enables search functionality searchFieldProps: TextFieldProps( - decoration: InputDecoration( - hintText: "Search...", - contentPadding: EdgeInsets.symmetric( - horizontal: 10, vertical: 1), + decoration: InputDecoration( + hintText: "Search...", + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 1, ), - style: TextStyle(fontSize: 12)), - menuProps: MenuProps( - backgroundColor: Colors.white, - ), - itemBuilder: (context, item, isSelected) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 6.0), - child: Text( - item, - style: TextStyle( - fontSize: - 13), // 👈 Set your desired text size here ), + style: TextStyle(fontSize: 12), ), + menuProps: MenuProps(backgroundColor: Colors.white), + itemBuilder: + (context, item, isSelected) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 6.0, + ), + child: Text( + item, + style: TextStyle( + fontSize: 13, + ), // 👈 Set your desired text size here + ), + ), ), items: countryMap.values.toList(), dropdownDecoratorProps: DropDownDecoratorProps( @@ -1371,48 +1406,46 @@ class FlightScreenState extends State { contentPadding: EdgeInsets.symmetric(horizontal: 1), ), ), - dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select Country", - style: TextStyle(fontSize: 12), - ), - ), + dropdownBuilder: + (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select Country", + style: TextStyle(fontSize: 12), + ), + ), onChanged: (String? newValue) { setState(() { - selectedTo[index] = countryMap.entries - .firstWhere((entry) => entry.value == newValue) - .key; + selectedTo[index] = + countryMap.entries + .firstWhere( + (entry) => entry.value == newValue, + ) + .key; print(selectedTo[index]); }); }, ), - )), + ), + ), if (errorMessages["to_place_$index"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Class *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -1423,50 +1456,49 @@ class FlightScreenState extends State { // : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, - child: isFlightClassLoading - ? const Center(child: CircularProgressIndicator()) - : DropdownButtonFormField( - focusNode: focusNodes["_class${index}FocusNode"], - // focusNode: _tripTypeFocusNode, // Assign the correct focus node - // controller: _hotelNameController, - value: selectedClasses[index], + child: + isFlightClassLoading + ? const Center(child: CircularProgressIndicator()) + : DropdownButtonFormField( + focusNode: focusNodes["_class${index}FocusNode"], + // focusNode: _tripTypeFocusNode, // Assign the correct focus node + // controller: _hotelNameController, + value: selectedClasses[index], - style: TextStyle(fontSize: 12), - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 10), // Proper padding + style: TextStyle(fontSize: 12), + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + ), // Proper padding + ), + onChanged: + purposeList.isNotEmpty + ? (newValue) { + setState(() { + selectedClasses[index] = newValue; + }); + + print(selectedClasses[index]); + } + : null, + items: dropdownItems, ), - onChanged: purposeList.isNotEmpty - ? (newValue) { - setState(() { - selectedClasses[index] = newValue; - }); - - print(selectedClasses[index]); - } - : null, - items: dropdownItems, - ), ), ), ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Date", + "Date*", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -1489,8 +1521,11 @@ class FlightScreenState extends State { floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), - suffixIcon: Icon(Icons.calendar_today, - size: 16, color: Colors.grey), + suffixIcon: Icon( + Icons.calendar_today, + size: 16, + color: Colors.grey, + ), ), ), ), @@ -1499,28 +1534,21 @@ class FlightScreenState extends State { ), if (errorMessages["date_$index"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Time", + "Time*", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -1538,7 +1566,8 @@ class FlightScreenState extends State { _selectCheckOutTime(context, index, () { validateTimeDifference(index); setState( - () {}); // ✅ Force rebuild to show the error immediately + () {}, + ); // ✅ Force rebuild to show the error immediately }); }, child: AbsorbPointer( @@ -1554,8 +1583,11 @@ class FlightScreenState extends State { floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), - suffixIcon: - Icon(Icons.access_time, size: 16, color: Colors.grey), + suffixIcon: Icon( + Icons.access_time, + size: 16, + color: Colors.grey, + ), ), ), ), @@ -1579,13 +1611,9 @@ class FlightScreenState extends State { onPressed: () { removeTrip(index); }, - icon: Icon( - Icons.close, - color: Colors.redAccent, - size: 20, - ), + icon: Icon(Icons.close, color: Colors.redAccent, size: 20), ), - ) + ), ]; } @@ -1594,12 +1622,15 @@ class FlightScreenState extends State { widget.apiData?['flight_visa_available'] ?? []; // Default selected value - List> dropdownItems = visa_available - .map((item) => DropdownMenuItem( - value: item['dropdown_key'], - child: Text(item['dropdown_value']), - )) - .toList(); + List> dropdownItems = + visa_available + .map( + (item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + ), + ) + .toList(); selectedvisa_available ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : null; @@ -1608,8 +1639,10 @@ class FlightScreenState extends State { dropdownItems.add( DropdownMenuItem( value: null, - child: Text("No options available", - style: TextStyle(color: Colors.grey)), + child: Text( + "No options available", + style: TextStyle(color: Colors.grey), + ), ), ); } @@ -1620,9 +1653,10 @@ class FlightScreenState extends State { Text( "Visa Required", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -1641,22 +1675,25 @@ class FlightScreenState extends State { style: TextStyle(fontSize: 12), decoration: InputDecoration( border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10), // Proper padding + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + ), // Proper padding ), - onChanged: visa_available.isNotEmpty - ? (newValue) { - setState(() { - selectedvisa_available = newValue; - // selectedTripType = "Oneway"; - // Reset `multiTripRowCount` when switching away from Multitrip - }); - print( - "Updating form data: Flight -> trip_type -> $selectedvisa_available"); + onChanged: + visa_available.isNotEmpty + ? (newValue) { + setState(() { + selectedvisa_available = newValue; + // selectedTripType = "Oneway"; + // Reset `multiTripRowCount` when switching away from Multitrip + }); + print( + "Updating form data: Flight -> trip_type -> $selectedvisa_available", + ); - // _initializeRows(); - } - : null, + // _initializeRows(); + } + : null, items: dropdownItems, ), @@ -1664,22 +1701,18 @@ class FlightScreenState extends State { ), ], ), - if (isDesktop) - SizedBox( - width: 20, - ), - SizedBox( - height: 5, - ), + if (isDesktop) SizedBox(width: 20), + SizedBox(height: 5), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Comments", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( @@ -1705,9 +1738,7 @@ class FlightScreenState extends State { ], ), if (isDesktop) Spacer(), - SizedBox( - height: 5, - ), + SizedBox(height: 5), Column( children: [ Row( @@ -1724,12 +1755,15 @@ class FlightScreenState extends State { widget.apiData?['flight_visa_available'] ?? []; // Default selected value - List> dropdownItems = visa_available - .map((item) => DropdownMenuItem( - value: item['dropdown_key'], - child: Text(item['dropdown_value']), - )) - .toList(); + List> dropdownItems = + visa_available + .map( + (item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + ), + ) + .toList(); selectedvisa_available ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : null; @@ -1738,8 +1772,10 @@ class FlightScreenState extends State { dropdownItems.add( DropdownMenuItem( value: null, - child: Text("No options available", - style: TextStyle(color: Colors.grey)), + child: Text( + "No options available", + style: TextStyle(color: Colors.grey), + ), ), ); } @@ -1751,18 +1787,20 @@ class FlightScreenState extends State { Text( "Visa Required", style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( // isFocused: _tripTypeFocused, isFocused: focusStates["_visa1Focused"] ?? false, isDesktop: isDesktop, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.66, + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, child: DropdownButtonFormField( @@ -1772,22 +1810,25 @@ class FlightScreenState extends State { style: TextStyle(fontSize: 12), decoration: InputDecoration( border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10), // Proper padding + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + ), // Proper padding ), - onChanged: visa_available.isNotEmpty - ? (newValue) { - setState(() { - selectedvisa_available = newValue; - // selectedTripType = "Oneway"; - // Reset `multiTripRowCount` when switching away from Multitrip - }); - print( - "Updating form data: Flight -> trip_type -> $selectedvisa_available"); + onChanged: + visa_available.isNotEmpty + ? (newValue) { + setState(() { + selectedvisa_available = newValue; + // selectedTripType = "Oneway"; + // Reset `multiTripRowCount` when switching away from Multitrip + }); + print( + "Updating form data: Flight -> trip_type -> $selectedvisa_available", + ); - // _initializeRows(); - } - : null, + // _initializeRows(); + } + : null, items: dropdownItems, ), @@ -1807,9 +1848,7 @@ class FlightScreenState extends State { }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( @@ -1818,7 +1857,6 @@ class FlightScreenState extends State { ), ), SizedBox(width: 10), // Space between buttons - // Save Changes Button ElevatedButton( onPressed: () { @@ -1826,9 +1864,7 @@ class FlightScreenState extends State { }, style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF114D8B), // Primary color for save - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( diff --git a/lib/Screens/itnerary/forex.dart b/lib/Screens/itnerary/forex.dart index bef348b..0d93906 100644 --- a/lib/Screens/itnerary/forex.dart +++ b/lib/Screens/itnerary/forex.dart @@ -223,8 +223,6 @@ class _ForexScreenState extends State { } bool isValidForexData(Map data) { - errorMessages.clear(); // Reset errors - // Required fields that must not be empty List requiredFields = [ "start_date", @@ -232,7 +230,7 @@ class _ForexScreenState extends State { "country_code", "deposit_on_card", "deposit_on_cash", - // "card_number" + // "card_number", ]; // If have_card is "1", then delivery_location is required @@ -246,11 +244,17 @@ class _ForexScreenState extends State { // Check validation for each field for (String field in requiredFields) { if (data[field] == null || data[field].toString().trim().isEmpty) { - errorMessages[field] = "This field is required"; + errorMessages[field] = "Required"; } } - return errorMessages.isEmpty; // Valid if there are no errors + return errorMessages.values.every((msg) => msg.trim().isEmpty); + + // if (hasErrors) { + // print("At least one error message is present."); + // } + // + // return errorMessages.isEmpty; // Valid if there are no errors } Map getFlightTripDateRange( @@ -286,7 +290,7 @@ class _ForexScreenState extends State { Map data = forexData; - if (!isValidForexData(data)) { + if (!isValidForexData(data) && errorMessages.isNotEmpty) { print("Validation Failed: Required fields are missing."); setState(() {}); return; // Stop execution if validation fails @@ -613,7 +617,7 @@ class _ForexScreenState extends State { errorMessages["deposit_on_card"] = "Enter Valid Amount"; // Clear error if valid } else { - errorMessages["deposit_on_card"] = ""; // Clear error if valid + errorMessages.remove("deposit_on_card"); // Clear error if valid } // Refresh UI if using StatefulWidget @@ -621,7 +625,8 @@ class _ForexScreenState extends State { } void _validateCashAmount(String value) { - errorMessages["deposit_on_card"] = " "; + // errorMessages["deposit_on_card"] = " "; + errorMessages.remove("deposit_on_cash"); print("_validateCashAmount - $value - $fifteenPercent"); int? enteredAmount = int.tryParse(value); @@ -642,11 +647,18 @@ class _ForexScreenState extends State { textControllers["_card"]?.text = difference.toString(); if (enteredAmount > fifteenPercent) { + if (checkValidAmount > 0) { + textControllers["_card"]?.text = "0"; + errorMessages["deposit_on_card"] = + "Enter Valid Amount"; // Clear error if valid + } errorMessages["deposit_on_cash"] = "Amount cannot exceed $fifteenPercent"; } else if (checkValidAmount == quotedAmount) { - errorMessages["deposit_on_card"] = " "; // Clear error if valid + errorMessages.remove("deposit_on_cash"); + // errorMessages["deposit_on_card"] = " "; // Clear error if valid } else { - errorMessages["deposit_on_cash"] = ""; // Clear error if valid + // errorMessages["deposit_on_cash"] = ""; // Clear error if valid + errorMessages.remove("deposit_on_cash"); } // Refresh UI if using StatefulWidget @@ -887,7 +899,7 @@ class _ForexScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Start Date", + "Start Date *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, @@ -958,7 +970,7 @@ class _ForexScreenState extends State { if (errorMessages["start_date"] != null) ...[ SizedBox(height: 5), // Space before error message Text( - "Select Start Date", + errorMessages["start_date"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], @@ -969,7 +981,7 @@ class _ForexScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "End Date", + "End Date *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, @@ -1123,7 +1135,7 @@ class _ForexScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Country", + "Country*", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, @@ -1179,7 +1191,7 @@ class _ForexScreenState extends State { if (errorMessages["country_code"] != null) ...[ SizedBox(height: 5), // Space before error message Text( - "Select Country", + errorMessages["country_code"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], @@ -1600,8 +1612,12 @@ class _ForexScreenState extends State { child: TextField( focusNode: focusNodes["_cash"], controller: textControllers["_cash"], - style: const TextStyle(fontSize: 12), keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, // ✅ Only allow digits + ], + style: const TextStyle(fontSize: 12), + // keyboardType: TextInputType.number, onChanged: (value) { // errorMessages["deposit_on_cash"] = ""; _validateCashAmount( @@ -1653,6 +1669,9 @@ class _ForexScreenState extends State { controller: textControllers["_card"], style: const TextStyle(fontSize: 12), keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, // ✅ Only allow digits + ], onChanged: (value) { _validateCardAmount( value, @@ -1884,7 +1903,7 @@ class _ForexScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Card Number", + "Card Number*", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, diff --git a/lib/Screens/itnerary/train.dart b/lib/Screens/itnerary/train.dart index e2ddd7d..3f2e5d8 100644 --- a/lib/Screens/itnerary/train.dart +++ b/lib/Screens/itnerary/train.dart @@ -17,14 +17,15 @@ class TrainScreen extends StatefulWidget { final String? loginUser; final String? tripType; - TrainScreen( - {required this.onClose, - this.apiData, - required this.onSavetrain, - required this.selectedItem, - required this.loginUser, - this.apiDataForClass, - this.tripType}); + TrainScreen({ + required this.onClose, + this.apiData, + required this.onSavetrain, + required this.selectedItem, + required this.loginUser, + this.apiDataForClass, + this.tripType, + }); @override _TrainScreenState createState() => _TrainScreenState(); @@ -232,7 +233,7 @@ class _TrainScreenState extends State { "from_station", "to_station", "date", - "time" + "time", ]; // Check validation for each field @@ -287,51 +288,54 @@ class _TrainScreenState extends State { @override Widget build(BuildContext context) { - return ResponsiveBuilder(builder: (context, sizingInfo) { - bool isMobile = sizingInfo.isMobile; - bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + return ResponsiveBuilder( + builder: (context, sizingInfo) { + bool isMobile = sizingInfo.isMobile; + bool isDesktop = + sizingInfo.deviceScreenType == DeviceScreenType.desktop; - return Container( - // color: Color(0xFFF4F4FB), - child: Form( - key: _formKey, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - // Align( - // alignment: Alignment.centerRight, - // child: InkWell( - // onTap: () { - // widget.onClose(false); - // }, - // child: Icon( - // Icons.close, - // size: 18, - // color: Color(0xFF575A74), - // ), - // ), - // ), - // Text("Train Booking List", - // style: TextStyle( - // fontSize: 18, - // fontWeight: FontWeight.bold, - // color: Color(0xFF575A74))), - // SizedBox( - // height: 6, - // ), - Padding( - padding: const EdgeInsets.all(28.0), - child: Center( - child: Column(children: _buildAccomadtionForm(isDesktop)), + return Container( + // color: Color(0xFFF4F4FB), + child: Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + // Align( + // alignment: Alignment.centerRight, + // child: InkWell( + // onTap: () { + // widget.onClose(false); + // }, + // child: Icon( + // Icons.close, + // size: 18, + // color: Color(0xFF575A74), + // ), + // ), + // ), + // Text("Train Booking List", + // style: TextStyle( + // fontSize: 18, + // fontWeight: FontWeight.bold, + // color: Color(0xFF575A74))), + // SizedBox( + // height: 6, + // ), + Padding( + padding: const EdgeInsets.all(28.0), + child: Center( + child: Column(children: _buildAccomadtionForm(isDesktop)), + ), ), - ) - ], + ], + ), ), ), - ), - ); - }); + ); + }, + ); } List _buildAccomadtionForm(bool isDesktop) { @@ -344,7 +348,7 @@ class _TrainScreenState extends State { List> rowBuilders = [ _builClassType(isDesktop), - _buildSecondRow(isDesktop) + _buildSecondRow(isDesktop), ]; return [ @@ -367,9 +371,10 @@ class _TrainScreenState extends State { Text( "Train Number", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), isDesktop @@ -377,38 +382,35 @@ class _TrainScreenState extends State { : Column(children: _buildTripType(isDesktop)), if (errorMessages["train_no"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), ]; } List _buildTripType(bool isDesktop) { List purposeList = widget.apiData?['flight_trip_type'] ?? []; - List> dropdownItems = purposeList - .map((item) => DropdownMenuItem( - value: item['dropdown_value'], - child: Text(item['dropdown_value']), - )) - .toList(); + List> dropdownItems = + purposeList + .map( + (item) => DropdownMenuItem( + value: item['dropdown_value'], + child: Text(item['dropdown_value']), + ), + ) + .toList(); if (dropdownItems.isEmpty) { dropdownItems.add( DropdownMenuItem( value: null, - child: Text("No options available", - style: TextStyle(color: Colors.grey)), + child: Text( + "No options available", + style: TextStyle(color: Colors.grey), + ), ), ); } @@ -455,10 +457,11 @@ class _TrainScreenState extends State { DateTime? pickedDate = await showDatePicker( context: context, - initialDate: _selectedCheckOutDate != null && - _selectedCheckOutDate!.isAfter(today) - ? _selectedCheckOutDate! - : today, + initialDate: + _selectedCheckOutDate != null && + _selectedCheckOutDate!.isAfter(today) + ? _selectedCheckOutDate! + : today, firstDate: today, lastDate: DateTime(2100), ); @@ -483,8 +486,13 @@ class _TrainScreenState extends State { // Formatting time to HH:mm (24-hour format) final now = DateTime.now(); final formattedTime = DateFormat('HH:mm').format( - DateTime(now.year, now.month, now.day, pickedTime.hour, - pickedTime.minute), + DateTime( + now.year, + now.month, + now.day, + pickedTime.hour, + pickedTime.minute, + ), ); _timeController.text = formattedTime; }); @@ -495,19 +503,24 @@ class _TrainScreenState extends State { List purposeList = widget.apiDataForClass?['train_class'] ?? []; - List> dropdownItems = purposeList - .map((item) => DropdownMenuItem( - value: item['dropdown_key'], - child: Text(item['dropdown_value']), - )) - .toList(); + List> dropdownItems = + purposeList + .map( + (item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + ), + ) + .toList(); if (dropdownItems.isEmpty) { dropdownItems.add( DropdownMenuItem( value: null, - child: Text("No options available", - style: TextStyle(color: Colors.grey)), + child: Text( + "No options available", + style: TextStyle(color: Colors.grey), + ), ), ); } @@ -523,9 +536,10 @@ class _TrainScreenState extends State { Text( "Class *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -543,86 +557,91 @@ class _TrainScreenState extends State { style: TextStyle(fontSize: 12), decoration: InputDecoration( border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10), // Proper padding + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + ), // Proper padding ), - onChanged: purposeList.isNotEmpty - ? (newValue) { - setState(() { - selectedClass = newValue; - }); - } - : null, + onChanged: + purposeList.isNotEmpty + ? (newValue) { + setState(() { + selectedClass = newValue; + }); + } + : null, items: dropdownItems, ), ), ), if (errorMessages["class"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "From", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( - isFocused: _fromFocus, - isDesktop: isDesktop, - child: SizedBox( - height: 40, - child: isCountryLoading - ? Center(child: CircularProgressIndicator()) - : DropdownSearch( + isFocused: _fromFocus, + isDesktop: isDesktop, + child: SizedBox( + height: 40, + child: + isCountryLoading + ? Center(child: CircularProgressIndicator()) + : DropdownSearch( // selectedItem: selectedFrom != null // ? countryMap[selectedFrom] // : null, - - selectedItem: selectedFrom != null - ? countryMap[ - selectedFrom] // get the display value from code - : null, + selectedItem: + selectedFrom != null + ? countryMap[selectedFrom] // get the display value from code + : null, popupProps: PopupProps.menu( + menuProps: MenuProps(backgroundColor: Colors.white), + constraints: BoxConstraints(maxHeight: 230), showSearchBox: true, searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search ...", - contentPadding: - EdgeInsets.symmetric(horizontal: 10), + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + ), ), ), ), + items: countryMap.values.toList(), + dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 5, + ), border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 1), - ), - ), - dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select", - style: TextStyle(fontSize: 12), ), ), + dropdownBuilder: + (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select", + style: TextStyle(fontSize: 12), + ), + ), + // onChanged: (String? newValue) { // setState(() { // // selectedFrom[index] = countryMap.entries @@ -636,56 +655,51 @@ class _TrainScreenState extends State { // print(selectedFrom); // }); // }, - onChanged: (String? newValue) { setState(() { - selectedFrom = countryMap.entries - .firstWhere((entry) => entry.value == newValue) - .key; + selectedFrom = + countryMap.entries + .firstWhere( + (entry) => entry.value == newValue, + ) + .key; }); }, ), - ) - // child: SizedBox( - // height: 40, - // child: TextField( - // focusNode: _fromFocusNode, - // controller: _fromController, - // style: const TextStyle(fontSize: 12), - // decoration: const InputDecoration( - // labelText: "From", - // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), - // floatingLabelBehavior: FloatingLabelBehavior.never, - // border: InputBorder.none, - // contentPadding: EdgeInsets.symmetric(vertical: 16), - // ), - // ), - // ), - ), + ), + // child: SizedBox( + // height: 40, + // child: TextField( + // focusNode: _fromFocusNode, + // controller: _fromController, + // style: const TextStyle(fontSize: 12), + // decoration: const InputDecoration( + // labelText: "From", + // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + // floatingLabelBehavior: FloatingLabelBehavior.never, + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric(vertical: 16), + // ), + // ), + // ), + ), if (errorMessages["from_station"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "To", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -693,71 +707,72 @@ class _TrainScreenState extends State { isDesktop: isDesktop, child: SizedBox( height: 40, - child: isCountryLoading - ? Center(child: CircularProgressIndicator()) - : DropdownSearch( - selectedItem: selectedTo != null - ? countryMap[ - selectedTo] // get the display value from code - : null, - popupProps: PopupProps.menu( - showSearchBox: true, - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - hintText: "Search ...", - contentPadding: - EdgeInsets.symmetric(horizontal: 10), + child: + isCountryLoading + ? Center(child: CircularProgressIndicator()) + : DropdownSearch( + selectedItem: + selectedTo != null + ? countryMap[selectedTo] // get the display value from code + : null, + popupProps: PopupProps.menu( + menuProps: MenuProps(backgroundColor: Colors.white), + constraints: BoxConstraints(maxHeight: 230), + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search ...", + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + ), + ), ), ), - ), - items: countryMap.values.toList(), - dropdownDecoratorProps: DropDownDecoratorProps( - dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 1), + items: countryMap.values.toList(), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), ), + dropdownBuilder: + (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select", + style: TextStyle(fontSize: 12), + ), + ), + onChanged: (String? newValue) { + setState(() { + selectedTo = + countryMap.entries + .firstWhere( + (entry) => entry.value == newValue, + ) + .key; + }); + }, ), - dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select", - style: TextStyle(fontSize: 12), - ), - ), - onChanged: (String? newValue) { - setState(() { - selectedTo = countryMap.entries - .firstWhere((entry) => entry.value == newValue) - .key; - }); - }, - ), ), ), if (errorMessages["to_station"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Date", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -779,8 +794,11 @@ class _TrainScreenState extends State { floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), - suffixIcon: Icon(Icons.calendar_today, - size: 16, color: Colors.grey), + suffixIcon: Icon( + Icons.calendar_today, + size: 16, + color: Colors.grey, + ), ), ), ), @@ -789,28 +807,21 @@ class _TrainScreenState extends State { ), if (errorMessages["date"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Time", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -832,8 +843,11 @@ class _TrainScreenState extends State { floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), - suffixIcon: - Icon(Icons.access_time, size: 16, color: Colors.grey), + suffixIcon: Icon( + Icons.access_time, + size: 16, + color: Colors.grey, + ), ), ), ), @@ -842,10 +856,7 @@ class _TrainScreenState extends State { ), if (errorMessages["time"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), @@ -860,9 +871,10 @@ class _TrainScreenState extends State { Text( "Comments", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( @@ -890,9 +902,7 @@ class _TrainScreenState extends State { ], ), if (isDesktop) Spacer(), - SizedBox( - height: 5, - ), + SizedBox(height: 5), Column( children: [ Row( @@ -900,7 +910,7 @@ class _TrainScreenState extends State { children: _handleAction(isDesktop), ), ], - ) + ), ]; } @@ -913,9 +923,7 @@ class _TrainScreenState extends State { }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( @@ -924,7 +932,6 @@ class _TrainScreenState extends State { ), ), SizedBox(width: 10), // Space between buttons - // Save Changes Button ElevatedButton( onPressed: () { @@ -932,9 +939,7 @@ class _TrainScreenState extends State { }, style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF114D8B), // Primary color for save - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index 3e86000..cb5bc4e 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -23,6 +23,7 @@ import '../../routes/custom_drawer.dart'; import '../../services/apiService.dart'; import '../../widgets/custom_radio_button.dart'; import '../../widgets/custom_text_field.dart'; +import '../../widgets/saving_loader.dart'; import '../approvals/approval_dialogs.dart'; import '../dialog/user_selection_dialog.dart'; import '../itnerary/flights.dart'; @@ -909,8 +910,8 @@ class CreateNewPlansState extends State { costCenterIds = costCenterMap.keys.toList(); // Optionally auto-select the first item if not already selected - selectedCostCenterId ??= - costCenterIds.isNotEmpty ? costCenterIds.first : null; + // selectedCostCenterId ??= + // costCenterIds.isNotEmpty ? costCenterIds.first : null; }); print('plansJSON'); @@ -1069,11 +1070,16 @@ class CreateNewPlansState extends State { miscellaneousList, ]; - bool anyServiceSelected = serviceLists.any( - (list) => list != null && list.isNotEmpty, - ); + // bool anyServiceSelected = serviceLists.any( + // (list) => list != null && list.isNotEmpty, + // ); + bool anyServiceSelected = serviceLists.any((list) { + return list != null && + list.any((entry) => entry['is_active'].toString() == "1"); + }); + if (!anyServiceSelected) { - // validationErrors["services"] = "Please select at least one service"; + validationErrors["services"] = "Please select at least one service"; setState(() { temporaryMessage = "Please select at least one service"; @@ -1172,7 +1178,7 @@ class CreateNewPlansState extends State { } void handleSubmit() { - setState(() { + setState(() async { if (validateForm() && temporaryMessage == null) { // if (selectedPlanId != null && selectedPlanId!.isNotEmpty) { // planData['plan_id'] = selectedPlanId; // Add plan_id for update @@ -1183,24 +1189,17 @@ class CreateNewPlansState extends State { // "${planData['traveller_id']}," // " ${selectedPlanId}, " // " "); + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const SavingLoader(), + ); + // postPlanData(planData); - postPlanData(planData); + await postPlanData(planData); - final currentUri = - GoRouterState.of( - context, - ).uri.toString(); // ✅ safer than `.location` - print("currentUri - $currentUri"); - - if (currentUri == "/allTrips/trips") { - context.go('/listAllPlan'); - } else if (currentUri == "/createPlan") { - context.go('/listPlan'); - } else { - widget.isApprover - ? context.go('/approvallist') - : context.go('/listPlan'); - } + // Close loading dialog (ONLY if still mounted) + if (mounted) Navigator.of(context, rootNavigator: true).pop(); } }); } @@ -1233,6 +1232,22 @@ class CreateNewPlansState extends State { if (response.statusCode == 200) { print("Plan submitted successfully!"); print("Response: ${response.body}"); + + final currentUri = + GoRouterState.of( + context, + ).uri.toString(); // ✅ safer than `.location` + print("currentUri - $currentUri"); + + if (currentUri == "/allTrips/trips") { + context.go('/listAllPlan'); + } else if (currentUri == "/createPlan") { + context.go('/listPlan'); + } else { + widget.isApprover + ? context.go('/approvallist') + : context.go('/listPlan'); + } } else { print("Failed to submit plan. Status: ${response.statusCode}"); print("Error: ${response.body}"); @@ -2184,6 +2199,14 @@ class CreateNewPlansState extends State { // ), // ), ), + if (validationErrors["cost_center_id"] != null) + Padding( + padding: EdgeInsets.only(top: 4), + child: Text( + validationErrors["cost_center_id"]!, + style: GoogleFonts.poppins(fontSize: 12, color: Colors.red), + ), + ), ], ), SizedBox(width: 25, height: 5), @@ -2307,6 +2330,14 @@ class CreateNewPlansState extends State { ), ), ), + if (validationErrors["purpose_of_travel"] != null) + Padding( + padding: EdgeInsets.only(top: 4), + child: Text( + validationErrors["purpose_of_travel"]!, + style: GoogleFonts.poppins(fontSize: 12, color: Colors.red), + ), + ), // CustomTextFieldWrapper( // isFocused: false, // Dropdown doesn't use focus @@ -2466,6 +2497,14 @@ class CreateNewPlansState extends State { ), ), ), + 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 { List> options = [ {"title": "Self", "value": "Option 1"}, {"title": "Other Employee", "value": "Option 2"}, - {"title": "Others", "value": "Option 3"}, + {"title": "Others (Non Employee)", "value": "Option 3"}, ]; return [ diff --git a/lib/Screens/plans/dynamic_itinerary_stepper.dart b/lib/Screens/plans/dynamic_itinerary_stepper.dart index 2d9ead4..fb143f7 100644 --- a/lib/Screens/plans/dynamic_itinerary_stepper.dart +++ b/lib/Screens/plans/dynamic_itinerary_stepper.dart @@ -33,25 +33,26 @@ class DynamicItinerary extends StatefulWidget { final List? apiCountryData; final String? loginUser; final Function(String, List>) - onItineraryUpdate; // Updated Signature + onItineraryUpdate; // Updated Signature final Map selectedPlanData; final bool isViewMode; final GlobalKey flightScreenKey; final ValueNotifier tripTypeNotifier; - const DynamicItinerary( - {super.key, - required this.apiData, - required this.onItineraryUpdate, - required this.apiCountryData, - required this.loginUser, - required this.selectedPlanData, - required this.isViewMode, - required this.hasAction, - this.tripType, - this.apiDataForClass, - required this.tripTypeNotifier, - required this.flightScreenKey}); + const DynamicItinerary({ + super.key, + required this.apiData, + required this.onItineraryUpdate, + required this.apiCountryData, + required this.loginUser, + required this.selectedPlanData, + required this.isViewMode, + required this.hasAction, + this.tripType, + this.apiDataForClass, + required this.tripTypeNotifier, + required this.flightScreenKey, + }); @override DynamicItineraryState createState() => DynamicItineraryState(); @@ -135,9 +136,10 @@ class DynamicItineraryState extends State { if (rawServices != null && rawServices is String) { try { List decoded = json.decode(rawServices); - List> formatted = decoded - .map((e) => {"service_id": e['service_id'].toString()}) - .toList(); + List> formatted = + decoded + .map((e) => {"service_id": e['service_id'].toString()}) + .toList(); setState(() { selectedOrgServiceIds = formatted; @@ -168,7 +170,7 @@ class DynamicItineraryState extends State { "insurance", "visa", "miscellaneous", - "taxi" + "taxi", ]; } else { // tripType is null or not 1/2, allow everything @@ -242,23 +244,27 @@ class DynamicItineraryState extends State { final selectedIds = selectedOrgServiceIds.map((e) => e['service_id']).toSet(); - final additionalServices = selectedAllServices!.where((service) { - final name = (service['name'] ?? "").toString().toLowerCase(); - final id = service['service_id'].toString(); - final isNameAllowed = - allowedServiceNames.isEmpty || allowedServiceNames.contains(name); - return filledItineraryKeys.contains(name) && - !selectedIds.contains(id) && - isNameAllowed; - }).toList(); + final additionalServices = + selectedAllServices!.where((service) { + final name = (service['name'] ?? "").toString().toLowerCase(); + final id = service['service_id'].toString(); + final isNameAllowed = + allowedServiceNames.isEmpty || + allowedServiceNames.contains(name); + return filledItineraryKeys.contains(name) && + !selectedIds.contains(id) && + isNameAllowed; + }).toList(); - final originalFiltered = selectedAllServices!.where((service) { - final name = (service['name'] ?? "").toString().toLowerCase(); - final id = service['service_id'].toString(); - final isNameAllowed = - allowedServiceNames.isEmpty || allowedServiceNames.contains(name); - return selectedIds.contains(id) && isNameAllowed; - }).toList(); + final originalFiltered = + selectedAllServices!.where((service) { + final name = (service['name'] ?? "").toString().toLowerCase(); + final id = service['service_id'].toString(); + final isNameAllowed = + allowedServiceNames.isEmpty || + allowedServiceNames.contains(name); + return selectedIds.contains(id) && isNameAllowed; + }).toList(); setState(() { ServicesChoosed = [...originalFiltered, ...additionalServices] @@ -266,22 +272,26 @@ class DynamicItineraryState extends State { }); print( - "Services chosen based on filled keys + selected: $ServicesChoosed"); + "Services chosen based on filled keys + selected: $ServicesChoosed", + ); } else { final selectedIds = selectedOrgServiceIds.map((e) => e['service_id']).toSet(); - final filtered = selectedAllServices!.where((service) { - final name = (service['name'] ?? "").toString().toLowerCase(); - final isNameAllowed = - allowedServiceNames.isEmpty || allowedServiceNames.contains(name); - return selectedIds.contains(service['service_id'].toString()) && - isNameAllowed; - }).toList(); + final filtered = + selectedAllServices!.where((service) { + final name = (service['name'] ?? "").toString().toLowerCase(); + final isNameAllowed = + allowedServiceNames.isEmpty || + allowedServiceNames.contains(name); + return selectedIds.contains(service['service_id'].toString()) && + isNameAllowed; + }).toList(); setState(() { - ServicesChoosed = filtered - ..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0)); + ServicesChoosed = + filtered + ..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0)); }); print("Filtered Selected Services Chosen: $ServicesChoosed"); @@ -296,23 +306,32 @@ class DynamicItineraryState extends State { setState(() { itineraryData = { "Train": List>.from( - widget.selectedPlanData['train'] ?? []), + widget.selectedPlanData['train'] ?? [], + ), "Bus": List>.from( - widget.selectedPlanData['bus'] ?? []), + widget.selectedPlanData['bus'] ?? [], + ), "Taxi": List>.from( - widget.selectedPlanData['taxi'] ?? []), + widget.selectedPlanData['taxi'] ?? [], + ), "Miscellaneous": List>.from( - widget.selectedPlanData['miscellaneous'] ?? []), + widget.selectedPlanData['miscellaneous'] ?? [], + ), "Flight": List>.from( - widget.selectedPlanData['flight'] ?? []), + widget.selectedPlanData['flight'] ?? [], + ), "Accomodation": List>.from( - widget.selectedPlanData['accomodation'] ?? []), + widget.selectedPlanData['accomodation'] ?? [], + ), "Insurance": List>.from( - widget.selectedPlanData['insurance'] ?? []), + widget.selectedPlanData['insurance'] ?? [], + ), "Visa": List>.from( - widget.selectedPlanData['visa'] ?? []), + widget.selectedPlanData['visa'] ?? [], + ), "Forex": List>.from( - widget.selectedPlanData['forex'] ?? []), + widget.selectedPlanData['forex'] ?? [], + ), }; }); } else { @@ -330,7 +349,7 @@ class DynamicItineraryState extends State { "accomodation", "insurance", "visa", - "forex" + "forex", ]; // for (String key in keys) { @@ -416,7 +435,8 @@ class DynamicItineraryState extends State { if (existingId != null && existingId != 0) { // int itemId = itemList.indexWhere((item) => item["id"] == existingId); int itemId = itemList.indexWhere( - (item) => item[idKey]?.toString() == existingId.toString()); + (item) => item[idKey]?.toString() == existingId.toString(), + ); if (itemId != -1) { print(" Updating existing item with id: $existingId"); @@ -428,8 +448,9 @@ class DynamicItineraryState extends State { // CASE 1: Update if indx exists in list if (existingIndex != null && existingIndex != 0) { - int itemIndex = - itemList.indexWhere((item) => item["indx"] == existingIndex); + int itemIndex = itemList.indexWhere( + (item) => item["indx"] == existingIndex, + ); if (itemIndex != -1) { print("Updating existing item with indx: $existingIndex"); newData["is_active"] = "1"; @@ -494,6 +515,7 @@ class DynamicItineraryState extends State { }); print(" onItineraryUpdate - $type - ${itineraryData[type]!} "); widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent + print("ItienreayDATE - $itineraryData"); } // void handleItineraryUpdate(String type, Map newData) { @@ -583,8 +605,8 @@ class DynamicItineraryState extends State { onOpen: handleEdit, onAddNew: handlecreateNewPlan, isViewMode: widget.isViewMode, - onDeleteAccommodation: (data) => - handleItinerarydelete("Accomodation", data), + onDeleteAccommodation: + (data) => handleItinerarydelete("Accomodation", data), ); break; case "Miscellaneous": @@ -594,50 +616,54 @@ class DynamicItineraryState extends State { isViewMode: widget.isViewMode, onAddNew: handlecreateNewPlan, apiData: widget.apiData, - onDeleteMiscellaneous: (data) => - handleItinerarydelete("Miscellaneous", data), + onDeleteMiscellaneous: + (data) => handleItinerarydelete("Miscellaneous", data), ); break; case "Flight": default: selectedListWidget = FlightListWidget( - hasAction: widget.hasAction, - tripType: widget.tripType, - flightList: itineraryData["Flight"]!, - onOpen: handleEdit, - onAddNew: handlecreateNewPlan, - isViewMode: widget.isViewMode, - apiData: widget.apiData, - onDeleteFlight: (data) => handleItinerarydelete("Flight", data)); + hasAction: widget.hasAction, + tripType: widget.tripType, + flightList: itineraryData["Flight"]!, + onOpen: handleEdit, + onAddNew: handlecreateNewPlan, + isViewMode: widget.isViewMode, + apiData: widget.apiData, + onDeleteFlight: (data) => handleItinerarydelete("Flight", data), + ); break; } switch (selectedOption) { case "Train": selectedWidget = TrainScreen( - onClose: handleClose, - apiData: widget.apiData, - apiDataForClass: widget.apiDataForClass, - loginUser: widget.loginUser, - onSavetrain: (data) => handleItineraryUpdate("Train", data), - tripType: widget.tripType, - selectedItem: selectedItem); + onClose: handleClose, + apiData: widget.apiData, + apiDataForClass: widget.apiDataForClass, + loginUser: widget.loginUser, + onSavetrain: (data) => handleItineraryUpdate("Train", data), + tripType: widget.tripType, + selectedItem: selectedItem, + ); break; case "Taxi": selectedWidget = TaxiScreen( - onClose: handleClose, - apiData: widget.apiData, - loginUser: widget.loginUser, - onSavetaxi: (data) => handleItineraryUpdate("Taxi", data), - selectedItem: selectedItem); + onClose: handleClose, + apiData: widget.apiData, + loginUser: widget.loginUser, + onSavetaxi: (data) => handleItineraryUpdate("Taxi", data), + selectedItem: selectedItem, + ); break; case "Bus": selectedWidget = BusScreen( - onClose: handleClose, - apiData: widget.apiData, - loginUser: widget.loginUser, - onSaveBus: (data) => handleItineraryUpdate("Bus", data), - selectedItem: selectedItem); + onClose: handleClose, + apiData: widget.apiData, + loginUser: widget.loginUser, + onSaveBus: (data) => handleItineraryUpdate("Bus", data), + selectedItem: selectedItem, + ); break; case "Insurance": selectedWidget = InsuranceScreen( @@ -666,8 +692,8 @@ class DynamicItineraryState extends State { onClose: handleClose, apiData: widget.apiData, loginUser: widget.loginUser, - onSaveMiscellaneous: (data) => - handleItineraryUpdate("Miscellaneous", data), + onSaveMiscellaneous: + (data) => handleItineraryUpdate("Miscellaneous", data), selectedItem: selectedItem, selectedIndex: selectedIndex, ); @@ -676,8 +702,8 @@ class DynamicItineraryState extends State { selectedWidget = AccomodationScreen( onClose: handleClose, loginUser: widget.loginUser, - onSaveAccomadation: (data) => - handleItineraryUpdate("Accomodation", data), + onSaveAccomadation: + (data) => handleItineraryUpdate("Accomodation", data), selectedItem: selectedItem, flightData: itineraryData["Flight"]!, ); @@ -776,85 +802,106 @@ class DynamicItineraryState extends State { // ); // }); - return ResponsiveBuilder(builder: (context, sizingInfo) { - bool isMobile = sizingInfo.isMobile; + return ResponsiveBuilder( + builder: (context, sizingInfo) { + bool isMobile = sizingInfo.isMobile; - return Stack( - clipBehavior: Clip.none, - children: [ - // Second container (yellow box) - Container( - margin: EdgeInsets.only( - top: 40), // Push it down to make room for the tab bar - padding: EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.white, // Card background - // color: Colors.yellow.shade50, // Card background - // color: Color(0xFFF9F9F9), // Slightly lighter than white - // border: Border.all(color: Color(0xFFE6E7F5), width: 1.3), - border: Border.all(color: Color(0xFFE6E7F5), width: 1.2), - borderRadius: BorderRadius.circular(8), - boxShadow: [ - BoxShadow( - // color: Color(0x0D000000), // 5% opacity black - color: Colors.black12, // 5% opacity black - blurRadius: 5, - offset: Offset(0, 0.2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - SizedBox(height: 2), - isSelected ? selectedWidget : selectedListWidget, - ], - ), - ), - - // First container (tab bar) — positioned above - Positioned( - top: 0, - left: MediaQuery.of(context).size.width * 0.05, - right: MediaQuery.of(context).size.width * 0.05, - child: Container( - padding: EdgeInsets.symmetric(vertical: 10, horizontal: 12), + return Stack( + clipBehavior: Clip.none, + children: [ + // Second container (yellow box) + Container( + margin: EdgeInsets.only( + top: 40, + ), // Push it down to make room for the tab bar + padding: EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, // Card background - borderRadius: BorderRadius.circular(8), - - // color: Color(0xFFE6E7F5) - // border: Border.all(color: Colors.black12, width: 1.3), + // color: Colors.yellow.shade50, // Card background + // color: Color(0xFFF9F9F9), // Slightly lighter than white + // border: Border.all(color: Color(0xFFE6E7F5), width: 1.3), border: Border.all(color: Color(0xFFE6E7F5), width: 1.2), + borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( - color: Colors.black12, // color: Color(0x0D000000), // 5% opacity black - blurRadius: 10, + color: Colors.black12, // 5% opacity black + blurRadius: 5, offset: Offset(0, 0.2), ), ], ), - child: isMobile - ? SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: _buildOptions(), - ), - ) - : Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: _buildOptions(), - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: 2), + isSelected ? selectedWidget : selectedListWidget, + ], + ), ), - ), - ], - ); - }); + + // First container (tab bar) — positioned above + Positioned( + top: 0, + left: MediaQuery.of(context).size.width * 0.05, + right: MediaQuery.of(context).size.width * 0.05, + child: Container( + padding: EdgeInsets.symmetric(vertical: 10, horizontal: 12), + decoration: BoxDecoration( + color: Colors.white, // Card background + borderRadius: BorderRadius.circular(8), + + // color: Color(0xFFE6E7F5) + // border: Border.all(color: Colors.black12, width: 1.3), + border: Border.all(color: Color(0xFFE6E7F5), width: 1.2), + boxShadow: [ + BoxShadow( + color: Colors.black12, + // color: Color(0x0D000000), // 5% opacity black + blurRadius: 10, + offset: Offset(0, 0.2), + ), + ], + ), + child: + isMobile + ? SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row(children: _buildOptions()), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: _buildOptions(), + ), + ), + ), + ], + ); + }, + ); + } + + bool hasValidItineraryEntries() { + if (ServicesChoosed == null || ServicesChoosed!.isEmpty) return false; + + for (var service in ServicesChoosed!) { + final serviceName = service['name']; + final entries = itineraryData[serviceName]; + + // Check if there is at least one active entry (is_active == 1) + final hasActive = + entries?.any((entry) => entry['is_active'] == 1) ?? false; + + if (!hasActive) { + return false; // Fail fast if any one service has no active entries + } + } + + return true; // All selected services have at least one active entry } List _buildOptions() { - if (ServicesChoosed == null) return []; + if (ServicesChoosed == null && !hasValidItineraryEntries()) return []; if (ServicesChoosed != null && ServicesChoosed!.isNotEmpty && @@ -875,18 +922,28 @@ class DynamicItineraryState extends State { // } return ServicesChoosed!.map((service) { + final serviceName = service['name']; + final serviceEntries = itineraryData[serviceName]; + + final hasActive = + serviceEntries?.any((entry) => entry['is_active'] == "1") ?? false; + + print("Service1: $serviceName"); + print("Entries1: $serviceEntries"); + print("Has Active1: $hasActive"); + return Padding( padding: const EdgeInsets.only(right: 20.0), child: _buildOption( - service, itineraryData[service['name']]?.isNotEmpty ?? false), + service, + hasActive, + // itineraryData[service['name']]?.isNotEmpty ?? false, + ), ); }).toList(); } - Widget _buildOption( - Map service, - bool hasData, - ) { + Widget _buildOption(Map service, bool hasData) { String name = service['name']; String iconUrl = service['icon']; // Can be empty string IconData fallbackIcon = _getLocalIconForService(name); @@ -914,26 +971,26 @@ class DynamicItineraryState extends State { children: [ iconUrl.isNotEmpty ? Image.network( - iconUrl, - width: 18, - height: 18, - errorBuilder: (context, error, stackTrace) { - return Icon( - fallbackIcon, - size: 25, - color: isOptionSelected - ? Color(0xFF114D8B) - : Color(0xFF475569), - ); - }, - ) + iconUrl, + width: 18, + height: 18, + errorBuilder: (context, error, stackTrace) { + return Icon( + fallbackIcon, + size: 25, + color: + isOptionSelected + ? Color(0xFF114D8B) + : Color(0xFF475569), + ); + }, + ) : Icon( - fallbackIcon, - size: 25, - color: isOptionSelected - ? Color(0xFF114D8B) - : Color(0xFF475569), - ), + fallbackIcon, + size: 25, + color: + isOptionSelected ? Color(0xFF114D8B) : Color(0xFF475569), + ), SizedBox(height: 2), Row( children: [ @@ -943,12 +1000,12 @@ class DynamicItineraryState extends State { // style: GoogleFonts.poppins( fontSize: 12, // fontWeight: FontWeight.w600, // color: Color(0xFF575A74)) - style: TextStyle( fontSize: 14, - color: isOptionSelected - ? Color(0xFF114D8B) - : Color(0xFF475569), + color: + isOptionSelected + ? Color(0xFF114D8B) + : Color(0xFF475569), fontFamily: "Inter", fontWeight: isOptionSelected ? FontWeight.bold : FontWeight.w500, @@ -964,86 +1021,87 @@ class DynamicItineraryState extends State { ); } - Widget _buildOption1( - Map service, - bool hasData, - ) { - String name = service['name']; - String iconUrl = service['icon']; // Can be empty string - // Optional: define local icon fallback if iconUrl is empty - IconData fallbackIcon = _getLocalIconForService(name); - // final idMap = {"service_id": service['service_id'].toString()}; - // final isSelected = selectedServiceIds.contains(idMap); - - String serviceId = service['service_id'].toString(); - // bool isSelected = selectedServiceIds.contains(serviceId); - // bool isSelected = - // selectedServiceIds.any((item) => item["service_id"] == serviceId); - - return GestureDetector( - onTap: () { - setState(() { - selectedListOption = name; - isSelected = false; - }); - }, - child: Row(children: [ - iconUrl.isNotEmpty - ? Image.network( - iconUrl, - width: 18, - height: 18, - errorBuilder: (context, error, stackTrace) { - return Icon( - fallbackIcon, - size: 18, - color: selectedListOption == name - ? Color(0xFF114D8B) - : Color(0xFF475569), - ); - }, - ) - : Icon( - fallbackIcon, - size: 18, - color: selectedListOption == name - ? Color(0xFF114D8B) - : Color(0xFF475569), - ), - - SizedBox(width: 2), - Text( - name, - style: TextStyle( - fontSize: 14, - // color: selectedListOption == title ? Colors.blueAccent : Color(0xFF575A74), - color: selectedListOption == name - ? Color(0xFF114D8B) - : Color(0xFF475569), - fontFamily: "Archivo", - fontWeight: selectedListOption == name - ? FontWeight.bold - : FontWeight.w500), - // fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)), - ), - - SizedBox(width: 2), - // if (selectedListOption == title && widget.isViewMode == false) - if (hasData) - Icon(Icons.circle, size: 8, color: Colors.green - // color: Colors.grey, - ) - // Container( - // height: 10, - // width: 10, - // // decoration: BoxDecoration( - // // shape: BoxShape.circle, - // // border: Border.all(color: Colors.green, width: 1.5), - // // ), - // child:), - ]), - ); - } + // Widget _buildOption1( + // Map service, + // bool hasData, + // ) + // { + // String name = service['name']; + // String iconUrl = service['icon']; // Can be empty string + // // Optional: define local icon fallback if iconUrl is empty + // IconData fallbackIcon = _getLocalIconForService(name); + // // final idMap = {"service_id": service['service_id'].toString()}; + // // final isSelected = selectedServiceIds.contains(idMap); + // + // String serviceId = service['service_id'].toString(); + // // bool isSelected = selectedServiceIds.contains(serviceId); + // // bool isSelected = + // // selectedServiceIds.any((item) => item["service_id"] == serviceId); + // + // return GestureDetector( + // onTap: () { + // setState(() { + // selectedListOption = name; + // isSelected = false; + // }); + // }, + // child: Row(children: [ + // iconUrl.isNotEmpty + // ? Image.network( + // iconUrl, + // width: 18, + // height: 18, + // errorBuilder: (context, error, stackTrace) { + // return Icon( + // fallbackIcon, + // size: 18, + // color: selectedListOption == name + // ? Color(0xFF114D8B) + // : Color(0xFF475569), + // ); + // }, + // ) + // : Icon( + // fallbackIcon, + // size: 18, + // color: selectedListOption == name + // ? Color(0xFF114D8B) + // : Color(0xFF475569), + // ), + // + // SizedBox(width: 2), + // Text( + // name, + // style: TextStyle( + // fontSize: 14, + // // color: selectedListOption == title ? Colors.blueAccent : Color(0xFF575A74), + // color: selectedListOption == name + // ? Color(0xFF114D8B) + // : Color(0xFF475569), + // fontFamily: "Archivo", + // fontWeight: selectedListOption == name + // ? FontWeight.bold + // : FontWeight.w500), + // // fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)), + // ), + // + // SizedBox(width: 2), + // // if (selectedListOption == title && widget.isViewMode == false) + // if (hasData) + // Icon(Icons.circle, size: 8, color: Colors.green + // // color: Colors.grey, + // ) + // // Container( + // // height: 10, + // // width: 10, + // // // decoration: BoxDecoration( + // // // shape: BoxShape.circle, + // // // border: Border.all(color: Colors.green, width: 1.5), + // // // ), + // // child:), + // ]), + // ); + // } IconData _getLocalIconForService(String name) { switch (name.toLowerCase()) { diff --git a/lib/Screens/policy/policy.dart b/lib/Screens/policy/policy.dart index 90b531a..d0ae7d4 100644 --- a/lib/Screens/policy/policy.dart +++ b/lib/Screens/policy/policy.dart @@ -79,22 +79,23 @@ class _PolicyState extends State { List>? policy_details = []; Map get policyData { - List> policyDetails = policy_details!.where((service) { - // Only check these specific fields for emptiness - final fieldsToCheck = [ - 'cost', - 'class', - 'a1_action', - 'a2_action', - 'a3_action' - ]; + List> policyDetails = + policy_details!.where((service) { + // Only check these specific fields for emptiness + final fieldsToCheck = [ + 'cost', + 'class', + 'a1_action', + 'a2_action', + 'a3_action', + ]; - // If any of the important fields has a value, keep it - return fieldsToCheck.any((field) { - final value = service[field]; - return value != null && value.toString().trim().isNotEmpty; - }); - }).toList(); + // If any of the important fields has a value, keep it + return fieldsToCheck.any((field) { + final value = service[field]; + return value != null && value.toString().trim().isNotEmpty; + }); + }).toList(); Map data = { "name": _policyController.text, @@ -124,8 +125,9 @@ class _PolicyState extends State { loadInitialData(); if (widget.policy != null) { - final details = - List>.from(widget.policy!['policy_details']); + final details = List>.from( + widget.policy!['policy_details'], + ); policyCriteriaKey.currentState?.loadPolicyDetails(details); policyCriteriaKey.currentState?.fetchTrainFlightClass(); @@ -138,13 +140,15 @@ class _PolicyState extends State { String? bodyStringColor = await getBodyColor(); setState(() { - layoutColor = layoutString != null - ? Color(int.parse(layoutString)) - : Colors.redAccent; + layoutColor = + layoutString != null + ? Color(int.parse(layoutString)) + : Colors.redAccent; - bodyColor = bodyStringColor != null - ? Color(int.parse(bodyStringColor)) - : Colors.white; + bodyColor = + bodyStringColor != null + ? Color(int.parse(bodyStringColor)) + : Colors.white; }); } @@ -175,9 +179,10 @@ class _PolicyState extends State { if (rawServices != null && rawServices is String) { try { List decoded = json.decode(rawServices); - List> formatted = decoded - .map((e) => {"service_id": e['service_id'].toString()}) - .toList(); + List> formatted = + decoded + .map((e) => {"service_id": e['service_id'].toString()}) + .toList(); setState(() { selectedOrgServiceIds = formatted; @@ -205,17 +210,22 @@ class _PolicyState extends State { selectedOrgServiceIds.map((e) => e['service_id']).toSet(); if (widget.policy != null) { - final details = - List>.from(widget.policy!['policy_details']); + final details = List>.from( + widget.policy!['policy_details'], + ); print( - "UUFiltered Selected Services - ${widget.policy!['services_ids']} "); + "UUFiltered Selected Services - ${widget.policy!['services_ids']} ", + ); // pr int("UUFiltered Selected Services - $details"); - final filtered = selectedAllServices! - .where((service) => - selectedIds.contains(service['service_id'].toString())) - .toList(); + final filtered = + selectedAllServices! + .where( + (service) => + selectedIds.contains(service['service_id'].toString()), + ) + .toList(); setState(() { ServicesChoosed = filtered; @@ -236,13 +246,16 @@ class _PolicyState extends State { final decoded = jsonDecode(widget.policy!['services_ids']); setState(() { - services = List>.from(decoded) - .map((service) => { - 'service_id': service['service_id'].toString(), - 'name': service['name'].toString(), - 'order': service['order'].toString(), - }) - .toList(); + services = + List>.from(decoded) + .map( + (service) => { + 'service_id': service['service_id'].toString(), + 'name': service['name'].toString(), + 'order': service['order'].toString(), + }, + ) + .toList(); }); print("✅ Loaded services from policy (decoded): $services"); @@ -250,10 +263,13 @@ class _PolicyState extends State { print("Filtered Selected Services Added to Policy: $ServicesChoosed"); } else { - final filtered = selectedAllServices! - .where((service) => - selectedIds.contains(service['service_id'].toString())) - .toList(); + final filtered = + selectedAllServices! + .where( + (service) => + selectedIds.contains(service['service_id'].toString()), + ) + .toList(); print("ServicesChoosedYY: $ServicesChoosed"); setState(() { @@ -264,14 +280,17 @@ class _PolicyState extends State { // ServicesChoosed = filtered; - services = ServicesChoosed! - .map((service) => { - 'service_id': service['service_id'].toString(), - 'name': - service['name'].toString(), // ✅ no space before 'name' - 'order': service['order'].toString(), - }) - .toList(); + services = + ServicesChoosed! + .map( + (service) => { + 'service_id': service['service_id'].toString(), + 'name': + service['name'].toString(), // ✅ no space before 'name' + 'order': service['order'].toString(), + }, + ) + .toList(); }); print("Filtered Selected Services Chooesed1: $ServicesChoosed"); @@ -336,7 +355,7 @@ class _PolicyState extends State { // Validate required fields if (data["name"] == null || data["name"].toString().trim().isEmpty) { - errorMessages["name"] = "Policy name is required."; + errorMessages["name"] = "Required"; // "Policy name is required."; } // Validate that either domestic or international is selected @@ -344,10 +363,12 @@ class _PolicyState extends State { final international = data["international"]?.toString() ?? "0"; print( - "domestic: ${data["domestic"]}, international: ${data["international"]}"); + "domestic: ${data["domestic"]}, international: ${data["international"]}", + ); if (domestic != "1" && international != "1") { - errorMessages["trip_type"] = "Please select Domestic or International."; + errorMessages["trip_type"] = + "Required"; // "Please select Domestic or International."; } // Validate at least one policy_detail with valid content @@ -359,7 +380,7 @@ class _PolicyState extends State { 'class', 'a1_action', 'a2_action', - 'a3_action' + 'a3_action', ]; return fieldsToCheck.any((field) { final value = service[field]; @@ -369,7 +390,7 @@ class _PolicyState extends State { if (!hasAtLeastOneDetail) { errorMessages["policy_details"] = - "At least one valid policy detail is required."; + "Required"; // "At least one valid policy detail is required."; } return errorMessages.isEmpty; @@ -424,76 +445,81 @@ class _PolicyState extends State { @override Widget build(BuildContext context) { - return ResponsiveBuilder(builder: (context, sizingInfo) { - bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + return ResponsiveBuilder( + builder: (context, sizingInfo) { + bool isDesktop = + sizingInfo.deviceScreenType == DeviceScreenType.desktop; - return Scaffold( - backgroundColor: Color(0xFFf5f5f5), - appBar: CustomAppBar(isDesktop: isDesktop), - drawer: CustomDrawer(isDesktop: false), - body: Padding( - padding: isDesktop - ? EdgeInsets.symmetric( - horizontal: MediaQuery.of(context).size.width * - 0.1, // 30% of screen width as horizontal padding - vertical: MediaQuery.of(context).size.height * - 0, // 5% of screen height as vertical padding - ) - : EdgeInsets.all(8), - child: Column( - children: [ - Expanded( - child: Row( - children: [ - // if (isDesktop) CustomDrawer(isDesktop: true), - - Expanded(child: buildData(isDesktop, context)), - // Expanded( - // child: Container( - // color: bodyColor, - // child: buildPolicyLayout(isDesktop), - // ), - // ), - ], + return Scaffold( + backgroundColor: Color(0xFFf5f5f5), + appBar: CustomAppBar(isDesktop: isDesktop), + drawer: CustomDrawer(isDesktop: false), + body: Padding( + padding: + isDesktop + ? EdgeInsets.symmetric( + horizontal: + MediaQuery.of(context).size.width * + 0.1, // 30% of screen width as horizontal padding + vertical: + MediaQuery.of(context).size.height * + 0, // 5% of screen height as vertical padding + ) + : EdgeInsets.all(8), + child: Column( + children: [ + Expanded( + child: Row( + children: [ + // if (isDesktop) CustomDrawer(isDesktop: true), + Expanded(child: buildData(isDesktop, context)), + // Expanded( + // child: Container( + // color: bodyColor, + // child: buildPolicyLayout(isDesktop), + // ), + // ), + ], + ), ), - ), - ], + ], + ), ), - ), - // Row( - // children: [ - // if (isDesktop) CustomDrawer(isDesktop: true), - // Expanded( - // child: Column( - // children: [ - // Expanded( - // child: Container( - // // height: MediaQuery.of(context).size.height * 0.8, - // color: bodyColor, - // child: buildPolicyLayout(isDesktop)), - // ), - // ], - // ), - // ), - // Container( - // color: Colors.white, - // child: Padding( - // padding: const EdgeInsets.all(8.0), - // child: isDesktop - // ? Row( - // mainAxisAlignment: MainAxisAlignment.end, - // children: _buildSubmit(isDesktop), - // ) - // : Row( - // mainAxisAlignment: MainAxisAlignment.center, - // children: _buildSubmit(isDesktop), - // )), - // ) - // ], - // ), - ); - }); + // Row( + // children: [ + // if (isDesktop) CustomDrawer(isDesktop: true), + // Expanded( + // child: Column( + // children: [ + // Expanded( + // child: Container( + // // height: MediaQuery.of(context).size.height * 0.8, + // color: bodyColor, + // child: buildPolicyLayout(isDesktop)), + // ), + // ], + // ), + // ), + // Container( + // color: Colors.white, + // child: Padding( + // padding: const EdgeInsets.all(8.0), + // child: isDesktop + // ? Row( + // mainAxisAlignment: MainAxisAlignment.end, + // children: _buildSubmit(isDesktop), + // ) + // : Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: _buildSubmit(isDesktop), + // )), + // ) + // ], + // ), + ); + }, + ); } Widget buildData(bool isDesktop, context) { @@ -524,15 +550,16 @@ class _PolicyState extends State { Container( color: Colors.white, padding: const EdgeInsets.all(8.0), - child: isDesktop - ? Row( - mainAxisAlignment: MainAxisAlignment.end, - children: _buildSubmit(isDesktop), - ) - : Row( - mainAxisAlignment: MainAxisAlignment.center, - children: _buildSubmit(isDesktop), - ), + child: + isDesktop + ? Row( + mainAxisAlignment: MainAxisAlignment.end, + children: _buildSubmit(isDesktop), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: _buildSubmit(isDesktop), + ), ), ], ), @@ -544,9 +571,10 @@ class _PolicyState extends State { // margin: isDesktop // ? EdgeInsets.all(10.0) // : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), - height: isDesktop - ? MediaQuery.of(context).size.height * 0.98 - : MediaQuery.of(context).size.height, + height: + isDesktop + ? MediaQuery.of(context).size.height * 0.98 + : MediaQuery.of(context).size.height, // decoration: BoxDecoration( // border: isDesktop // ? Border.all( @@ -580,9 +608,10 @@ class _PolicyState extends State { Text( "Choose Policy Type", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), ], ), @@ -593,102 +622,96 @@ class _PolicyState extends State { isDesktop ? SizedBox(height: 0) : SizedBox(height: 5), Container( padding: isDesktop ? const EdgeInsets.only(left: 35) : null, - child: isDesktop - ? Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded(child: _buildPolicyNameField(isDesktop)), - Spacer(), - Expanded(child: _buildPolicyTypeField(isDesktop)), - ], - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildPolicyNameField(isDesktop), - SizedBox(height: 20), - _buildPolicyTypeField(isDesktop), - ], - ), - ), - SizedBox( - height: 10, - ), - Divider( - thickness: 0.1, - color: Colors.grey, + child: + isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildPolicyNameField(isDesktop)), + Spacer(), + Expanded(child: _buildPolicyTypeField(isDesktop)), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildPolicyNameField(isDesktop), + SizedBox(height: 20), + _buildPolicyTypeField(isDesktop), + ], + ), ), + SizedBox(height: 10), + Divider(thickness: 0.1, color: Colors.grey), if (errorMessages["policy_details"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["policy_details"]!, - style: GoogleFonts.poppins(color: Colors.red, fontSize: 10), + style: GoogleFonts.poppins(color: Colors.red, fontSize: 12), ), ], isDesktop ? Padding( - padding: const EdgeInsets.only(left: 30.0, right: 98.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Approval Criteria", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + padding: const EdgeInsets.only(left: 30.0, right: 98.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Approval Criteria", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), - Text( - "Service Priority", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), - ) - ], - ), - ) + ), + Text( + "Service Priority", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), + ), + ], + ), + ) : SizedBox.shrink(), isDesktop ? Container( - // color: Colors.yellow.shade50, - height: MediaQuery.of(context).size.height * 0.54, - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - children: [ - _buildPolicyOrdering(isDesktop), - _buildPolicyCategory(isDesktop), - ], - ), - Column( - children: [ - _buildPolicyCategoryList(isDesktop), - ], - ), - ], - ), - ), - ) - : Expanded( - child: Container( - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Column( + // color: Colors.yellow.shade50, + height: MediaQuery.of(context).size.height * 0.54, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( children: [ - _buildPolicyCategoryList(isDesktop), _buildPolicyOrdering(isDesktop), _buildPolicyCategory(isDesktop), ], ), + Column(children: [_buildPolicyCategoryList(isDesktop)]), + ], + ), + ), + ) + : Expanded( + child: Container( + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Column( + children: [ + _buildPolicyCategoryList(isDesktop), + _buildPolicyOrdering(isDesktop), + _buildPolicyCategory(isDesktop), + ], ), ), ), + ), // isDesktop // ? Expanded( // child: Row( @@ -719,11 +742,14 @@ class _PolicyState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Policy Name", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74))), + Text( + "Policy Name *", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -736,8 +762,10 @@ class _PolicyState extends State { onChanged: (value) => _clearError("name"), decoration: InputDecoration( labelText: "Policy Name", - labelStyle: - GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + labelStyle: GoogleFonts.poppins( + fontSize: 12, + color: Colors.grey, + ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -747,8 +775,10 @@ class _PolicyState extends State { ), if (errorMessages["name"] != null) ...[ SizedBox(height: 5), - Text(errorMessages["name"]!, - style: GoogleFonts.poppins(color: Colors.red, fontSize: 10)), + Text( + errorMessages["name"]!, + style: GoogleFonts.poppins(color: Colors.red, fontSize: 12), + ), ], ], ); @@ -758,11 +788,14 @@ class _PolicyState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Policy Type", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74))), + Text( + "Policy Type *", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), + ), SizedBox(height: 5), Row( mainAxisAlignment: MainAxisAlignment.start, @@ -770,8 +803,10 @@ class _PolicyState extends State { ), if (errorMessages["trip_type"] != null) ...[ SizedBox(height: 5), - Text(errorMessages["trip_type"]!, - style: GoogleFonts.poppins(color: Colors.red, fontSize: 10)), + Text( + errorMessages["trip_type"]!, + style: GoogleFonts.poppins(color: Colors.red, fontSize: 12), + ), ], ], ); @@ -786,29 +821,31 @@ class _PolicyState extends State { // color: Colors.blueGrey.shade200, width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, decoration: BoxDecoration( - border: Border.all(color: Colors.blueGrey.shade100, width: 0.35)), - child: isDesktop - ? Padding( - padding: const EdgeInsets.all(10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.start, - children: [_buildPolicySubCategoryList(isDesktop)], - ), - ) - : Column( - children: [ - Container( - // color: Colors.amber, - child: _buildPolicySubCategoryList(isDesktop), - // child: Text("FAta"), - // child: Row( - // mainAxisAlignment: MainAxisAlignment.start, - // children: [_buildPolicySubCategoryList(isDesktop)], - // ), + border: Border.all(color: Colors.blueGrey.shade100, width: 0.35), + ), + child: + isDesktop + ? Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [_buildPolicySubCategoryList(isDesktop)], ), - ], - ), + ) + : Column( + children: [ + Container( + // color: Colors.amber, + child: _buildPolicySubCategoryList(isDesktop), + // child: Text("FAta"), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.start, + // children: [_buildPolicySubCategoryList(isDesktop)], + // ), + ), + ], + ), ); } @@ -900,16 +937,13 @@ class _PolicyState extends State { color: Colors.grey.withOpacity(0.3), blurRadius: 2, offset: const Offset(0, 1), - ) + ), ], ), alignment: Alignment.center, child: Text( name, - style: GoogleFonts.poppins( - color: Colors.black, - fontSize: 12, - ), + style: GoogleFonts.poppins(color: Colors.black, fontSize: 12), ), ), ); @@ -942,7 +976,7 @@ class _PolicyState extends State { color: Colors.grey.withOpacity(0.3), blurRadius: 2, offset: const Offset(0, 1), - ) + ), ], ), alignment: Alignment.center, @@ -967,22 +1001,22 @@ class _PolicyState extends State { padding: isDesktop ? const EdgeInsets.only(left: 30, top: 8) : null, width: isDesktop ? MediaQuery.of(context).size.width * 0.62 : null, // width: isDesktop ? MediaQuery.of(context).size.width * 0.75 : null, - child: isDesktop - ? Container( - // color: Colors.amber, - - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [_buildPolicyServiceOrdering(isDesktop)], + child: + isDesktop + ? Container( + // color: Colors.amber, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [_buildPolicyServiceOrdering(isDesktop)], + ), + ) + : Container( + // color: Colors.amber, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [_buildPolicyServiceOrdering(isDesktop)], + ), ), - ) - : Container( - // color: Colors.amber, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [_buildPolicyServiceOrdering(isDesktop)], - ), - ), ); } @@ -992,8 +1026,9 @@ class _PolicyState extends State { } // Sort services by 'order' - ServicesChoosed! - .sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0)); + ServicesChoosed!.sort( + (a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0), + ); List services = ServicesChoosed!.map((service) => service['name'].toString()).toList(); @@ -1003,85 +1038,102 @@ class _PolicyState extends State { scrollDirection: Axis.horizontal, child: Flex( direction: Axis.horizontal, - children: services.asMap().entries.map((entry) { - int index = entry.key + 1; - String service = entry.value; - String serviceId = index.toString(); - bool isSelected = selectedServiceIndex.value == index.toString(); + children: + services.asMap().entries.map((entry) { + int index = entry.key + 1; + String service = entry.value; + String serviceId = index.toString(); + bool isSelected = + selectedServiceIndex.value == index.toString(); - return SizedBox( - // width: isDesktop ? 40 : null, - height: isDesktop - ? max((MediaQuery.of(context).size.height * 0.075), 10) - : 45, + return SizedBox( + // width: isDesktop ? 40 : null, + height: + isDesktop + ? max( + (MediaQuery.of(context).size.height * 0.075), + 10, + ) + : 45, - // max((MediaQuery.of(context).size.height * 0.09), 10) - child: GestureDetector( - onTap: () { - print("Selected Services - $service - $index"); - setState(() { - selectedServiceIndex.value = index.toString(); - selectedService = service; + // max((MediaQuery.of(context).size.height * 0.09), 10) + child: GestureDetector( + onTap: () { + print("Selected Services - $service - $index"); + setState(() { + selectedServiceIndex.value = index.toString(); + selectedService = service; - print( - " selectedServiceIndex.value - ${selectedServiceIndex.value}"); + print( + " selectedServiceIndex.value - ${selectedServiceIndex.value}", + ); - // policyCriteriaKey.currentState?.fieldForPolicy(); - // policyCriteriaKey.currentState - // ?.addOrUpdatePolicy(selectedServiceIndex.value); - if (selectedService == "Flight" || - selectedService == "Train") { - showClass = true; - showCost = true; - int serviceCode = selectedService == "Flight" ? 1 : 2; + // policyCriteriaKey.currentState?.fieldForPolicy(); + // policyCriteriaKey.currentState + // ?.addOrUpdatePolicy(selectedServiceIndex.value); + if (selectedService == "Flight" || + selectedService == "Train") { + showClass = true; + showCost = true; + int serviceCode = selectedService == "Flight" ? 1 : 2; - policyCriteriaKey.currentState?.fetchTrainFlightClass(); - } else if (selectedService == "Accommodation") { - showClass = true; - showCost = false; - } else { - showClass = false; - showCost = false; - } - }); - }, - child: Container( - margin: const EdgeInsets.all(5), - padding: isDesktop - ? const EdgeInsets.all(8) - : const EdgeInsets.symmetric( - horizontal: 8, vertical: 3), - alignment: Alignment.center, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - service, - style: GoogleFonts.poppins( - color: isSelected - ? const Color(0xFF114D8B) - : Colors.black87, - fontSize: 13, - fontWeight: - isSelected ? FontWeight.bold : FontWeight.w500, - decoration: TextDecoration - .none, // remove built-in underline + policyCriteriaKey.currentState + ?.fetchTrainFlightClass(); + } else if (selectedService == "Accommodation") { + showClass = true; + showCost = false; + } else { + showClass = false; + showCost = false; + } + }); + }, + child: Container( + margin: const EdgeInsets.all(5), + padding: + isDesktop + ? const EdgeInsets.all(8) + : const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + service, + style: GoogleFonts.poppins( + color: + isSelected + ? const Color(0xFF114D8B) + : Colors.black87, + fontSize: 13, + fontWeight: + isSelected + ? FontWeight.bold + : FontWeight.w500, + decoration: + TextDecoration + .none, // remove built-in underline + ), ), - ), - if (isSelected) - const SizedBox( - height: 1), // spacing between text and underline - if (isSelected) - Container( - height: 2, - width: 30, // or based on text width - color: const Color(0xFF114D8B), - ), - ], + if (isSelected) + const SizedBox( + height: 1, + ), // spacing between text and underline + if (isSelected) + Container( + height: 2, + width: 30, // or based on text width + color: const Color(0xFF114D8B), + ), + ], + ), ), ), - )); - }).toList(), + ); + }).toList(), ), ), ); @@ -1112,75 +1164,89 @@ class _PolicyState extends State { scrollDirection: isDesktop ? Axis.vertical : Axis.horizontal, child: Flex( direction: isDesktop ? Axis.vertical : Axis.horizontal, - children: services.asMap().entries.map((entry) { - int index = entry.key + 1; - String service = entry.value; - bool isSelected = selectedServiceIndex.value == index.toString(); + children: + services.asMap().entries.map((entry) { + int index = entry.key + 1; + String service = entry.value; + bool isSelected = + selectedServiceIndex.value == index.toString(); - return SizedBox( - width: isDesktop ? 180 : null, - height: isDesktop - ? max((MediaQuery.of(context).size.height * 0.075), 10) - : 45, + return SizedBox( + width: isDesktop ? 180 : null, + height: + isDesktop + ? max( + (MediaQuery.of(context).size.height * 0.075), + 10, + ) + : 45, - // max((MediaQuery.of(context).size.height * 0.09), 10) - child: GestureDetector( - onTap: () { - print("Selected Services - $service - $index"); - setState(() { - // selectedServiceIndex.value = index.toString(); - // selectedService = service; - // - // if (selectedService == "Flight" || - // selectedService == "Train") { - // showClass = true; - // showCost = true; - // int serviceCode = selectedService == "Flight" ? 1 : 2; - // policyCriteriaKey.currentState?.fetchTrainFlightClass(); - // } else if (selectedService == "Accommodation") { - // showClass = true; - // showCost = false; - // } else { - // showClass = false; - // showCost = false; - // } - }); - }, - child: Container( - margin: EdgeInsets.all(5), - padding: isDesktop - ? EdgeInsets.all(8) - : EdgeInsets.only(top: 3, bottom: 3, left: 8, right: 8), - decoration: BoxDecoration( - // color: Colors.blue, - color: isSelected ? Color(0xFF114D8B) : Colors.white, - // : Color(0xFFEBEBF7), - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: Colors.white, // Light grey border - width: 1, - ), - boxShadow: [ - BoxShadow( - color: Colors.grey.withAlpha(90), // Shadow color - blurRadius: 1, // Blur radius - spreadRadius: 1, // Spread radius - offset: Offset(0, 1), // Shadow position + // max((MediaQuery.of(context).size.height * 0.09), 10) + child: GestureDetector( + onTap: () { + print("Selected Services - $service - $index"); + setState(() { + // selectedServiceIndex.value = index.toString(); + // selectedService = service; + // + // if (selectedService == "Flight" || + // selectedService == "Train") { + // showClass = true; + // showCost = true; + // int serviceCode = selectedService == "Flight" ? 1 : 2; + // policyCriteriaKey.currentState?.fetchTrainFlightClass(); + // } else if (selectedService == "Accommodation") { + // showClass = true; + // showCost = false; + // } else { + // showClass = false; + // showCost = false; + // } + }); + }, + child: Container( + margin: EdgeInsets.all(5), + padding: + isDesktop + ? EdgeInsets.all(8) + : EdgeInsets.only( + top: 3, + bottom: 3, + left: 8, + right: 8, + ), + decoration: BoxDecoration( + // color: Colors.blue, + color: isSelected ? Color(0xFF114D8B) : Colors.white, + // : Color(0xFFEBEBF7), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.white, // Light grey border + width: 1, ), - ], - ), - alignment: Alignment.center, - child: Text( - service, - style: GoogleFonts.poppins( + boxShadow: [ + BoxShadow( + color: Colors.grey.withAlpha(90), // Shadow color + blurRadius: 1, // Blur radius + spreadRadius: 1, // Spread radius + offset: Offset(0, 1), // Shadow position + ), + ], + ), + alignment: Alignment.center, + child: Text( + service, + style: GoogleFonts.poppins( color: isSelected ? Colors.white : Colors.black87, fontSize: 13, fontWeight: - isSelected ? FontWeight.bold : FontWeight.w100), + isSelected ? FontWeight.bold : FontWeight.w100, + ), + ), ), ), - )); - }).toList(), + ); + }).toList(), ), ), ); @@ -1188,29 +1254,30 @@ class _PolicyState extends State { Widget _buildPolicyCategory(bool isDesktop) { return Container( - margin: isDesktop ? EdgeInsets.all(5) : null, - // color: Colors.brown.shade100, - // child: Text("data"), - // color: Colors.white60, - child: PolicyCriteria( - key: policyCriteriaKey, - isDesktop: isDesktop, - isClass: showClass, - isCost: showCost, - selectedTabNotifier: selectedServiceIndex, - selectedService: selectedService, - userId: userId, - onPolicyDataChanged: (List>? policyData) { - print("🟢 policyData received from child: $policyData"); + margin: isDesktop ? EdgeInsets.all(5) : null, + // color: Colors.brown.shade100, + // child: Text("data"), + // color: Colors.white60, + child: PolicyCriteria( + key: policyCriteriaKey, + isDesktop: isDesktop, + isClass: showClass, + isCost: showCost, + selectedTabNotifier: selectedServiceIndex, + selectedService: selectedService, + userId: userId, + onPolicyDataChanged: (List>? policyData) { + print("🟢 policyData received from child: $policyData"); - WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - policy_details = policyData; - _clearError("policy_details"); - }); + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + policy_details = policyData; + _clearError("policy_details"); }); - }, - )); + }); + }, + ), + ); } List _buildTripType(bool isDesktop) { @@ -1230,9 +1297,10 @@ class _PolicyState extends State { Text( "Domestic", style: GoogleFonts.poppins( - color: _selectedTripType == "1" ? Colors.white : Colors.black, - fontWeight: _selectedTripType == "1" ? FontWeight.w600 : null, - fontSize: 13), + color: _selectedTripType == "1" ? Colors.white : Colors.black, + fontWeight: _selectedTripType == "1" ? FontWeight.w600 : null, + fontSize: 13, + ), ), GestureDetector( onTap: () { @@ -1259,11 +1327,12 @@ class _PolicyState extends State { width: _selectedTripType == "1" ? 2 : 1, ), ), - child: _selectedTripType == "1" - ? Icon(Icons.rectangle, size: 8, color: Colors.white) - : null, // Add checkmark if selected + child: + _selectedTripType == "1" + ? Icon(Icons.rectangle, size: 8, color: Colors.white) + : null, // Add checkmark if selected ), - ) + ), ], ), ), @@ -1314,11 +1383,12 @@ class _PolicyState extends State { width: _selectedTripType == "2" ? 2 : 1, ), ), - child: _selectedTripType == "2" - ? Icon(Icons.rectangle, size: 8, color: Colors.white) - : null, // Add checkmark if selected + child: + _selectedTripType == "2" + ? Icon(Icons.rectangle, size: 8, color: Colors.white) + : null, // Add checkmark if selected ), - ) + ), ], ), @@ -1344,29 +1414,26 @@ class _PolicyState extends State { List _buildSubmit(isDesktop) { return [ ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: layoutColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: BorderSide(color: layoutColor ?? Colors.grey, width: 2), - ), - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: layoutColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(color: layoutColor ?? Colors.grey, width: 2), ), - onPressed: () { - context.go('/PolicyList'); - }, - child: Text( - "Cancel", - style: GoogleFonts.poppins(fontSize: 10), - )), - SizedBox( - width: 20, + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + ), + onPressed: () { + context.go('/PolicyList'); + }, + child: Text("Cancel", style: GoogleFonts.poppins(fontSize: 10)), ), + SizedBox(width: 20), MouseRegion( - cursor: isViewMode - ? SystemMouseCursors.forbidden - : SystemMouseCursors.click, + cursor: + isViewMode + ? SystemMouseCursors.forbidden + : SystemMouseCursors.click, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: @@ -1384,12 +1451,9 @@ class _PolicyState extends State { ), onPressed: isViewMode ? null : handleSubmit, // Disable when in view mode - child: Text( - "Submit", - style: GoogleFonts.poppins(fontSize: 10), - ), + child: Text("Submit", style: GoogleFonts.poppins(fontSize: 10)), ), - ) + ), ]; } @@ -1410,10 +1474,7 @@ class _PolicyState extends State { contentPadding: EdgeInsets.zero, visualDensity: VisualDensity.compact, dense: true, - title: Text( - "Domestic", - style: GoogleFonts.poppins(fontSize: 12), - ), + title: Text("Domestic", style: GoogleFonts.poppins(fontSize: 12)), value: "1", groupValue: _selectedTripType, onChanged: (value) { diff --git a/lib/Screens/policy/policy_list.dart b/lib/Screens/policy/policy_list.dart index 4c8ece3..6c69199 100644 --- a/lib/Screens/policy/policy_list.dart +++ b/lib/Screens/policy/policy_list.dart @@ -14,14 +14,12 @@ import '../../services/apiService.dart'; import '../../utils/auth_utils.dart'; import '../../utils/pagination.dart'; - class PolicyList extends StatefulWidget { @override _PolicyListState createState() => _PolicyListState(); } class _PolicyListState extends State { - final ApiService apiService = ApiService(); late Future> futurePolicy; @@ -37,7 +35,6 @@ class _PolicyListState extends State { List filteredPolicy = []; TextEditingController searchController = TextEditingController(); - int currentPage = 0; int itemsPerPage = 10; @@ -68,14 +65,14 @@ class _PolicyListState extends State { setState(() { layoutColor = - layoutString != null - ? Color(int.parse(layoutString)) - : Colors.redAccent; + layoutString != null + ? Color(int.parse(layoutString)) + : Colors.redAccent; bodyColor = - bodyStringColor != null - ? Color(int.parse(bodyStringColor)) - : Colors.white; + bodyStringColor != null + ? Color(int.parse(bodyStringColor)) + : Colors.white; }); } @@ -84,44 +81,49 @@ class _PolicyListState extends State { return prefs.getString('auth_token'); } - - Future> fetchPolicy() async { - final data = await apiService.fetchAllPolicy(); - return data; // Returning raw JSON list + final data = await apiService.fetchAllPolicy(); + return data; // Returning raw JSON list } - + // Refresh user list after update void refreshPolicyList() { setState(() { futurePolicy = fetchPolicy(); // Re-fetch users after status update + + futurePolicy.then((object) { + setState(() { + allPolicy = object; + }); + }); // Wait for futurePlans to be fetched and update allPlans }); } void filterPolicy(String query) { - print("allPolicy before filtering: $query"); final lowerQuery = query.toLowerCase(); setState(() { filteredPolicy = allPolicy.where((object) { - return (object['name']?.toLowerCase().contains(lowerQuery) ?? - false) || + final isActiveStatus = + object['is_active'] == "1" ? "active" : "inactive"; + return (object['policy_id']?.toLowerCase().contains(lowerQuery) ?? + false) || + (object['name']?.toLowerCase().contains(lowerQuery) ?? false) || (object['policy_type']?.toLowerCase().contains(lowerQuery) ?? false) || - (object['is_active']?.toLowerCase().contains(lowerQuery) ?? false);}).toList(); + (isActiveStatus.contains(lowerQuery)); + }).toList(); currentPage = 0; }); - print("filtered: $filteredPolicy"); + print("filteredPolicy: $filteredPolicy"); } - - void handleActiveStatus( - Map policyData, - String policyId, - String currentStatus, - ) async { + Map policyData, + String policyId, + String currentStatus, + ) async { print("Toggling user status - $policyId (Current: $currentStatus)"); final String apiUrlData = @@ -159,7 +161,6 @@ class _PolicyListState extends State { if (response.statusCode == 200) { print("policyData submitted successfully!"); print("Response: ${response.body}"); - loadAllGroups(); } else { print("Failed to submit policyData. Status: ${response.statusCode}"); @@ -175,31 +176,45 @@ class _PolicyListState extends State { print("policystatus: $status"); print("policysData: $policydata"); - // handleActiveStatus(groupdata, groupId, status); - print("Calling handleActiveStatus with: id=$policyId, status=$status"); handleActiveStatus(policydata, policyId.toString(), status.toString()); } + Future refreshData() async { + loadAllGroups(); + } + Future loadAllGroups() async { try { final result = await apiService.fetchAllPolicy(); - - // Sort by policy_id descending (latest first) - result.sort((a, b) { - int idA = int.tryParse(a['policy_id'].toString()) ?? 0; - int idB = int.tryParse(b['policy_id'].toString()) ?? 0; - return idB.compareTo(idA); // latest first - }); - setState(() { - futurePolicy = result as Future; + allPolicy = result; }); - print("Fetched services: $futurePolicy"); + print("Fetched services: $allPolicy"); } catch (e) { print('Error fetching role list: $e'); } } + // Future 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; + // }); + // print("Fetched services: $futurePolicy"); + // refreshPolicyList(); + // } catch (e) { + // print('Error fetching role list: $e'); + // } + // } @override Widget build(BuildContext context) { @@ -216,16 +231,16 @@ class _PolicyListState extends State { drawer: CustomDrawer(isDesktop: false), body: Padding( padding: - isDesktop - ? EdgeInsets.symmetric( - horizontal: - MediaQuery.of(context).size.width * - 0.1, // 30% of screen width as horizontal padding - vertical: - MediaQuery.of(context).size.height * - 0, // 5% of screen height as vertical padding - ) - : EdgeInsets.all(0), + isDesktop + ? EdgeInsets.symmetric( + horizontal: + MediaQuery.of(context).size.width * + 0.1, // 30% of screen width as horizontal padding + vertical: + MediaQuery.of(context).size.height * + 0, // 5% of screen height as vertical padding + ) + : EdgeInsets.all(0), child: Row( children: [ // if (isDesktop) CustomDrawer(isDesktop: true), @@ -265,9 +280,9 @@ class _PolicyListState extends State { // : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), // padding: const EdgeInsets.all(10), height: - isDesktop - ? MediaQuery.of(context).size.height * 0.98 - : MediaQuery.of(context).size.height, + isDesktop + ? MediaQuery.of(context).size.height * 0.98 + : MediaQuery.of(context).size.height, child: Padding( padding: const EdgeInsets.all(10.0), @@ -362,11 +377,10 @@ class _PolicyListState extends State { // Print the resolved value print("CREATELIAS - $policyData"); context.go('/Policy'); - }, child: Row( mainAxisSize: - MainAxisSize.min, // Ensures content fits nicely + MainAxisSize.min, // Ensures content fits nicely children: [ Text( "Add New Policy", @@ -390,49 +404,49 @@ class _PolicyListState extends State { isDesktop ? SizedBox.shrink() : Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Container( - width: MediaQuery.of(context).size.width * 0.8, - height: 35, - child: TextField( - controller: searchController, - onChanged: filterPolicy, - decoration: InputDecoration( - hintText: "Search for a Policy", - hintStyle: TextStyle( - fontSize: 12, - color: Color(0xFF9E9DBD), - ), - prefixIcon: Icon( - Icons.search, - color: Color(0xFF9E9DBD), - size: 18, - ), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: Colors.grey.shade200, - width: 0.5, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: Colors.grey.shade300, - width: 1, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: MediaQuery.of(context).size.width * 0.8, + height: 35, + child: TextField( + controller: searchController, + onChanged: filterPolicy, + decoration: InputDecoration( + hintText: "Search for a Policy", + hintStyle: TextStyle( + fontSize: 12, + color: Color(0xFF9E9DBD), + ), + prefixIcon: Icon( + Icons.search, + color: Color(0xFF9E9DBD), + size: 18, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade200, + width: 0.5, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, + width: 1, + ), + ), ), + style: GoogleFonts.poppins(fontSize: 12), ), ), - style: GoogleFonts.poppins(fontSize: 12), - ), + // SizedBox(width: 16), + ], ), - // SizedBox(width: 16), - ], - ), const SizedBox(height: 10), FutureBuilder>( future: futurePolicy, @@ -475,7 +489,7 @@ class _PolicyListState extends State { } List policyData = - filteredPolicy.isNotEmpty ? filteredPolicy : allPolicy; + filteredPolicy.isNotEmpty ? filteredPolicy : allPolicy; policyData.sort((a, b) { DateTime dateA = DateTime.parse(a['created_on']); @@ -485,10 +499,10 @@ class _PolicyListState extends State { }); List paginatedUser = - policyData - .skip(currentPage * itemsPerPage) - .take(itemsPerPage) - .toList(); + policyData + .skip(currentPage * itemsPerPage) + .take(itemsPerPage) + .toList(); Widget table = LayoutBuilder( builder: (context, constraints) { @@ -544,104 +558,119 @@ class _PolicyListState extends State { ), ], rows: - paginatedUser.map((policy) { - String policyId = - policy['policy_id'].toString(); // Get policy ID - bool isSelected = selectedPolicyId == policyId; + paginatedUser.map((policy) { + String policyId = + policy['policy_id'] + .toString(); // Get policy ID + bool isSelected = selectedPolicyId == policyId; - return DataRow( - cells: [ - DataCell( - Text( - "${policy['name'] ?? ''}", - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", + return DataRow( + cells: [ + DataCell( + Text( + "${policy['name'] ?? ''}", + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ), + ), ), - ), - ), - DataCell( - Text( - policy['domestic'] == "1" - ? "Domestic" - : "International", - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", + DataCell( + Text( + policy['domestic'] == "1" + ? "Domestic" + : "International", + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ), + ), ), - ), - ), - DataCell( - Text( - policy['is_active'] == "1" - ? "Active" - : "Inactive", - style: TextStyle( - color: + DataCell( + Text( policy['is_active'] == "1" - ? Colors.green - : Colors.grey, - fontFamily: "Inter", - fontWeight: FontWeight.w400, + ? "Active" + : "Inactive", + style: TextStyle( + color: + policy['is_active'] == "1" + ? Colors.green + : Colors.grey, + fontFamily: "Inter", + fontWeight: FontWeight.w400, + ), ), ), - ), - DataCell( - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - GestureDetector( - onTap: () async { - final rawId = policy['policy_id']; - final intPolicyId = - rawId is int - ? rawId - : int.tryParse(rawId.toString()) ?? 0; + DataCell( + Row( + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + GestureDetector( + onTap: () async { + final rawId = policy['policy_id']; + final intPolicyId = + rawId is int + ? rawId + : int.tryParse( + rawId.toString(), + ) ?? + 0; - Map policyData = await apiService - .getSinglePolicy(intPolicyId); + Map policyData = + await apiService + .getSinglePolicy( + intPolicyId, + ); - print("PolicyDATa: $policyData"); + print("PolicyDATa: $policyData"); - context.go("/Policy", extra: policyData); - }, - child: Tooltip( - message: 'Edit Policy Details', - child: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15, + context.go( + "/Policy", + extra: policyData, + ); + }, + child: Tooltip( + message: 'Edit Policy Details', + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15, + ), + ), ), - ), + SizedBox(width: 5), + GestureDetector( + onTap: () { + final idStr = policy['policy_id']; + final id = int.tryParse( + idStr.toString(), + ); + + if (id == null) { + print("group_id is null"); + return; + } + final status = + policy['is_active']; + + deletePolicy(policy, id, status); + }, + child: Tooltip( + message: 'Delete Policy Details', + child: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15, + ), + ), + ), + ], ), - SizedBox(width: 5), - GestureDetector( - onTap: () { - final idStr = policy['policy_id']; - final id = int.tryParse(idStr.toString()); - - if (id == null) { - print("group_id is null"); - return; - } - final status = policy['is_active']; - - deletePolicy(policy, id, status); - }, - child: Tooltip( - message: 'Delete Policy Details', - child: Image.asset( - 'assets/images/IconsImg/delete.png', - width: 20, - height: 15, - ), - ),), - ], - ), - ), - ], - ); - }).toList(), + ), + ], + ); + }).toList(), ), ); }, @@ -654,7 +683,10 @@ class _PolicyListState extends State { return Card( color: Colors.white, - margin: EdgeInsets.symmetric(horizontal: 12, vertical: 6), + margin: EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), @@ -666,7 +698,8 @@ class _PolicyListState extends State { children: [ // Row 1: Policy Name and Actions Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ Expanded( child: RichText( @@ -684,11 +717,12 @@ class _PolicyListState extends State { ), ), TextSpan( - text: "${object['name'] ?? 'N/A'}", + text: + "${object['name'] ?? 'N/A'}", style: TextStyle( fontSize: 13, fontFamily: "Inter", - fontWeight: FontWeight.normal + fontWeight: FontWeight.normal, ), ), ], @@ -701,16 +735,26 @@ class _PolicyListState extends State { GestureDetector( onTap: () async { final rawId = object['policy_id']; - final intPolicyId = rawId is int - ? rawId - : int.tryParse(rawId.toString()) ?? 0; + final intPolicyId = + rawId is int + ? rawId + : int.tryParse( + rawId.toString(), + ) ?? + 0; Map policyData = - await apiService.getSinglePolicy(intPolicyId); + await apiService + .getSinglePolicy( + intPolicyId, + ); print("PolicyDATa: $policyData"); - context.go("/Policy", extra: policyData); + context.go( + "/Policy", + extra: policyData, + ); }, child: Tooltip( message: 'Edit Policy Details', @@ -725,7 +769,9 @@ class _PolicyListState extends State { GestureDetector( onTap: () { final idStr = object['policy_id']; - final id = int.tryParse(idStr.toString()); + final id = int.tryParse( + idStr.toString(), + ); if (id == null) { print("policy_id is null"); @@ -750,7 +796,6 @@ class _PolicyListState extends State { ), SizedBox(height: 8), // Spacing - // Row 2: Policy Type RichText( text: TextSpan( @@ -767,13 +812,14 @@ class _PolicyListState extends State { ), ), TextSpan( - text: object['domestic'] == "1" - ? "Domestic" - : "International", + text: + object['domestic'] == "1" + ? "Domestic" + : "International", style: TextStyle( fontSize: 13, fontFamily: "Inter", - fontWeight: FontWeight.normal + fontWeight: FontWeight.normal, ), ), ], @@ -787,7 +833,6 @@ class _PolicyListState extends State { ); } - Widget buildMobileCardView2(List paginatedUser) { return ListView.builder( itemCount: paginatedUser.length, @@ -810,7 +855,6 @@ class _PolicyListState extends State { children: [ Column( children: [ - Expanded( flex: 1, child: Text( @@ -859,30 +903,41 @@ class _PolicyListState extends State { onTap: () async { final rawId = object['policy_id']; final intPolicyId = - rawId is int - ? rawId - : int.tryParse(rawId.toString()) ?? 0; + rawId is int + ? rawId + : int.tryParse( + rawId.toString(), + ) ?? + 0; - Map policyData = await apiService - .getSinglePolicy(intPolicyId); + Map policyData = + await apiService.getSinglePolicy( + intPolicyId, + ); print("PolicyDATa: $policyData"); - context.go("/Policy", extra: policyData); + context.go( + "/Policy", + extra: policyData, + ); }, child: Tooltip( message: 'Edit Policy Details', - child: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15, - ),), + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15, + ), + ), ), SizedBox(width: 5), GestureDetector( onTap: () { final idStr = object['policy_id']; - final id = int.tryParse(idStr.toString()); + final id = int.tryParse( + idStr.toString(), + ); if (id == null) { print("group_id is null"); @@ -894,11 +949,12 @@ class _PolicyListState extends State { }, child: Tooltip( message: 'Delete Policy Details', - child: Image.asset( - 'assets/images/IconsImg/delete.png', - width: 20, - height: 15, - ),), + child: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15, + ), + ), ), ], ), @@ -916,12 +972,12 @@ class _PolicyListState extends State { children: [ Expanded( child: - isDesktop - ? SingleChildScrollView( - scrollDirection: Axis.vertical, - child: table, // <-- your existing table - ) - : buildMobileCardView(paginatedUser), + isDesktop + ? SingleChildScrollView( + scrollDirection: Axis.vertical, + child: table, // <-- your existing table + ) + : buildMobileCardView(paginatedUser), ), PaginationControls( currentPage: currentPage, @@ -951,4 +1007,4 @@ class _PolicyListState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/Screens/traveller/travellerDetails.dart b/lib/Screens/traveller/travellerDetails.dart index 1cf933b..31ac927 100644 --- a/lib/Screens/traveller/travellerDetails.dart +++ b/lib/Screens/traveller/travellerDetails.dart @@ -19,13 +19,14 @@ class TravellerData extends StatefulWidget { final int? travellerId; // <-- Add this final Map? travellerData; - const TravellerData( - {super.key, - required this.isDesktop, - this.layoutColor, - required this.fetchGetTraveller, - this.travellerId, - this.travellerData}); + const TravellerData({ + super.key, + required this.isDesktop, + this.layoutColor, + required this.fetchGetTraveller, + this.travellerId, + this.travellerData, + }); @override TravellerDataState createState() => TravellerDataState(); @@ -49,12 +50,7 @@ class TravellerDataState extends State { int? travellerDataId; late String isActive = "1"; - List dataHeader = [ - "first_name", - "last_name", - "email", - "mobile", - ]; + List dataHeader = ["first_name", "last_name", "email", "mobile"]; Map travellerDetails() { final data = { @@ -72,7 +68,6 @@ class TravellerDataState extends State { void initState() { super.initState(); - apiData = null; for (var field in dataHeader) { controllers[field] = TextEditingController(); @@ -115,7 +110,6 @@ class TravellerDataState extends State { }); } - void toggleStatus() { setState(() { isActive = isActive == "1" ? "0" : "1"; @@ -132,7 +126,7 @@ class TravellerDataState extends State { "mobile": controllers["mobile"]?.text, }; - final requiredFields = ["first_name","last_name","email","mobile"]; + final requiredFields = ["first_name", "last_name", "email", "mobile"]; bool hasFocused = false; // Check validation for each field @@ -150,13 +144,14 @@ class TravellerDataState extends State { if (data["mobile"] != null && data["mobile"].toString().isNotEmpty) { if (!RegExp(r"^\d{10}$").hasMatch(data["mobile"].toString())) { errorMessages["mobile"] = - "Enter 10 digits"; // Invalid mobile number format + "Enter 10 digits"; // Invalid mobile number format } } if (data["email"] != null && data["email"].toString().isNotEmpty) { - if (!RegExp(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") - .hasMatch(data["email"].toString())) { + if (!RegExp( + r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", + ).hasMatch(data["email"].toString())) { errorMessages["email"] = "Invalid email format"; // Invalid email format } } @@ -195,7 +190,9 @@ class TravellerDataState extends State { apiUrldata = '$apiUrl/api/travellers/update/$travellerDataId'; travellerData["traveller_id"] = travellerDataId.toString(); travellerData["updated_by"] = userId; - (travellerData.containsKey("created_by")) ? travellerData.remove("created_by") : '' ; + (travellerData.containsKey("created_by")) + ? travellerData.remove("created_by") + : ''; } else { print("for add Traveller id - null"); apiUrldata = '$apiUrl/api/travellers/create'; @@ -217,10 +214,10 @@ class TravellerDataState extends State { }; final body = jsonEncode(travellerData); - final response = travellerDataId != null - ? await http.put(uri, headers: headers, body: body) - : await http.post(uri, headers: headers, body: body); - + final response = + travellerDataId != null + ? await http.put(uri, headers: headers, body: body) + : await http.post(uri, headers: headers, body: body); switch (response.statusCode) { case 200: @@ -241,7 +238,6 @@ class TravellerDataState extends State { print("Failed to submit traveller. Status: ${response.statusCode}"); print("Error: ${response.body}"); } - } catch (e) { print(" Error submitting plan: $e"); } @@ -249,7 +245,6 @@ class TravellerDataState extends State { @override Widget build(BuildContext context) { - return AlertDialog( backgroundColor: Colors.white, contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30), @@ -264,27 +259,30 @@ class TravellerDataState extends State { Row( children: [ Text( - (travellerDataId != null) ? 'Edit Traveller' : 'Create Traveller', - style: GoogleFonts.poppins(fontSize: 15, color: Colors.black), + (travellerDataId != null) + ? 'Edit Traveller' + : 'Create Traveller', + style: GoogleFonts.poppins( + fontSize: 15, + color: Colors.black, + ), ), const Spacer(), ], ), const SizedBox(height: 2), - Divider( - thickness: 0.2, - color: Colors.blueGrey.shade100, - ), + Divider(thickness: 0.2, color: Colors.blueGrey.shade100), const SizedBox(height: 5), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "First Name", + "First Name *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -292,19 +290,23 @@ class TravellerDataState extends State { isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( - height: 40, - child: TextField( - controller: controllers["first_name"], - focusNode: focusNodes["first_name"], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "First Name", - labelStyle: TextStyle(fontSize: 11, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), + height: 40, + child: TextField( + controller: controllers["first_name"], + focusNode: focusNodes["first_name"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "First Name", + labelStyle: TextStyle( + fontSize: 11, + color: Colors.grey, ), - )), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), ), if (errorMessages["first_name"] != null) ...[ SizedBox(height: 5), // Space before error message @@ -315,18 +317,17 @@ class TravellerDataState extends State { ], ], ), - SizedBox( - height: 15, - ), + SizedBox(height: 15), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Last Name", + "Last Name *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -334,19 +335,23 @@ class TravellerDataState extends State { isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( - height: 40, - child: TextField( - controller: controllers["last_name"], - focusNode: focusNodes["last_name"], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "Last Name", - labelStyle: TextStyle(fontSize: 11, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), + height: 40, + child: TextField( + controller: controllers["last_name"], + focusNode: focusNodes["last_name"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Last Name", + labelStyle: TextStyle( + fontSize: 11, + color: Colors.grey, ), - )), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), ), if (errorMessages["last_name"] != null) ...[ SizedBox(height: 5), // Space before error message @@ -357,18 +362,17 @@ class TravellerDataState extends State { ], ], ), - SizedBox( - height: 15, - ), + SizedBox(height: 15), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Email", + "Email *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -376,19 +380,23 @@ class TravellerDataState extends State { isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( - height: 40, - child: TextField( - controller: controllers["email"], - focusNode: focusNodes["email"], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "Email", - labelStyle: TextStyle(fontSize: 11, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), + height: 40, + child: TextField( + controller: controllers["email"], + focusNode: focusNodes["email"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Email", + labelStyle: TextStyle( + fontSize: 11, + color: Colors.grey, ), - )), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), ), if (errorMessages["email"] != null) ...[ SizedBox(height: 5), // Space before error message @@ -399,18 +407,17 @@ class TravellerDataState extends State { ], ], ), - SizedBox( - height: 15, - ), + SizedBox(height: 15), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Mobile", + "Mobile *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( @@ -418,19 +425,23 @@ class TravellerDataState extends State { isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( - height: 40, - child: TextField( - controller: controllers["mobile"], - focusNode: focusNodes["mobile"], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "Mobile", - labelStyle: TextStyle(fontSize: 11, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), + height: 40, + child: TextField( + controller: controllers["mobile"], + focusNode: focusNodes["mobile"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Mobile", + labelStyle: TextStyle( + fontSize: 11, + color: Colors.grey, ), - )), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), ), if (errorMessages["mobile"] != null) ...[ SizedBox(height: 5), // Space before error message @@ -441,9 +452,7 @@ class TravellerDataState extends State { ], ], ), - SizedBox( - height: 15, - ), + SizedBox(height: 15), if (travellerDataId != null) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -451,13 +460,16 @@ class TravellerDataState extends State { Text( "Change Status ", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), Tooltip( message: - isActive == "1" ? "Tap to deactivate" : "Tap to activate", + isActive == "1" + ? "Tap to deactivate" + : "Tap to activate", child: GestureDetector( onTap: toggleStatus, child: Text( @@ -469,13 +481,10 @@ class TravellerDataState extends State { ), ), ), - ) + ), ], ), - if (travellerDataId != null) - SizedBox( - height: 15, - ), + if (travellerDataId != null) SizedBox(height: 15), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -512,18 +521,22 @@ class TravellerDataState extends State { borderRadius: BorderRadius.circular(8), ), ), - child: Text('Save', - style: GoogleFonts.poppins( - fontSize: 11, color: Colors.white)), + child: Text( + 'Save', + style: GoogleFonts.poppins( + fontSize: 11, + color: Colors.white, + ), + ), ), ), ], - ) + ), // : SizedBox.shrink(), ], ), - ) - ) + ), + ), ); } -} \ No newline at end of file +} diff --git a/lib/Screens/traveller/travellerList.dart b/lib/Screens/traveller/travellerList.dart index 7cb3ecd..c26997b 100644 --- a/lib/Screens/traveller/travellerList.dart +++ b/lib/Screens/traveller/travellerList.dart @@ -25,7 +25,7 @@ class TravellerList extends StatefulWidget { class TravellerListState extends State { final GlobalKey travellerListKey = - GlobalKey(); + GlobalKey(); final ApiService apiService = ApiService(); late Future> futureTraveller; @@ -67,13 +67,15 @@ class TravellerListState extends State { String? bodyStringColor = await getBodyColor(); setState(() { - layoutColor = layoutString != null - ? Color(int.parse(layoutString)) - : Colors.redAccent; + layoutColor = + layoutString != null + ? Color(int.parse(layoutString)) + : Colors.redAccent; - bodyColor = bodyStringColor != null - ? Color(int.parse(bodyStringColor)) - : Colors.white; + bodyColor = + bodyStringColor != null + ? Color(int.parse(bodyStringColor)) + : Colors.white; }); } @@ -98,8 +100,8 @@ class TravellerListState extends State { Future> fetchGetTraveller() async { String? ordId = await getOrgId(); - final String apiUrlData = '$apiUrl/api/travellers?org_id=$ordId'; - + final String apiUrlData = + '$apiUrl/api/travellers?for=table_view&org_id=$ordId'; final String? token = await getToken(); @@ -143,16 +145,24 @@ class TravellerListState extends State { print("all before filtering: $query"); final lowerQuery = query.toLowerCase(); setState(() { - filteredTraveller = allTraveller.where((object) { - final isActiveStatus = - object['is_active'] == "1" ? "active" : "inactive"; - return (object['traveller_id']?.toLowerCase().contains(lowerQuery) ?? - false) || - (object['name']?.toLowerCase().contains(lowerQuery) ?? false) || - (object['description']?.toLowerCase().contains(lowerQuery) ?? - false) || - (isActiveStatus.contains(lowerQuery)); - }).toList(); + filteredTraveller = + allTraveller.where((object) { + final isActiveStatus = + object['is_active'] == "1" ? "active" : "inactive"; + return (object['traveller_id']?.toLowerCase().contains( + lowerQuery, + ) ?? + false) || + (object['first_name']?.toLowerCase().contains(lowerQuery) ?? + false) || + (object['last_name']?.toLowerCase().contains(lowerQuery) ?? + false) || + (object['mobile']?.toLowerCase().contains(lowerQuery) ?? + false) || + (object['email']?.toLowerCase().contains(lowerQuery) ?? + false) || + (isActiveStatus.contains(lowerQuery)); + }).toList(); currentPage = 0; }); print("filteredTraveller: $filteredTraveller"); @@ -160,34 +170,40 @@ class TravellerListState extends State { @override Widget build(BuildContext context) { - return ResponsiveBuilder(builder: (context, sizingInfo) { - bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + return ResponsiveBuilder( + builder: (context, sizingInfo) { + bool isDesktop = + sizingInfo.deviceScreenType == DeviceScreenType.desktop; - return Scaffold( - backgroundColor: Color(0xFFf5f5f5), - // appBar: isDesktop ? null : const CustomAppBar(title: 'User Management'), - // drawer: isDesktop ? null : CustomDrawer(isDesktop: false), - appBar: CustomAppBar(isDesktop: isDesktop), - drawer: CustomDrawer(isDesktop: false), - body: Padding( - padding: isDesktop - ? EdgeInsets.symmetric( - horizontal: MediaQuery.of(context).size.width * - 0.1, // 30% of screen width as horizontal padding - vertical: MediaQuery.of(context).size.height * - 0, // 5% of screen height as vertical padding - ) - : EdgeInsets.all(0), - child: Row( - children: [ - // if (isDesktop) CustomDrawer(isDesktop: true), - // const Expanded(child: Center(child: Text("User Page Content"))), - Expanded(child: buildGroupList(isDesktop)), - ], + return Scaffold( + backgroundColor: Color(0xFFf5f5f5), + // appBar: isDesktop ? null : const CustomAppBar(title: 'User Management'), + // drawer: isDesktop ? null : CustomDrawer(isDesktop: false), + appBar: CustomAppBar(isDesktop: isDesktop), + drawer: CustomDrawer(isDesktop: false), + body: Padding( + padding: + isDesktop + ? EdgeInsets.symmetric( + horizontal: + MediaQuery.of(context).size.width * + 0.1, // 30% of screen width as horizontal padding + vertical: + MediaQuery.of(context).size.height * + 0, // 5% of screen height as vertical padding + ) + : EdgeInsets.all(0), + child: Row( + children: [ + // if (isDesktop) CustomDrawer(isDesktop: true), + // const Expanded(child: Center(child: Text("User Page Content"))), + Expanded(child: buildGroupList(isDesktop)), + ], + ), ), - ), - ); - }); + ); + }, + ); } Widget buildGroupList(bool isDesktop) { @@ -215,98 +231,103 @@ class TravellerListState extends State { // ? EdgeInsets.all(10.0) // : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), // padding: const EdgeInsets.all(10), - height: isDesktop - ? MediaQuery.of(context).size.height * 0.98 - : MediaQuery.of(context).size.height, + height: + isDesktop + ? MediaQuery.of(context).size.height * 0.98 + : MediaQuery.of(context).size.height, child: Padding( + padding: const EdgeInsets.all(10.0), + child: Container( + color: Colors.white, padding: const EdgeInsets.all(10.0), - child: Container( - color: Colors.white, - padding: const EdgeInsets.all(10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Divider( + // thickness: 0.2, // how "thick" the line is + // color: Colors.grey, // optional + // ), + Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - // Divider( - // thickness: 0.2, // how "thick" the line is - // color: Colors.grey, // optional - // ), Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - Text( - 'Traveller Details', - style: GoogleFonts.poppins( - fontSize: isDesktop ? 16 : 14, - fontWeight: FontWeight.w600, - color: Colors.black, - ), - ), - ], + Text( + 'Traveller Details', + style: GoogleFonts.poppins( + fontSize: isDesktop ? 16 : 14, + fontWeight: FontWeight.w600, + color: Colors.black, + ), ), - if (isDesktop) - SizedBox( - width: MediaQuery.of(context).size.width * 0.16, - ), + ], + ), + if (isDesktop) + SizedBox(width: MediaQuery.of(context).size.width * 0.16), - if (isDesktop) - Container( - width: MediaQuery.of(context).size.width * 0.2, - height: 40, - child: TextField( - controller: searchController, - onChanged: filterTraveller, - decoration: InputDecoration( - hintText: "Search ...", - hintStyle: TextStyle( - fontSize: 12, color: Color(0xFF9E9DBD)), - prefixIcon: Icon( - Icons.search, - color: Color(0xFF9E9DBD), - size: 18, - ), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: Colors.grey.shade200, width: 0.5), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: Colors.grey.shade300, width: 1), - ), + if (isDesktop) + Container( + width: MediaQuery.of(context).size.width * 0.2, + height: 40, + child: TextField( + controller: searchController, + onChanged: filterTraveller, + decoration: InputDecoration( + hintText: "Search ...", + hintStyle: TextStyle( + fontSize: 12, + color: Color(0xFF9E9DBD), + ), + prefixIcon: Icon( + Icons.search, + color: Color(0xFF9E9DBD), + size: 18, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade200, + width: 0.5, ), - style: GoogleFonts.poppins( - fontSize: 12, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, + width: 1, ), ), ), - // SizedBox(width: 16), - Spacer(), + style: GoogleFonts.poppins(fontSize: 12), + ), + ), + // SizedBox(width: 16), + Spacer(), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFF114D8B), - foregroundColor: Colors.white, - disabledBackgroundColor: Color(0xFF114D8B), - disabledForegroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: - BorderSide(color: Color(0xFF114D8B), width: 2), - ), - padding: EdgeInsets.symmetric( - horizontal: 20, vertical: 12), - ), - onPressed: () async { - showDialog( - context: context, - builder: (context) => TravellerData( + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF114D8B), + foregroundColor: Colors.white, + disabledBackgroundColor: Color(0xFF114D8B), + disabledForegroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(color: Color(0xFF114D8B), width: 2), + ), + padding: EdgeInsets.symmetric( + horizontal: 20, + vertical: 12, + ), + ), + onPressed: () async { + showDialog( + context: context, + builder: + (context) => TravellerData( isDesktop: isDesktop, layoutColor: layoutColor!, fetchGetTraveller: refreshData, @@ -314,37 +335,34 @@ class TravellerListState extends State { // role: // "Travel Agent" ), - ); - }, - child: Row( - mainAxisSize: + ); + }, + child: Row( + mainAxisSize: MainAxisSize.min, // Ensures content fits nicely - children: [ - Text( - "Add Traveller", - style: GoogleFonts.poppins( - fontSize: isDesktop ? 13 : 11, - ), - ), - SizedBox(width: 8), // spacing between icon and text - Icon( - Icons.add_circle_outline_rounded, - size: 15, - color: Colors.white, - ), - ], + children: [ + Text( + "Add Traveller", + style: GoogleFonts.poppins( + fontSize: isDesktop ? 13 : 11, + ), ), - ), - ], - ), - - if (!isDesktop) - SizedBox( - height: 5, + SizedBox(width: 8), // spacing between icon and text + Icon( + Icons.add_circle_outline_rounded, + size: 15, + color: Colors.white, + ), + ], ), - isDesktop - ? SizedBox.shrink() - : Row( + ), + ], + ), + + if (!isDesktop) SizedBox(height: 5), + isDesktop + ? SizedBox.shrink() + : Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Container( @@ -356,7 +374,9 @@ class TravellerListState extends State { decoration: InputDecoration( hintText: "Search ...", hintStyle: TextStyle( - fontSize: 12, color: Color(0xFF9E9DBD)), + fontSize: 12, + color: Color(0xFF9E9DBD), + ), prefixIcon: Icon( Icons.search, color: Color(0xFF9E9DBD), @@ -368,226 +388,334 @@ class TravellerListState extends State { enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( - color: Colors.grey.shade200, - width: 0.5), + color: Colors.grey.shade200, + width: 0.5, + ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( - color: Colors.grey.shade300, width: 1), + color: Colors.grey.shade300, + width: 1, + ), ), ), - style: GoogleFonts.poppins( - fontSize: 12, - ), + style: GoogleFonts.poppins(fontSize: 12), ), ), // SizedBox(width: 16), ], ), - const SizedBox(height: 10), - FutureBuilder>( - future: futureTraveller, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError || - !snapshot.hasData || - snapshot.data!.isEmpty) { - return Center( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // const Icon(Icons.error_outline, - // color: Colors.redAccent, size: 60), - // const SizedBox(height: 16), - // Text( - // "Oops!", - // style: GoogleFonts.poppins( - // fontSize: 20, - // fontWeight: FontWeight.bold, - // color: Colors.redAccent), - // ), - const SizedBox(height: 8), - Text( - "No Traveller Available ", - textAlign: TextAlign.center, - style: GoogleFonts.poppins( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.grey), - ), - const SizedBox(height: 20), - Text( - "Please Create Traveller Details", - textAlign: TextAlign.center, - style: GoogleFonts.poppins( - fontSize: 16, color: Colors.grey), - ), - const SizedBox(height: 20), - ], + const SizedBox(height: 10), + FutureBuilder>( + future: futureTraveller, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError || + !snapshot.hasData || + snapshot.data!.isEmpty) { + return Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8), + Text( + "No Traveller Available ", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.grey, + ), ), - ), - ); - } - /* Here collect the list to displayed the data in table or card Used */ - List object = filteredTraveller.isNotEmpty + const SizedBox(height: 20), + Text( + "Please Create Traveller Details", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 16, + color: Colors.grey, + ), + ), + const SizedBox(height: 20), + ], + ), + ), + ); + } + /* Here collect the list to displayed the data in table or card Used */ + List object = + filteredTraveller.isNotEmpty ? filteredTraveller : allTraveller; - /* List is Sorting here */ - object.sort((a, b) { - DateTime dateA = DateTime.parse(a['created_on']); - DateTime dateB = DateTime.parse(b['created_on']); + /* List is Sorting here */ + object.sort((a, b) { + DateTime dateA = DateTime.parse(a['created_on']); + DateTime dateB = DateTime.parse(b['created_on']); - return dateB - .compareTo(dateA); // Descending: newest first - }); + return dateB.compareTo(dateA); // Descending: newest first + }); - /* For pagination for list ... */ - List paginatedTraveller = object + /* For pagination for list ... */ + List paginatedTraveller = + object .skip(currentPage * itemsPerPage) .take(itemsPerPage) .toList(); - /* Table ... */ - Widget table = LayoutBuilder( - builder: (context, constraints) { - double minWidth = - isDesktop ? constraints.maxWidth : 1300; + /* Table ... */ + Widget table = LayoutBuilder( + builder: (context, constraints) { + double minWidth = isDesktop ? constraints.maxWidth : 1300; - return ConstrainedBox( - constraints: BoxConstraints(minWidth: minWidth), - child: DataTable( - dividerThickness: 0.5, - columnSpacing: isDesktop ? 24.0 : 16.0, - border: TableBorder( - horizontalInside: BorderSide( - width: 0.5, color: Colors.grey.shade200), + return ConstrainedBox( + constraints: BoxConstraints(minWidth: minWidth), + child: DataTable( + dividerThickness: 0.5, + columnSpacing: isDesktop ? 24.0 : 16.0, + border: TableBorder( + horizontalInside: BorderSide( + width: 0.5, + color: Colors.grey.shade200, + ), + ), + columns: [ + DataColumn( + label: Text( + 'Name', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600, + ), ), - columns: [ - DataColumn( - label: Text( - 'Name', - style: GoogleFonts.poppins( - fontSize: 13, - fontWeight: FontWeight.w600), - )), - DataColumn( - label: Text( - 'Email', - style: GoogleFonts.poppins( - fontSize: 13, - fontWeight: FontWeight.w600), - )), - DataColumn( - label: Text( - 'Mobile', - style: GoogleFonts.poppins( - fontSize: 13, - fontWeight: FontWeight.w600), - )), - DataColumn( - label: Text( - 'Status', - style: GoogleFonts.poppins( - fontSize: 13, - fontWeight: FontWeight.w600), - )), - DataColumn( - label: Text( - 'Actions', - style: GoogleFonts.poppins( - fontSize: 13, - fontWeight: FontWeight.w600), - )), - ], - rows: paginatedTraveller.map((tableObject) { - String fullName = '${tableObject['first_name'] ?? ''} ${tableObject['last_name'] ?? ''}'; + ), + DataColumn( + label: Text( + 'Email', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + ), + DataColumn( + label: Text( + 'Mobile', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + ), + DataColumn( + label: Text( + 'Status', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + ), + DataColumn( + label: Text( + 'Actions', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + rows: + paginatedTraveller.map((tableObject) { + String fullName = + '${tableObject['first_name'] ?? ''} ${tableObject['last_name'] ?? ''}'; String travellerId = - tableObject['traveller_id'] - .toString(); // Get user ID + tableObject['traveller_id'] + .toString(); // Get user ID bool isSelected = selectedTravellerId == travellerId; - return DataRow(cells: [ - DataCell(Text(fullName ?? 'N/A', - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", - ))), - DataCell( - Text(tableObject['email'] ?? 'N/A', - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", - ), - softWrap: true, - overflow: TextOverflow.ellipsis)), - DataCell( - Text(tableObject['mobile'] ?? 'N/A', - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", - ), - softWrap: true, - overflow: TextOverflow.ellipsis)), - DataCell( - Text( - tableObject['is_active'] == "1" - ? 'Active' - : 'Inactive', - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", - color: tableObject['is_active'] == "1" - ? Colors.green - : Colors.red, + return DataRow( + cells: [ + DataCell( + Text( + fullName ?? 'N/A', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ), ), - softWrap: true, - overflow: TextOverflow.ellipsis, ), - ), - DataCell( - // UserActionsMenu( - // user: forex, - // getUserDetails: (id) => - // apiService.getSingleUser(id), - // ), - GestureDetector( - child: Image.asset( + DataCell( + Text( + tableObject['email'] ?? 'N/A', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ), + softWrap: true, + overflow: TextOverflow.ellipsis, + ), + ), + DataCell( + Text( + tableObject['mobile'] ?? 'N/A', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ), + softWrap: true, + overflow: TextOverflow.ellipsis, + ), + ), + DataCell( + Text( + tableObject['is_active'] == "1" + ? 'Active' + : 'Inactive', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + color: + tableObject['is_active'] == "1" + ? Colors.green + : Colors.grey, + ), + softWrap: true, + overflow: TextOverflow.ellipsis, + ), + ), + DataCell( + GestureDetector( + child: Image.asset( 'assets/images/IconsImg/edit.png', width: 20, - height: 15), + height: 15, + ), + onTap: () async { + // final userId = getUserId(user['user_id']); + // final usersData = await getUserDetails(userId); + // + final travellerId = int.tryParse( + tableObject['traveller_id'] + .toString(), + ); + + if (travellerId != null) { + print( + "Table cell - traveller Id -- $travellerId", + ); + final data = await apiService + .getTravellerDetailsFind( + travellerId, + ); + print("TravellerId -- $data"); + + showDialog( + context: context, + builder: + (context) => TravellerData( + isDesktop: isDesktop, + travellerId: + travellerId, // Pass the ID + travellerData: data, + layoutColor: layoutColor!, + fetchGetTraveller: + refreshData, + // role: + // "Travel Agent" + ), + ); + } else { + print("Invalid ID"); + } + }, + ), + ), + ], + ); + }).toList(), + ), + ); + }, + ); + + /* Card ... */ + Widget buildMobileCardView(List paginatedUser) { + return ListView.builder( + itemCount: paginatedUser.length, + itemBuilder: (context, index) { + final cardObject = paginatedUser[index]; + String fullName = + '${cardObject['first_name'] ?? ''} ${cardObject['last_name'] ?? ''}'; + return Card( + color: Colors.white, + margin: EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status and Employee Code + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + fullName ?? 'N/A', + style: GoogleFonts.poppins( + fontSize: 10, + color: Colors.black87, + fontWeight: FontWeight.w700, + ), + ), + + GestureDetector( + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15, + ), onTap: () async { // final userId = getUserId(user['user_id']); // final usersData = await getUserDetails(userId); // final travellerId = int.tryParse( - tableObject['traveller_id'] - .toString()); + cardObject['traveller_id'].toString(), + ); if (travellerId != null) { - print( - "Table cell - traveller Id -- $travellerId"); + print("travellerId -- $travellerId"); final data = await apiService .getTravellerDetailsFind( - travellerId); + travellerId, + ); print("TravellerId -- $data"); showDialog( context: context, - builder: (context) => - TravellerData( + builder: + (context) => TravellerData( isDesktop: isDesktop, travellerId: - travellerId, // Pass the ID + travellerId, // Pass the ID travellerData: data, layoutColor: layoutColor!, - // fetchGetForex: fetchGetForex, - fetchGetTraveller: refreshData, + // fetchGetTraveller: fetchGetTraveller, + fetchGetTraveller: + refreshData, // role: // "Travel Agent" ), @@ -597,274 +725,124 @@ class TravellerListState extends State { } }, ), - ), - ]); - }).toList(), - ), - ); - }, - ); - - /* Card ... */ - Widget buildMobileCardView(List paginatedUser) { - return ListView.builder( - itemCount: paginatedUser.length, - itemBuilder: (context, index) { - final cardObject = paginatedUser[index]; - String fullName = '${cardObject['first_name'] ?? ''} ${cardObject['last_name'] ?? ''}'; - return Card( - color: Colors.white, - margin: EdgeInsets.symmetric( - horizontal: 12, vertical: 6), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - elevation: 3, - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Status and Employee Code - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - fullName ?? 'N/A', - style: GoogleFonts.poppins( - fontSize: 10, - color: Colors.black87, - fontWeight: FontWeight.w700), - ), - - GestureDetector( - child: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15), - onTap: () async { - // final userId = getUserId(user['user_id']); - // final usersData = await getUserDetails(userId); - // - final travellerId = int.tryParse( - cardObject['traveller_id'] - .toString()); - - if (travellerId != null) { - print( - "travellerId -- $travellerId"); - final data = await apiService - .getTravellerDetailsFind( - travellerId); - print("TravellerId -- $data"); - - showDialog( - context: context, - builder: (context) => - TravellerData( - isDesktop: isDesktop, - travellerId: - travellerId, // Pass the ID - travellerData: data, - layoutColor: layoutColor!, - // fetchGetTraveller: fetchGetTraveller, - fetchGetTraveller: - refreshData, - // role: - // "Travel Agent" - ), - ); - } else { - print("Invalid ID"); - } - }, - ), - // PopupMenuButton( - // color: Colors.white, - // padding: EdgeInsets.zero, - // offset: Offset(0, 30), - // icon: Icon( - // Icons.more_vert, - // color: Color(0xFF475569), - // size: 14, - // ), - // itemBuilder: (context) => [ - // CustomPopupMenuEntry( - // child: Container( - // padding: EdgeInsets.symmetric( - // horizontal: 8, vertical: 8), - // child: Row( - // mainAxisSize: - // MainAxisSize.min, - // mainAxisAlignment: - // MainAxisAlignment.center, - // children: [ - // IconButton( - // icon: Icon( - // Icons - // .remove_red_eye, - // color: Color( - // 0xFF475569), - // size: 18), - // onPressed: () { - // print( - // "USerDAta - $user"); - // // dynamic usersData = apiService - // // .getSingleUser(user[ - // // 'user_id'] - // // is String - // // ? int.parse(user[ - // // 'user_id']) - // // : user[ - // // 'user_id']); - // // - // // print( - // // "USerDAta - $usersData"); - // - // context.go( - // "/CreateUserDetails", - // extra: { - // "selectedUser": - // user, - // "isViewMode": true - // }, - // ); - // }), - // IconButton( - // icon: Image.asset( - // 'assets/images/IconsImg/edit.png', - // width: 20, - // height: 15), - // onPressed: () { - // context.go( - // "/CreateUserDetails", - // extra: { - // "selectedUser": - // user, - // "isViewMode": false - // }, - // ); - // }, - // ), - // ], - // ), - // ), - // ), - // ], - // ), - ], - ), - - SizedBox(height: 2), - // Trip Id and Trip Name - // Name - Row( - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - cardObject['email'] ?? '', - style: GoogleFonts.poppins( - fontSize: 10, - color: Colors.black87), - ), - ], - ), - SizedBox( - width: 10, - ), - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - cardObject['mobile'] ?? '', - style: GoogleFonts.poppins( - fontSize: 10, - color: Colors.black87), - ), - ], - ), - ], - ), - // Actions - // Actions ], ), - ), - ); - }, - ); - } - return Expanded( - child: Column( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: isDesktop + SizedBox(height: 2), + // Trip Id and Trip Name + // Name + Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + '${cardObject['email'] ?? 'N/A'}', + style: GoogleFonts.poppins( + fontSize: 10, + color: Colors.black87, + ), + ), + ], + ), + SizedBox(width: 10), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + '${cardObject['mobile'] ?? 'N/A'}', + style: GoogleFonts.poppins( + fontSize: 10, + color: Colors.black87, + ), + ), + ], + ), + ], + ), + // Actions + // Actions + ], + ), + ), + ); + }, + ); + } + + return Expanded( + child: Column( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: + isDesktop ? (searchController.text.isNotEmpty && - filteredTraveller.isEmpty - ? Center( - child: Text( - "No matches found", - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.grey), - ), - ) - : SingleChildScrollView( - scrollDirection: Axis.vertical, - child: table, - )) + filteredTraveller.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, + color: Colors.grey, + ), + ), + ) + : SingleChildScrollView( + scrollDirection: Axis.vertical, + child: table, + )) : (searchController.text.isNotEmpty && - filteredTraveller.isEmpty - ? Center( - child: Text( - "No matches found", - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.grey), - ), - ) - : buildMobileCardView( - paginatedTraveller)), - ), - // Expanded( - // child: isDesktop - // ? SingleChildScrollView( - // scrollDirection: Axis.vertical, - // child: table, // <-- your existing table - // ) - // : buildMobileCardView(paginatedTraveller), - // ), - PaginationControls( - currentPage: currentPage, - itemsPerPage: itemsPerPage, - totalItems: object.length, - activeColor: layoutColor, // your theme color - onPageChanged: (page) { - setState(() { - currentPage = page; - }); - }, - onItemsPerPageChanged: (items) { - setState(() { - itemsPerPage = items; - currentPage = 0; - }); - }, - ), - ], + filteredTraveller.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, + color: Colors.grey, + ), + ), + ) + : buildMobileCardView( + paginatedTraveller, + )), ), - ); - }, - ) - ]), - )), + // Expanded( + // child: isDesktop + // ? SingleChildScrollView( + // scrollDirection: Axis.vertical, + // child: table, // <-- your existing table + // ) + // : buildMobileCardView(paginatedTraveller), + // ), + PaginationControls( + currentPage: currentPage, + itemsPerPage: itemsPerPage, + totalItems: object.length, + activeColor: layoutColor, // your theme color + onPageChanged: (page) { + setState(() { + currentPage = page; + }); + }, + onItemsPerPageChanged: (items) { + setState(() { + itemsPerPage = items; + currentPage = 0; + }); + }, + ), + ], + ), + ); + }, + ), + ], + ), + ), + ), ); } -} \ No newline at end of file +} diff --git a/lib/Screens/userManagement/create_user/create_user.dart b/lib/Screens/userManagement/create_user/create_user.dart index 74aff24..5b7537e 100644 --- a/lib/Screens/userManagement/create_user/create_user.dart +++ b/lib/Screens/userManagement/create_user/create_user.dart @@ -663,6 +663,7 @@ class _CreateUserFormDetialsState extends State { "last_name", "email", "mobile_no", + "role_id", // "employeeCode", ]; @@ -1125,6 +1126,9 @@ class _CreateUserFormDetialsState extends State { }, ); case "travel": + final fullName = + "${controllers["Fname"]?.text ?? ""} ${controllers["Lname"]?.text ?? ""}" + .trim(); return TravellerDetails( key: travellerDetailsKey, isDesktop: isDesktop, @@ -1134,6 +1138,7 @@ class _CreateUserFormDetialsState extends State { travelDetails: travelDetailsDataFromAPI, // 👈 Pass this down passportFileUrlFromApi: passportFileUrlFromApi, userIdApi: userIdApi, + fullName: fullName, ); default: return PersonalDetails( diff --git a/lib/Screens/userManagement/create_user/personal_details.dart b/lib/Screens/userManagement/create_user/personal_details.dart index 0f757e3..3472508 100644 --- a/lib/Screens/userManagement/create_user/personal_details.dart +++ b/lib/Screens/userManagement/create_user/personal_details.dart @@ -314,6 +314,8 @@ class PersonalDetailsState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 20), + _buildPassportDataRow1(widget.isDesktop), + SizedBox(height: 10), _buildFirstRow(widget.isDesktop), SizedBox(height: 10), _buildSecondRow(widget.isDesktop), @@ -329,6 +331,25 @@ class PersonalDetailsState extends State { ); } + 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 loadAllServices() async { try { final result = await apiService.fetchAllServices(); @@ -1500,7 +1521,7 @@ class PersonalDetailsState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Role", + "Role*", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, @@ -1579,6 +1600,13 @@ class PersonalDetailsState extends State { ), ), ), + 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), + ), + ], ], ); } diff --git a/lib/Screens/userManagement/create_user/traveller_details.dart b/lib/Screens/userManagement/create_user/traveller_details.dart index c952059..ffec240 100644 --- a/lib/Screens/userManagement/create_user/traveller_details.dart +++ b/lib/Screens/userManagement/create_user/traveller_details.dart @@ -32,6 +32,7 @@ class TravellerDetails extends StatefulWidget { final Map? travelDetails; final String? passportFileUrlFromApi; final String? userIdApi; + final String fullName; const TravellerDetails({ Key? key, @@ -42,6 +43,7 @@ class TravellerDetails extends StatefulWidget { this.travelDetails, this.passportFileUrlFromApi, this.userIdApi, + required this.fullName, }) : super(key: key); @override TravellerDetailsState createState() => TravellerDetailsState(); @@ -54,6 +56,8 @@ class TravellerDetailsState extends State { int? expandedIndex; bool isCountryLoading = true; + Map errorMessages = {}; + late final userId; String? selectedFileNames; Uint8List? passportDocumentBytes; @@ -154,18 +158,16 @@ class TravellerDetailsState extends State { String value, String? userId, ) async { - var newField = ""; try { final response = await apiService.CheckDuplicate( label, - newField, + field, value, userId, ); if (response.isNotEmpty) { _clearError(field); - widget.errorMessages[field] = - response['message'] ?? "$label already exists"; + errorMessages[field] = response['message'] ?? "$label Already Exists"; print("Duplicate found: ${response['message']}"); return; } else { @@ -177,9 +179,37 @@ class TravellerDetailsState extends State { } } + // Future apiCheckDuplicate( + // String label, + // String field, + // String value, + // String? userId, + // ) async { + // var newField = ""; + // try { + // final response = await apiService.CheckDuplicate( + // label, + // newField, + // value, + // userId, + // ); + // if (response.isNotEmpty) { + // _clearError(field); + // errorMessages[field] = response['message'] ?? "$label already exists"; + // print("Duplicate found: ${response['message']}"); + // return; + // } else { + // _clearError(field); + // print("No duplicates found."); + // } + // } catch (e) { + // print("Error in checkDuplicate: $e"); + // } + // } + void _clearError(String field) { setState(() { - widget.errorMessages.remove(field); + errorMessages.remove(field); }); } @@ -858,7 +888,7 @@ class TravellerDetailsState extends State { // SizedBox( // height: 10, // ), - // _buildPassportDataRow1(widget.isDesktop), + _buildPassportDataRow1(widget.isDesktop), SizedBox(height: 10), _buildPassportDataRow2(widget.isDesktop), SizedBox(height: 10), @@ -870,28 +900,49 @@ class TravellerDetailsState extends State { Widget _buildPassportDataRow1(bool isDesktop) { return Container( color: Colors.white, - child: - widget.isDesktop - ? Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildFirstNameField(), - Spacer(), - buildLastNameField(), - Spacer(), // Space after Last Name - buildNationality(), - ], - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildFirstNameField(), - SizedBox(height: 8), // Vertical space - buildLastNameField(), - SizedBox(height: 8), - buildNationality(), - ], - ), + child: Row( + children: [ + Text( + "Name as per passport : ", + style: GoogleFonts.poppins( + fontSize: 11, + fontStyle: FontStyle.italic, + letterSpacing: 0.5, + ), + ), + Text( + "${widget.fullName} ", + style: GoogleFonts.poppins( + fontSize: 10, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + color: Colors.black87, + // fontStyle: FontStyle.italic, + ), + ), + ], + ), + // child: widget.isDesktop + // ? Row( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // buildFirstNameField(), + // Spacer(), + // buildLastNameField(), + // Spacer(), // Space after Last Name + // buildNationality(), + // ], + // ) + // : Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // buildFirstNameField(), + // SizedBox(height: 8), // Vertical space + // buildLastNameField(), + // SizedBox(height: 8), + // buildNationality(), + // ], + // ), ); } @@ -984,13 +1035,6 @@ class TravellerDetailsState extends State { ), ), ), - 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 { ), ), ), - 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 { ), ), ), - 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 { ), ), ), - if (widget.errorMessages["passport_number"] != null) ...[ + if (errorMessages["passport_number"] != null) ...[ SizedBox(height: 5), // Space before error message Text( - widget.errorMessages["passport_number"]!, + errorMessages["passport_number"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], @@ -2375,6 +2405,13 @@ class TravellerDetailsState extends State { ), ), ), + 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), + ), + ], ], ); } diff --git a/lib/config/apiUrl.dart b/lib/config/apiUrl.dart index 145911c..eb751b8 100644 --- a/lib/config/apiUrl.dart +++ b/lib/config/apiUrl.dart @@ -1,3 +1,3 @@ //api url const String apiUrl = 'http://apitest.tripapprovaltool.com/tstat_be'; -// const String apiUrl = 'https://uat.tripapprovaltool.com'; +// const String apiUrl = 'https://uat.tripapprovaltool.com/tstat_be'; diff --git a/lib/routes/organizationSetting.dart b/lib/routes/organizationSetting.dart index bf5cde3..c06b3d9 100644 --- a/lib/routes/organizationSetting.dart +++ b/lib/routes/organizationSetting.dart @@ -143,7 +143,7 @@ class OrganizationSettingState extends State { { 'value': '/traveller', 'icon': Icons.travel_explore, - 'label': 'Traveller', + 'label': 'Traveller (Non Employee)', 'description': 'Create and Edit Traveller', }, ]; diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index 5a63f9b..1b1155f 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -318,7 +318,7 @@ class ApiService { Future> fetchAllGroup() async { String? orgId = await getOrgId(); - final String apiUrldata = '$apiUrl/api/groups?org_id=$orgId'; + final String apiUrldata = '$apiUrl/api/groups?for=table_view&org_id=$orgId'; final token = await getToken(); @@ -392,7 +392,7 @@ class ApiService { Future> fetchAllPolicy() async { String? orgId = await getOrgId(); - final String apiUrldata = '$apiUrl/api/policy?org_id=$orgId'; + final String apiUrldata = '$apiUrl/api/policy?for=table_view&org_id=$orgId'; final token = await getToken(); diff --git a/lib/widgets/saving_loader.dart b/lib/widgets/saving_loader.dart new file mode 100644 index 0000000..fe7d0bc --- /dev/null +++ b/lib/widgets/saving_loader.dart @@ -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), + ), + ], + ), + ), + ); + } +}