From 86da658dcd2aa19ab9bdb04c28773ad4d61af2e7 Mon Sep 17 00:00:00 2001 From: vadivelJ96 Date: Fri, 6 Jun 2025 11:49:17 +0530 Subject: [PATCH 1/5] latest commit 06-06-2025 --- pubspec.lock | 20 ++++++++++---------- pubspec.yaml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 4e5e26c..6a829df 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" bcrypt: dependency: "direct main" description: @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: @@ -433,18 +433,18 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.20.2" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -974,10 +974,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" web: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 6ef992f..a35d46d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: responsive_builder: ^0.7.1 shared_preferences: ^2.5.2 easy_stepper: ^0.8.5+1 - intl: ^0.19.0 + intl: 0.20.2 dropdown_search: ^5.0.6 file_picker: ^10.0.0 bcrypt: ^1.1.3 From f663fd776f56124a18a7461ec3ad0571f4fb009f Mon Sep 17 00:00:00 2001 From: vadivelJ96 Date: Fri, 6 Jun 2025 12:04:50 +0530 Subject: [PATCH 2/5] latest commit 06-06-2025 --- lib/Screens/plans/create_plans.dart | 13 +- lib/routes/custom_appBar.dart | 539 ++++++++++++++-------------- 2 files changed, 283 insertions(+), 269 deletions(-) diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index 3c6fb9e..b5d9ef3 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -1267,7 +1267,12 @@ class CreateNewPlansState extends State { ), ); - return ResponsiveBuilder( + return + + FocusTraversalGroup( + policy: OrderedTraversalPolicy(), // 👈 more predictable tab order + descendantsAreFocusable: true, + child: ResponsiveBuilder( builder: (context, sizingInfo) { bool isMobile = sizingInfo.isMobile; bool isDesktop = @@ -1627,7 +1632,11 @@ class CreateNewPlansState extends State { // ], // ) }, - ); + ) + // replace with your full form column + ); + + } // String? getSelectedCostCenterName() { diff --git a/lib/routes/custom_appBar.dart b/lib/routes/custom_appBar.dart index 8539d59..6b5ac2f 100644 --- a/lib/routes/custom_appBar.dart +++ b/lib/routes/custom_appBar.dart @@ -284,292 +284,297 @@ class _CustomAppBarState extends State { @override Widget build(BuildContext context) { - return AppBar( - backgroundColor: Colors.white, - surfaceTintColor: Colors.white, - // elevation: 3, - automaticallyImplyLeading: !widget.isDesktop, - iconTheme: IconThemeData( - color: layoutColor, // 👈 Set your desired icon color here - ), + return + FocusTraversalGroup( + descendantsAreFocusable: false, + child: + AppBar( + backgroundColor: Colors.white, + surfaceTintColor: Colors.white, + // elevation: 3, + automaticallyImplyLeading: !widget.isDesktop, + iconTheme: IconThemeData( + color: layoutColor, // 👈 Set your desired icon color here + ), - titleSpacing: 0, + titleSpacing: 0, - title: - !widget.isDesktop - ? Text('') - : Padding( + title: + !widget.isDesktop + ? Text('') + : Padding( + padding: EdgeInsets.symmetric( + horizontal: MediaQuery.of(context).size.width * 0.05, + ), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(10), + + // padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 10), + child: + selectedOrg?['logo'] != null + ? SizedBox( + height: 50, + child: ClipRect( + child: Image.network( + selectedOrg!['logo'], + width: 130, //130 + height: 80, //80 + fit: BoxFit.contain, + errorBuilder: (context, error, stackTrace) { + return const CircleAvatar( + radius: 20, + backgroundColor: Colors.redAccent, + child: Icon(Icons.error, size: 10), + ); + }, + ), + ), + ) + : const CircleAvatar( + radius: 20, + // backgroundColor: Colors.white, + child: Icon( + Icons.add_a_photo, + size: 10, + color: Colors.grey, + ), + ), + ), + SizedBox(width: MediaQuery.of(context).size.width * 0.18), + Container( + width: MediaQuery.of(context).size.width * 0.35, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (userData?["role"] == "Org Admin" || + userData?["role"] == "Travel Admin") + buildNavItem( + "Dashboard", + () => handleTabChange( + TabSelection.dashboard, + '/StatusDashboard', + ), + layoutColor!, + isSelected: selectedTab == TabSelection.dashboard, + icon: Icons.dashboard, + // icon: Icons.insights_outlined, + ), + if (userData?["role"] == "Org Admin" || + userData?["role"] == "Travel Admin") + const SizedBox(width: 20), + if (userData?["role"] == "Org Admin" || + userData?["role"] == "Travel Admin") + buildNavItem( + "All Trips", + () => handleTabChange( + TabSelection.allTrips, + '/listAllPlan', + ), + layoutColor!, + isSelected: selectedTab == TabSelection.allTrips, + icon: Icons.format_list_bulleted_rounded, + // icon: Icons.insights_outlined, + ), + if (userDetails["role"] != "Travel Agent") + const SizedBox(width: 20), + if (userData?["role"] == "Travel Agent") + buildNavItem( + "Trips", + () => handleTabChange( + TabSelection.myTrips, + '/listTravelAgentPlan', + ), + layoutColor!, + // () => context.go('/listTravelAgentPlan'), + isSelected: selectedTab == TabSelection.myTrips, + icon: Icons.shopping_bag_outlined, + // icon: Icons.request_page_outlined, + ), + if (userData?["role"] != "Travel Agent") // for others + buildNavItem( + "My Trips", + () => handleTabChange( + TabSelection.myTrips, + '/listPlan', + ), + layoutColor!, + // () => context.go('/listPlan'), + isSelected: selectedTab == TabSelection.myTrips, + icon: Icons.shopping_bag_outlined, + ), + const SizedBox(width: 20), + if (userData?["role"] != "Travel Agent") + buildNavItem( + "My Approvals", + + () => handleTabChange( + TabSelection.myApprovals, + '/ApprovalList', + ), + layoutColor!, + // () => context.go('/ApprovalList'), + isSelected: + selectedTab == TabSelection.myApprovals, + icon: Icons.verified_outlined, + ), + ], + ), + ), + Spacer(), + ], + ), + ), + actions: [ + Padding( padding: EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width * 0.05, ), child: Row( children: [ - Padding( - padding: const EdgeInsets.all(10), + // if (userData?["role"] != "User") + Builder( + builder: + (context) => PopupMenuButton( + color: Colors.white, + padding: EdgeInsets.zero, + offset: const Offset(0, 50), // 👈 shift it 50 pixels down + onSelected: (String value) { + switch (value) { + case '/OrganizationSettings': + context.go('/OrganizationSettings'); + break; + // case '/OrganizationSetup': + // context.go('/OrganizationSetup'); + // break; + case '/listUser': + context.go('/listUser'); + break; + // case '/group': + // context.go('/group'); + // break; + // case '/department': + // context.go('/department'); + // break; + // case '/PolicyList': + // context.go('/PolicyList'); + // case '/getPerdiem': + // context.go('/getPerdiem'); + // case '/templateList': + // context.go('/templateList'); + // case '/template': + // context.go('/template'); - // padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 10), - child: - selectedOrg?['logo'] != null - ? SizedBox( - height: 50, - child: ClipRect( - child: Image.network( - selectedOrg!['logo'], - width: 130, //130 - height: 80, //80 - fit: BoxFit.contain, - errorBuilder: (context, error, stackTrace) { - return const CircleAvatar( - radius: 20, - backgroundColor: Colors.redAccent, - child: Icon(Icons.error, size: 10), - ); + case '/CreateUserDetails': + context.go( + "/CreateUserDetails", + extra: { + "selectedUser": profileUserDetails, + "isEditProfile": true, + "isViewMode": false, }, + ); + case '/logout': + context.go('/'); + break; + } + }, + + // itemBuilder: (BuildContext context) => + // menuItems.map(buildMenuItem).toList(), + itemBuilder: (BuildContext context) { + // final isUser = userData?["role"] == "User"; + final role = userData?["role"]; + List> filteredItems; + + if (role == "User") { + filteredItems = + menuItems + .where( + (item) => + item['value'] == '/CreateUserDetails' || + item['value'] == '/logout', + ) + .toList(); + } else if (role == "Travel Agent") { + filteredItems = + menuItems + .where((item) => item['value'] == '/logout') + .toList(); + } else { + filteredItems = menuItems; + } + + // Create a new list starting with role display and divider + return [ + PopupMenuItem( + enabled: false, // ❌ Not clickable + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + userData?["role"] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + const Divider(), // 👈 Divider after role + ], ), ), - ) - : const CircleAvatar( - radius: 20, - // backgroundColor: Colors.white, - child: Icon( - Icons.add_a_photo, - size: 10, - color: Colors.grey, - ), - ), - ), - SizedBox(width: MediaQuery.of(context).size.width * 0.18), - Container( - width: MediaQuery.of(context).size.width * 0.35, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (userData?["role"] == "Org Admin" || - userData?["role"] == "Travel Admin") - buildNavItem( - "Dashboard", - () => handleTabChange( - TabSelection.dashboard, - '/StatusDashboard', - ), - layoutColor!, - isSelected: selectedTab == TabSelection.dashboard, - icon: Icons.dashboard, - // icon: Icons.insights_outlined, - ), - if (userData?["role"] == "Org Admin" || - userData?["role"] == "Travel Admin") - const SizedBox(width: 20), - if (userData?["role"] == "Org Admin" || - userData?["role"] == "Travel Admin") - buildNavItem( - "All Trips", - () => handleTabChange( - TabSelection.allTrips, - '/listAllPlan', - ), - layoutColor!, - isSelected: selectedTab == TabSelection.allTrips, - icon: Icons.format_list_bulleted_rounded, - // icon: Icons.insights_outlined, - ), - if (userDetails["role"] != "Travel Agent") - const SizedBox(width: 20), - if (userData?["role"] == "Travel Agent") - buildNavItem( - "Trips", - () => handleTabChange( - TabSelection.myTrips, - '/listTravelAgentPlan', - ), - layoutColor!, - // () => context.go('/listTravelAgentPlan'), - isSelected: selectedTab == TabSelection.myTrips, - icon: Icons.shopping_bag_outlined, - // icon: Icons.request_page_outlined, - ), - if (userData?["role"] != "Travel Agent") // for others - buildNavItem( - "My Trips", - () => handleTabChange( - TabSelection.myTrips, - '/listPlan', - ), - layoutColor!, - // () => context.go('/listPlan'), - isSelected: selectedTab == TabSelection.myTrips, - icon: Icons.shopping_bag_outlined, - ), - const SizedBox(width: 20), - if (userData?["role"] != "Travel Agent") - buildNavItem( - "My Approvals", + ...filteredItems + .map(buildMenuItem) + .toList(), // 👈 then normal items + ]; + }, - () => handleTabChange( - TabSelection.myApprovals, - '/ApprovalList', + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + userData?["name"] ?? "N/A", + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + // style: const TextStyle( + // fontSize: 14, + // fontWeight: FontWeight.w500, + // fontFamily: "Roboto", + // color: Colors.black, + // ), + ), + const Icon( + Icons.arrow_drop_down, + size: 20, + color: Colors.black87, + ), + ], ), - layoutColor!, - // () => context.go('/ApprovalList'), - isSelected: - selectedTab == TabSelection.myApprovals, - icon: Icons.verified_outlined, ), - ], - ), + ), ), - Spacer(), + const SizedBox(width: 8), ], ), ), - actions: [ - Padding( - padding: EdgeInsets.symmetric( - horizontal: MediaQuery.of(context).size.width * 0.05, - ), - child: Row( - children: [ - // if (userData?["role"] != "User") - Builder( - builder: - (context) => PopupMenuButton( - color: Colors.white, - padding: EdgeInsets.zero, - offset: const Offset(0, 50), // 👈 shift it 50 pixels down - onSelected: (String value) { - switch (value) { - case '/OrganizationSettings': - context.go('/OrganizationSettings'); - break; - // case '/OrganizationSetup': - // context.go('/OrganizationSetup'); - // break; - case '/listUser': - context.go('/listUser'); - break; - // case '/group': - // context.go('/group'); - // break; - // case '/department': - // context.go('/department'); - // break; - // case '/PolicyList': - // context.go('/PolicyList'); - // case '/getPerdiem': - // context.go('/getPerdiem'); - // case '/templateList': - // context.go('/templateList'); - // case '/template': - // context.go('/template'); - - case '/CreateUserDetails': - context.go( - "/CreateUserDetails", - extra: { - "selectedUser": profileUserDetails, - "isEditProfile": true, - "isViewMode": false, - }, - ); - case '/logout': - context.go('/'); - break; - } - }, - - // itemBuilder: (BuildContext context) => - // menuItems.map(buildMenuItem).toList(), - itemBuilder: (BuildContext context) { - // final isUser = userData?["role"] == "User"; - final role = userData?["role"]; - List> filteredItems; - - if (role == "User") { - filteredItems = - menuItems - .where( - (item) => - item['value'] == '/CreateUserDetails' || - item['value'] == '/logout', - ) - .toList(); - } else if (role == "Travel Agent") { - filteredItems = - menuItems - .where((item) => item['value'] == '/logout') - .toList(); - } else { - filteredItems = menuItems; - } - - // Create a new list starting with role display and divider - return [ - PopupMenuItem( - enabled: false, // ❌ Not clickable - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - userData?["role"] ?? '', - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), - const Divider(), // 👈 Divider after role - ], - ), - ), - ...filteredItems - .map(buildMenuItem) - .toList(), // 👈 then normal items - ]; - }, - - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - userData?["name"] ?? "N/A", - style: GoogleFonts.poppins( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.black, - ), - // style: const TextStyle( - // fontSize: 14, - // fontWeight: FontWeight.w500, - // fontFamily: "Roboto", - // color: Colors.black, - // ), - ), - const Icon( - Icons.arrow_drop_down, - size: 20, - color: Colors.black87, - ), - ], - ), - ), - ), - ), - const SizedBox(width: 8), ], - ), - ), - ], - bottom: PreferredSize( - preferredSize: Size.fromHeight(1), - child: Container( - height: 1, - // color: Colors.grey.shade200, // Set the color of the bottom border - color: layoutColor, // Set the color of the bottom border - ), - ), - ); + bottom: PreferredSize( + preferredSize: Size.fromHeight(1), + child: Container( + height: 1, + // color: Colors.grey.shade200, // Set the color of the bottom border + color: layoutColor, // Set the color of the bottom border + ), + ), + ) + ); } @override From 2a77ebc7d1604ed450841b2ccc4c3115c2e7a31f Mon Sep 17 00:00:00 2001 From: venbaittech Date: Wed, 11 Jun 2025 12:05:57 +0530 Subject: [PATCH 3/5] validations --- lib/Screens/itnerary/flights.dart | 843 +++++++++--------- lib/Screens/itnerary/forex.dart | 70 +- .../create_user/create_user.dart | 381 ++++---- web/index.html | 2 +- 4 files changed, 688 insertions(+), 608 deletions(-) diff --git a/lib/Screens/itnerary/flights.dart b/lib/Screens/itnerary/flights.dart index 0a98e7d..cbfe605 100644 --- a/lib/Screens/itnerary/flights.dart +++ b/lib/Screens/itnerary/flights.dart @@ -151,16 +151,16 @@ 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"), + () => _clearError("from_place_$i"), ); textControllers["_to${i}Controller"]?.addListener( - () => _clearError("to_place_$i"), + () => _clearError("to_place_$i"), ); textControllers["_date${i}Controller"]?.addListener( - () => _clearError("date_$i"), + () => _clearError("date_$i"), ); textControllers["_time${i}Controller"]?.addListener( - () => _clearError("time_$i"), + () => _clearError("time_$i"), ); } // loadCountryList(); @@ -184,13 +184,13 @@ class FlightScreenState extends State { } Map getFlightTripDateRange( - List> flightData, - ) { + List> flightData, + ) { final allTrips = - flightData - .expand((flight) => flight['trips'] ?? []) - .whereType>() - .toList(); + flightData + .expand((flight) => flight['trips'] ?? []) + .whereType>() + .toList(); if (allTrips.isEmpty) { return {'firstTripDate': null, 'lastTripDate': null}; @@ -304,11 +304,11 @@ class FlightScreenState extends State { // Determine the row count based on selectedTripType int rowCount = - selectedTripType == "Roundtrip" - ? 2 - : selectedTripType == "Multitrip" - ? multiTripRowCount - : 1; + selectedTripType == "Roundtrip" + ? 2 + : selectedTripType == "Multitrip" + ? multiTripRowCount + : 1; // Initialize fields dynamically for (var field in dataHeader) { @@ -636,6 +636,8 @@ class FlightScreenState extends State { print("Error: from_place_$i duplicates a previous departure"); } else { seenFrom.add(fromValue); + + errorMessages.remove("from_place_$i"); } } @@ -644,6 +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"); seenTo.add(toValue); } } @@ -663,7 +666,7 @@ class FlightScreenState extends State { try { DateTime date = format.parseStrict(dateStr); date = DateTime( - now.year, + date.year, date.month, date.day, ); // Assume current year @@ -681,13 +684,13 @@ class FlightScreenState extends State { // Same date - check time gap // Combine parsedDates and times into DateTime objects String prevDateStr = - textControllers["_date${i}Controller"]!.text.trim(); + textControllers["_date${i}Controller"]!.text.trim(); String prevTimeStr = - textControllers["_time${i}Controller"]!.text.trim(); + textControllers["_time${i}Controller"]!.text.trim(); String currDateStr = - textControllers["_date${i + 1}Controller"]!.text.trim(); + textControllers["_date${i + 1}Controller"]!.text.trim(); String currTimeStr = - textControllers["_time${i + 1}Controller"]!.text.trim(); + textControllers["_time${i + 1}Controller"]!.text.trim(); final dtFormat = DateFormat("dd-MM-yyyy HH:mm"); final prevDT = dtFormat.parse("$prevDateStr $prevTimeStr"); @@ -709,6 +712,8 @@ class FlightScreenState extends State { } else if (!parsedDates[i].isAfter(parsedDates[i - 1])) { errorMessages["date_${i + 1}"] = "Must be after date_${i}"; print("Error: date_${i + 1} is not after date_${i}"); + } else { + errorMessages.remove("date_${i + 1}"); } } @@ -739,11 +744,11 @@ class FlightScreenState extends State { // updatedTextControllers["_from${newIndex}Controller"] = // textControllers["_from${i}Controller"]!; updatedTextControllers["_to${newIndex}Controller"] = - textControllers["_to${i}Controller"]!; + textControllers["_to${i}Controller"]!; updatedTextControllers["_date${newIndex}Controller"] = - textControllers["_date${i}Controller"]!; + textControllers["_date${i}Controller"]!; updatedTextControllers["_time${newIndex}Controller"] = - textControllers["_time${i}Controller"]!; + textControllers["_time${i}Controller"]!; newIndex++; } textControllers = updatedTextControllers; @@ -953,14 +958,14 @@ class FlightScreenState extends State { List purposeList = widget.apiData?['flight_trip_type'] ?? []; List> dropdownItems = - purposeList - .map( - (item) => DropdownMenuItem( - value: item['dropdown_value'], - child: Text(item['dropdown_value']), - ), - ) - .toList(); + purposeList + .map( + (item) => DropdownMenuItem( + value: item['dropdown_value'], + child: Text(item['dropdown_value']), + ), + ) + .toList(); if (dropdownItems.isEmpty) { dropdownItems.add( @@ -985,9 +990,9 @@ class FlightScreenState extends State { width: double.infinity, child: DropdownSearch( items: - purposeList - .map((item) => item['dropdown_value'] as String) - .toList(), + purposeList + .map((item) => item['dropdown_value'] as String) + .toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, @@ -1019,28 +1024,28 @@ class FlightScreenState extends State { selectedItem: selectedTripType, dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select", - style: TextStyle(fontSize: 12), - ), - ), + 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 - ), - ), + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 6.0, + ), + child: Text( + item, + style: TextStyle( + fontSize: 13, + ), // Custom text size for dropdown items + ), + ), ), ), @@ -1155,9 +1160,9 @@ class FlightScreenState extends State { ), SizedBox( width: - isDesktop - ? MediaQuery.of(context).size.width * 0.58 - : 80, // Ensure full width + isDesktop + ? MediaQuery.of(context).size.width * 0.58 + : 80, // Ensure full width child: Stack( alignment: Alignment.center, // Centers the icon children: [ @@ -1167,7 +1172,7 @@ class FlightScreenState extends State { color: Colors.white, // Background to avoid overlapping child: Row( mainAxisSize: - MainAxisSize.min, // Prevents row from taking full width + MainAxisSize.min, // Prevents row from taking full width children: [ Icon(Icons.add_circle_sharp, color: Colors.blue, size: 28), ], @@ -1224,14 +1229,14 @@ 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(); + purposeList + .map( + (item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + ), + ) + .toList(); if (dropdownItems.isEmpty) { dropdownItems.add( @@ -1247,7 +1252,7 @@ class FlightScreenState extends State { // Default selected value selectedClasses[index] ??= - dropdownItems.isNotEmpty ? dropdownItems.first.value : null; + dropdownItems.isNotEmpty ? dropdownItems.first.value : null; // ------------------------------------------------- DateTime? _selectedCheckOutDate; @@ -1290,10 +1295,10 @@ class FlightScreenState extends State { } DateTime initialDate = - _selectedCheckOutDate != null && - _selectedCheckOutDate!.isAfter(firstDate) - ? _selectedCheckOutDate! - : firstDate; + _selectedCheckOutDate != null && + _selectedCheckOutDate!.isAfter(firstDate) + ? _selectedCheckOutDate! + : firstDate; DateTime? pickedDate = await showDatePicker( context: context, @@ -1315,10 +1320,10 @@ class FlightScreenState extends State { } 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(), @@ -1344,8 +1349,8 @@ class FlightScreenState extends State { // ✅ Only validate past time if date is today final isToday = selectedDate.year == now.year && - selectedDate.month == now.month && - selectedDate.day == now.day; + selectedDate.month == now.month && + selectedDate.day == now.day; bool isPastTime = selectedDateTime.isBefore(now); @@ -1412,159 +1417,159 @@ class FlightScreenState extends State { child: SizedBox( height: 40, child: - isCountryLoading - ? Center(child: CircularProgressIndicator()) - : DropdownSearch( - enabled: - !(selectedTripType == "Roundtrip" && index == 2), - 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, - ), - ), - style: TextStyle(fontSize: 12), - ), - menuProps: MenuProps(backgroundColor: Colors.white), - itemBuilder: (context, item, isSelected) { - final parts = item.split('\n'); - final cityAndCode = parts[0]; - final airport = parts.length > 1 ? parts[1] : ''; - - // Extract city and code from "City (CODE)" - final cityMatch = RegExp( - r'^(.*)\s+\(([^)]+)\)$', - ).firstMatch(cityAndCode); - final city = cityMatch?.group(1) ?? ''; - final code = cityMatch?.group(2) ?? ''; - - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 6.0, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - city, - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - ), + isCountryLoading + ? Center(child: CircularProgressIndicator()) + : DropdownSearch( + enabled: + !(selectedTripType == "Roundtrip" && index == 2), + 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, ), - Text( - code, - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(height: 1), - Text( - airport, - style: const TextStyle( - fontSize: 11, - color: Colors.grey, - overflow: TextOverflow.ellipsis, ), + style: TextStyle(fontSize: 12), ), - ], + menuProps: MenuProps(backgroundColor: Colors.white), + itemBuilder: (context, item, isSelected) { + final parts = item.split('\n'); + final cityAndCode = parts[0]; + final airport = parts.length > 1 ? parts[1] : ''; + + // Extract city and code from "City (CODE)" + final cityMatch = RegExp( + r'^(.*)\s+\(([^)]+)\)$', + ).firstMatch(cityAndCode); + final city = cityMatch?.group(1) ?? ''; + final code = cityMatch?.group(2) ?? ''; + + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 6.0, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + city, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + Text( + code, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 1), + Text( + airport, + style: const TextStyle( + fontSize: 11, + color: Colors.grey, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + }, + + // 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( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), + ), + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null) { + return Align( + alignment: Alignment.centerLeft, + child: const Text( + "Select", + style: TextStyle(fontSize: 12), + ), + ); + } + + final parts = selectedItem.split('\n'); + final cityAndCode = parts[0]; + final airport = parts.length > 1 ? parts[1] : ''; + + return Align( + alignment: Alignment.centerLeft, + child: Text( + cityAndCode ?? "Select", + style: const TextStyle(fontSize: 12), + ), + ); + }, + + // dropdownBuilder: + // (context, selectedItem) => Align( + // alignment: Alignment.centerLeft, + // child: Text( + // selectedItem ?? "Select", + // style: TextStyle(fontSize: 12), + // ), + // ), + onChanged: (String? newValue) { + setState(() { + errorMessages.remove("from_place_$index"); + // selectedFrom[index] = countryMap.entries + // .firstWhere((entry) => entry.value == newValue) + // .key; + + selectedFrom[index] = + countryMap.entries + .firstWhere( + (entry) => entry.value == newValue, + ) + .key; + + if (selectedTripType == "Roundtrip") { + print('rounfTo${selectedFrom[index]}'); + // textControllers["_to${index+1}Controller"]?.text = selectedFrom[index]!; + selectedTo[2] = selectedFrom[index]!; + } + }); + }, ), - ); - }, - - // 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( - dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 1), - ), - ), - dropdownBuilder: (context, selectedItem) { - if (selectedItem == null) { - return Align( - alignment: Alignment.centerLeft, - child: const Text( - "Select", - style: TextStyle(fontSize: 12), - ), - ); - } - - final parts = selectedItem.split('\n'); - final cityAndCode = parts[0]; - final airport = parts.length > 1 ? parts[1] : ''; - - return Align( - alignment: Alignment.centerLeft, - child: Text( - cityAndCode ?? "Select", - style: const TextStyle(fontSize: 12), - ), - ); - }, - - // dropdownBuilder: - // (context, selectedItem) => Align( - // alignment: Alignment.centerLeft, - // child: Text( - // selectedItem ?? "Select", - // style: TextStyle(fontSize: 12), - // ), - // ), - onChanged: (String? newValue) { - setState(() { - errorMessages.remove("from_place_$index"); - // selectedFrom[index] = countryMap.entries - // .firstWhere((entry) => entry.value == newValue) - // .key; - - selectedFrom[index] = - countryMap.entries - .firstWhere( - (entry) => entry.value == newValue, - ) - .key; - - if (selectedTripType == "Roundtrip") { - print('rounfTo${selectedFrom[index]}'); - // textControllers["_to${index+1}Controller"]?.text = selectedFrom[index]!; - selectedTo[2] = selectedFrom[index]!; - } - }); - }, - ), ), // child: SizedBox( @@ -1612,156 +1617,156 @@ class FlightScreenState extends State { child: SizedBox( height: 40, child: - isCountryLoading - ? Center(child: CircularProgressIndicator()) - : DropdownSearch( - enabled: - !(selectedTripType == "Roundtrip" && index == 2), - 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, - ), - ), - style: TextStyle(fontSize: 12), - ), - menuProps: MenuProps(backgroundColor: Colors.white), - itemBuilder: (context, item, isSelected) { - final parts = item.split('\n'); - final cityAndCode = parts[0]; - final airport = parts.length > 1 ? parts[1] : ''; - - // Extract city and code from "City (CODE)" - final cityMatch = RegExp( - r'^(.*)\s+\(([^)]+)\)$', - ).firstMatch(cityAndCode); - final city = cityMatch?.group(1) ?? ''; - final code = cityMatch?.group(2) ?? ''; - - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 6.0, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - city, - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - ), + isCountryLoading + ? Center(child: CircularProgressIndicator()) + : DropdownSearch( + enabled: + !(selectedTripType == "Roundtrip" && index == 2), + 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, ), - Text( - code, - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(height: 1), - Text( - airport, - style: const TextStyle( - fontSize: 11, - color: Colors.grey, - overflow: TextOverflow.ellipsis, ), + style: TextStyle(fontSize: 12), ), - ], + menuProps: MenuProps(backgroundColor: Colors.white), + itemBuilder: (context, item, isSelected) { + final parts = item.split('\n'); + final cityAndCode = parts[0]; + final airport = parts.length > 1 ? parts[1] : ''; + + // Extract city and code from "City (CODE)" + final cityMatch = RegExp( + r'^(.*)\s+\(([^)]+)\)$', + ).firstMatch(cityAndCode); + final city = cityMatch?.group(1) ?? ''; + final code = cityMatch?.group(2) ?? ''; + + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 6.0, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + city, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + Text( + code, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 1), + Text( + airport, + style: const TextStyle( + fontSize: 11, + color: Colors.grey, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + }, + // 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( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), + ), + + // dropdownBuilder: + // (context, selectedItem) => Align( + // alignment: Alignment.centerLeft, + // child: Text( + // selectedItem ?? "Select Country", + // style: TextStyle(fontSize: 12), + // ), + // ), + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null) { + return Align( + alignment: Alignment.centerLeft, + child: const Text( + "Select", + style: TextStyle(fontSize: 12), + ), + ); + } + + final parts = selectedItem.split('\n'); + final cityAndCode = parts[0]; + final airport = parts.length > 1 ? parts[1] : ''; + + return Align( + alignment: Alignment.centerLeft, + child: Text( + cityAndCode ?? "Select", + style: const TextStyle(fontSize: 12), + ), + ); + }, + onChanged: (String? newValue) { + setState(() { + errorMessages.remove("to_place_$index"); + selectedTo[index] = + countryMap.entries + .firstWhere( + (entry) => entry.value == newValue, + ) + .key; + + print(selectedTo[index]); + + if (selectedTripType == "Roundtrip") { + print('rounfTo${selectedTo[index]}'); + // textControllers["_to${index+1}Controller"]?.text = selectedFrom[index]!; + selectedFrom[2] = selectedTo[index]!; + } + }); + }, ), - ); - }, - // 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( - dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 1), - ), - ), - - // dropdownBuilder: - // (context, selectedItem) => Align( - // alignment: Alignment.centerLeft, - // child: Text( - // selectedItem ?? "Select Country", - // style: TextStyle(fontSize: 12), - // ), - // ), - dropdownBuilder: (context, selectedItem) { - if (selectedItem == null) { - return Align( - alignment: Alignment.centerLeft, - child: const Text( - "Select", - style: TextStyle(fontSize: 12), - ), - ); - } - - final parts = selectedItem.split('\n'); - final cityAndCode = parts[0]; - final airport = parts.length > 1 ? parts[1] : ''; - - return Align( - alignment: Alignment.centerLeft, - child: Text( - cityAndCode ?? "Select", - style: const TextStyle(fontSize: 12), - ), - ); - }, - onChanged: (String? newValue) { - setState(() { - errorMessages.remove("to_place_$index"); - selectedTo[index] = - countryMap.entries - .firstWhere( - (entry) => entry.value == newValue, - ) - .key; - - print(selectedTo[index]); - - if (selectedTripType == "Roundtrip") { - print('rounfTo${selectedTo[index]}'); - // textControllers["_to${index+1}Controller"]?.text = selectedFrom[index]!; - selectedFrom[2] = selectedTo[index]!; - } - }); - }, - ), ), ), if (errorMessages["to_place_$index"] != null) ...[ @@ -1848,7 +1853,7 @@ class FlightScreenState extends State { child: DropdownSearch>( items: purposeList.cast>(), selectedItem: purposeList.firstWhere( - (item) => item['dropdown_key'] == selectedClasses[index], + (item) => item['dropdown_key'] == selectedClasses[index], orElse: () => {}, ), itemAsString: (item) => item['dropdown_value'] ?? '', @@ -1900,14 +1905,14 @@ class FlightScreenState extends State { ); }, onChanged: - purposeList.isNotEmpty - ? (Map? newValue) { - setState(() { - selectedClasses[index] = newValue?['dropdown_key']; - print("Selected Class: ${selectedClasses[index]}"); - }); - } - : null, + purposeList.isNotEmpty + ? (Map? newValue) { + setState(() { + selectedClasses[index] = newValue?['dropdown_key']; + print("Selected Class: ${selectedClasses[index]}"); + }); + } + : null, ), ), ), @@ -2003,7 +2008,7 @@ class FlightScreenState extends State { _selectCheckOutTime(context, index, () { validateTimeDifference(index); setState( - () {}, + () {}, ); // ✅ Force rebuild to show the error immediately }); }, @@ -2060,17 +2065,17 @@ class FlightScreenState extends State { // Default selected value List> dropdownItems = - visa_available - .map( - (item) => DropdownMenuItem( - value: item['dropdown_key'], - child: Text(item['dropdown_value']), - ), - ) - .toList(); + visa_available + .map( + (item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + ), + ) + .toList(); selectedvisa_available ??= - dropdownItems.isNotEmpty ? dropdownItems.first.value : null; + dropdownItems.isNotEmpty ? dropdownItems.first.value : null; if (dropdownItems.isEmpty) { dropdownItems.add( @@ -2161,7 +2166,7 @@ class FlightScreenState extends State { child: DropdownSearch>( items: visa_available.cast>(), selectedItem: visa_available.firstWhere( - (item) => item['dropdown_key'] == selectedvisa_available, + (item) => item['dropdown_key'] == selectedvisa_available, orElse: () => {}, ), itemAsString: (item) => item['dropdown_value'] ?? '', @@ -2213,14 +2218,14 @@ class FlightScreenState extends State { ); }, onChanged: - visa_available.isNotEmpty - ? (Map? newValue) { - setState(() { - selectedvisa_available = newValue?['dropdown_key']; - print("Selected Visa: $selectedvisa_available"); - }); - } - : null, + visa_available.isNotEmpty + ? (Map? newValue) { + setState(() { + selectedvisa_available = newValue?['dropdown_key']; + print("Selected Visa: $selectedvisa_available"); + }); + } + : null, ), ), ), @@ -2281,17 +2286,17 @@ class FlightScreenState extends State { // Default selected value List> dropdownItems = - visa_available - .map( - (item) => DropdownMenuItem( - value: item['dropdown_key'], - child: Text(item['dropdown_value']), - ), - ) - .toList(); + visa_available + .map( + (item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + ), + ) + .toList(); selectedvisa_available ??= - dropdownItems.isNotEmpty ? dropdownItems.first.value : null; + dropdownItems.isNotEmpty ? dropdownItems.first.value : null; if (dropdownItems.isEmpty) { dropdownItems.add( @@ -2340,20 +2345,20 @@ class FlightScreenState extends State { ), // 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", - ); + 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, ), diff --git a/lib/Screens/itnerary/forex.dart b/lib/Screens/itnerary/forex.dart index 611a049..4797a41 100644 --- a/lib/Screens/itnerary/forex.dart +++ b/lib/Screens/itnerary/forex.dart @@ -267,6 +267,11 @@ class _ForexScreenState extends State { errorMessages["end_date"] = "End date cannot be earlier than start date"; ; + } else if (checkEndDate.isAtSameMomentAs(checkStartDate)) { + setState(() { + errorMessages["end_date"] = + "Start and end dates cannot be the same"; + }); } } catch (e) { errorMessages["end_date"] = "Invalid date format"; @@ -320,6 +325,16 @@ class _ForexScreenState extends State { final cash = data["deposit_on_cash"]; print("card - $card"); print("cash - $cash"); + // Call validations first (they populate errorMessages) + _validateCardAmount(card); + _validateCashAmount(cash); + + // Now check if any errors exist + if (errorMessages.isNotEmpty) { + print("Validation Failed: ${errorMessages}"); + setState(() {}); // Refresh UI with error messages + return; + } if (!isValidForexData(data)) { // && errorMessages.isNotEmpty) { @@ -495,6 +510,7 @@ class _ForexScreenState extends State { // Check if all required fields have data bool _isForexDataDurationComplete() { + print("Calculate 13ww Duration"); final data = getForexData; return (data["start_date"]?.isNotEmpty ?? false) && (data["end_date"]?.isNotEmpty ?? false); @@ -503,7 +519,7 @@ class _ForexScreenState extends State { // Handle field changes void _onFieldChanged() { if (_isForexDataDurationComplete()) { - print("Calculate 1"); + print("Calculate 1 Duration"); CalculateDuration(); } @@ -517,7 +533,7 @@ class _ForexScreenState extends State { } void CalculateDuration() { - print("Calculate 2"); + print("Calculate 2 Duration"); final data = getForexData; final startDateString = data["start_date"]; final endDateString = data["end_date"]; @@ -539,7 +555,7 @@ class _ForexScreenState extends State { // Calculate difference final durationInDays = - endDate.difference(startDate).inDays + 1; // +1 to include both days + endDate.difference(startDate).inDays; // +1 to include both days // You can now use durationInDays however you want: print("Duration: $durationInDays days"); @@ -976,18 +992,36 @@ class _ForexScreenState extends State { textControllers["_forexEndDate"]!.text, ); - if (startDate != null && - endDate != null && - endDate.isBefore(startDate)) { - setState(() { - errorMessages["end_date"] = - "End date cannot be earlier than start date"; - }); - } else { - setState(() { - errorMessages.remove("end_date"); - }); + if (startDate != null && endDate != null) { + if (endDate.isBefore(startDate)) { + setState(() { + errorMessages["end_date"] = + "End date cannot be earlier than start date"; + }); + } else if (endDate.isAtSameMomentAs(startDate)) { + setState(() { + errorMessages["end_date"] = + "Start and end dates cannot be the same"; + }); + } else { + setState(() { + errorMessages.remove("end_date"); + }); + } } + + // if (startDate != null && + // endDate != null && + // endDate.isBefore(startDate)) { + // setState(() { + // errorMessages["end_date"] = + // "End date cannot be earlier than start date"; + // }); + // } else { + // setState(() { + // errorMessages.remove("end_date"); + // }); + // } } }, child: AbsorbPointer( @@ -1062,6 +1096,11 @@ class _ForexScreenState extends State { errorMessages["end_date"] = "End date cannot be earlier than start date"; }); + } else if (endDate!.isAtSameMomentAs(startDate!)) { + setState(() { + errorMessages["end_date"] = + "Start and end dates cannot be the same"; + }); } else { setState(() { errorMessages.remove("end_date"); @@ -1098,7 +1137,8 @@ class _ForexScreenState extends State { errorMessages["end_date"]!, style: const TextStyle(color: Colors.red, fontSize: 12), 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/Screens/userManagement/create_user/create_user.dart b/lib/Screens/userManagement/create_user/create_user.dart index da6bfb4..0e2bfd5 100644 --- a/lib/Screens/userManagement/create_user/create_user.dart +++ b/lib/Screens/userManagement/create_user/create_user.dart @@ -42,9 +42,9 @@ class _CreateUserFormDetialsState extends State { final ApiService apiService = ApiService(); final GlobalKey personalDetailsKey = - GlobalKey(); + GlobalKey(); final GlobalKey travellerDetailsKey = - GlobalKey(); + GlobalKey(); // late List?> travelDetailsData; Map? travelDetailsData; @@ -64,6 +64,7 @@ class _CreateUserFormDetialsState extends State { final Map controllers = {}; bool isViewMode = false; bool isEditProfile = false; + // late final List? apiCountryData ; late List? apiCountryData; @@ -86,6 +87,7 @@ class _CreateUserFormDetialsState extends State { String? selectedCountry; String? selectedGender; + // String? selectedGender = personalDetailsKey.currentState?.selectedGender; String? selectedUserType; @@ -299,7 +301,7 @@ class _CreateUserFormDetialsState extends State { // Fix the invalid JSON (dangerous if the format changes) final fixedJson = raw.replaceAllMapped( RegExp(r'(\w+):'), // matches `service_id:` - (match) => '"${match.group(1)}":', + (match) => '"${match.group(1)}":', ); List decodedList = jsonDecode(fixedJson); @@ -363,7 +365,7 @@ class _CreateUserFormDetialsState extends State { // } final extraData = - GoRouterState.of(context).extra as Map?; + GoRouterState.of(context).extra as Map?; if (extraData != null) { print("extraData: ${extraData['selectedUser']}"); @@ -381,8 +383,8 @@ class _CreateUserFormDetialsState extends State { // Handle selectedUser as a Map (not a List) apiselectedUser = - extraData['selectedUser'] - as Map?; // Cast it as a Map + extraData['selectedUser'] + as Map?; // Cast it as a Map isViewMode = extraData['isViewMode'] ?? false; isEditProfile = extraData['isEditProfile'] ?? false; }); @@ -444,7 +446,7 @@ class _CreateUserFormDetialsState extends State { userMap = { for (var user in userList) user['user_id'].toString(): - "${user['first_name']} ${user['last_name']}", + "${user['first_name']} ${user['last_name']}", }; userIdsApi = userMap.keys.toList(); }); @@ -483,14 +485,14 @@ class _CreateUserFormDetialsState 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; }); } @@ -656,9 +658,7 @@ class _CreateUserFormDetialsState extends State { // printFormData(); bool isValid = travellerDetailsKey.currentState?.boolValidation() ?? false; - if (selectedTab == "travel" || - selectedRole == "5" || - setSelectesUserType == true) { + if (selectedRole == "5" || setSelectesUserType == true) { print("NO validation"); Map data = userDetials; @@ -702,30 +702,64 @@ class _CreateUserFormDetialsState extends State { // block-submit-here - // Additional validation starts - travelDetailsData passport - DateTime? start_Date = travelDetailsData?['date_of_issue']; - DateTime? end_Date = travelDetailsData?['date_of_expiry']; + final format = DateFormat("dd-MM-yyyy"); - 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"); + DateTime? start_Date; + DateTime? 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"; + try { + if (travelDetailsData?['date_of_issue'] != null && + travelDetailsData!['date_of_issue'].toString().isNotEmpty) { + start_Date = format.parse(travelDetailsData!['date_of_issue']); } + if (travelDetailsData?['date_of_expiry'] != null && + travelDetailsData!['date_of_expiry'].toString().isNotEmpty) { + end_Date = format.parse(travelDetailsData!['date_of_expiry']); + } + + if (start_Date != null && + end_Date != null && + end_Date.isBefore(start_Date)) { + print("End date cannot be earlier than start date"); + return; + } + } catch (e) { + print("Invalid passport date format"); + return; } + + // 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) { + 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"); @@ -733,10 +767,11 @@ class _CreateUserFormDetialsState extends State { if (checkValidUpto.isBefore(checkValidFrom)) { // return "valid upto cannot be earlier than valid from"; - return ; + return; } - } catch (e) { // return "valid upto cannot be earlier than valid from"; - return ; + } catch (e) { + // return "valid upto cannot be earlier than valid from"; + return; // errorMessages["end_date"] = "Invalid date format"; } } @@ -785,7 +820,7 @@ class _CreateUserFormDetialsState extends State { if (data["mobile_no"] != null && data["mobile_no"].toString().isNotEmpty) { if (!RegExp(r"^\d{10}$").hasMatch(data["mobile_no"].toString())) { errorMessages["mobile_no"] = - "Enter 10 digits"; // Invalid mobile number format + "Enter 10 digits"; // Invalid mobile number format } } @@ -795,7 +830,7 @@ class _CreateUserFormDetialsState extends State { r"^\d{10}$", ).hasMatch(data["alternate_mobile_no"].toString())) { errorMessages["alternate_mobile_no"] = - "Enter 10 digits"; // Invalid mobile number format + "Enter 10 digits"; // Invalid mobile number format } } @@ -985,7 +1020,7 @@ class _CreateUserFormDetialsState extends State { // ✅ Ensure UI updates if (isMatch) { errorMessages["password"] = - "New password is not similar to old password"; + "New password is not similar to old password"; print(" Password match!"); } else { print(" Password NOT match!"); @@ -1014,16 +1049,16 @@ class _CreateUserFormDetialsState 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: [Expanded(child: buildData(isDesktop, context))], ), @@ -1045,9 +1080,9 @@ class _CreateUserFormDetialsState extends State { Expanded( child: Container( 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: EdgeInsets.all(0.0), child: _buildUserDetails(isDesktop), @@ -1059,39 +1094,39 @@ class _CreateUserFormDetialsState extends State { child: Padding( padding: const EdgeInsets.all(8.0), child: - isDesktop - ? Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (selectedTab != "personal") - ..._buildBack(isDesktop, layoutColor!), - Spacer(), // spacing between buttons - // Next or Submit based on role or user type - if (selectedTab == "travel" || - selectedRole == "5" || - setSelectesUserType == true) - ..._buildSubmit(isDesktop, layoutColor!) - else - ..._buildNext(isDesktop, layoutColor!), - // (selectedTab == "travel" || - // selectedRole == "5" || - // setSelectesUserType == true) - // ? _buildSubmit(isDesktop, layoutColor!) - // : _buildNext( - // isDesktop, - // layoutColor!, - // ), // _buildGoBack(isDesktop, layoutColor!), - ], - ) - : Row( - mainAxisAlignment: MainAxisAlignment.end, - children: - (selectedTab == "travel" || - selectedRole == "5" || - setSelectesUserType == true) - ? _buildSubmit(isDesktop, layoutColor!) - : _buildNext(isDesktop, layoutColor!), - ), + isDesktop + ? Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (selectedTab != "personal") + ..._buildBack(isDesktop, layoutColor!), + Spacer(), // spacing between buttons + // Next or Submit based on role or user type + if (selectedTab == "travel" || + selectedRole == "5" || + setSelectesUserType == true) + ..._buildSubmit(isDesktop, layoutColor!) + else + ..._buildNext(isDesktop, layoutColor!), + // (selectedTab == "travel" || + // selectedRole == "5" || + // setSelectesUserType == true) + // ? _buildSubmit(isDesktop, layoutColor!) + // : _buildNext( + // isDesktop, + // layoutColor!, + // ), // _buildGoBack(isDesktop, layoutColor!), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.end, + children: + (selectedTab == "travel" || + selectedRole == "5" || + setSelectesUserType == true) + ? _buildSubmit(isDesktop, layoutColor!) + : _buildNext(isDesktop, layoutColor!), + ), ), ), ], @@ -1136,9 +1171,9 @@ class _CreateUserFormDetialsState extends State { isDesktop ? buildTabsForUser() : SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: buildTabsForUser(), - ), + scrollDirection: Axis.horizontal, + child: buildTabsForUser(), + ), Container( // color: Colors.yellow.shade50, height: MediaQuery.of(context).size.height * 0.64, @@ -1241,8 +1276,8 @@ class _CreateUserFormDetialsState extends State { ); case "travel": final fullName = - "${controllers["Fname"]?.text ?? ""} ${controllers["Lname"]?.text ?? ""}" - .trim(); + "${controllers["Fname"]?.text ?? ""} ${controllers["Lname"]?.text ?? ""}" + .trim(); return TravellerDetails( key: travellerDetailsKey, isDesktop: isDesktop, @@ -1301,69 +1336,69 @@ class _CreateUserFormDetialsState extends State { return Row( crossAxisAlignment: CrossAxisAlignment.end, // important children: - tabs.entries.map((entry) { - final targetTab = entry.key; + tabs.entries.map((entry) { + final targetTab = entry.key; - print("TargetsTAb: $targetTab"); + print("TargetsTAb: $targetTab"); - final isSelected = selectedTab == entry.key; - print("isSelected: $isSelected"); + final isSelected = selectedTab == entry.key; + print("isSelected: $isSelected"); - return GestureDetector( - onTap: () { - setState(() { - bool isValid = false; + return GestureDetector( + onTap: () { + setState(() { + bool isValid = false; - final currentTab = selectedTab; - if (currentTab == "personal") { - isValid = isValidData(userDetials); + final currentTab = selectedTab; + if (currentTab == "personal") { + isValid = isValidData(userDetials); - if (isValid) { - selectedTab = entry.key; - } - } else if (currentTab == "office" && - targetTab == "personal") { - selectedTab = entry.key; - } else if (currentTab == "office") { - isValid = isValidDataTwo(userDetials); - if (isValid) { - selectedTab = entry.key; - } - } else { - isValid = - true; // Travel tab might not need validation at this point - selectedTab = entry.key; - } - }); - }, - child: Padding( - padding: const EdgeInsets.only( - right: 24.0, - ), // space between tabs - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - entry.value, - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: - isSelected ? Color(0xFF114D8B) : Color(0xFF475569), - ), + if (isValid) { + selectedTab = entry.key; + } + } else if (currentTab == "office" && + targetTab == "personal") { + selectedTab = entry.key; + } else if (currentTab == "office") { + isValid = isValidDataTwo(userDetials); + if (isValid) { + selectedTab = entry.key; + } + } else { + isValid = + true; // Travel tab might not need validation at this point + selectedTab = entry.key; + } + }); + }, + child: Padding( + padding: const EdgeInsets.only( + right: 24.0, + ), // space between tabs + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + entry.value, + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: + isSelected ? Color(0xFF114D8B) : Color(0xFF475569), + ), + ), + const SizedBox(height: 8), + AnimatedContainer( + duration: Duration(milliseconds: 300), + height: 2, + width: isSelected ? 50 : 0, // small line + color: Color(0xFF114D8B), + ), + ], ), - const SizedBox(height: 8), - AnimatedContainer( - duration: Duration(milliseconds: 300), - height: 2, - width: isSelected ? 50 : 0, // small line - color: Color(0xFF114D8B), - ), - ], - ), - ), - ); - }).toList(), + ), + ); + }).toList(), ); } @@ -1373,17 +1408,17 @@ class _CreateUserFormDetialsState extends State { return [ MouseRegion( cursor: - isViewMode - ? SystemMouseCursors.forbidden - : SystemMouseCursors.click, + isViewMode + ? SystemMouseCursors.forbidden + : SystemMouseCursors.click, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: - isViewMode ? layoutColor : layoutColor, // Keep original color + isViewMode ? layoutColor : layoutColor, // Keep original color foregroundColor: - isViewMode ? Colors.white : Colors.white, // Keep original color + isViewMode ? Colors.white : Colors.white, // Keep original color disabledBackgroundColor: - layoutColor, // Ensure color remains when disabled + layoutColor, // Ensure color remains when disabled disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), @@ -1402,17 +1437,17 @@ class _CreateUserFormDetialsState extends State { return [ MouseRegion( cursor: - isViewMode - ? SystemMouseCursors.forbidden - : SystemMouseCursors.click, + isViewMode + ? SystemMouseCursors.forbidden + : SystemMouseCursors.click, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: - isViewMode ? layoutColor : layoutColor, // Keep original color + isViewMode ? layoutColor : layoutColor, // Keep original color foregroundColor: - isViewMode ? Colors.white : Colors.white, // Keep original color + isViewMode ? Colors.white : Colors.white, // Keep original color disabledBackgroundColor: - layoutColor, // Ensure color remains when disabled + layoutColor, // Ensure color remains when disabled disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), @@ -1433,17 +1468,17 @@ class _CreateUserFormDetialsState extends State { return [ MouseRegion( cursor: - isViewMode - ? SystemMouseCursors.forbidden - : SystemMouseCursors.click, + isViewMode + ? SystemMouseCursors.forbidden + : SystemMouseCursors.click, child: TextButton( style: ElevatedButton.styleFrom( backgroundColor: - isViewMode ? Colors.white : Colors.white, // Keep original color + isViewMode ? Colors.white : Colors.white, // Keep original color foregroundColor: - isViewMode ? layoutColor : layoutColor, // Keep original color + isViewMode ? layoutColor : layoutColor, // Keep original color disabledBackgroundColor: - layoutColor, // Ensure color remains when disabled + layoutColor, // Ensure color remains when disabled disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), @@ -1481,19 +1516,19 @@ class _CreateUserFormDetialsState extends State { if (!isViewMode) MouseRegion( cursor: - isViewMode - ? SystemMouseCursors.forbidden - : SystemMouseCursors.click, + isViewMode + ? SystemMouseCursors.forbidden + : SystemMouseCursors.click, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: - isViewMode ? layoutColor : layoutColor, // Keep original color + isViewMode ? layoutColor : layoutColor, // Keep original color foregroundColor: - isViewMode - ? Colors.white - : Colors.white, // Keep original color + isViewMode + ? Colors.white + : Colors.white, // Keep original color disabledBackgroundColor: - layoutColor, // Ensure color remains when disabled + layoutColor, // Ensure color remains when disabled disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), @@ -1502,7 +1537,7 @@ class _CreateUserFormDetialsState extends State { padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: - isViewMode ? null : handleSubmit, // Disable when in view mode + isViewMode ? null : handleSubmit, // Disable when in view mode child: Text("Submit"), ), ), diff --git a/web/index.html b/web/index.html index be7f178..6bf4dec 100644 --- a/web/index.html +++ b/web/index.html @@ -14,7 +14,7 @@ This is a placeholder for base href that will be replaced by the value of the `--base-href` argument provided to `flutter build`. --> - + From 675d82eab541cfb4a11cb53a7a68ea44eab77f2b Mon Sep 17 00:00:00 2001 From: venbaittech Date: Wed, 11 Jun 2025 12:17:18 +0530 Subject: [PATCH 4/5] # --- lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 148a99a..2361b59 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,6 @@ import 'app.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; void main() { - setUrlStrategy(PathUrlStrategy()); + // setUrlStrategy(PathUrlStrategy()); runApp(const MyApp()); } From b66391fbb5d6ce5de44fe2be10853ad88ee51d94 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Wed, 11 Jun 2025 17:41:07 +0530 Subject: [PATCH 5/5] 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,