From b66391fbb5d6ce5de44fe2be10853ad88ee51d94 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Wed, 11 Jun 2025 17:41:07 +0530 Subject: [PATCH] date validations --- lib/Screens/approvals/approval_list.dart | 2 + .../authentication/login/login_widget.dart | 1 + lib/Screens/itnerary/flights.dart | 6 +- lib/Screens/plans/create_plans.dart | 701 +++++++++--------- .../create_user/create_user.dart | 2 +- .../create_user/traveller_details.dart | 193 +++-- lib/data/models/plan.dart | 3 + lib/routes/custom_appBar.dart | 29 +- lib/services/apiService.dart | 3 + 9 files changed, 535 insertions(+), 405 deletions(-) diff --git a/lib/Screens/approvals/approval_list.dart b/lib/Screens/approvals/approval_list.dart index 43c9e15..647c3b4 100644 --- a/lib/Screens/approvals/approval_list.dart +++ b/lib/Screens/approvals/approval_list.dart @@ -898,6 +898,7 @@ class _ApprovalListState extends State { plan.planId, plan.approverId, plan.delegaterId, + plan.approver_status, isViewMode: false, isApprover: @@ -1144,6 +1145,7 @@ class _ApprovalListState extends State { plan.planId, plan.approverId, plan.delegaterId, + plan.approver_status, isViewMode: false, isApprover: true, ); diff --git a/lib/Screens/authentication/login/login_widget.dart b/lib/Screens/authentication/login/login_widget.dart index e8a72fd..f6f62e7 100644 --- a/lib/Screens/authentication/login/login_widget.dart +++ b/lib/Screens/authentication/login/login_widget.dart @@ -125,6 +125,7 @@ class _LoginWidgetState extends State { final token = data['token']; // Assuming the token is in response // final userId = data['user_id'].toString(); + print("Token - $token"); await storeUserDetails(token); ScaffoldMessenger.of(context).showSnackBar( diff --git a/lib/Screens/itnerary/flights.dart b/lib/Screens/itnerary/flights.dart index cbfe605..dd56b7d 100644 --- a/lib/Screens/itnerary/flights.dart +++ b/lib/Screens/itnerary/flights.dart @@ -568,7 +568,7 @@ class FlightScreenState extends State { } bool validateFields() { - // errorMessages.clear(); // Reset errors + errorMessages.clear(); // Reset errors rowCount = 1; // Default row count for One-way if (selectedTripType == "Roundtrip") { @@ -637,7 +637,7 @@ class FlightScreenState extends State { } else { seenFrom.add(fromValue); - errorMessages.remove("from_place_$i"); + // errorMessages.remove("from_place_$i"); } } @@ -646,7 +646,7 @@ class FlightScreenState extends State { errorMessages["to_place_$i"] = "Duplicate Destination "; print("Error: to_place_$i duplicates a previous departure"); } else { - errorMessages.remove("to_place_$i"); + // errorMessages.remove("to_place_$i"); seenTo.add(toValue); } } diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index 31fff2a..cee265f 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -124,6 +124,7 @@ class _CreatePlansState extends State { final bool isApprover = args?['isApprover'] ?? false; final String approverId = args['approverId'] ?? ""; final String delegaterId = args['delegaterId'] ?? ""; + final String approverStatus = args['approver_status'] ?? ""; final Map planData = args['planData'] as Map? ?? {}; @@ -216,6 +217,7 @@ class _CreatePlansState extends State { isApprover: isApprover, approverId: approverId, delegaterId: delegaterId, + approverStatus: approverStatus, ), ), ), @@ -325,6 +327,7 @@ class CreateNewPlan extends StatefulWidget { final bool isDesktop; final bool isViewMode; final bool isApprover; + final String? approverStatus; final Color? bodyColor; final Color? layoutColor; @@ -341,6 +344,7 @@ class CreateNewPlan extends StatefulWidget { required this.selectedPlanData, required this.isViewMode, required this.isApprover, + required this.approverStatus, required this.approverId, required this.delegaterId, }); @@ -375,6 +379,7 @@ class CreateNewPlansState extends State { bool isStatusExpanded = false; bool isShowApprovalAction = false; String? selectedPlanId; + String? approverStatus; String? userDetails; String? userName; @@ -502,7 +507,11 @@ class CreateNewPlansState extends State { @override void initState() { super.initState(); + print("approverStatus - ${widget.approverStatus}"); + if (widget.approverStatus == "Approval pending") { + print("Status approver - Approval Pending"); + } flightTripTypeNotifier = ValueNotifier(null); fetchUserDetails(); @@ -1169,6 +1178,8 @@ class CreateNewPlansState extends State { data['plan_id'], data['approverId'], data['delegaterId'], + data['approver_status'], + isViewMode: false, isApprover: true, ); @@ -1295,376 +1306,374 @@ class CreateNewPlansState extends State { ), ); - return - - FocusTraversalGroup( + return FocusTraversalGroup( policy: OrderedTraversalPolicy(), // 👈 more predictable tab order descendantsAreFocusable: true, child: ResponsiveBuilder( - builder: (context, sizingInfo) { - bool isMobile = sizingInfo.isMobile; - bool isDesktop = - sizingInfo.deviceScreenType == DeviceScreenType.desktop; + builder: (context, sizingInfo) { + bool isMobile = sizingInfo.isMobile; + bool isDesktop = + sizingInfo.deviceScreenType == DeviceScreenType.desktop; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ResponsiveBuilder( - builder: (context, sizingInfo) { - bool isDesktop = - sizingInfo.deviceScreenType == - DeviceScreenType.desktop; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ResponsiveBuilder( + builder: (context, sizingInfo) { + bool isDesktop = + sizingInfo.deviceScreenType == + DeviceScreenType.desktop; - return Container( - padding: const EdgeInsets.all(5), - child: - isDesktop - ? Row( - crossAxisAlignment: - CrossAxisAlignment.start, - // mainAxisAlignment: MainAxisAlignment.end, - children: [ - _buildTripName(isDesktop), - if (isDesktop) Spacer(), - ..._buildApproverControls(isDesktop), + return Container( + padding: const EdgeInsets.all(5), + child: + isDesktop + ? Row( + crossAxisAlignment: + CrossAxisAlignment.start, + // mainAxisAlignment: MainAxisAlignment.end, + children: [ + _buildTripName(isDesktop), + if (isDesktop) Spacer(), + ..._buildApproverControls(isDesktop), - // Text( - // widget.isViewMode - // ? "View Plan" - // : (selectedPlanId != null && - // selectedPlanId!.isNotEmpty - // ? "Update Plan" - // : "New Plan"), - // style: TextStyle(fontSize: 18), - // ), - // Spacer(), - if (statusValue != "") - ..._buildPlanPdf(isDesktop), - ], - ) - : Column( - children: [ - Row( - // crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: - MainAxisAlignment.end, - children: [ - ..._buildApproverControls( - isDesktop, - ), - if (statusValue != "") - ..._buildPlanPdf(isDesktop), - ], - ), - Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [_buildTripName(isDesktop)], - ), - ], - ), - ); - }, - ), - // if (isStatusExpanded) - // Container( - // margin: isDesktop - // ? const EdgeInsets.only(left: 0, top: 0) - // : const EdgeInsets.only(left: 5, top: 2), - // padding: const EdgeInsets.all(12), - // width: isDesktop - // ? MediaQuery.of(context).size.width * 0.2 - // : MediaQuery.of(context).size.width, - // decoration: BoxDecoration( - // color: Color(0xFFF5F5F5), - // border: Border.all( - // // color: Colors.grey.shade300, - // color: Colors.white, - // width: 0.2), - // borderRadius: BorderRadius.circular(8), - // // boxShadow: [ - // // BoxShadow( - // // // color: Colors.grey.withAlpha(20), - // // color: Colors.grey.withAlpha(20), - // // spreadRadius: 1.5, - // // blurRadius: 7, - // // offset: Offset(0, 4), // shadow direction: bottom - // // ), - // // ], - // ), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // if (!hasApprovals) - // Center( - // child: Text( - // "--- No Approvals ---", - // style: TextStyle( - // fontFamily: "Archivo", - // fontSize: 11, - // fontWeight: FontWeight.w500, - // color: Colors.black87, - // ), - // )), - // for (int i = 0; i < planStatusList.length; i++) ...[ - // if (planStatusList[i].entries.any((entry) => - // entry.key.contains('status') && - // entry.value != null && - // entry.value.toString().isNotEmpty)) ...[ - // _buildApprovalItem( - // "Approver ${i + 1}", - // planStatusList[i] - // .entries - // .firstWhere( - // (entry) => entry.key.contains('status'), - // orElse: () => MapEntry('', ''), - // ) - // .value - // .toString(), - // ), - // SizedBox(height: 6), - // ], - // ], - // ], - // ), - // ), - if (isApproverRejected) - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Padding( - padding: EdgeInsets.only(top: 4, left: 8), - child: Text( - "Remarks : ", - style: TextStyle( - fontFamily: "Archivo", - fontWeight: FontWeight.w600, - fontSize: 11, - ), - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only( - top: 0, - ), // tweak if needed - child: TextFormField( - controller: _remarksController, + // Text( + // widget.isViewMode + // ? "View Plan" + // : (selectedPlanId != null && + // selectedPlanId!.isNotEmpty + // ? "Update Plan" + // : "New Plan"), + // style: TextStyle(fontSize: 18), + // ), + // Spacer(), + if (statusValue != "") + ..._buildPlanPdf(isDesktop), + ], + ) + : Column( + children: [ + Row( + // crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + ..._buildApproverControls( + isDesktop, + ), + if (statusValue != "") + ..._buildPlanPdf(isDesktop), + ], + ), + Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + _buildTripName(isDesktop), + ], + ), + ], + ), + ); + }, + ), + // if (isStatusExpanded) + // Container( + // margin: isDesktop + // ? const EdgeInsets.only(left: 0, top: 0) + // : const EdgeInsets.only(left: 5, top: 2), + // padding: const EdgeInsets.all(12), + // width: isDesktop + // ? MediaQuery.of(context).size.width * 0.2 + // : MediaQuery.of(context).size.width, + // decoration: BoxDecoration( + // color: Color(0xFFF5F5F5), + // border: Border.all( + // // color: Colors.grey.shade300, + // color: Colors.white, + // width: 0.2), + // borderRadius: BorderRadius.circular(8), + // // boxShadow: [ + // // BoxShadow( + // // // color: Colors.grey.withAlpha(20), + // // color: Colors.grey.withAlpha(20), + // // spreadRadius: 1.5, + // // blurRadius: 7, + // // offset: Offset(0, 4), // shadow direction: bottom + // // ), + // // ], + // ), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // if (!hasApprovals) + // Center( + // child: Text( + // "--- No Approvals ---", + // style: TextStyle( + // fontFamily: "Archivo", + // fontSize: 11, + // fontWeight: FontWeight.w500, + // color: Colors.black87, + // ), + // )), + // for (int i = 0; i < planStatusList.length; i++) ...[ + // if (planStatusList[i].entries.any((entry) => + // entry.key.contains('status') && + // entry.value != null && + // entry.value.toString().isNotEmpty)) ...[ + // _buildApprovalItem( + // "Approver ${i + 1}", + // planStatusList[i] + // .entries + // .firstWhere( + // (entry) => entry.key.contains('status'), + // orElse: () => MapEntry('', ''), + // ) + // .value + // .toString(), + // ), + // SizedBox(height: 6), + // ], + // ], + // ], + // ), + // ), + if (isApproverRejected) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.only(top: 4, left: 8), + child: Text( + "Remarks : ", style: TextStyle( fontFamily: "Archivo", fontWeight: FontWeight.w600, fontSize: 11, ), - decoration: InputDecoration( - hintText: "Please enter remarks...", - hintStyle: GoogleFonts.poppins( - fontSize: 13, - color: Colors.grey, - ), - border: InputBorder.none, - isDense: true, - ), - maxLines: null, ), ), - ), - ], - ), - - // if (widget.isApprover || isStatusExpanded) - Divider(thickness: 0.1, color: Colors.blueGrey), - if (widget.isApprover) SizedBox(height: 5), - - Text( - "Planning This Trip For*", // Your label - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74), - ), - ), - SizedBox(height: 6), - isDesktop - ? SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row(children: _buildPlanTrip(isDesktop)), - ) - : Row(children: _buildPlanTrip(isDesktop)), - SizedBox(height: 7), - - // Text( - // otherUserName ?? userName ?? " ", // Your label - // - // style: GoogleFonts.poppins( - // fontSize: 11, - // fontWeight: FontWeight.w400, - // color: widget.layoutColor, - // ), - // ), - Text.rich( - TextSpan( - text: "Trip Planned User : ", // Static text - style: GoogleFonts.poppins( - fontSize: 11, - fontWeight: FontWeight.w400, - color: Color(0xFF212121), - // color: Color(0xFF575A74), // Default color - ), - children: [ - TextSpan( - text: - otherUserName ?? - userName ?? - " ", // Dynamic username - style: GoogleFonts.poppins( - fontSize: 11, - fontWeight: FontWeight.w400, - color: - widget - .layoutColor, // Change this to any color - // color: Colors.blueAccent, // Change this to any color + Expanded( + child: Padding( + padding: const EdgeInsets.only( + top: 0, + ), // tweak if needed + child: TextFormField( + controller: _remarksController, + style: TextStyle( + fontFamily: "Archivo", + fontWeight: FontWeight.w600, + fontSize: 11, + ), + decoration: InputDecoration( + hintText: "Please enter remarks...", + hintStyle: GoogleFonts.poppins( + fontSize: 13, + color: Colors.grey, + ), + border: InputBorder.none, + isDense: true, + ), + maxLines: null, + ), + ), ), - ), - ], + ], + ), + + // if (widget.isApprover || isStatusExpanded) + Divider(thickness: 0.1, color: Colors.blueGrey), + if (widget.isApprover) SizedBox(height: 5), + + Text( + "Planning This Trip For*", // Your label + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), - ), - SizedBox(height: 5), - ], - ), - ), - ], - ), + SizedBox(height: 6), + isDesktop + ? SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row(children: _buildPlanTrip(isDesktop)), + ) + : Row(children: _buildPlanTrip(isDesktop)), + SizedBox(height: 7), - // Padding( - // padding: const EdgeInsets.all(8.0), - // child: Divider( - // color: Color(0xFFE6E7F5), // Change color - // thickness: 0.5, - // ), - // ), - SizedBox(height: 10), - - // Row( - // children: [ - // Expanded( - // child: - // ), - // ], - // ), - isDesktop - ? Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildTripRow(isMobile), - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildTripRow(isMobile), - ), - SizedBox(height: 15), - - isDesktop - ? Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildCostCenter(isDesktop), - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildCostCenter(isDesktop), - ), - SizedBox(height: 8), - isDesktop - ? Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // _buildNonDescriptionColumn(), - // SizedBox(width: 25), - _buildDescriptionColumn(isDesktop), - ], - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // _buildNonDescriptionColumn(), - // SizedBox(height: 15), - _buildDescriptionColumn(isDesktop), - ], - ), - - // Padding( - // padding: const EdgeInsets.all(8.0), - // child: Divider( - // color: Color(0xFFE6E7F5), // Change color - // thickness: 0.5, - // ), - // ), - SizedBox(height: 20), - - if (temporaryMessage != null) - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - // validationErrors["services"]!, - temporaryMessage!, - style: GoogleFonts.poppins( - color: Colors.red, - fontSize: 12, - fontWeight: FontWeight.w400, + // Text( + // otherUserName ?? userName ?? " ", // Your label + // + // style: GoogleFonts.poppins( + // fontSize: 11, + // fontWeight: FontWeight.w400, + // color: widget.layoutColor, + // ), + // ), + Text.rich( + TextSpan( + text: "Trip Planned User : ", // Static text + style: GoogleFonts.poppins( + fontSize: 11, + fontWeight: FontWeight.w400, + color: Color(0xFF212121), + // color: Color(0xFF575A74), // Default color + ), + children: [ + TextSpan( + text: + otherUserName ?? + userName ?? + " ", // Dynamic username + style: GoogleFonts.poppins( + fontSize: 11, + fontWeight: FontWeight.w400, + color: + widget + .layoutColor, // Change this to any color + // color: Colors.blueAccent, // Change this to any color + ), + ), + ], + ), + ), + SizedBox(height: 5), + ], ), ), ], ), - if (temporaryMessage != null) SizedBox(height: 10), - Row( - children: [ - Expanded( - child: DynamicItinerary( - key: dynamicItineraryKey, - flightScreenKey: flightScreenKey, - tripTypeNotifier: flightTripTypeNotifier, - hasAction: hasAction, - tripType: _selectedTripType, - apiData: apiData, - apiDataForClass: apiDataForClass, - apiCountryData: apiCountryData, - onItineraryUpdate: handleItineraryUpdate, - loginUser: selfId, - selectedPlanData: planData, - isViewMode: widget.isViewMode, + // Padding( + // padding: const EdgeInsets.all(8.0), + // child: Divider( + // color: Color(0xFFE6E7F5), // Change color + // thickness: 0.5, + // ), + // ), + SizedBox(height: 10), + + // Row( + // children: [ + // Expanded( + // child: + // ), + // ], + // ), + isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildTripRow(isMobile), + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildTripRow(isMobile), ), - ), // Wrap with Expanded if needed - ], - ), - SizedBox(height: 15), - ], - ); + SizedBox(height: 15), - // Row( - // children: [ - // isDesktop - // ? Row( - // mainAxisAlignment: MainAxisAlignment.end, - // children: _buildSubmit(isDesktop), - // ) - // : Row( - // mainAxisAlignment: MainAxisAlignment.center, - // children: _buildSubmit(isDesktop), - // ) - // ], - // ) - }, - ) - // replace with your full form column - ); + isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildCostCenter(isDesktop), + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildCostCenter(isDesktop), + ), + SizedBox(height: 8), + isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // _buildNonDescriptionColumn(), + // SizedBox(width: 25), + _buildDescriptionColumn(isDesktop), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // _buildNonDescriptionColumn(), + // SizedBox(height: 15), + _buildDescriptionColumn(isDesktop), + ], + ), + // Padding( + // padding: const EdgeInsets.all(8.0), + // child: Divider( + // color: Color(0xFFE6E7F5), // Change color + // thickness: 0.5, + // ), + // ), + SizedBox(height: 20), + if (temporaryMessage != null) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + // validationErrors["services"]!, + temporaryMessage!, + style: GoogleFonts.poppins( + color: Colors.red, + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + + if (temporaryMessage != null) SizedBox(height: 10), + Row( + children: [ + Expanded( + child: DynamicItinerary( + key: dynamicItineraryKey, + flightScreenKey: flightScreenKey, + tripTypeNotifier: flightTripTypeNotifier, + hasAction: hasAction, + tripType: _selectedTripType, + apiData: apiData, + apiDataForClass: apiDataForClass, + apiCountryData: apiCountryData, + onItineraryUpdate: handleItineraryUpdate, + loginUser: selfId, + selectedPlanData: planData, + isViewMode: widget.isViewMode, + ), + ), // Wrap with Expanded if needed + ], + ), + SizedBox(height: 15), + ], + ); + + // Row( + // children: [ + // isDesktop + // ? Row( + // mainAxisAlignment: MainAxisAlignment.end, + // children: _buildSubmit(isDesktop), + // ) + // : Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: _buildSubmit(isDesktop), + // ) + // ], + // ) + }, + ), + // replace with your full form column + ); } // String? getSelectedCostCenterName() { @@ -3141,7 +3150,7 @@ class CreateNewPlansState extends State { // ], // ), const SizedBox(height: 10, width: 10), - if (widget.isApprover) + if (widget.isApprover && (widget.approverStatus == "Approval pending")) GestureDetector( // onTap: () { // setState(() { diff --git a/lib/Screens/userManagement/create_user/create_user.dart b/lib/Screens/userManagement/create_user/create_user.dart index 0e2bfd5..d6d265f 100644 --- a/lib/Screens/userManagement/create_user/create_user.dart +++ b/lib/Screens/userManagement/create_user/create_user.dart @@ -656,7 +656,7 @@ class _CreateUserFormDetialsState extends State { void handleSubmit() async { print("USR Detail Submit"); // printFormData(); - + print("travel validation"); bool isValid = travellerDetailsKey.currentState?.boolValidation() ?? false; if (selectedRole == "5" || setSelectesUserType == true) { print("NO validation"); diff --git a/lib/Screens/userManagement/create_user/traveller_details.dart b/lib/Screens/userManagement/create_user/traveller_details.dart index 3781bce..02526f9 100644 --- a/lib/Screens/userManagement/create_user/traveller_details.dart +++ b/lib/Screens/userManagement/create_user/traveller_details.dart @@ -155,6 +155,9 @@ class TravellerDetailsState extends State { } bool boolValidation() { + print("travel validation start"); + print("visaEntries - $visaEntries"); + final passportText = controllers["passportNumber"]?.text ?? ""; if (_selectedTripType == "Indian") { @@ -173,6 +176,80 @@ class TravellerDetailsState extends State { } } + final format = DateFormat("dd-MM-yyyy"); + + DateTime? start_Date; + DateTime? end_Date; + + try { + if (controllers["dateOfIssue"]?.text != null && + controllers["dateOfIssue"]!.text.isNotEmpty) { + start_Date = format.parse(controllers["dateOfIssue"]!.text); + } + if (controllers["dateOfExpiry"]?.text != null && + controllers["dateOfExpiry"]!.text.isNotEmpty) { + end_Date = format.parse(controllers["dateOfExpiry"]!.text); + } + + if (start_Date != null && + end_Date != null && + end_Date.isBefore(start_Date)) { + setState(() { + errorMessages["date_of_expiry"] = "End date cannot be earlier"; + }); + print("End date cannot be earlier than start date"); + } + } catch (e) { + print("Invalid passport date format"); + } + + // Additional validation starts - travelDetailsData passport + // DateTime? start_Date = travelDetailsData?['date_of_issue']; + // DateTime? end_Date = travelDetailsData?['date_of_expiry']; + // + // if (start_Date != null && + // end_Date != null && + // start_Date.toString().isNotEmpty && + // end_Date.toString().isNotEmpty) { + // try { + // final format = DateFormat("dd-MM-yyyy"); + // final checkStartDate = format.parse("$start_Date"); + // final checkEndDate = format.parse("$end_Date"); + // + // if (checkEndDate.isBefore(checkStartDate)) { + // // return "End date cannot be earlier than start date";; + // return; + // } + // } catch (e) { + // // return "End date cannot be earlier than start date"; + // return; + // // errorMessages["end_date"] = "Invalid date format"; + // } + // } + + // valid_from: 20-06-2025, valid_upto: 19-06-2025 + // DateTime? valid_from = data?['valid_from']; + // DateTime? valid_upto = data?['valid_upto']; + // + // if (valid_from != null && + // valid_upto != null && + // valid_from.toString().isNotEmpty && + // valid_upto.toString().isNotEmpty) { + // try { + // final format = DateFormat("dd-MM-yyyy"); + // final checkValidFrom = format.parse("$valid_from"); + // final checkValidUpto = format.parse("$valid_upto"); + // + // if (checkValidUpto.isBefore(checkValidFrom)) { + // // return "valid upto cannot be earlier than valid from"; + // } + // } catch (e) { + // // return "valid upto cannot be earlier than valid from"; + // + // // errorMessages["end_date"] = "Invalid date format"; + // } + // } + print("travel validation end"); return true; } @@ -1355,30 +1432,34 @@ class TravellerDetailsState extends State { DateTime? _selectedDateOfExpiry; Widget buildDateOfIssue() { - Future _selectCheckDateOfIssue(BuildContext context) async { DateTime now = DateTime.now(); DateTime today = DateTime(now.year, now.month, now.day); DateTime? pickedDate = await showDatePicker( context: context, - initialDate: _selectedDateOfIssue != null && _selectedDateOfIssue!.isAfter(today) - ? _selectedDateOfIssue! - : today, + initialDate: + _selectedDateOfIssue != null && _selectedDateOfIssue!.isAfter(today) + ? _selectedDateOfIssue! + : today, firstDate: DateTime(1900), - lastDate: DateTime(2100), + // lastDate: DateTime(2100), + lastDate: today, initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedDateOfIssue) { setState(() { _selectedDateOfIssue = pickedDate; - controllers["dateOfIssue"]?.text = DateFormat('dd-MM-yyyy').format(pickedDate); + controllers["dateOfIssue"]?.text = DateFormat( + 'dd-MM-yyyy', + ).format(pickedDate); // Revalidate expiry if (_selectedDateOfExpiry != null && _selectedDateOfExpiry!.isBefore(_selectedDateOfIssue!)) { - errorMessages["date_of_expiry"] = "Expiry date cannot be earlier than Issue date"; + errorMessages["date_of_expiry"] = + "Expiry date cannot be earlier than Issue date"; } else { errorMessages.remove("date_of_expiry"); } @@ -1400,25 +1481,25 @@ class TravellerDetailsState extends State { SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( width: - widget.isDesktop - ? MediaQuery.of(context).size.width * 0.13 - : null, + widget.isDesktop + ? MediaQuery.of(context).size.width * 0.13 + : null, isFocused: false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: GestureDetector( onTap: - widget.isViewMode - ? null - : () async { - await _selectCheckDateOfIssue(context); - if (controllers["dateOfIssue"]!.text.isNotEmpty) { - setState(() { - // errorMessages.remove("start_date"); - }); - } - }, + widget.isViewMode + ? null + : () async { + await _selectCheckDateOfIssue(context); + if (controllers["dateOfIssue"]!.text.isNotEmpty) { + setState(() { + // errorMessages.remove("start_date"); + }); + } + }, child: AbsorbPointer( child: TextField( controller: controllers["dateOfIssue"], @@ -1448,9 +1529,7 @@ class TravellerDetailsState extends State { } Widget buildDateOfExpiry() { - Future _selectCheckDateOfExpiry(BuildContext context) async { - DateTime now = DateTime.now(); DateTime today = DateTime(now.year, now.month, now.day); // DateTime firstDate = today; @@ -1499,11 +1578,14 @@ class TravellerDetailsState extends State { if (pickedDate != null) { setState(() { _selectedDateOfExpiry = pickedDate; - controllers["dateOfExpiry"]?.text = DateFormat("dd-MM-yyyy").format(pickedDate); + controllers["dateOfExpiry"]?.text = DateFormat( + "dd-MM-yyyy", + ).format(pickedDate); if (_selectedDateOfIssue != null && _selectedDateOfExpiry!.isBefore(_selectedDateOfIssue!)) { - errorMessages["date_of_expiry"] = "Expiry date cannot be earlier than Issue date"; + errorMessages["date_of_expiry"] = + "Expiry date cannot be earlier than Issue date"; } else { errorMessages.remove("date_of_expiry"); } @@ -1539,23 +1621,23 @@ class TravellerDetailsState extends State { isFocused: false, isDesktop: widget.isDesktop, width: - widget.isDesktop - ? MediaQuery.of(context).size.width * 0.13 - : null, + widget.isDesktop + ? MediaQuery.of(context).size.width * 0.13 + : null, child: SizedBox( height: 40, child: GestureDetector( onTap: - widget.isViewMode - ? null - : () async { - await _selectCheckDateOfExpiry(context); - if (controllers["dateOfExpiry"]!.text.isNotEmpty) { - setState(() { - // errorMessages.remove("start_date"); - }); - } - }, + widget.isViewMode + ? null + : () async { + await _selectCheckDateOfExpiry(context); + if (controllers["dateOfExpiry"]!.text.isNotEmpty) { + setState(() { + // errorMessages.remove("start_date"); + }); + } + }, child: AbsorbPointer( child: TextField( controller: controllers["dateOfExpiry"], @@ -1586,7 +1668,8 @@ class TravellerDetailsState extends State { errorMessages["date_of_expiry"]!, style: const TextStyle(color: Colors.red, fontSize: 10), maxLines: 2, // Allow it to wrap onto two lines - overflow: TextOverflow.ellipsis, // Add ellipsis if it still overflows + overflow: + TextOverflow.ellipsis, // Add ellipsis if it still overflows ), ], ], @@ -4054,7 +4137,6 @@ class TravellerDetailsState extends State { DateTime? _selectedCheckInDate; Widget buildVisaValidFrom(Map entry) { - TimeOfDay? _selectedCheckOutTime; Future _selectValidFromDate(BuildContext context) async { @@ -4075,19 +4157,23 @@ class TravellerDetailsState extends State { // initialDate: initialDate, // firstDate: initialDate, firstDate: DateTime(1900), - lastDate: DateTime(2100), + // lastDate: DateTime(2100), + lastDate: today, initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedCheckOutDate) { setState(() { _selectedCheckInDate = pickedDate; - entry["controller_valid_from"]?.text = DateFormat('dd-MM-yyyy').format(pickedDate); + entry["controller_valid_from"]?.text = DateFormat( + 'dd-MM-yyyy', + ).format(pickedDate); // Revalidate expiry if (_selectedCheckOutDate != null && _selectedCheckOutDate!.isBefore(_selectedCheckInDate!)) { - errorMessages["valid_upto"] = "valid upto cannot be earlier than valid from"; + errorMessages["valid_upto"] = + "valid upto cannot be earlier than valid from"; } else { errorMessages.remove("valid_upto"); } @@ -4109,9 +4195,9 @@ class TravellerDetailsState extends State { SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( width: - widget.isDesktop - ? MediaQuery.of(context).size.width * 0.15 - : null, + widget.isDesktop + ? MediaQuery.of(context).size.width * 0.15 + : null, isFocused: false, isDesktop: widget.isDesktop, child: SizedBox( @@ -4148,7 +4234,6 @@ class TravellerDetailsState extends State { } Widget buildVisaValidUpTo(Map entry) { - TimeOfDay? _selectedCheckOutTime; Future _selectValidUpToDate(BuildContext context) async { @@ -4199,11 +4284,14 @@ class TravellerDetailsState extends State { if (pickedDate != null && pickedDate != _selectedCheckOutDate) { setState(() { _selectedCheckOutDate = pickedDate; - entry["controller_valid_upto"].text = DateFormat("dd-MM-yyyy").format(pickedDate); + entry["controller_valid_upto"].text = DateFormat( + "dd-MM-yyyy", + ).format(pickedDate); if (_selectedCheckInDate != null && _selectedCheckOutDate!.isBefore(_selectedCheckInDate!)) { - errorMessages["valid_upto"] = "valid upto cannot be earlier than valid from"; + errorMessages["valid_upto"] = + "valid upto cannot be earlier than valid from"; } else { errorMessages.remove("valid_upto"); } @@ -4225,9 +4313,9 @@ class TravellerDetailsState extends State { SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( width: - widget.isDesktop - ? MediaQuery.of(context).size.width * 0.15 - : null, + widget.isDesktop + ? MediaQuery.of(context).size.width * 0.15 + : null, isFocused: false, isDesktop: widget.isDesktop, child: SizedBox( @@ -4265,7 +4353,8 @@ class TravellerDetailsState extends State { errorMessages["valid_upto"]!, style: const TextStyle(color: Colors.red, fontSize: 10), maxLines: 2, // Allow it to wrap onto two lines - overflow: TextOverflow.ellipsis, // Add ellipsis if it still overflows + overflow: + TextOverflow.ellipsis, // Add ellipsis if it still overflows ), ], ], diff --git a/lib/data/models/plan.dart b/lib/data/models/plan.dart index 4dc036d..be2f706 100644 --- a/lib/data/models/plan.dart +++ b/lib/data/models/plan.dart @@ -5,6 +5,7 @@ class Plan { final String tripTitle; final String tripType; final String status; + final String approver_status; final String costCenter; final String functionalDepartment; final String purposeOfTravel; @@ -23,6 +24,7 @@ class Plan { required this.planId, this.employeeCode, required this.tripTitle, + required this.approver_status, required this.tripType, required this.status, required this.costCenter, @@ -46,6 +48,7 @@ class Plan { employeeCode: json['employee_code'], tripTitle: json['trip_title'], tripType: json['trip_type_value'], + approver_status: json['approver_status'], status: json['status'] == "0" ? "Inactive" : "Active", costCenter: json['cost_center_value'] ?? '', functionalDepartment: json['functional_department_value'] ?? '', diff --git a/lib/routes/custom_appBar.dart b/lib/routes/custom_appBar.dart index 3498243..36adee3 100644 --- a/lib/routes/custom_appBar.dart +++ b/lib/routes/custom_appBar.dart @@ -117,6 +117,7 @@ class _CustomAppBarState extends State { "name": "${userDetails["first_name"]} ${userDetails["last_name"]}", "email": userDetails["email"] ?? "", "role": userDetails["role"] ?? "", + "last_login_at": userDetails["last_login_at"] ?? "", }; } catch (e) { print("Error decoding user data: $e"); @@ -282,6 +283,18 @@ class _CustomAppBarState extends State { }); } + Future logout(BuildContext context) async { + // Clear localStorage + final prefs = await SharedPreferences.getInstance(); + await prefs.clear(); // Clears all keys + + // Optional: clear sessionStorage if used + // html.window.sessionStorage.clear(); + + // Navigate to login or home page + context.go('/'); + } + @override Widget build(BuildContext context) { return AppBar( @@ -431,7 +444,7 @@ class _CustomAppBarState extends State { actions: [ Padding( padding: EdgeInsets.symmetric( - horizontal: MediaQuery.of(context).size.width * 0.05, + horizontal: MediaQuery.of(context).size.width * 0.055, ), child: Row( children: [ @@ -478,7 +491,8 @@ class _CustomAppBarState extends State { }, ); case '/logout': - context.go('/'); + logout(context); + // context.go('/'); break; } }, @@ -523,6 +537,15 @@ class _CustomAppBarState extends State { color: Colors.black, ), ), + Text( + "Last Login: ${userData?["last_login_at"]}" ?? + '', + style: GoogleFonts.poppins( + fontSize: 8.5, + fontWeight: FontWeight.w400, + color: Colors.black, + ), + ), const Divider(), // 👈 Divider after role ], ), @@ -702,4 +725,4 @@ Widget buildNavItem( ), ), ); -} \ No newline at end of file +} diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index 272aaa5..dca8153 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -102,6 +102,7 @@ class ApiService { throw Exception('Failed to load country list'); } } + Future> fetchAirlineList() async { final String apiUrldata = '$apiUrl/api/getAirlineMaster'; final token = await getToken(); @@ -617,6 +618,7 @@ class ApiService { BuildContext context, String planId, String? approverId, + String? approver_status, String? delegaterId, { bool isViewMode = false, bool isApprover = true, @@ -629,6 +631,7 @@ class ApiService { '/approver/plans', extra: { 'planData': planData, + 'approver_status': approver_status, 'approverId': approverId, 'delegaterId': delegaterId, 'isViewMode': isViewMode,