diff --git a/lib/Screens/itnerary/flights.dart b/lib/Screens/itnerary/flights.dart index a369c55..dff5ea0 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) { @@ -612,14 +612,81 @@ class FlightScreenState extends State { // if (textControllers["_to${i}Controller"]?.text.trim().isEmpty ?? true) { // errorMessages["to_place_$i"] = "Required"; // } + // if (textControllers["_date${i}Controller"]?.text.trim().isEmpty ?? true) { + // errorMessages["date_$i"] = "Required"; + // } if (textControllers["_date${i}Controller"]?.text.trim().isEmpty ?? true) { errorMessages["date_$i"] = "Required"; } + if (textControllers["_time${i}Controller"]?.text.trim().isEmpty ?? true) { errorMessages["time_$i"] = "Required"; } } + // Step 2: Sequential Date Comparison + DateFormat format = DateFormat("dd-MM-yyyy"); // Assumes "12 Jun" format + DateTime now = DateTime.now(); + List parsedDates = []; + + for (int i = 1; i <= rowCount; i++) { + String? dateStr = textControllers["_date${i}Controller"]?.text.trim(); + String? timeStr = textControllers["_time${i}Controller"]?.text.trim(); + + print("VAlidationDATE - $dateStr "); + if (dateStr != null && dateStr.isNotEmpty) { + try { + DateTime date = format.parseStrict(dateStr); + date = DateTime( + now.year, + date.month, + date.day, + ); // Assume current year + parsedDates.add(date); + } catch (e) { + errorMessages["date_$i"] = "Invalid format"; + } + } + } + + for (int i = 1; i < parsedDates.length; i++) { + if (parsedDates[i].isAtSameMomentAs(parsedDates[i - 1])) { + //Here check time[i] and time[i-1] if same error 30 mins gap reeuired + print("Ckecking Same DAte"); + // Same date - check time gap + // Combine parsedDates and times into DateTime objects + String prevDateStr = + textControllers["_date${i}Controller"]!.text.trim(); + String prevTimeStr = + textControllers["_time${i}Controller"]!.text.trim(); + String currDateStr = + textControllers["_date${i + 1}Controller"]!.text.trim(); + String currTimeStr = + textControllers["_time${i + 1}Controller"]!.text.trim(); + + final dtFormat = DateFormat("dd-MM-yyyy HH:mm"); + final prevDT = dtFormat.parse("$prevDateStr $prevTimeStr"); + final currDT = dtFormat.parse("$currDateStr $currTimeStr"); + + int diffMins = currDT.difference(prevDT).inMinutes; + print("Time difference between row ${i} and ${i + 1}: $diffMins mins"); + + // If same day (diff >= 0 & < 1440 minutes), enforce 30‑min minimum gap + if (diffMins < 0) { + errorMessages["time_${i + 1}"] = "Must be after previous"; + } else if (diffMins < 30) { + errorMessages["time_${i + 1}"] = "30 mins gap required"; + } else { + errorMessages.remove("time_${i + 1}"); + } + + print("Ckecking Same DAte...1"); + } 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}"); + } + } + setState(() {}); // Update UI to show error messages return errorMessages @@ -647,11 +714,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; @@ -861,14 +928,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( @@ -893,9 +960,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, @@ -927,28 +994,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 + ), + ), ), ), @@ -1063,9 +1130,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: [ @@ -1075,7 +1142,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), ], @@ -1132,14 +1199,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( @@ -1155,7 +1222,7 @@ class FlightScreenState extends State { // Default selected value selectedClasses[index] ??= - dropdownItems.isNotEmpty ? dropdownItems.first.value : null; + dropdownItems.isNotEmpty ? dropdownItems.first.value : null; // ------------------------------------------------- DateTime? _selectedCheckOutDate; @@ -1198,10 +1265,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, @@ -1223,10 +1290,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(), @@ -1252,8 +1319,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); @@ -1320,159 +1387,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, - ), - ), - 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]!; - } - }); - }, + 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, + ), + ), + 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]!; + } + }); + }, + ), ), // child: SizedBox( @@ -1517,156 +1584,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, - ), - ), - 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]!; - } - }); - }, + 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, + ), + ), + 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]!; + } + }); + }, + ), ), ), if (errorMessages["to_place_$index"] != null) ...[ @@ -1753,7 +1820,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'] ?? '', @@ -1805,14 +1872,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, ), ), ), @@ -1872,7 +1939,10 @@ class FlightScreenState extends State { ), if (errorMessages["date_$index"] != null) ...[ SizedBox(height: 5), // Space before error message - Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), + Text( + errorMessages["date_$index"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), ], ], ), @@ -1905,7 +1975,7 @@ class FlightScreenState extends State { _selectCheckOutTime(context, index, () { validateTimeDifference(index); setState( - () {}, + () {}, ); // βœ… Force rebuild to show the error immediately }); }, @@ -1962,17 +2032,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( @@ -2063,7 +2133,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'] ?? '', @@ -2115,14 +2185,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, ), ), ), @@ -2183,17 +2253,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( @@ -2242,20 +2312,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/userManagement/create_user/create_user.dart b/lib/Screens/userManagement/create_user/create_user.dart index c4a76df..92869b6 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; @@ -299,7 +299,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 +363,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 +381,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 +444,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 +483,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; }); } @@ -582,7 +582,6 @@ class _CreateUserFormDetialsState extends State { travelDetailsData = travellerDetailsKey.currentState?.travel_Detials; // errorMessagesTravel = travellerDetailsKey.currentState!.errorMessages; - print("TRAVEL DETAILS FROM CHILD: $travelDetailsData"); Map data = userDetials; @@ -657,56 +656,38 @@ class _CreateUserFormDetialsState extends State { // printFormData(); bool isValid = travellerDetailsKey.currentState?.boolValidation() ?? false; + if (selectedTab == "travel" || + selectedRole == "5" || + setSelectesUserType == true) { + print("NO validation"); - print("isValid- $isValid"); - if (!isValid) { - print("Validation failed. Please check the inputs."); - setState(() {}); - return; // ❌ STOP execution here if not valid - } + Map data = userDetials; + + if (!isValidData(data)) { + print("USERDETAILS : $userDetials"); + print("Validation Failed: Required fields are missing."); + setState(() {}); + return; // Stop execution if validation fails + } else { + print("USERDETAILS : $userDetials"); + orgId = await getOrgId(); + + createUserData(userDetials); + } + } else { + print("isValid- $isValid"); + if (!isValid) { + print("Validation failed. Please check the inputs."); + setState(() {}); + return; // ❌ STOP execution here if not valid + } travelDetailsData = travellerDetailsKey.currentState?.travel_Detials; + print("travelDetailsData - $travelDetailsData"); - // Additional validation starts - travelDetailsData passport - String start_Date = travelDetailsData?['date_of_issue']; - String 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"; - } - } - - // String valid_from = travelDetailsData?['valid_from']; - // String valid_upto = travelDetailsData?['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 "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"; - // } - // } - // errorMessagesTravel = travellerDetailsKey.currentState!.errorMessages; + print("travelDetailsData - $travelDetailsData"); + // errorMessagesTravel = travellerDetailsKey.currentState!.errorMessages; print("TRAVEL DETAILS FROM CHILD"); // print("TRAVEL DETAILS FROM CHILD: $travelDetailsData"); @@ -731,6 +712,7 @@ class _CreateUserFormDetialsState extends State { orgId = await getOrgId(); createUserData(userDetials); + } } } @@ -762,7 +744,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 } } @@ -772,7 +754,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 } } @@ -962,7 +944,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!"); @@ -991,16 +973,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))], ), @@ -1022,9 +1004,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), @@ -1036,39 +1018,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!), + ), ), ), ], @@ -1113,9 +1095,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, @@ -1218,8 +1200,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, @@ -1278,69 +1260,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), - ), - ), - const SizedBox(height: 8), - AnimatedContainer( - duration: Duration(milliseconds: 300), - height: 2, - width: isSelected ? 50 : 0, // small line - color: Color(0xFF114D8B), - ), - ], + 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), + ), ), - ), - ); - }).toList(), + const SizedBox(height: 8), + AnimatedContainer( + duration: Duration(milliseconds: 300), + height: 2, + width: isSelected ? 50 : 0, // small line + color: Color(0xFF114D8B), + ), + ], + ), + ), + ); + }).toList(), ); } @@ -1350,17 +1332,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), @@ -1379,17 +1361,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), @@ -1410,17 +1392,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), @@ -1458,19 +1440,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), @@ -1479,7 +1461,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/lib/app.dart b/lib/app.dart index 988c9f1..ff0f116 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; +// import 'package:flutter/rendering.dart'; import 'dart:html' as html; import 'package:frontend/config/apiUrl.dart'; // 1 newly added import 'package:frontend/services/apiService.dart'; @@ -31,7 +31,7 @@ class _MyAppState extends State { @override void initState() { super.initState(); - SemanticsBinding.instance.ensureSemantics(); // βœ… Safe here + // SemanticsBinding.instance.ensureSemantics(); // βœ… Safe here if (kIsWeb) { final uri = Uri.parse(html.window.location.href); if (uri.path == '/authredirection' &&