diff --git a/assets/images/IconsImg/alternate.png b/assets/images/IconsImg/alternate.png new file mode 100644 index 0000000..90e6e77 Binary files /dev/null and b/assets/images/IconsImg/alternate.png differ diff --git a/assets/images/IconsImg/path.png b/assets/images/IconsImg/path.png new file mode 100644 index 0000000..861b382 Binary files /dev/null and b/assets/images/IconsImg/path.png differ diff --git a/lib/Screens/allTrips/list_all_plans.dart b/lib/Screens/allTrips/list_all_plans.dart index 536b13f..87b6d1a 100644 --- a/lib/Screens/allTrips/list_all_plans.dart +++ b/lib/Screens/allTrips/list_all_plans.dart @@ -913,7 +913,7 @@ class _ListAllPlansState extends State { size: 18, ), tooltip: - 'Download The Trip Details', + 'Download The PDF', onPressed: () { Navigator.pop( context, diff --git a/lib/Screens/approvals/approval_list.dart b/lib/Screens/approvals/approval_list.dart index 0c464b4..43c9e15 100644 --- a/lib/Screens/approvals/approval_list.dart +++ b/lib/Screens/approvals/approval_list.dart @@ -931,7 +931,7 @@ class _ApprovalListState extends State { size: 18, ), tooltip: - 'Download The Trip Details', + 'Download The PDF', onPressed: () { Navigator.pop( context, @@ -1170,7 +1170,7 @@ class _ApprovalListState extends State { size: 18, ), tooltip: - 'Download The Trip Details', + 'Download The PDF', onPressed: () { Navigator.pop(context); apiService diff --git a/lib/Screens/costCenter/costCenter_list.dart b/lib/Screens/costCenter/costCenter_list.dart index af92cdb..1970b8b 100644 --- a/lib/Screens/costCenter/costCenter_list.dart +++ b/lib/Screens/costCenter/costCenter_list.dart @@ -336,7 +336,7 @@ class CostCenterListState extends State { MainAxisSize.min, // Ensures content fits nicely children: [ Text( - "Add CostCenter", + "Add Cost Center", style: GoogleFonts.poppins( fontSize: isDesktop ? 13 : 11, ), diff --git a/lib/Screens/department/departmentDetails.dart b/lib/Screens/department/departmentDetails.dart index faa0ca3..4d461fb 100644 --- a/lib/Screens/department/departmentDetails.dart +++ b/lib/Screens/department/departmentDetails.dart @@ -196,24 +196,27 @@ class DepartmentDataState extends State { ? await http.put(uri, headers: headers, body: body) : await http.post(uri, headers: headers, body: body); - switch (response.statusCode) { - case 200: - print("Update - Response: ${response.body}"); - _clearError(); - widget.fetchGetDepartment(); - Navigator.of(context).pop(); - break; + if (response.statusCode == 200 || response.statusCode == 201) { + print("successfully!"); + print("Response: ${response.body}"); + _clearError(); + widget.fetchGetDepartment(); + // dispose(); + Navigator.of(context).pop(); + } else if (response.statusCode == 404) { + Navigator.of(context).pop(); + final message = jsonDecode(response.body)['message'] ?? 'Unknown error'; - case 201: - print("Save - Response: ${response.body}"); - _clearError(); - await widget.fetchGetDepartment(); - Navigator.of(context).pop(); - break; - - default: - print("Failed to submit department. Status: ${response.statusCode}"); - print("Error: ${response.body}"); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: Colors.redAccent, + behavior: SnackBarBehavior.floating, + ), + ); + } else { + print("Failed to submit. Status: ${response.statusCode}"); + print("Error: ${response.body}"); } } catch (e) { print(" Error submitting plan: $e"); diff --git a/lib/Screens/dialog/user_selection_dialog.dart b/lib/Screens/dialog/user_selection_dialog.dart index 5dc4955..2495a8b 100644 --- a/lib/Screens/dialog/user_selection_dialog.dart +++ b/lib/Screens/dialog/user_selection_dialog.dart @@ -349,7 +349,7 @@ class _UserSelectionDialogState extends State { mainAxisSize: MainAxisSize.min, // Ensures content doesn't expand unnecessarily children: [ - widget.title == "Others" + widget.title == "Others (Non Employee)" ? Text( "Please Select Other User", style: GoogleFonts.poppins(fontSize: 14), @@ -367,7 +367,7 @@ class _UserSelectionDialogState extends State { setState(() { _showTravellerForm = false; }); - widget.title == "Others" + widget.title == "Others (Non Employee)" ? _filterTravellers(query) : _filterUsers(query); }, @@ -395,7 +395,7 @@ class _UserSelectionDialogState extends State { SizedBox(height: 10), - if (widget.title == "Others") ...[ + if (widget.title == "Others (Non Employee)") ...[ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/Screens/forex/forexDetails.dart b/lib/Screens/forex/forexDetails.dart index 44c60bc..78002e5 100644 --- a/lib/Screens/forex/forexDetails.dart +++ b/lib/Screens/forex/forexDetails.dart @@ -396,7 +396,7 @@ class ForexDataState extends State { ), searchFieldProps: TextFieldProps( decoration: InputDecoration( - hintText: "Search Country...", + hintText: "Search ...", hintStyle: GoogleFonts.poppins(fontSize: 11), contentPadding: EdgeInsets.symmetric(horizontal: 4), ), @@ -414,7 +414,7 @@ class ForexDataState extends State { // Center-align selected item alignment: Alignment.centerLeft, child: Text( - selectedItem ?? "Select Country", + selectedItem ?? "Select ", style: GoogleFonts.poppins(fontSize: 11), ), ), diff --git a/lib/Screens/forex/forex_list.dart b/lib/Screens/forex/forex_list.dart index 64daa91..2d99fc6 100644 --- a/lib/Screens/forex/forex_list.dart +++ b/lib/Screens/forex/forex_list.dart @@ -746,9 +746,9 @@ class ForexDataListState extends State { style: TextStyle( fontSize: 13, fontFamily: "Inter", - // color: forex['is_active'] == "1" - // ? Colors.green - // : Colors.grey, + color: forex['is_active'] == "1" + ? Colors.green + : Colors.grey, ), softWrap: true, overflow: TextOverflow.ellipsis, diff --git a/lib/Screens/group/group.dart b/lib/Screens/group/group.dart index c0fa831..85ebdf2 100644 --- a/lib/Screens/group/group.dart +++ b/lib/Screens/group/group.dart @@ -93,13 +93,15 @@ class _groupState extends State { String? bodyStringColor = await getBodyColor(); setState(() { - layoutColor = layoutString != null - ? Color(int.parse(layoutString)) - : Colors.redAccent; + layoutColor = + layoutString != null + ? Color(int.parse(layoutString)) + : Colors.redAccent; - bodyColor = bodyStringColor != null - ? Color(int.parse(bodyStringColor)) - : Colors.white; + bodyColor = + bodyStringColor != null + ? Color(int.parse(bodyStringColor)) + : Colors.white; }); } @@ -154,10 +156,7 @@ class _groupState extends State { errorMessages.clear(); // Reset errors // Required fields that must not be empty - List requiredFields = [ - "name", - "description", - ]; + List requiredFields = ["name", "description"]; // Check validation for each field for (String field in requiredFields) { @@ -165,6 +164,14 @@ class _groupState extends State { errorMessages[field] = "Required"; } } + // ✅ At least one of the two policies must be selected + if ((selectedDomestic == null || selectedDomestic!.isEmpty) && + (selectedInternational == null || selectedInternational!.isEmpty)) { + errorMessages["domestic_policy_id"] = "Select at least one policy"; + errorMessages["international_policy_id"] = "Select at least one policy"; + } + + return errorMessages.isEmpty; return errorMessages.isEmpty; // Valid if there are no errors } @@ -223,23 +230,24 @@ class _groupState extends State { } try { - final response = await (isEdit - ? http.put( - Uri.parse(apiUrlData), - headers: { - 'Authorization': 'Bearer $token', - 'Content-Type': 'application/json', - }, - body: jsonEncode(groupData), - ) - : http.post( - Uri.parse(apiUrlData), - headers: { - 'Authorization': 'Bearer $token', - 'Content-Type': 'application/json', - }, - body: jsonEncode(groupData), - )); + final response = + await (isEdit + ? http.put( + Uri.parse(apiUrlData), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + body: jsonEncode(groupData), + ) + : http.post( + Uri.parse(apiUrlData), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + body: jsonEncode(groupData), + )); if (response.statusCode == 200 || response.statusCode == 201) { print("Group submitted successfully!"); @@ -320,32 +328,38 @@ class _groupState extends State { @override Widget build(BuildContext context) { - return ResponsiveBuilder(builder: (context, sizingInfo) { - bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + return ResponsiveBuilder( + builder: (context, sizingInfo) { + bool isDesktop = + sizingInfo.deviceScreenType == DeviceScreenType.desktop; - return Scaffold( - // backgroundColor: Colors.white, - backgroundColor: Color(0xFFf5f5f5), - appBar: CustomAppBar(isDesktop: isDesktop), - drawer: CustomDrawer(isDesktop: false), - body: Padding( - padding: isDesktop - ? EdgeInsets.symmetric( - horizontal: MediaQuery.of(context).size.width * - 0.1, // 30% of screen width as horizontal padding - vertical: MediaQuery.of(context).size.height * - 0, // 5% of screen height as vertical padding - ) - : EdgeInsets.all(8), - child: Row( - children: [ - // if (isDesktop) CustomDrawer(isDesktop: true), - Expanded(child: buildData(isDesktop, context)) - ], + return Scaffold( + // backgroundColor: Colors.white, + backgroundColor: Color(0xFFf5f5f5), + appBar: CustomAppBar(isDesktop: isDesktop), + drawer: CustomDrawer(isDesktop: false), + body: Padding( + padding: + isDesktop + ? EdgeInsets.symmetric( + horizontal: + MediaQuery.of(context).size.width * + 0.1, // 30% of screen width as horizontal padding + vertical: + MediaQuery.of(context).size.height * + 0, // 5% of screen height as vertical padding + ) + : EdgeInsets.all(8), + child: Row( + children: [ + // if (isDesktop) CustomDrawer(isDesktop: true), + Expanded(child: buildData(isDesktop, context)), + ], + ), ), - ), - ); - }); + ); + }, + ); } Widget buildData(bool isDesktop, context) { @@ -367,15 +381,16 @@ class _groupState extends State { Container( color: Colors.white, padding: const EdgeInsets.all(8.0), - child: isDesktop - ? Row( - mainAxisAlignment: MainAxisAlignment.end, - children: _buildSubmit(isDesktop), - ) - : Row( - mainAxisAlignment: MainAxisAlignment.center, - children: _buildSubmit(isDesktop), - ), + child: + isDesktop + ? Row( + mainAxisAlignment: MainAxisAlignment.end, + children: _buildSubmit(isDesktop), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: _buildSubmit(isDesktop), + ), ), ], ), @@ -387,9 +402,10 @@ class _groupState extends State { // margin: isDesktop // ? EdgeInsets.all(10.0) // : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), - height: isDesktop - ? MediaQuery.of(context).size.height * 0.98 - : MediaQuery.of(context).size.height, + height: + isDesktop + ? MediaQuery.of(context).size.height * 0.98 + : MediaQuery.of(context).size.height, // decoration: BoxDecoration( // border: isDesktop // ? Border.all( @@ -414,10 +430,7 @@ class _groupState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - "Create Group", - style: GoogleFonts.poppins(fontSize: 18), - ), + Text("Create Group", style: GoogleFonts.poppins(fontSize: 18)), // Container( // color: Color(0xFFE9EBF6), // child: IconButton( @@ -436,53 +449,52 @@ class _groupState extends State { // height: 5, // ), Container( - margin: const EdgeInsets.all(10), - padding: const EdgeInsets.all(20), - color: Colors.white, - height: MediaQuery.of(context).size.height * 0.6, - child: Column( - children: [ - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // Text( - // "Mail Settings", - // style: TextStyle(color: Colors.blueAccent), - // ), - // Icon( - // Icons.keyboard_arrow_down_outlined, - // color: Colors.blueAccent, - // size: 30, - // ), - // ], - // ), - isDesktop - ? Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildFirstRow(isDesktop), - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: _buildFirstRow(isDesktop), - ), + margin: const EdgeInsets.all(10), + padding: const EdgeInsets.all(20), + color: Colors.white, + height: MediaQuery.of(context).size.height * 0.6, + child: Column( + children: [ + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Text( + // "Mail Settings", + // style: TextStyle(color: Colors.blueAccent), + // ), + // Icon( + // Icons.keyboard_arrow_down_outlined, + // color: Colors.blueAccent, + // size: 30, + // ), + // ], + // ), + isDesktop + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildFirstRow(isDesktop), + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: _buildFirstRow(isDesktop), + ), - SizedBox( - height: 10, - ), + SizedBox(height: 10), - isDesktop - ? Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildSecondRow(isDesktop), - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: _buildSecondRow(isDesktop), - ), + isDesktop + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildSecondRow(isDesktop), + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: _buildSecondRow(isDesktop), + ), - // SizedBox(height: 15), - ], - )), + // SizedBox(height: 15), + ], + ), + ), ], ), ); @@ -496,17 +508,19 @@ class _groupState extends State { Text( "Group Name", style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: false, isDesktop: isDesktop, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.85, + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.85, child: SizedBox( height: 40, child: TextField( @@ -540,17 +554,19 @@ class _groupState extends State { Text( "Description", style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: false, isDesktop: isDesktop, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.85, + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.85, child: SizedBox( height: 40, child: TextField( @@ -589,72 +605,82 @@ class _groupState extends State { Text( "Select Policy For Domestic", style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: false, isDesktop: isDesktop, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.85, + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.85, child: SizedBox( height: 40, - child: apiForDomestic == null - ? Center( - child: Transform.scale( - scale: 0.5, - child: CircularProgressIndicator(), - ), - ) - : DropdownSearch( - selectedItem: selectedDomestic == null - ? null - : apiForDomestic! - .firstWhere((policy) => - policy['policy_id'] == - selectedDomestic)['name'] - .toString(), - popupProps: PopupProps.menu( - showSearchBox: true, - fit: FlexFit.loose, - constraints: BoxConstraints(maxHeight: 250), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - hintText: "Search Policy...", - contentPadding: - EdgeInsets.symmetric(horizontal: 10), + child: + apiForDomestic == null + ? Center( + child: Transform.scale( + scale: 0.5, + child: CircularProgressIndicator(), + ), + ) + : DropdownSearch( + selectedItem: + selectedDomestic == null + ? null + : apiForDomestic! + .firstWhere( + (policy) => + policy['policy_id'] == + selectedDomestic, + )['name'] + .toString(), + popupProps: PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search Policy...", + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + ), + ), ), ), - ), - items: apiForDomestic! - .map((policy) => policy['name'].toString()) - .toList(), - dropdownDecoratorProps: DropDownDecoratorProps( - dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 1), + items: + apiForDomestic! + .map((policy) => policy['name'].toString()) + .toList(), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), ), + dropdownBuilder: + (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select", + style: TextStyle(fontSize: 12), + ), + ), + onChanged: (String? newValue) { + setState(() { + selectedDomestic = + apiForDomestic!.firstWhere( + (policy) => policy['name'] == newValue, + )['policy_id']; + }); + }, ), - dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select", - style: TextStyle(fontSize: 12), - ), - ), - onChanged: (String? newValue) { - setState(() { - selectedDomestic = apiForDomestic!.firstWhere( - (policy) => - policy['name'] == newValue)['policy_id']; - }); - }, - ), ), - ) + ), ], ), Column( @@ -663,72 +689,82 @@ class _groupState extends State { Text( "Select Policy For International", style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: false, isDesktop: isDesktop, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.85, + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.85, child: SizedBox( height: 40, - child: apiForInternational == null - ? Center( - child: Transform.scale( - scale: 0.5, - child: CircularProgressIndicator(), - ), - ) - : DropdownSearch( - selectedItem: selectedInternational == null - ? null - : apiForInternational! - .firstWhere((policy) => - policy['policy_id'] == - selectedInternational)['name'] - .toString(), - popupProps: PopupProps.menu( - showSearchBox: true, - fit: FlexFit.loose, - constraints: BoxConstraints(maxHeight: 250), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - hintText: "Search Policy...", - contentPadding: - EdgeInsets.symmetric(horizontal: 10), + child: + apiForInternational == null + ? Center( + child: Transform.scale( + scale: 0.5, + child: CircularProgressIndicator(), + ), + ) + : DropdownSearch( + selectedItem: + selectedInternational == null + ? null + : apiForInternational! + .firstWhere( + (policy) => + policy['policy_id'] == + selectedInternational, + )['name'] + .toString(), + popupProps: PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search Policy...", + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + ), + ), ), ), - ), - items: apiForInternational! - .map((policy) => policy['name'].toString()) - .toList(), - dropdownDecoratorProps: DropDownDecoratorProps( - dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 1), + items: + apiForInternational! + .map((policy) => policy['name'].toString()) + .toList(), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), ), + dropdownBuilder: + (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select", + style: TextStyle(fontSize: 12), + ), + ), + onChanged: (String? newValue) { + setState(() { + selectedInternational = + apiForInternational!.firstWhere( + (policy) => policy['name'] == newValue, + )['policy_id']; + }); + }, ), - dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select", - style: TextStyle(fontSize: 12), - ), - ), - onChanged: (String? newValue) { - setState(() { - selectedInternational = apiForInternational! - .firstWhere((policy) => - policy['name'] == newValue)['policy_id']; - }); - }, - ), ), - ) + ), ], ), ]; @@ -737,22 +773,21 @@ class _groupState extends State { List _buildSubmit(isDesktop) { return [ ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: layoutColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: BorderSide(color: layoutColor ?? Colors.green, width: 2), - ), - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: layoutColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(color: layoutColor ?? Colors.green, width: 2), ), - onPressed: () { - context.go('/group'); - }, - child: Text("Cancel")), - SizedBox( - width: 20, + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + ), + onPressed: () { + context.go('/group'); + }, + child: Text("Cancel"), ), + SizedBox(width: 20), MouseRegion( // cursor: widget.isViewMode // ? SystemMouseCursors.forbidden @@ -773,7 +808,7 @@ class _groupState extends State { onPressed: handleSubmit, // Disable when in view mode child: Text("Submit"), ), - ) + ), ]; } } diff --git a/lib/Screens/group/groupDetails.dart b/lib/Screens/group/groupDetails.dart index fc3156b..9f0cb19 100644 --- a/lib/Screens/group/groupDetails.dart +++ b/lib/Screens/group/groupDetails.dart @@ -407,7 +407,7 @@ class GroupDataState extends State { ), searchFieldProps: TextFieldProps( decoration: InputDecoration( - hintText: "Select Policy For International", + hintText: "Search ...", hintStyle: GoogleFonts.poppins(fontSize: 11), contentPadding: EdgeInsets.symmetric(horizontal: 4), ), @@ -425,7 +425,7 @@ class GroupDataState extends State { // Center-align selected item alignment: Alignment.centerLeft, child: Text( - selectedItem ?? "Select Policy For International", + selectedItem ?? "Select", style: GoogleFonts.poppins(fontSize: 11), ), ), @@ -481,7 +481,7 @@ class GroupDataState extends State { ), searchFieldProps: TextFieldProps( decoration: InputDecoration( - hintText: "Select Policy For Domestic...", + hintText: "Search ...", hintStyle: GoogleFonts.poppins(fontSize: 11), contentPadding: EdgeInsets.symmetric(horizontal: 4), ), @@ -499,7 +499,7 @@ class GroupDataState extends State { // Center-align selected item alignment: Alignment.centerLeft, child: Text( - selectedItem ?? "Select Policy For Domestic", + selectedItem ?? "Select ", style: GoogleFonts.poppins(fontSize: 11), ), ), diff --git a/lib/Screens/group/groupList.dart b/lib/Screens/group/groupList.dart index d653297..02e0ff4 100644 --- a/lib/Screens/group/groupList.dart +++ b/lib/Screens/group/groupList.dart @@ -353,7 +353,7 @@ class _GroupListState extends State { MainAxisSize.min, // Ensures content fits nicely children: [ Text( - "Add New Group", + "Add Group", style: GoogleFonts.poppins( fontSize: isDesktop ? 13 : 11, ), diff --git a/lib/Screens/hotels/hotelsDetails.dart b/lib/Screens/hotels/hotelsDetails.dart index e759d09..76eb5f9 100644 --- a/lib/Screens/hotels/hotelsDetails.dart +++ b/lib/Screens/hotels/hotelsDetails.dart @@ -418,7 +418,7 @@ class HotelsDataState extends State { ), searchFieldProps: TextFieldProps( decoration: InputDecoration( - hintText: "Search Country...", + hintText: "Search ...", hintStyle: GoogleFonts.poppins(fontSize: 11), contentPadding: EdgeInsets.symmetric(horizontal: 4), ), @@ -436,7 +436,7 @@ class HotelsDataState extends State { // Center-align selected item alignment: Alignment.centerLeft, child: Text( - selectedItem ?? "Select Country", + selectedItem ?? "Select ", style: GoogleFonts.poppins(fontSize: 11), ), ), diff --git a/lib/Screens/itnerary/accomodations.dart b/lib/Screens/itnerary/accomodations.dart index 2d085f1..47c12ce 100644 --- a/lib/Screens/itnerary/accomodations.dart +++ b/lib/Screens/itnerary/accomodations.dart @@ -15,13 +15,14 @@ class AccomodationScreen extends StatefulWidget { final String? loginUser; final String? tripType; - AccomodationScreen( - {required this.onClose, - required this.onSaveAccomadation, - required this.selectedItem, - required this.loginUser, - required this.flightData, - this.tripType}); + AccomodationScreen({ + required this.onClose, + required this.onSaveAccomadation, + required this.selectedItem, + required this.loginUser, + required this.flightData, + this.tripType, + }); @override _AccomodationScreenState createState() => _AccomodationScreenState(); @@ -107,15 +108,23 @@ class _AccomodationScreenState extends State { super.initState(); _addFocusListener( - _destinationFocusNode, (focus) => _destinationFocused = focus); + _destinationFocusNode, + (focus) => _destinationFocused = focus, + ); _addFocusListener( - _hotelNameFocusNode, (focus) => _isHotelNameFocused = focus); + _hotelNameFocusNode, + (focus) => _isHotelNameFocused = focus, + ); _addFocusListener(_checkInFocusNode, (focus) => _checkInFocus = focus); _addFocusListener( - _checkInTimeFocusNode, (focus) => _checkInTimeFocus = focus); + _checkInTimeFocusNode, + (focus) => _checkInTimeFocus = focus, + ); _addFocusListener(_checkOutFocusNode, (focus) => _checkOutFocus = focus); _addFocusListener( - _checkOutTimeFocusNode, (focus) => _checkOutTimeFocus = focus); + _checkOutTimeFocusNode, + (focus) => _checkOutTimeFocus = focus, + ); _addFocusListener(_commentsFocusNode, (focus) => _commentsFocus = focus); _destinationController = initController("destination_city"); @@ -149,25 +158,29 @@ class _AccomodationScreenState extends State { flightFirstToDestinationNotifier.value = result['firstToDestination']; print( - "flightfirstToDestinationNotifier: $flightFirstToDestinationNotifier.value "); + "flightfirstToDestinationNotifier: $flightFirstToDestinationNotifier.value ", + ); // ✅ Only set controller after value is updated // final parsedDate = DateTime.tryParse(flightFirstTripDateNotifier.value ?? ''); - final parsedDate = DateFormat("dd-MM-yyyy") - .parse(flightFirstTripDateNotifier.value ?? ''); + final parsedDate = DateFormat( + "dd-MM-yyyy", + ).parse(flightFirstTripDateNotifier.value ?? ''); if (parsedDate != null) { _checkInController.text = DateFormat('dd-MM-yyyy').format(parsedDate); } // final parsedEndDate = DateTime.tryParse(flightLastTripDateNotifier.value ?? ''); - final parsedEndDate = DateFormat("dd-MM-yyyy") - .parse(flightLastTripDateNotifier.value ?? ''); + final parsedEndDate = DateFormat( + "dd-MM-yyyy", + ).parse(flightLastTripDateNotifier.value ?? ''); if (parsedEndDate != null) { // _checkOutController.text = DateFormat('dd-MM-yyyy').format(parsedEndDate); - _checkOutController.text = - DateFormat('dd-MM-yyyy').format(parsedEndDate); + _checkOutController.text = DateFormat( + 'dd-MM-yyyy', + ).format(parsedEndDate); } // ✅ Check and set default times if empty @@ -202,17 +215,16 @@ class _AccomodationScreenState extends State { } Map getFlightTripDateRange( - List> flightData) { - final allTrips = flightData - .expand((flight) => flight['trips'] ?? []) - .whereType>() - .toList(); + List> flightData, + ) { + final allTrips = + flightData + .expand((flight) => flight['trips'] ?? []) + .whereType>() + .toList(); if (allTrips.isEmpty) { - return { - 'firstTripDate': null, - 'lastTripDate': null, - }; + return {'firstTripDate': null, 'lastTripDate': null}; } allTrips.sort((a, b) { @@ -230,7 +242,7 @@ class _AccomodationScreenState extends State { return { 'firstTripDate': firstTrip['date'], 'lastTripDate': lastTrip['date'], - 'firstToDestination': firstTripToDestination['to_place'] + 'firstToDestination': firstTripToDestination['to_place'], }; } @@ -244,7 +256,7 @@ class _AccomodationScreenState extends State { "checkin_date", "checkin_time", "checkout_date", - "checkout_time" + "checkout_time", ]; // Check validation for each field @@ -284,8 +296,9 @@ class _AccomodationScreenState extends State { checkOutTime.toString().isNotEmpty) { try { final checkInDateTime = formatTime.parse("$checkIn $checkInTime"); - final checkOutDateTime = - formatTime.parse("$checkOut $checkOutTime"); + final checkOutDateTime = formatTime.parse( + "$checkOut $checkOutTime", + ); if (!checkOutDateTime.isAfter(checkInDateTime)) { errorMessages["checkout_time"] = @@ -359,60 +372,51 @@ class _AccomodationScreenState extends State { @override Widget build(BuildContext context) { - return ResponsiveBuilder(builder: (context, sizingInfo) { - bool isMobile = sizingInfo.isMobile; - bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + return ResponsiveBuilder( + builder: (context, sizingInfo) { + bool isMobile = sizingInfo.isMobile; + bool isDesktop = + sizingInfo.deviceScreenType == DeviceScreenType.desktop; - return Container( - // color: Color(0xFFF4F4FB), - child: Form( - key: _formKey, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(28.0), - child: Center( - child: Column(children: _buildAccomadtionForm(isDesktop)), + return Container( + // color: Color(0xFFF4F4FB), + child: Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 30.0), + child: Center( + child: Column(children: _buildAccomadtionForm(isDesktop)), + ), ), - ) - ], + ], + ), ), ), - ), - ); - }); + ); + }, + ); } List _buildAccomadtionForm(isDesktop) { return [ isDesktop - ? Row( - children: _buildFirstRow(isDesktop), - ) - : Column( - children: _buildFirstRow(isDesktop), - ), + ? Row(children: _buildFirstRow(isDesktop)) + : Column(children: _buildFirstRow(isDesktop)), SizedBox(height: 10), // Spacing between first and second row isDesktop - ? Row( - children: _buildSecondRow(isDesktop), - ) - : Column( - children: _buildSecondRow(isDesktop), - ), + ? Row(children: _buildSecondRow(isDesktop)) + : Column(children: _buildSecondRow(isDesktop)), SizedBox(height: 10), isDesktop - ? Row( - children: _buildThirdRow(isDesktop), - ) - : Column( - children: _buildThirdRow(isDesktop), - ), + ? Row(children: _buildThirdRow(isDesktop)) + : Column(children: _buildThirdRow(isDesktop)), ]; } @@ -432,24 +436,25 @@ class _AccomodationScreenState extends State { // ), // ], // ), - Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Destination", + "Destination *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: _destinationFocused, isDesktop: isDesktop, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.66, + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, child: TextField( @@ -475,29 +480,26 @@ class _AccomodationScreenState extends State { ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Hotel Name", + "Hotel Name *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: _isHotelNameFocused, isDesktop: isDesktop, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.66, + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, child: TextField( @@ -557,6 +559,7 @@ class _AccomodationScreenState extends State { initialDate: initialDate, firstDate: initialDate, lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); // DateTime? pickedDate = await showDatePicker( @@ -583,18 +586,61 @@ class _AccomodationScreenState extends State { initialTime: _selectedCheckInTime ?? TimeOfDay.now(), ); - if (pickedTime != null && pickedTime != _selectedCheckInTime) { + if (pickedTime != null) { + final now = DateTime.now(); + + // Parse the selected date + final dateText = _checkInController.text ?? ""; + final selectedDate = DateFormat( + 'dd-MM-yyyy', + ).parse(dateText); // or 'yyyy-MM-dd' depending on your format + + final selectedDateTime = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + pickedTime.hour, + pickedTime.minute, + ); + + // ✅ Only validate past time if date is today + final isToday = + selectedDate.year == now.year && + selectedDate.month == now.month && + selectedDate.day == now.day; + + bool isPastTime = selectedDateTime.isBefore(now); + + if (isToday && isPastTime) { + setState(() { + errorMessages["checkin_time"] = "You can't select a past time."; + }); + return; + } + + // ✅ Valid time selection setState(() { _selectedCheckInTime = pickedTime; - // Formatting time to HH:mm (24-hour format) - final now = DateTime.now(); - final formattedTime = DateFormat('HH:mm').format( - DateTime(now.year, now.month, now.day, pickedTime.hour, - pickedTime.minute), - ); + + final formattedTime = DateFormat('HH:mm').format(selectedDateTime); _checkInTimeController.text = formattedTime; + // errorMessages["time_$index"] = ""; // clear previous error + errorMessages.remove("checkin_time"); }); } + + // if (pickedTime != null && pickedTime != _selectedCheckInTime) { + // setState(() { + // _selectedCheckInTime = pickedTime; + // // Formatting time to HH:mm (24-hour format) + // final now = DateTime.now(); + // final formattedTime = DateFormat('HH:mm').format( + // DateTime(now.year, now.month, now.day, pickedTime.hour, + // pickedTime.minute), + // ); + // _checkInTimeController.text = formattedTime; + // }); + // } } //-------------------------------Check-In End @@ -647,23 +693,26 @@ class _AccomodationScreenState extends State { // : firstDate; DateTime firstDate = checkInDate; - DateTime initialDate = _selectedCheckOutDate != null && - _selectedCheckOutDate!.isAfter(firstDate) - ? _selectedCheckOutDate! - : firstDate; + DateTime initialDate = + _selectedCheckOutDate != null && + _selectedCheckOutDate!.isAfter(firstDate) + ? _selectedCheckOutDate! + : firstDate; final pickedDate = await showDatePicker( context: context, initialDate: initialDate, firstDate: firstDate, lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedCheckOutDate) { setState(() { _selectedCheckOutDate = pickedDate; - _checkOutController.text = - DateFormat('dd-MM-yyyy').format(pickedDate); + _checkOutController.text = DateFormat( + 'dd-MM-yyyy', + ).format(pickedDate); errorMessages.remove("checkout_date"); }); } @@ -681,8 +730,13 @@ class _AccomodationScreenState extends State { // Formatting time to HH:mm (24-hour format) final now = DateTime.now(); final formattedTime = DateFormat('HH:mm').format( - DateTime(now.year, now.month, now.day, pickedTime.hour, - pickedTime.minute), + DateTime( + now.year, + now.month, + now.day, + pickedTime.hour, + pickedTime.minute, + ), ); _checkOutTimeController.text = formattedTime; errorMessages.remove("checkout_time"); @@ -695,11 +749,12 @@ class _AccomodationScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Check-in*", + "Check-in *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -720,8 +775,11 @@ class _AccomodationScreenState extends State { floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), - suffixIcon: Icon(Icons.calendar_today, - size: 16, color: Colors.grey), + suffixIcon: Icon( + Icons.calendar_today, + size: 16, + color: Colors.grey, + ), ), ), ), @@ -737,21 +795,17 @@ class _AccomodationScreenState extends State { ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Time (Check-in)", + "Time (Check-in) *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -760,7 +814,10 @@ class _AccomodationScreenState extends State { child: SizedBox( height: 40, child: GestureDetector( - onTap: () => _selectCheckInTime(context), + onTap: () { + _checkInTimeController.text = ""; + _selectCheckInTime(context); + }, child: AbsorbPointer( child: TextField( focusNode: _checkInTimeFocusNode, @@ -772,8 +829,11 @@ class _AccomodationScreenState extends State { floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), - suffixIcon: - Icon(Icons.access_time, size: 16, color: Colors.grey), + suffixIcon: Icon( + Icons.access_time, + size: 16, + color: Colors.grey, + ), ), ), ), @@ -789,21 +849,17 @@ class _AccomodationScreenState extends State { ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Check-out*", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -817,15 +873,18 @@ class _AccomodationScreenState extends State { child: TextField( focusNode: _checkOutFocusNode, controller: _checkOutController, - style: const TextStyle(fontSize: 12), + style: const TextStyle(fontSize: 12, color: Colors.black), decoration: const InputDecoration( labelText: "Select Date", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), - suffixIcon: Icon(Icons.calendar_today, - size: 16, color: Colors.grey), + suffixIcon: Icon( + Icons.calendar_today, + size: 16, + color: Colors.grey, + ), ), ), ), @@ -841,21 +900,17 @@ class _AccomodationScreenState extends State { ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Time (Check-out)", + "Time (Check-out) *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -876,8 +931,11 @@ class _AccomodationScreenState extends State { floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), - suffixIcon: - Icon(Icons.access_time, size: 16, color: Colors.grey), + suffixIcon: Icon( + Icons.access_time, + size: 16, + color: Colors.grey, + ), ), ), ), @@ -904,17 +962,19 @@ class _AccomodationScreenState extends State { Text( "Comments", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: _commentsFocus, // Dropdown doesn't use focus isDesktop: isDesktop, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.66, + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, child: TextField( @@ -934,9 +994,7 @@ class _AccomodationScreenState extends State { ], ), if (isDesktop) Spacer(), - SizedBox( - height: 5, - ), + SizedBox(height: 5), Column( children: [ Row( @@ -957,9 +1015,7 @@ class _AccomodationScreenState extends State { }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( @@ -968,7 +1024,6 @@ class _AccomodationScreenState extends State { ), ), SizedBox(width: 10), // Space between buttons - // Save Changes Button ElevatedButton( onPressed: () { @@ -976,9 +1031,7 @@ class _AccomodationScreenState extends State { }, style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF114D8B), // Primary color for save - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( diff --git a/lib/Screens/itnerary/bus.dart b/lib/Screens/itnerary/bus.dart index 6738e33..241c925 100644 --- a/lib/Screens/itnerary/bus.dart +++ b/lib/Screens/itnerary/bus.dart @@ -13,12 +13,13 @@ class BusScreen extends StatefulWidget { final Map? selectedItem; final String? loginUser; - BusScreen( - {required this.onClose, - this.apiData, - required this.onSaveBus, - required this.selectedItem, - required this.loginUser}); + BusScreen({ + required this.onClose, + this.apiData, + required this.onSaveBus, + required this.selectedItem, + required this.loginUser, + }); @override _BusScreenState createState() => _BusScreenState(); @@ -195,51 +196,54 @@ class _BusScreenState extends State { @override Widget build(BuildContext context) { - return ResponsiveBuilder(builder: (context, sizingInfo) { - bool isMobile = sizingInfo.isMobile; - bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + return ResponsiveBuilder( + builder: (context, sizingInfo) { + bool isMobile = sizingInfo.isMobile; + bool isDesktop = + sizingInfo.deviceScreenType == DeviceScreenType.desktop; - return Container( - // color: Colors.white, - child: Form( - key: _formKey, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - // Align( - // alignment: Alignment.centerRight, - // child: InkWell( - // onTap: () { - // widget.onClose(false); - // }, - // child: Icon( - // Icons.close, - // size: 18, - // color: Color(0xFF575A74), - // ), - // ), - // ), - // Text("Bus Booking List", - // style: TextStyle( - // fontSize: 18, - // fontWeight: FontWeight.bold, - // color: Color(0xFF575A74))), - // SizedBox( - // height: 6, - // ), - Padding( - padding: const EdgeInsets.all(28.0), - child: Center( - child: Column(children: _buildAccomadtionForm(isDesktop)), + return Container( + // color: Colors.white, + child: Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + // Align( + // alignment: Alignment.centerRight, + // child: InkWell( + // onTap: () { + // widget.onClose(false); + // }, + // child: Icon( + // Icons.close, + // size: 18, + // color: Color(0xFF575A74), + // ), + // ), + // ), + // Text("Bus Booking List", + // style: TextStyle( + // fontSize: 18, + // fontWeight: FontWeight.bold, + // color: Color(0xFF575A74))), + // SizedBox( + // height: 6, + // ), + Padding( + padding: const EdgeInsets.only(top: 30.0), + child: Center( + child: Column(children: _buildAccomadtionForm(isDesktop)), + ), ), - ) - ], + ], + ), ), ), - ), - ); - }); + ); + }, + ); } List _buildAccomadtionForm(bool isDesktop) { @@ -252,7 +256,7 @@ class _BusScreenState extends State { List> rowBuilders = [ // _builClassType(isDesktop), - _buildSecondRow(isDesktop) + _buildSecondRow(isDesktop), ]; return [ @@ -275,41 +279,42 @@ class _BusScreenState extends State { Text( "Trip Type", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), isDesktop ? Row(children: _buildTripType(isDesktop)) - : Column(children: _buildTripType(isDesktop)) + : Column(children: _buildTripType(isDesktop)), ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), ]; } List _buildTripType(bool isDesktop) { List purposeList = widget.apiData?['flight_trip_type'] ?? []; - List> dropdownItems = purposeList - .map((item) => DropdownMenuItem( - value: item['dropdown_key'], - child: Text(item['dropdown_value']), - )) - .toList(); + List> dropdownItems = + purposeList + .map( + (item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + ), + ) + .toList(); if (dropdownItems.isEmpty) { dropdownItems.add( DropdownMenuItem( value: null, - child: Text("No options available", - style: TextStyle(color: Colors.grey)), + child: Text( + "No options available", + style: TextStyle(color: Colors.grey), + ), ), ); } @@ -327,21 +332,25 @@ class _BusScreenState extends State { child: DropdownButtonFormField( focusNode: _tripTypeFocusNode, // Assign the correct focus node value: selectedPurpose, - style: TextStyle(fontSize: 12), + // style: TextStyle(fontSize: 12), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), decoration: InputDecoration( border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10), // Proper padding + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + ), // Proper padding ), - onChanged: purposeList.isNotEmpty - ? (newValue) { - setState(() { - selectedPurpose = newValue; - }); - print( - "Updating form data: Flight -> trip_type -> ${newValue ?? ""}"); - } - : null, + onChanged: + purposeList.isNotEmpty + ? (newValue) { + setState(() { + selectedPurpose = newValue; + }); + print( + "Updating form data: Flight -> trip_type -> ${newValue ?? ""}", + ); + } + : null, items: dropdownItems, ), @@ -360,12 +369,14 @@ class _BusScreenState extends State { DateTime? pickedDate = await showDatePicker( context: context, - initialDate: _selectedCheckOutDate != null && - _selectedCheckOutDate!.isAfter(today) - ? _selectedCheckOutDate! - : today, + initialDate: + _selectedCheckOutDate != null && + _selectedCheckOutDate!.isAfter(today) + ? _selectedCheckOutDate! + : today, firstDate: today, lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedCheckOutDate) { @@ -383,16 +394,50 @@ class _BusScreenState extends State { ); if (pickedTime != null && pickedTime != _selectedCheckOutTime) { - setState(() { - _selectedCheckOutTime = pickedTime; - // Formatting time to HH:mm (24-hour format) - final now = DateTime.now(); - final formattedTime = DateFormat('HH:mm').format( - DateTime(now.year, now.month, now.day, pickedTime.hour, - pickedTime.minute), - ); - _timeController.text = formattedTime; - }); + // Parse the selected date + final dateText = _dateController.text ?? ""; + final selectedDate = DateFormat( + 'dd-MM-yyyy', + ).parse(dateText); // or 'yyyy-MM-dd' depending on your format + final now = DateTime.now(); + + final selectedDateTime = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + pickedTime.hour, + pickedTime.minute, + ); + + final isToday = + selectedDate.year == now.year && + selectedDate.month == now.month && + selectedDate.day == now.day; + + bool isPastTime = selectedDateTime.isBefore(now); + + if (isToday && isPastTime) { + setState(() { + errorMessages["time"] = "You can't select a past time."; + }); + return; + } else { + setState(() { + _selectedCheckOutTime = pickedTime; + // Formatting time to HH:mm (24-hour format) + final now = DateTime.now(); + final formattedTime = DateFormat('HH:mm').format( + DateTime( + now.year, + now.month, + now.day, + pickedTime.hour, + pickedTime.minute, + ), + ); + _timeController.text = formattedTime; + }); + } } } @@ -401,11 +446,12 @@ class _BusScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "From", + "From *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -429,28 +475,21 @@ class _BusScreenState extends State { ), if (errorMessages["from"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "To", + "To *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -474,28 +513,21 @@ class _BusScreenState extends State { ), if (errorMessages["to"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Date", + "Date *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -516,8 +548,11 @@ class _BusScreenState extends State { floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), - suffixIcon: Icon(Icons.calendar_today, - size: 16, color: Colors.grey), + suffixIcon: Icon( + Icons.calendar_today, + size: 16, + color: Colors.grey, + ), ), ), ), @@ -526,28 +561,21 @@ class _BusScreenState extends State { ), if (errorMessages["date"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Time", + "Time *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( @@ -556,20 +584,27 @@ class _BusScreenState extends State { child: SizedBox( height: 40, child: GestureDetector( - onTap: () => _selectCheckOutTime(context), + onTap: () { + // _selectedCheckOutTime = null; // ✅ Reset time variable + _timeController.text = ""; // ✅ Clear text field + _selectCheckOutTime(context); + }, child: AbsorbPointer( child: TextField( focusNode: _timeFocusNode, controller: _timeController, - style: const TextStyle(fontSize: 12), + style: const TextStyle(fontSize: 12, color: Colors.black), decoration: const InputDecoration( labelText: "Select Time", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), - suffixIcon: - Icon(Icons.access_time, size: 16, color: Colors.grey), + suffixIcon: Icon( + Icons.access_time, + size: 16, + color: Colors.grey, + ), ), ), ), @@ -579,7 +614,7 @@ class _BusScreenState extends State { if (errorMessages["time"] != null) ...[ SizedBox(height: 5), // Space before error message Text( - "Required", + errorMessages["time"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], @@ -596,9 +631,10 @@ class _BusScreenState extends State { Text( "Comments", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( @@ -644,9 +680,7 @@ class _BusScreenState extends State { }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( @@ -655,7 +689,6 @@ class _BusScreenState extends State { ), ), SizedBox(width: 10), // Space between buttons - // Save Changes Button ElevatedButton( onPressed: () { @@ -663,9 +696,7 @@ class _BusScreenState extends State { }, style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF114D8B), // Primary color for save - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( diff --git a/lib/Screens/itnerary/flights.dart b/lib/Screens/itnerary/flights.dart index df04dd9..0a98e7d 100644 --- a/lib/Screens/itnerary/flights.dart +++ b/lib/Screens/itnerary/flights.dart @@ -55,6 +55,8 @@ class FlightScreenState extends State { Map selectedValues = {}; + int rowCount = 1; + String? selectedTripType; Map selectedClasses = {}; // Store class selection for each trip Map selectedFrom = {}; // Store class selection for each trip @@ -149,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(); @@ -182,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}; @@ -251,9 +253,14 @@ class FlightScreenState extends State { Map tempCountryMap = {}; for (var country in result) { - String city = country['City'] ?? ''; - String airport = country['Airport'] ?? ''; - String displayName = '${country['City']} - ${country['Airport']}'; + // String city = country['City'] ?? ''; + // String airport = country['Airport'] ?? ''; + // String displayName = '${country['City']} - ${country['Airport']}'; + + final code = country['Code'] ?? ''; + final city = country['City'] ?? ''; + final airport = country['Airport'] ?? ''; + final displayName = '$city ($code)\n$airport'; tempCountryMap[country['Code']] = displayName; } @@ -297,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) { @@ -561,9 +568,9 @@ class FlightScreenState extends State { } bool validateFields() { - errorMessages.clear(); // Reset errors + // errorMessages.clear(); // Reset errors - int rowCount = 1; // Default row count for One-way + rowCount = 1; // Default row count for One-way if (selectedTripType == "Roundtrip") { rowCount = 2; // Fixed for Roundtrip } else if (selectedTripType == "Multitrip") { @@ -579,19 +586,131 @@ class FlightScreenState extends State { if (selectedFrom[i] == null) { errorMessages["from_place_$i"] = "Required"; } + + // if (selectedTo[i] == null) { + // errorMessages["to_place_$i"] = "Required"; + // }else if ( + // selectedFrom[i] == selectedTo[i]) { + // errorMessages["to_place_$i"] = "Change Destination"; + // } + if (selectedTo[i] == null) { errorMessages["to_place_$i"] = "Required"; + print("Error: to_place_$i -> Required (selectedTo[$i] is null)"); + } else if (selectedFrom[i] == selectedTo[i]) { + if (selectedTripType == "Roundtrip") { + errorMessages["to_place_1"] = "Change Destination"; + } else { + errorMessages["to_place_$i"] = "Change Destination"; + } + + print( + "Error: to_place_$i -> Change Destination (selectedFrom[$i] == selectedTo[$i])", + ); } + // 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"; } } + //selectedFrom For every trips should n't same + Set seenFrom = {}; + Set seenTo = {}; + + for (int i = 1; i <= rowCount; i++) { + final fromValue = selectedFrom[i]; + final toValue = selectedTo[i]; + if (fromValue != null) { + if (seenFrom.contains(fromValue)) { + errorMessages["from_place_$i"] = "Duplicate Departure "; + print("Error: from_place_$i duplicates a previous departure"); + } else { + seenFrom.add(fromValue); + } + } + + if (toValue != null) { + if (seenTo.contains(toValue)) { + errorMessages["to_place_$i"] = "Duplicate Destination "; + print("Error: to_place_$i duplicates a previous departure"); + } else { + seenTo.add(toValue); + } + } + } + + // Step 3: 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 @@ -620,11 +739,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; @@ -686,7 +805,7 @@ class FlightScreenState extends State { child: Column( children: [ Padding( - padding: const EdgeInsets.all(20.0), + padding: const EdgeInsets.only(top: 30.0), child: Center( child: Column(children: _buildAccomadtionForm(isDesktop)), ), @@ -834,14 +953,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( @@ -866,9 +985,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, @@ -882,6 +1001,15 @@ class FlightScreenState extends State { multiTripRowCount = 1; } errorMessages.clear(); + + for (int i = 1; i <= rowCount; i++) { + textControllers["_date${i}Controller"]?.clear(); + textControllers["_time${i}Controller"]?.clear(); + + // Also clear the actual selected data (not just the text in controllers) + selectedFrom[i] = null; // Assuming null means no selection + selectedTo[i] = null; + } }); print( "Updating form data: Flight -> trip_type -> $selectedTripType", @@ -891,28 +1019,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 + ), + ), ), ), @@ -1027,9 +1155,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: [ @@ -1039,7 +1167,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), ], @@ -1096,14 +1224,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( @@ -1119,7 +1247,7 @@ class FlightScreenState extends State { // Default selected value selectedClasses[index] ??= - dropdownItems.isNotEmpty ? dropdownItems.first.value : null; + dropdownItems.isNotEmpty ? dropdownItems.first.value : null; // ------------------------------------------------- DateTime? _selectedCheckOutDate; @@ -1162,16 +1290,17 @@ 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, initialDate: initialDate, firstDate: firstDate, lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedCheckOutDate) { @@ -1186,50 +1315,82 @@ 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(), ); - if (pickedTime != null && pickedTime != _selectedCheckOutTime) { + if (pickedTime != null) { + final now = DateTime.now(); + + // Parse the selected date + final dateText = textControllers["_date${index}Controller"]?.text ?? ""; + final selectedDate = DateFormat( + 'dd-MM-yyyy', + ).parse(dateText); // or 'yyyy-MM-dd' depending on your format + + final selectedDateTime = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + pickedTime.hour, + pickedTime.minute, + ); + + // ✅ Only validate past time if date is today + final isToday = + selectedDate.year == now.year && + selectedDate.month == now.month && + selectedDate.day == now.day; + + bool isPastTime = selectedDateTime.isBefore(now); + + if (isToday && isPastTime) { + setState(() { + errorMessages["time_$index"] = "You can't select a past time."; + }); + return; + } + + // ✅ Valid time selection setState(() { _selectedCheckOutTime = pickedTime; - // Formatting time to HH:mm (24-hour format) - final now = DateTime.now(); - final formattedTime = DateFormat('HH:mm').format( - DateTime( - now.year, - now.month, - now.day, - pickedTime.hour, - pickedTime.minute, - ), - ); - // _timeController.text = formattedTime; - textControllers["_time${index}Controller"]?.text = formattedTime; - onPicked(); + final formattedTime = DateFormat('HH:mm').format(selectedDateTime); + textControllers["_time${index}Controller"]?.text = formattedTime; + // errorMessages["time_$index"] = ""; // clear previous error + errorMessages.remove("time_$index"); + onPicked(); // Trigger callback }); } + + // if (pickedTime != null && pickedTime != _selectedCheckOutTime) { + // setState(() { + // _selectedCheckOutTime = pickedTime; + // // Formatting time to HH:mm (24-hour format) + // final now = DateTime.now(); + // final formattedTime = DateFormat('HH:mm').format( + // DateTime( + // now.year, + // now.month, + // now.day, + // pickedTime.hour, + // pickedTime.minute, + // ), + // ); + // + // // _timeController.text = formattedTime; + // textControllers["_time${index}Controller"]?.text = formattedTime; + // + // onPicked(); + // }); + // } } - // late Map countryMap; // Mapping country_code -> country_name - // late List countryCodes; // List of country codes - // countryMap = { - // for (var item in countryList) - // if (item['country_code'] != null && item['country_name'] != null) - // item['country_code'] as String: item['country_name'] as String - // }; - - // // Extract only country codes for processing - // countryCodes = countryMap.keys.toList(); - // - // selectedCountry ??= null; - return [ Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1251,74 +1412,159 @@ class FlightScreenState extends State { child: SizedBox( height: 40, child: - isCountryLoading - ? Center(child: CircularProgressIndicator()) - : DropdownSearch( - selectedItem: - selectedFrom[index] != null - ? countryMap[selectedFrom[index]] - : null, - popupProps: PopupProps.menu( - fit: FlexFit.loose, - constraints: BoxConstraints(maxHeight: 220), - showSearchBox: true, // Enables search functionality - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - hintText: "Search...", - contentPadding: EdgeInsets.symmetric( - horizontal: 10, - vertical: 1, - ), - ), - style: TextStyle(fontSize: 12), - ), - menuProps: MenuProps(backgroundColor: Colors.white), - itemBuilder: - (context, item, isSelected) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 6.0, - ), - child: Text( - item, - style: TextStyle( - fontSize: 13, - ), // 👈 Set your desired text size here - ), - ), - ), - items: countryMap.values.toList(), - dropdownDecoratorProps: DropDownDecoratorProps( - dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 1), - ), - ), - dropdownBuilder: - (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select", - style: TextStyle(fontSize: 12), - ), - ), - onChanged: (String? newValue) { - setState(() { - // selectedFrom[index] = countryMap.entries - // .firstWhere((entry) => entry.value == newValue) - // .key; - - selectedFrom[index] = - countryMap.entries - .firstWhere( - (entry) => entry.value == newValue, - ) - .key; - - print(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( @@ -1340,7 +1586,10 @@ class FlightScreenState extends State { ), if (errorMessages["from_place_$index"] != null) ...[ SizedBox(height: 5), // Space before error message - Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), + Text( + errorMessages["from_place_$index"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), ], ], ), @@ -1363,79 +1612,221 @@ class FlightScreenState extends State { child: SizedBox( height: 40, child: - isCountryLoading - ? Center(child: CircularProgressIndicator()) - : DropdownSearch( - selectedItem: - selectedTo[index] != null - ? countryMap[selectedTo[index]] - : null, - popupProps: PopupProps.menu( - fit: FlexFit.loose, - constraints: BoxConstraints(maxHeight: 220), - showSearchBox: true, // Enables search functionality - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - hintText: "Search...", - contentPadding: EdgeInsets.symmetric( - horizontal: 10, - vertical: 1, - ), - ), - style: TextStyle(fontSize: 12), - ), - menuProps: MenuProps(backgroundColor: Colors.white), - itemBuilder: - (context, item, isSelected) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 6.0, - ), - child: Text( - item, - style: TextStyle( - fontSize: 13, - ), // 👈 Set your desired text size here - ), - ), - ), - items: countryMap.values.toList(), - dropdownDecoratorProps: DropDownDecoratorProps( - 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), - ), - ), - onChanged: (String? newValue) { - setState(() { - selectedTo[index] = - countryMap.entries - .firstWhere( - (entry) => entry.value == newValue, - ) - .key; - - print(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) ...[ SizedBox(height: 5), // Space before error message - Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), + Text( + errorMessages["to_place_$index"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), ], ], ), if (isDesktop) Spacer() else SizedBox(height: 8), + + // Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // "Class *", + // style: GoogleFonts.poppins( + // fontSize: 12, + // fontWeight: FontWeight.w600, + // color: Color(0xFF575A74), + // ), + // ), + // SizedBox(height: 5), + // CustomTextFieldItnerarySubWrapper( + // isFocused: focusStates["_class${index}Focused"] ?? false, + // isDesktop: isDesktop, + // // width: isDesktop + // // ? MediaQuery.of(context).size.width * 0.34 + // // : MediaQuery.of(context).size.width * 0.66, + // child: SizedBox( + // height: 40, + // child: + // isFlightClassLoading + // ? const Center(child: CircularProgressIndicator()) + // : DropdownButtonFormField( + // focusNode: focusNodes["_class${index}FocusNode"], + // // focusNode: _tripTypeFocusNode, // Assign the correct focus node + // // controller: _hotelNameController, + // value: selectedClasses[index], + // + // style: TextStyle(fontSize: 12), + // decoration: InputDecoration( + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric( + // horizontal: 10, + // ), // Proper padding + // ), + // onChanged: + // purposeList.isNotEmpty + // ? (newValue) { + // setState(() { + // selectedClasses[index] = newValue; + // }); + // + // print(selectedClasses[index]); + // } + // : null, + // items: dropdownItems, + // ), + // ), + // ), + // ], + // ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1451,41 +1842,83 @@ class FlightScreenState extends State { CustomTextFieldItnerarySubWrapper( isFocused: focusStates["_class${index}Focused"] ?? false, isDesktop: isDesktop, - // width: isDesktop - // ? MediaQuery.of(context).size.width * 0.34 - // : MediaQuery.of(context).size.width * 0.66, child: SizedBox( - height: 40, - child: - isFlightClassLoading - ? const Center(child: CircularProgressIndicator()) - : DropdownButtonFormField( - focusNode: focusNodes["_class${index}FocusNode"], - // focusNode: _tripTypeFocusNode, // Assign the correct focus node - // controller: _hotelNameController, - value: selectedClasses[index], - - style: TextStyle(fontSize: 12), - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 10, - ), // Proper padding - ), - onChanged: - purposeList.isNotEmpty - ? (newValue) { - setState(() { - selectedClasses[index] = newValue; - }); - - print(selectedClasses[index]); - } - : null, - items: dropdownItems, + height: 35, + width: double.infinity, + child: DropdownSearch>( + items: purposeList.cast>(), + selectedItem: purposeList.firstWhere( + (item) => item['dropdown_key'] == selectedClasses[index], + orElse: () => {}, + ), + itemAsString: (item) => item['dropdown_value'] ?? '', + popupProps: PopupProps.menu( + showSearchBox: false, + fit: FlexFit.loose, + menuProps: const MenuProps(backgroundColor: Colors.white), + itemBuilder: (context, item, isSelected) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 8, ), + child: Text( + item['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ), + ); + }, + ), + dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 5, + ), + ), + ), + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return Text( + "Select Class", + style: GoogleFonts.poppins( + color: Colors.grey, + fontSize: 13, + ), + ); + } + return Text( + selectedItem['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ); + }, + onChanged: + purposeList.isNotEmpty + ? (Map? newValue) { + setState(() { + selectedClasses[index] = newValue?['dropdown_key']; + print("Selected Class: ${selectedClasses[index]}"); + }); + } + : null, + ), ), ), + if (errorMessages["class_$index"] != null) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + errorMessages["class_$index"]!, + style: GoogleFonts.poppins(fontSize: 12, color: Colors.red), + ), + ), ], ), if (isDesktop) Spacer() else SizedBox(height: 8), @@ -1534,7 +1967,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), + ), ], ], ), @@ -1559,6 +1995,7 @@ class FlightScreenState extends State { height: 40, child: GestureDetector( onTap: () { + textControllers["_time${index}Controller"]?.text = ""; _clearError("time_$index"); // validateTimeDifference(index); // _selectCheckOutTime(context); @@ -1566,7 +2003,7 @@ class FlightScreenState extends State { _selectCheckOutTime(context, index, () { validateTimeDifference(index); setState( - () {}, + () {}, ); // ✅ Force rebuild to show the error immediately }); }, @@ -1623,17 +2060,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( @@ -1647,6 +2084,61 @@ class FlightScreenState extends State { ); } return [ + // Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // "Visa Required", + // style: GoogleFonts.poppins( + // fontSize: 12, + // fontWeight: FontWeight.w600, + // color: Color(0xFF575A74), + // ), + // ), + // SizedBox(height: 5), + // CustomTextFieldItnerarySubWrapper( + // // isFocused: _tripTypeFocused, + // isFocused: focusStates["_visa1Focused"] ?? false, + // isDesktop: isDesktop, + // // width: isDesktop + // // ? MediaQuery.of(context).size.width * 0.34 + // // : MediaQuery.of(context).size.width * 0.66, + // child: SizedBox( + // height: 40, + // child: DropdownButtonFormField( + // // focusNode: _tripTypeFocusNode, // Assign the correct focus node + // focusNode: focusNodes["_visa1FocusNode"], + // value: selectedvisa_available, + // style: TextStyle(fontSize: 12), + // decoration: InputDecoration( + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric( + // horizontal: 10, + // ), // Proper padding + // ), + // onChanged: + // visa_available.isNotEmpty + // ? (newValue) { + // setState(() { + // selectedvisa_available = newValue; + // // selectedTripType = "Oneway"; + // // Reset `multiTripRowCount` when switching away from Multitrip + // }); + // print( + // "Updating form data: Flight -> trip_type -> $selectedvisa_available", + // ); + // + // // _initializeRows(); + // } + // : null, + // + // items: dropdownItems, + // ), + // ), + // ), + // ], + // ), + //********// Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1660,42 +2152,75 @@ class FlightScreenState extends State { ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( - // isFocused: _tripTypeFocused, + // use same wrapper as class isFocused: focusStates["_visa1Focused"] ?? false, isDesktop: isDesktop, - // width: isDesktop - // ? MediaQuery.of(context).size.width * 0.34 - // : MediaQuery.of(context).size.width * 0.66, child: SizedBox( - height: 40, - child: DropdownButtonFormField( - // focusNode: _tripTypeFocusNode, // Assign the correct focus node - focusNode: focusNodes["_visa1FocusNode"], - value: selectedvisa_available, - style: TextStyle(fontSize: 12), - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 10, - ), // Proper padding + height: 35, + width: double.infinity, + child: DropdownSearch>( + items: visa_available.cast>(), + selectedItem: visa_available.firstWhere( + (item) => item['dropdown_key'] == selectedvisa_available, + orElse: () => {}, ), + itemAsString: (item) => item['dropdown_value'] ?? '', + popupProps: PopupProps.menu( + showSearchBox: false, + fit: FlexFit.loose, + menuProps: const MenuProps(backgroundColor: Colors.white), + itemBuilder: (context, item, isSelected) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 8, + ), + child: Text( + item['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ), + ); + }, + ), + dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 5, + ), + ), + ), + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return Text( + "Select Option", + style: GoogleFonts.poppins( + color: Colors.grey, + fontSize: 13, + ), + ); + } + return Text( + selectedItem['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ); + }, onChanged: - visa_available.isNotEmpty - ? (newValue) { - setState(() { - selectedvisa_available = newValue; - // selectedTripType = "Oneway"; - // Reset `multiTripRowCount` when switching away from Multitrip - }); - print( - "Updating form data: Flight -> trip_type -> $selectedvisa_available", - ); - - // _initializeRows(); - } - : null, - - items: dropdownItems, + visa_available.isNotEmpty + ? (Map? newValue) { + setState(() { + selectedvisa_available = newValue?['dropdown_key']; + print("Selected Visa: $selectedvisa_available"); + }); + } + : null, ), ), ), @@ -1756,17 +2281,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( @@ -1793,14 +2318,14 @@ class FlightScreenState extends State { ), ), SizedBox(height: 5), - CustomTextFieldWrapper( + CustomTextFieldItnerarySubWrapper( // isFocused: _tripTypeFocused, isFocused: focusStates["_visa1Focused"] ?? false, isDesktop: isDesktop, - width: - isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.66, + // width: + // isDesktop + // ? MediaQuery.of(context).size.width * 0.34 + // : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, child: DropdownButtonFormField( @@ -1815,20 +2340,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 0d93906..611a049 100644 --- a/lib/Screens/itnerary/forex.dart +++ b/lib/Screens/itnerary/forex.dart @@ -53,6 +53,7 @@ class _ForexScreenState extends State { int fifteenPercent = 0; int remainingAmount = 0; + bool isWidget = false; Map focusNodes = {}; Map focusStates = {}; @@ -248,6 +249,31 @@ class _ForexScreenState extends State { } } + // Additional validation starts + final start_Date = data["start_date"]; + final end_Date = data["end_date"]; + + 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)) { + errorMessages["end_date"] = + "End date cannot be earlier than start date"; + ; + } + } catch (e) { + errorMessages["end_date"] = "Invalid date format"; + } + return errorMessages.isEmpty; + } + // Additional validation ends return errorMessages.values.every((msg) => msg.trim().isEmpty); // if (hasErrors) { @@ -290,7 +316,13 @@ class _ForexScreenState extends State { Map data = forexData; - if (!isValidForexData(data) && errorMessages.isNotEmpty) { + final card = data["deposit_on_card"]; + final cash = data["deposit_on_cash"]; + print("card - $card"); + print("cash - $cash"); + + if (!isValidForexData(data)) { + // && errorMessages.isNotEmpty) { print("Validation Failed: Required fields are missing."); setState(() {}); return; // Stop execution if validation fails @@ -419,12 +451,16 @@ class _ForexScreenState extends State { selectedCashPercent = int.tryParse( widget.selectedItem!["cash_percentage"]?.toString() ?? "", ); + + print("selectedCashPercentUPdae - $selectedCashPercent"); selectedPerdiemAmount = widget.selectedItem!["perdiem_amount"] as String?; isChecked = widget.selectedItem!["have_card"] == "1"; // Convert string to bool textControllers["_cardNumber"]?.text = widget.selectedItem!["card_number"]?.toString() ?? ""; + isWidget = true; + // if (textControllers["_cardNumber"] != null) { // print("userCardNumber11 - $userCardNumber"); // textControllers["_cardNumber"]!.text = userCardNumber ?? ''; @@ -548,6 +584,13 @@ class _ForexScreenState extends State { selectedQuotedAmount = ((perdiemAmount + calclateVal).toString() ?? 0) as String?; + + if (isWidget) { + print("IsWidget Update"); + + int? quotedAmount = int.tryParse(selectedQuotedAmount!); + fifteenPercent = (quotedAmount! * selectedCashPercent!) ~/ 100; + } }); if (userEdited) { @@ -612,7 +655,8 @@ class _ForexScreenState extends State { ); if (enteredAmount == null || calculateAmnt > qouteAmount!) { - errorMessages["deposit_on_card"] = "Amount cannot exceed $qouteAmount"; + errorMessages["deposit_on_card"] = + "Sum of cash card cannot exceed $qouteAmount"; } else if (checkValidAmount < qouteAmount) { errorMessages["deposit_on_card"] = "Enter Valid Amount"; // Clear error if valid @@ -627,6 +671,7 @@ class _ForexScreenState extends State { void _validateCashAmount(String value) { // errorMessages["deposit_on_card"] = " "; errorMessages.remove("deposit_on_cash"); + print("_validateCashAmount - $value - $fifteenPercent"); int? enteredAmount = int.tryParse(value); @@ -652,6 +697,7 @@ class _ForexScreenState extends State { errorMessages["deposit_on_card"] = "Enter Valid Amount"; // Clear error if valid } + print("CASHfifteenPercent - $fifteenPercent"); errorMessages["deposit_on_cash"] = "Amount cannot exceed $fifteenPercent"; } else if (checkValidAmount == quotedAmount) { errorMessages.remove("deposit_on_cash"); @@ -819,6 +865,7 @@ class _ForexScreenState extends State { initialDate: initialDate, firstDate: initialDate, lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); // DateTime? pickedDate = await showDatePicker( @@ -870,6 +917,7 @@ class _ForexScreenState extends State { initialDate: initialDate, firstDate: initialDate, lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); // DateTime? pickedDate = await showDatePicker( @@ -1048,8 +1096,9 @@ class _ForexScreenState extends State { SizedBox(height: 5), // Space before error message Text( errorMessages["end_date"]!, - // "Select End Date", - style: TextStyle(color: Colors.red, fontSize: 12), + 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 ), ], ], @@ -1143,6 +1192,51 @@ class _ForexScreenState extends State { ), ), SizedBox(height: 5), + // CustomTextFieldForexWrapper( + // isFocused: focusStates["_countries"] ?? false, + // isDesktop: isDesktop, + // child: SizedBox( + // height: 40, + // child: DropdownSearch( + // selectedItem: countryMap[selectedCountry], + // popupProps: PopupProps.menu( + // showSearchBox: true, // Enables search functionality + // searchFieldProps: TextFieldProps( + // decoration: InputDecoration( + // hintText: "Search ...", + // contentPadding: EdgeInsets.symmetric(horizontal: 10), + // ), + // ), + // ), + // items: countryMap.values.toList(), + // dropdownDecoratorProps: DropDownDecoratorProps( + // dropdownSearchDecoration: InputDecoration( + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric(horizontal: 1), + // ), + // ), + // dropdownBuilder: + // (context, selectedItem) => Align( + // // Center-align selected item + // alignment: Alignment.centerLeft, + // child: Text( + // selectedItem ?? "Select ", + // style: TextStyle(fontSize: 12), + // ), + // ), + // onChanged: (String? newValue) { + // setState(() { + // // Find the country_code based on selected country_name + // selectedCountry = + // countryMap.entries + // .firstWhere((entry) => entry.value == newValue) + // .key; + // _onCountryChanged(selectedCountry); + // }); + // }, + // ), + // ), + // ), CustomTextFieldForexWrapper( isFocused: focusStates["_countries"] ?? false, isDesktop: isDesktop, @@ -1151,10 +1245,25 @@ class _ForexScreenState extends State { child: DropdownSearch( selectedItem: countryMap[selectedCountry], popupProps: PopupProps.menu( - showSearchBox: true, // Enables search functionality + showSearchBox: true, + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: const MenuProps(backgroundColor: Colors.white), + itemBuilder: + (context, item, isSelected) => Container( + color: Colors.white, + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + child: Text( + item, + style: GoogleFonts.poppins(fontSize: 11.5), + ), + ), searchFieldProps: TextFieldProps( decoration: InputDecoration( - hintText: "Search Country...", + hintText: "Search ...", contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), @@ -1168,11 +1277,13 @@ class _ForexScreenState extends State { ), dropdownBuilder: (context, selectedItem) => Align( - // Center-align selected item alignment: Alignment.centerLeft, child: Text( - selectedItem ?? "Select Country", - style: TextStyle(fontSize: 12), + selectedItem ?? "Select ", + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), ), ), onChanged: (String? newValue) { @@ -1203,7 +1314,8 @@ class _ForexScreenState extends State { // height: 8, // ), if (isDesktop) - SizedBox(width: MediaQuery.of(context).size.width * 0.048) + Spacer() + // SizedBox(width: MediaQuery.of(context).size.width * 0.048) else SizedBox(height: 8), Column( @@ -1620,6 +1732,12 @@ class _ForexScreenState extends State { // keyboardType: TextInputType.number, onChanged: (value) { // errorMessages["deposit_on_cash"] = ""; + errorMessages.remove("deposit_on_cash"); + errorMessages.remove("deposit_on_card"); + // errorMessages["deposit_on_card"] = ""; + setState(() { + userEdited = true; + }); _validateCashAmount( value, ); // Call validation when text changes @@ -1673,6 +1791,9 @@ class _ForexScreenState extends State { FilteringTextInputFormatter.digitsOnly, // ✅ Only allow digits ], onChanged: (value) { + errorMessages.remove("deposit_on_cash"); + errorMessages.remove("deposit_on_card"); + userEdited = true; _validateCardAmount( value, ); // Call validation when text changes diff --git a/lib/Screens/itnerary/insurance.dart b/lib/Screens/itnerary/insurance.dart index d1a903c..98de2a1 100644 --- a/lib/Screens/itnerary/insurance.dart +++ b/lib/Screens/itnerary/insurance.dart @@ -1,3 +1,4 @@ +import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; @@ -14,13 +15,14 @@ class InsuranceScreen extends StatefulWidget { final Map? selectedItem; final String? loginUser; - InsuranceScreen( - {required this.onClose, - required this.apiData, - required this.onSaveInsurance, - required this.selectedItem, - required this.loginUser, - required this.flightData}); + InsuranceScreen({ + required this.onClose, + required this.apiData, + required this.onSaveInsurance, + required this.selectedItem, + required this.loginUser, + required this.flightData, + }); @override _InsuranceScreenState createState() => _InsuranceScreenState(); @@ -36,19 +38,23 @@ class _InsuranceScreenState extends State { final FocusNode _tripTypeFocusNode = FocusNode(); final FocusNode _hotelNameFocusNode = FocusNode(); - final FocusNode _fromFocusNode = FocusNode(); - final FocusNode _dateFocusNode = FocusNode(); + final FocusNode _startDateFocusNode = FocusNode(); + final FocusNode _endDateFocusNode = FocusNode(); final FocusNode _commentsFocusNode = FocusNode(); + final FocusNode _nomineeFocusNode = FocusNode(); late TextEditingController _tripTypeController = TextEditingController(); - late TextEditingController _startdateController = TextEditingController(); + late TextEditingController _startDateController = TextEditingController(); late TextEditingController _endDateController = TextEditingController(); late TextEditingController _insuranceCommentsController = TextEditingController(); + late TextEditingController _nomineeController = TextEditingController(); bool _isHotelNameFocused = false; - bool _dateFocus = false; + bool _startDateFocus = false; + bool _endDateFocus = false; bool _commentsFocus = false; + bool _nomineeFocus = false; String? selectedTripType; String? selectedInsuranceType; @@ -58,9 +64,10 @@ class _InsuranceScreenState extends State { Map get InsuranceData { Map data = { "type_of_insurance": selectedInsuranceType, - "start_date": _startdateController.text, + "start_date": _startDateController.text, "end_date": _endDateController.text, "comments": _insuranceCommentsController.text, + "nominee_name": _nomineeController.text, "created_by": widget.loginUser, "updated_by": widget.loginUser, }; @@ -87,9 +94,14 @@ class _InsuranceScreenState extends State { _isHotelNameFocused = _hotelNameFocusNode.hasFocus; }); }); - _dateFocusNode.addListener(() { + _endDateFocusNode.addListener(() { setState(() { - _dateFocus = _fromFocusNode.hasFocus; + _endDateFocus = _endDateFocusNode.hasFocus; + }); + }); + _startDateFocusNode.addListener(() { + setState(() { + _startDateFocus = _startDateFocusNode.hasFocus; }); }); _commentsFocusNode.addListener(() { @@ -98,12 +110,24 @@ class _InsuranceScreenState extends State { }); }); - _insuranceCommentsController = - TextEditingController(text: widget.selectedItem?["comments"] ?? ""); - _startdateController = - TextEditingController(text: widget.selectedItem?["start_date"] ?? ""); - _endDateController = - TextEditingController(text: widget.selectedItem?['end_date'] ?? ""); + _nomineeFocusNode.addListener(() { + setState(() { + _nomineeFocus = _nomineeFocusNode.hasFocus; + }); + }); + + _insuranceCommentsController = TextEditingController( + text: widget.selectedItem?["comments"] ?? "", + ); + _startDateController = TextEditingController( + text: widget.selectedItem?["start_date"] ?? "", + ); + _endDateController = TextEditingController( + text: widget.selectedItem?['end_date'] ?? "", + ); + _nomineeController = TextEditingController( + text: widget.selectedItem?['nominee_name'] ?? "", + ); // Set the selected value if available if (widget.selectedItem != null && @@ -123,38 +147,29 @@ class _InsuranceScreenState extends State { // ✅ Only set controller after value is updated // final parsedDate = // DateTime.tryParse(flightFirstTripDateNotifier.value ?? ''); - final parsedDate = DateFormat("dd-MM-yyyy") - .parse(flightFirstTripDateNotifier.value ?? ''); - if (parsedDate != null) { - _startdateController.text = DateFormat('dd-MM-yyyy').format(parsedDate); - } + final parsedDate = DateFormat("dd-MM-yyyy").parse(flightFirstTripDateNotifier.value ?? ''); + if (parsedDate != null) { _startDateController.text = DateFormat('dd-MM-yyyy').format(parsedDate); } - final parsedEndDate = DateFormat("dd-MM-yyyy") - .parse(flightLastTripDateNotifier.value ?? ''); - if (parsedEndDate != null) { - _endDateController.text = - DateFormat('dd-MM-yyyy').format(parsedEndDate); + final parsedEndDate = DateFormat("dd-MM-yyyy").parse(flightLastTripDateNotifier.value ?? ''); + if (parsedEndDate != null) { _endDateController.text = DateFormat('dd-MM-yyyy').format(parsedEndDate); } + + print("parsedDate $parsedDate"); + print("parsedEndDate $parsedEndDate"); - print("parsedDate"); - print(parsedDate); - print("parsedEndDate"); - print(parsedEndDate); - } }); } Map getFlightTripDateRange( - List> flightData) { - final allTrips = flightData - .expand((flight) => flight['trips'] ?? []) - .whereType>() - .toList(); + List> flightData, + ) { + final allTrips = + flightData + .expand((flight) => flight['trips'] ?? []) + .whereType>() + .toList(); if (allTrips.isEmpty) { - return { - 'firstTripDate': null, - 'lastTripDate': null, - }; + return {'firstTripDate': null, 'lastTripDate': null}; } allTrips.sort((a, b) { @@ -187,7 +202,7 @@ class _InsuranceScreenState extends State { List requiredFields = [ "type_of_insurance", "start_date", - "end_date" + "end_date", ]; // Check validation for each field @@ -247,37 +262,42 @@ class _InsuranceScreenState extends State { void dispose() { _tripTypeFocusNode.dispose(); _tripTypeController.dispose(); - _startdateController.dispose(); + _startDateController.dispose(); + _endDateController.dispose(); _insuranceCommentsController.dispose(); + _nomineeController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { - return ResponsiveBuilder(builder: (context, sizingInfo) { - bool isMobile = sizingInfo.isMobile; - bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + return ResponsiveBuilder( + builder: (context, sizingInfo) { + bool isMobile = sizingInfo.isMobile; + bool isDesktop = + sizingInfo.deviceScreenType == DeviceScreenType.desktop; - return Container( - // color: Color(0xFFF4F4FB), - child: Form( - key: _formKey, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(28.0), - child: Center( - child: Column(children: _buildAccomadtionForm(isDesktop)), + return Container( + // color: Color(0xFFF4F4FB), + child: Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 30.0), + child: Center( + child: Column(children: _buildAccomadtionForm(isDesktop)), + ), ), - ) - ], + ], + ), ), ), - ), - ); - }); + ); + }, + ); } List _buildAccomadtionForm(bool isDesktop) { @@ -290,7 +310,7 @@ class _InsuranceScreenState extends State { List> rowBuilders = [ // _builClassType(isDesktop), - _buildSecondRow(isDesktop) + _buildSecondRow(isDesktop), ]; return [ @@ -305,51 +325,52 @@ class _InsuranceScreenState extends State { ]; } - List _buildFirstRow(isDesktop) { - return [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Insurance Type", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), - ), - SizedBox(height: 5), - isDesktop - ? Row(children: _buildTripType(isDesktop)) - : Column(children: _buildTripType(isDesktop)) - ], - ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), - ]; - } + // List _buildFirstRow(isDesktop) { + // return [ + // Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // "Insurance Type", // not in use + // style: GoogleFonts.poppins( + // fontSize: 12, + // fontWeight: FontWeight.w500, + // color: Color(0xFF575A74), + // ), + // ), + // SizedBox(height: 5), + // isDesktop + // ? Row(children: _buildTripType(isDesktop)) + // : Column(children: _buildTripType(isDesktop)), + // ], + // ), + // if (isDesktop) Spacer() else SizedBox(height: 8), + // ]; + // } List _buildTripType(bool isDesktop) { List purposeList = widget.apiData?['insurance_type_of_insurance'] ?? []; // selectedInsuranceType = purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null; - List> dropdownItems = purposeList - .map((item) => DropdownMenuItem( - value: item['dropdown_key'], - child: Text(item['dropdown_value']), - )) - .toList(); + List> dropdownItems = + purposeList + .map( + (item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + ), + ) + .toList(); if (dropdownItems.isEmpty) { dropdownItems.add( DropdownMenuItem( value: null, - child: Text("No options available", - style: TextStyle(color: Colors.grey)), + child: Text( + "No options available", + style: TextStyle(color: Colors.grey), + ), ), ); } @@ -362,32 +383,35 @@ class _InsuranceScreenState extends State { CustomTextFieldWrapper( isFocused: _isHotelNameFocused, isDesktop: isDesktop, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.66, + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, child: DropdownButtonFormField( focusNode: _tripTypeFocusNode, // Assign the correct focus node value: selectedInsuranceType, - style: TextStyle(fontSize: 12), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), decoration: InputDecoration( border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10), // Proper padding + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + ), // Proper padding ), - onChanged: purposeList.isNotEmpty - ? (newValue) { - setState(() { - selectedInsuranceType = newValue; - if (selectedInsuranceType!.isNotEmpty) { - errorMessages.remove("type_of_insurance"); - } - }); + onChanged: + purposeList.isNotEmpty + ? (newValue) { + setState(() { + selectedInsuranceType = newValue; + if (selectedInsuranceType!.isNotEmpty) { + errorMessages.remove("type_of_insurance"); + } + }); - print(selectedInsuranceType); - } - : null, + print(selectedInsuranceType); + } + : null, items: dropdownItems, ), @@ -396,24 +420,118 @@ class _InsuranceScreenState extends State { ]; } + List _buildInsuranceTypeDropdown(bool isDesktop) { + // List purposeList = widget.apiData?['insurance_type_of_insurance'] ?? []; + List> purposeList = + (widget.apiData?['insurance_type_of_insurance'] as List?) + ?.map((e) => Map.from(e as Map)) + .toList() ?? + []; + + // Find selected item object based on key + Map? selectedItem = purposeList.firstWhere( + (item) => item['dropdown_key'] == selectedInsuranceType, + orElse: () => {}, + ); + + return [ + CustomTextFieldWrapper( + isFocused: false, + isDesktop: isDesktop, + child: SizedBox( + height: 35, + width: double.infinity, + child: DropdownSearch>( + popupProps: PopupProps.menu( + showSearchBox: false, + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 10), + menuProps: const MenuProps(backgroundColor: Colors.white), + itemBuilder: (context, item, isSelected) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 8, + ), + child: Text( + item['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, // 👈 Smaller font size + color: Colors.black, + ), + ), + ); + }, + ), + dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, // No underline + + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 5, + ), + ), + ), + + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return Text( + "Select Insurance Type", + style: GoogleFonts.poppins(color: Colors.grey, fontSize: 13), + ); + } + return Text( + selectedItem['dropdown_value'] ?? '', + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + ); + }, + selectedItem: selectedItem!.isNotEmpty ? selectedItem : null, + itemAsString: (item) => item['dropdown_value'] ?? '', + onChanged: + purposeList.isNotEmpty + ? (Map? newItem) { + if (newItem != null) { + setState(() { + selectedInsuranceType = newItem['dropdown_key']; + errorMessages.remove("type_of_insurance"); + }); + print( + "Selected Insurance Type: $selectedInsuranceType", + ); + } + } + : null, + items: purposeList, + ), + ), + ), + ]; + } + List _buildSecondRow(bool isDesktop) { List purposeList = widget.apiData?['insurance_type_of_insurance'] ?? []; // selectedInsuranceType = purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null; - List> dropdownItems = purposeList - .map((item) => DropdownMenuItem( - value: item['dropdown_key'], - child: Text(item['dropdown_value']), - )) - .toList(); + List> dropdownItems = + purposeList + .map( + (item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + ), + ) + .toList(); if (dropdownItems.isEmpty) { dropdownItems.add( DropdownMenuItem( value: null, - child: Text("No options available", - style: TextStyle(color: Colors.grey)), + child: Text( + "No options available", + style: TextStyle(color: Colors.grey), + ), ), ); } @@ -454,6 +572,7 @@ class _InsuranceScreenState extends State { initialDate: initialDate, firstDate: initialDate, lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); // DateTime? pickedDate = await showDatePicker( @@ -469,8 +588,9 @@ class _InsuranceScreenState extends State { if (pickedDate != null && pickedDate != _selectedCheckOutDate) { setState(() { _selectedCheckOutDate = pickedDate; - _startdateController.text = - DateFormat('dd-MM-yyyy').format(pickedDate); + _startDateController.text = DateFormat( + 'dd-MM-yyyy', + ).format(pickedDate); }); } } @@ -479,12 +599,12 @@ class _InsuranceScreenState extends State { DateTime now = DateTime.now(); DateTime today = DateTime(now.year, now.month, now.day); - DateTime? checkInDate; - try { - checkInDate = DateTime.parse(_startdateController.text); - } catch (e) { - checkInDate = today; - } + // DateTime? checkInDate; + // try { + // checkInDate = DateTime.parse(_startDateController.text); + // } catch (e) { + // checkInDate = today; + // } // // Ensure at least today is used // DateTime firstDate = checkInDate.isAfter(today) ? checkInDate : today; @@ -493,19 +613,52 @@ class _InsuranceScreenState extends State { // ? _selectedCheckOutDate! // : firstDate; - DateTime firstDate = checkInDate; - DateTime initialDate = _selectedCheckOutDate != null && - _selectedCheckOutDate!.isAfter(firstDate) - ? _selectedCheckOutDate! - : firstDate; + // DateTime firstDate = checkInDate; + // DateTime initialDate = + // _selectedCheckOutDate != null && + // _selectedCheckOutDate!.isAfter(firstDate) + // ? _selectedCheckOutDate! + // : firstDate; + DateTime? validFromDate; + + try { + String fromDateText = _startDateController.text.trim(); + print("Valid From Text: $fromDateText"); + if (fromDateText.isNotEmpty) { + validFromDate = DateFormat('dd-MM-yyyy').parseStrict(fromDateText); + print("Parsed Valid From: $validFromDate"); + } + } catch (e) { + print("Error parsing valid from date: $e"); + } + + // Use max(today, validFromDate) as firstDate + // DateTime firstDate = today; + DateTime firstDate = validFromDate ?? today; + if (validFromDate != null && validFromDate.isAfter(today)) { + firstDate = validFromDate; + } final pickedDate = await showDatePicker( context: context, - initialDate: initialDate, + // initialDate: initialDate, + // firstDate: initialDate, + // firstDate: DateTime(1900), + initialDate: firstDate, firstDate: firstDate, lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); + // final pickedDate = await showDatePicker( + // context: context, + // // initialDate: initialDate, + // initialDate: initialDate, + // firstDate: initialDate, + // lastDate: DateTime(2100), + // initialEntryMode: DatePickerEntryMode.calendarOnly, + // ); + // DateTime? pickedDate = await showDatePicker( // context: context, // initialDate: _selectedCheckOutDate != null && @@ -529,93 +682,223 @@ class _InsuranceScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Insurance Type", + "Insurance Type *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: const Color(0xFF575A74), + ), ), - SizedBox(height: 5), + const SizedBox(height: 5), + + // ▼ Dropdown Wrapper ▼ + // CustomTextFieldWrapper( + // isFocused: _isHotelNameFocused, + // isDesktop: isDesktop, + // width: isDesktop ? MediaQuery.of(context).size.width * 0.31 : null, + // child: SizedBox( + // height: 35, + // width: double.infinity, + // child: DropdownButtonFormField( + // focusNode: _tripTypeFocusNode, + // value: selectedInsuranceType, + // style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + // decoration: const InputDecoration( + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric( + // horizontal: 10, + // vertical: 5, + // ), + // ), + // hint: Text( + // "Select ", + // style: GoogleFonts.poppins(color: Colors.grey, fontSize: 13), + // ), + // items: + // purposeList.map>((item) { + // return DropdownMenuItem( + // value: item['dropdown_key'], + // child: Text( + // item['dropdown_value'] ?? '', + // style: GoogleFonts.poppins(fontSize: 12), + // ), + // ); + // }).toList(), + // onChanged: + // purposeList.isNotEmpty + // ? (String? newValue) { + // setState(() { + // selectedInsuranceType = newValue; + // if ((selectedInsuranceType ?? '').isNotEmpty) { + // errorMessages.remove("type_of_insurance"); + // } + // }); + // print( + // "Selected Insurance Type: $selectedInsuranceType", + // ); + // } + // : null, + // ), + // ), + // ), + // ▼ Dropdown Wrapper ▼ CustomTextFieldWrapper( isFocused: _isHotelNameFocused, isDesktop: isDesktop, - width: isDesktop ? MediaQuery.of(context).size.width * 0.34 : null, + width: isDesktop ? MediaQuery.of(context).size.width * 0.31 : null, child: SizedBox( height: 40, - child: DropdownButtonFormField( - focusNode: _tripTypeFocusNode, // Assign the correct focus node - value: selectedInsuranceType, + width: double.infinity, + child: DropdownSearch>( + items: purposeList.cast>(), + selectedItem: purposeList.firstWhere( + (item) => item['dropdown_key'] == selectedInsuranceType, + orElse: () => {}, + ), + itemAsString: (item) => item['dropdown_value'] ?? '', + popupProps: PopupProps.menu( + showSearchBox: false, + fit: FlexFit.loose, + menuProps: const MenuProps(backgroundColor: Colors.white), + itemBuilder: (context, item, isSelected) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 5, + ), + child: Text( + item['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ), + ); + }, + ), + dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 10,vertical: 10,), + ), + ), + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return Text( + "Select ", + style: GoogleFonts.poppins( + color: Colors.grey, + fontSize: 13, + ), + ); + } + return Text( + selectedItem['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ); + }, + onChanged: + purposeList.isNotEmpty + ? (Map? newValue) { + setState(() { + selectedInsuranceType= newValue?['dropdown_key']; + print("selected Insurance Type: ${selectedInsuranceType}"); + }); + } + : null, + ), + ), + ), + + ], + ), + + if (isDesktop) Spacer() else SizedBox(height: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Nominee", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), + ), + SizedBox(height: 5), + CustomTextFieldWrapper( + isFocused: _nomineeFocus, + isDesktop: isDesktop, + width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, + // width: isDesktop ? MediaQuery.of(context).size.width * 0.34 : null, + child: SizedBox( + height: 40, + child: TextField( + focusNode: _nomineeFocusNode, + controller: _nomineeController, style: TextStyle(fontSize: 12), decoration: InputDecoration( + labelText: "Nominee", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10), // Proper padding + contentPadding: EdgeInsets.symmetric(vertical: 16), + // contentPadding: EdgeInsets.symmetric(horizontal: 1,vertical: 1), ), - onChanged: purposeList.isNotEmpty - ? (newValue) { - setState(() { - selectedInsuranceType = newValue; - if (selectedInsuranceType!.isNotEmpty) { - errorMessages.remove("type_of_insurance"); - } - }); - - print(selectedInsuranceType); - } - : null, - - items: dropdownItems, ), ), ), ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Start Date", + "Start Date *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( - isFocused: _dateFocus, + isFocused: _startDateFocus, isDesktop: isDesktop, - width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, + width: isDesktop ? MediaQuery.of(context).size.width * 0.11 : null, child: SizedBox( height: 40, child: GestureDetector( onTap: () async { await _selectCheckOutDate(context); - if (_startdateController.text.isNotEmpty) { + if (_startDateController.text.isNotEmpty) { setState(() { - errorMessages - .remove("start_date"); // Removes the key completely + errorMessages.remove( + "start_date", + ); // Removes the key completely }); } }, child: AbsorbPointer( child: TextField( - focusNode: _dateFocusNode, - controller: _startdateController, + focusNode: _startDateFocusNode, + controller: _startDateController, style: const TextStyle(fontSize: 12), decoration: const InputDecoration( - labelText: "Select Date", + labelText: "Select ", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), - suffixIcon: Icon(Icons.calendar_today, - size: 16, color: Colors.grey), + suffixIcon: Icon( + Icons.calendar_today, + size: 16, + color: Colors.grey, + ), ), ), ), @@ -624,34 +907,27 @@ class _InsuranceScreenState extends State { ), if (errorMessages["start_date"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "End Date", + "End Date *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( - isFocused: _dateFocus, + isFocused: _endDateFocus, isDesktop: isDesktop, - width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, + width: isDesktop ? MediaQuery.of(context).size.width * 0.11 : null, child: SizedBox( height: 40, child: GestureDetector( @@ -659,7 +935,7 @@ class _InsuranceScreenState extends State { await _selectEndCheckOutDate(context); if (_endDateController.text.isNotEmpty) { - DateTime? startDate = _parseDate(_startdateController.text); + DateTime? startDate = _parseDate(_startDateController.text); DateTime? endDate = _parseDate(_endDateController.text); if (startDate != null && @@ -678,17 +954,20 @@ class _InsuranceScreenState extends State { }, child: AbsorbPointer( child: TextField( - focusNode: _dateFocusNode, + focusNode: _endDateFocusNode, controller: _endDateController, style: const TextStyle(fontSize: 12), decoration: const InputDecoration( - labelText: "Select Date", + labelText: "Select ", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), - suffixIcon: Icon(Icons.calendar_today, - size: 16, color: Colors.grey), + suffixIcon: Icon( + Icons.calendar_today, + size: 16, + color: Colors.grey, + ), ), ), ), @@ -698,19 +977,15 @@ class _InsuranceScreenState extends State { if (errorMessages["end_date"] != null) ...[ SizedBox(height: 5), // Space before error message Text( - // "Required", errorMessages["end_date"]!, - style: TextStyle(color: Colors.red, fontSize: 12), + 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 ), ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + if (isDesktop) Spacer() else SizedBox(height: 8), ]; } @@ -722,17 +997,19 @@ class _InsuranceScreenState extends State { Text( "Comments", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: _commentsFocus, // Dropdown doesn't use focus isDesktop: isDesktop, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.66, + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.31 + : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, child: TextField( @@ -752,9 +1029,7 @@ class _InsuranceScreenState extends State { ], ), if (isDesktop) Spacer(), - SizedBox( - height: 5, - ), + SizedBox(height: 5), Column( children: [ Row( @@ -775,9 +1050,7 @@ class _InsuranceScreenState extends State { }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( @@ -786,7 +1059,6 @@ class _InsuranceScreenState extends State { ), ), SizedBox(width: 10), // Space between buttons - // Save Changes Button ElevatedButton( onPressed: () { @@ -794,9 +1066,7 @@ class _InsuranceScreenState extends State { }, style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF114D8B), // Primary color for save - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( diff --git a/lib/Screens/itnerary/miscellaneous.dart b/lib/Screens/itnerary/miscellaneous.dart index 47ec8a8..409bd3d 100644 --- a/lib/Screens/itnerary/miscellaneous.dart +++ b/lib/Screens/itnerary/miscellaneous.dart @@ -1,3 +1,4 @@ +import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; @@ -14,13 +15,14 @@ class MiscellaneousScreen extends StatefulWidget { final int? selectedIndex; final String? loginUser; - MiscellaneousScreen( - {required this.onClose, - required this.apiData, - required this.onSaveMiscellaneous, - this.selectedItem, - this.selectedIndex, - required this.loginUser}); + MiscellaneousScreen({ + required this.onClose, + required this.apiData, + required this.onSaveMiscellaneous, + this.selectedItem, + this.selectedIndex, + required this.loginUser, + }); @override _MiscellaneousScreenState createState() => _MiscellaneousScreenState(); @@ -87,8 +89,9 @@ class _MiscellaneousScreenState extends State { }); }); - _commentsController = - TextEditingController(text: widget.selectedItem?["comments"] ?? ""); + _commentsController = TextEditingController( + text: widget.selectedItem?["comments"] ?? "", + ); // Set the selected value if available if (widget.selectedItem != null && @@ -114,7 +117,7 @@ class _MiscellaneousScreenState extends State { // Check validation for each field for (String field in requiredFields) { if (data[field] == null || data[field].toString().trim().isEmpty) { - errorMessages[field] = "This field is required"; + errorMessages[field] = "Required"; } } @@ -143,29 +146,32 @@ class _MiscellaneousScreenState extends State { @override Widget build(BuildContext context) { - return ResponsiveBuilder(builder: (context, sizingInfo) { - bool isMobile = sizingInfo.isMobile; - bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + return ResponsiveBuilder( + builder: (context, sizingInfo) { + bool isMobile = sizingInfo.isMobile; + bool isDesktop = + sizingInfo.deviceScreenType == DeviceScreenType.desktop; - return Container( - child: Form( - key: _formKey, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(28.0), - child: Center( - child: Column(children: _buildAccomadtionForm(isDesktop)), + return Container( + child: Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 30.0), + child: Center( + child: Column(children: _buildAccomadtionForm(isDesktop)), + ), ), - ) - ], + ], + ), ), ), - ), - ); - }); + ); + }, + ); } List _buildAccomadtionForm(bool isDesktop) { @@ -191,16 +197,17 @@ class _MiscellaneousScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Special Type", + "Special Type *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), isDesktop ? Row(children: _buildTripType(isDesktop)) - : Column(children: _buildTripType(isDesktop)) + : Column(children: _buildTripType(isDesktop)), ], ), ]; @@ -211,19 +218,24 @@ class _MiscellaneousScreenState extends State { widget.apiData?['miscellaneous_special_request'] ?? []; // selectedInsuranceType = purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null; - List> dropdownItems = purposeList - .map((item) => DropdownMenuItem( - value: item['dropdown_key'], - child: Text(item['dropdown_value']), - )) - .toList(); + List> dropdownItems = + purposeList + .map( + (item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + ), + ) + .toList(); if (dropdownItems.isEmpty) { dropdownItems.add( DropdownMenuItem( value: null, - child: Text("No options available", - style: TextStyle(color: Colors.grey)), + child: Text( + "No options available", + style: TextStyle(color: Colors.grey), + ), ), ); } @@ -233,7 +245,40 @@ class _MiscellaneousScreenState extends State { dropdownItems.isNotEmpty ? dropdownItems.first.value : "No options"; return [ - CustomTextFieldWrapper( + // CustomTextFieldWrapper( + // isFocused: _isHotelNameFocused, + // isDesktop: isDesktop, + // width: + // isDesktop + // ? MediaQuery.of(context).size.width * 0.34 + // : MediaQuery.of(context).size.width * 0.66, + // child: SizedBox( + // height: 40, + // child: DropdownButtonFormField( + // focusNode: _tripTypeFocusNode, // Assign the correct focus node + // value: selectedSpecialType, + // style: TextStyle(fontSize: 12), + // decoration: InputDecoration( + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric( + // horizontal: 10, + // ), // Proper padding + // ), + // onChanged: + // purposeList.isNotEmpty + // ? (newValue) { + // setState(() { + // selectedSpecialType = newValue; + // }); + // + // print(selectedSpecialType); + // } + // : null, + // items: dropdownItems, + // ), + // ), + // ), + CustomTextFieldItnerarySubWrapper( isFocused: _isHotelNameFocused, isDesktop: isDesktop, width: isDesktop @@ -241,34 +286,79 @@ class _MiscellaneousScreenState extends State { : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, - child: DropdownButtonFormField( - focusNode: _tripTypeFocusNode, // Assign the correct focus node - value: selectedSpecialType, - style: TextStyle(fontSize: 12), - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10), // Proper padding + width: double.infinity, + child: DropdownSearch>( + // focusNode: _taxiReqFocusNode, + items: purposeList.cast>(), + selectedItem: purposeList.firstWhere( + (item) => item['dropdown_key'] == selectedSpecialType, + orElse: () => {}, ), - onChanged: purposeList.isNotEmpty + itemAsString: (item) => item['dropdown_value'] ?? '', + popupProps: PopupProps.menu( + showSearchBox: false, + fit: FlexFit.loose, + menuProps: const MenuProps(backgroundColor: Colors.white), + itemBuilder: (context, item, isSelected) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 5, + ), + child: Text( + item['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ), + ); + }, + ), + dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 10, + ), + ), + ), + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return Text( + "Select ", + style: GoogleFonts.poppins( + color: Colors.grey, + fontSize: 13, + ), + ); + } + return Text( + selectedItem['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ); + }, + onChanged: + purposeList.isNotEmpty ? (newValue) { - setState(() { - selectedSpecialType = newValue; - }); - - print(selectedSpecialType); - } + setState(() { + selectedSpecialType = newValue as String?; + }); + print( + "Updating form data: -> ${newValue ?? ""}", + ); + } : null, - items: dropdownItems, ), ), ), if (errorMessages["special_request"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Select Type", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ]; } @@ -279,19 +369,21 @@ class _MiscellaneousScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Comments", + "Comments *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: _commentsFocus, // Dropdown doesn't use focus isDesktop: isDesktop, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.66, + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, child: TextField( @@ -310,17 +402,12 @@ class _MiscellaneousScreenState extends State { ), if (errorMessages["comments"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), if (isDesktop) Spacer(), - SizedBox( - height: 5, - ), + SizedBox(height: 5), Row( mainAxisAlignment: MainAxisAlignment.end, children: _handleAction(isDesktop), @@ -338,9 +425,7 @@ class _MiscellaneousScreenState extends State { }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( @@ -349,7 +434,6 @@ class _MiscellaneousScreenState extends State { ), ), SizedBox(width: 10), // Space between buttons - // Save Changes Button ElevatedButton( onPressed: () { @@ -357,9 +441,7 @@ class _MiscellaneousScreenState extends State { }, style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF114D8B), // Primary color for save - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( diff --git a/lib/Screens/itnerary/taxi.dart b/lib/Screens/itnerary/taxi.dart index 969ba4e..2b55e21 100644 --- a/lib/Screens/itnerary/taxi.dart +++ b/lib/Screens/itnerary/taxi.dart @@ -1,3 +1,4 @@ +import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -219,7 +220,7 @@ class _TaxiScreenState extends State { child: Column( children: [ Padding( - padding: const EdgeInsets.all(28.0), + padding: const EdgeInsets.only(top: 30.0), child: Center( child: Column(children: _buildAccomadtionForm(isDesktop)), ), @@ -362,34 +363,109 @@ class _TaxiScreenState extends State { ), ), SizedBox(height: 5), + // CustomTextFieldItnerarySubWrapper( + // isFocused: _toFocus, + // isDesktop: isDesktop, + // child: SizedBox( + // height: 40, + // child: DropdownButtonFormField( + // focusNode: _toFocusNode, // Assign the correct focus node + // value: selectedCarType, + // style: TextStyle(fontSize: 12), + // decoration: InputDecoration( + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric( + // horizontal: 10, + // ), // Proper padding + // ), + // onChanged: + // purposeList.isNotEmpty + // ? (newValue) { + // setState(() { + // selectedCarType = newValue; + // }); + // print( + // "Updating form data: Flight -> trip_type -> ${newValue ?? ""}", + // ); + // } + // : null, + // + // items: dropdownItems, + // ), + // ), + // ), CustomTextFieldItnerarySubWrapper( isFocused: _toFocus, isDesktop: isDesktop, child: SizedBox( height: 40, - child: DropdownButtonFormField( - focusNode: _toFocusNode, // Assign the correct focus node - value: selectedCarType, - style: TextStyle(fontSize: 12), - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 10, - ), // Proper padding + width: double.infinity, + child: DropdownSearch>( + // focusNode: _taxiReqFocusNode, + items: purposeList.cast>(), + selectedItem: purposeList.firstWhere( + (item) => item['dropdown_key'] == selectedCarType, + orElse: () => {}, ), + itemAsString: (item) => item['dropdown_value'] ?? '', + popupProps: PopupProps.menu( + showSearchBox: false, + fit: FlexFit.loose, + menuProps: const MenuProps(backgroundColor: Colors.white), + itemBuilder: (context, item, isSelected) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 5, + ), + child: Text( + item['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ), + ); + }, + ), + dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 10, + ), + ), + ), + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return Text( + "Select ", + style: GoogleFonts.poppins( + color: Colors.grey, + fontSize: 13, + ), + ); + } + return Text( + selectedItem['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ); + }, onChanged: - purposeList.isNotEmpty - ? (newValue) { - setState(() { - selectedCarType = newValue; - }); - print( - "Updating form data: Flight -> trip_type -> ${newValue ?? ""}", - ); - } - : null, - - items: dropdownItems, + purposeList.isNotEmpty + ? (newValue) { + setState(() { + selectedCarType = newValue as String?; + }); + print( + "Updating form data: -> ${newValue ?? ""}", + ); + } + : null, ), ), ), @@ -429,38 +505,117 @@ class _TaxiScreenState extends State { dropdownItems.isNotEmpty ? dropdownItems.first.value : null; return [ - CustomTextFieldWrapper( + // CustomTextFieldWrapper( + // isFocused: _taxiReqFocused, + // isDesktop: isDesktop, + // width: + // isDesktop + // ? MediaQuery.of(context).size.width * 0.34 + // : MediaQuery.of(context).size.width * 0.66, + // child: SizedBox( + // height: 40, + // child: DropdownButtonFormField( + // focusNode: _taxiReqFocusNode, // Assign the correct focus node + // value: selectedReqTaxi, + // style: TextStyle(fontSize: 12), + // decoration: InputDecoration( + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric( + // horizontal: 10, + // ), // Proper padding + // ), + // onChanged: + // purposeList.isNotEmpty + // ? (newValue) { + // setState(() { + // selectedReqTaxi = newValue; + // }); + // print( + // "Updating form data: Flight -> trip_type -> ${newValue ?? ""}", + // ); + // } + // : null, + // + // items: dropdownItems, + // ), + // ), + // ), + CustomTextFieldItnerarySubWrapper( isFocused: _taxiReqFocused, isDesktop: isDesktop, width: - isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.66, + isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, - child: DropdownButtonFormField( - focusNode: _taxiReqFocusNode, // Assign the correct focus node - value: selectedReqTaxi, - style: TextStyle(fontSize: 12), - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 10, - ), // Proper padding + width: double.infinity, + child: DropdownSearch>( + // focusNode: _taxiReqFocusNode, + items: purposeList.cast>(), + selectedItem: purposeList.firstWhere( + (item) => item['dropdown_key'] == selectedReqTaxi, + orElse: () => {}, ), + itemAsString: (item) => item['dropdown_value'] ?? '', + popupProps: PopupProps.menu( + showSearchBox: false, + fit: FlexFit.loose, + menuProps: const MenuProps(backgroundColor: Colors.white), + itemBuilder: (context, item, isSelected) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 5, + ), + child: Text( + item['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ), + ); + }, + ), + dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 10, + ), + ), + ), + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return Text( + "Select ", + style: GoogleFonts.poppins( + color: Colors.grey, + fontSize: 13, + ), + ); + } + return Text( + selectedItem['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ); + }, onChanged: - purposeList.isNotEmpty - ? (newValue) { - setState(() { - selectedReqTaxi = newValue; - }); - print( - "Updating form data: Flight -> trip_type -> ${newValue ?? ""}", - ); - } - : null, - - items: dropdownItems, + purposeList.isNotEmpty + ? (newValue) { + setState(() { + selectedReqTaxi = newValue as String?; + }); + print( + "Updating form data: -> ${newValue ?? ""}", + ); + } + : null, ), ), ), @@ -484,6 +639,7 @@ class _TaxiScreenState extends State { : today, firstDate: today, lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedCheckOutDate) { @@ -501,30 +657,87 @@ class _TaxiScreenState extends State { ); if (pickedTime != null && pickedTime != _selectedCheckOutTime) { - setState(() { - _selectedCheckOutTime = pickedTime; - // Formatting time to HH:mm (24-hour format) - final now = DateTime.now(); - final formattedTime = DateFormat('HH:mm').format( - DateTime( - now.year, - now.month, - now.day, - pickedTime.hour, - pickedTime.minute, - ), - ); - _timeController.text = formattedTime; - }); + // Parse the selected date + final dateText = _dateController.text ?? ""; + final selectedDate = DateFormat( + 'dd-MM-yyyy', + ).parse(dateText); // or 'yyyy-MM-dd' depending on your format + final now = DateTime.now(); + + final selectedDateTime = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + pickedTime.hour, + pickedTime.minute, + ); + + final isToday = + selectedDate.year == now.year && + selectedDate.month == now.month && + selectedDate.day == now.day; + + bool isPastTime = selectedDateTime.isBefore(now); + + if (isToday && isPastTime) { + setState(() { + errorMessages["time"] = "You can't select a past time."; + _selectedCheckOutTime = null; // ✅ Reset time variable + _timeController.text = ""; // ✅ Clear text field + }); + return; + } else { + setState(() { + _selectedCheckOutTime = pickedTime; + // Formatting time to HH:mm (24-hour format) + final now = DateTime.now(); + final formattedTime = DateFormat('HH:mm').format( + DateTime( + now.year, + now.month, + now.day, + pickedTime.hour, + pickedTime.minute, + ), + ); + errorMessages["time"] = ""; + _timeController.text = formattedTime; + }); + } } } + // Future _selectCheckOutTime(BuildContext context) async { + // TimeOfDay? pickedTime = await showTimePicker( + // context: context, + // initialTime: _selectedCheckOutTime ?? TimeOfDay.now(), + // ); + // + // if (pickedTime != null && pickedTime != _selectedCheckOutTime) { + // setState(() { + // _selectedCheckOutTime = pickedTime; + // // Formatting time to HH:mm (24-hour format) + // final now = DateTime.now(); + // final formattedTime = DateFormat('HH:mm').format( + // DateTime( + // now.year, + // now.month, + // now.day, + // pickedTime.hour, + // pickedTime.minute, + // ), + // ); + // _timeController.text = formattedTime; + // }); + // } + // } + return [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "City", + "City *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, @@ -562,7 +775,7 @@ class _TaxiScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Pickup Location", + "Pickup Location *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, @@ -600,7 +813,7 @@ class _TaxiScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Date", + "Date *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, @@ -648,7 +861,7 @@ class _TaxiScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Time", + "Time *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, @@ -662,7 +875,10 @@ class _TaxiScreenState extends State { child: SizedBox( height: 40, child: GestureDetector( - onTap: () => _selectCheckOutTime(context), + onTap: () { + _timeController.text = ""; + _selectCheckOutTime(context); + }, child: AbsorbPointer( child: TextField( focusNode: _timeFocusNode, @@ -687,7 +903,11 @@ class _TaxiScreenState extends State { ), if (errorMessages["time"] != null) ...[ SizedBox(height: 5), // Space before error message - Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), + // Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), + Text( + errorMessages["time"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), ], ], ), diff --git a/lib/Screens/itnerary/train.dart b/lib/Screens/itnerary/train.dart index 3f2e5d8..a48e864 100644 --- a/lib/Screens/itnerary/train.dart +++ b/lib/Screens/itnerary/train.dart @@ -243,6 +243,12 @@ class _TrainScreenState extends State { } } + if (selectedFrom == selectedTo) { + errorMessages["to_station"] = "Change Destination"; + } + + print("Error: to_place -> Change Destination (selectedFrom == selectedTo)"); + return errorMessages.isEmpty; // Valid if there are no errors } @@ -324,7 +330,7 @@ class _TrainScreenState extends State { // height: 6, // ), Padding( - padding: const EdgeInsets.all(28.0), + padding: const EdgeInsets.only(top: 30.0), child: Center( child: Column(children: _buildAccomadtionForm(isDesktop)), ), @@ -369,7 +375,7 @@ class _TrainScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Train Number", + "Train Number *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, @@ -464,6 +470,7 @@ class _TrainScreenState extends State { : today, firstDate: today, lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedCheckOutDate) { @@ -480,23 +487,66 @@ class _TrainScreenState extends State { initialTime: _selectedCheckOutTime ?? TimeOfDay.now(), ); - if (pickedTime != null && pickedTime != _selectedCheckOutTime) { + if (pickedTime != null) { + final now = DateTime.now(); + + // Parse the selected date + final dateText = _dateController.text ?? ""; + final selectedDate = DateFormat( + 'dd-MM-yyyy', + ).parse(dateText); // or 'yyyy-MM-dd' depending on your format + + final selectedDateTime = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + pickedTime.hour, + pickedTime.minute, + ); + + // ✅ Only validate past time if date is today + final isToday = + selectedDate.year == now.year && + selectedDate.month == now.month && + selectedDate.day == now.day; + + bool isPastTime = selectedDateTime.isBefore(now); + + if (isToday && isPastTime) { + setState(() { + errorMessages["time"] = "You can't select a past time."; + }); + return; + } + + // ✅ Valid time selection setState(() { _selectedCheckOutTime = pickedTime; - // Formatting time to HH:mm (24-hour format) - final now = DateTime.now(); - final formattedTime = DateFormat('HH:mm').format( - DateTime( - now.year, - now.month, - now.day, - pickedTime.hour, - pickedTime.minute, - ), - ); + + final formattedTime = DateFormat('HH:mm').format(selectedDateTime); _timeController.text = formattedTime; + // errorMessages["time_$index"] = ""; // clear previous error + errorMessages.remove("time"); }); } + + // if (pickedTime != null && pickedTime != _selectedCheckOutTime) { + // setState(() { + // _selectedCheckOutTime = pickedTime; + // // Formatting time to HH:mm (24-hour format) + // final now = DateTime.now(); + // final formattedTime = DateFormat('HH:mm').format( + // DateTime( + // now.year, + // now.month, + // now.day, + // pickedTime.hour, + // pickedTime.minute, + // ), + // ); + // _timeController.text = formattedTime; + // }); + // } } //---------------------------------------------- @@ -542,34 +592,104 @@ class _TrainScreenState extends State { ), ), SizedBox(height: 5), - CustomTextFieldItnerarySubWrapper( + // CustomTextFieldItnerarySubWrapper( + // isFocused: _isHotelNameFocused, + // isDesktop: isDesktop, + // // width: isDesktop + // // ? MediaQuery.of(context).size.width * 0.34 + // // : MediaQuery.of(context).size.width * 0.66, + // child: SizedBox( + // height: 40, + // child: DropdownButtonFormField( + // focusNode: _hotelNameFocusNode, // Assign the correct focus node + // // controller: _hotelNameController, + // value: selectedClass, + // style: TextStyle(fontSize: 12), + // decoration: InputDecoration( + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric( + // horizontal: 10, + // ), // Proper padding + // ), + // onChanged: + // purposeList.isNotEmpty + // ? (newValue) { + // setState(() { + // selectedClass = newValue; + // }); + // } + // : null, + // items: dropdownItems, + // ), + // ), + // ), + CustomTextFieldWrapper( isFocused: _isHotelNameFocused, isDesktop: isDesktop, - // width: isDesktop - // ? MediaQuery.of(context).size.width * 0.34 - // : MediaQuery.of(context).size.width * 0.66, + width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, child: SizedBox( height: 40, - child: DropdownButtonFormField( - focusNode: _hotelNameFocusNode, // Assign the correct focus node - // controller: _hotelNameController, - value: selectedClass, - style: TextStyle(fontSize: 12), - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 10, - ), // Proper padding + width: double.infinity, + child: DropdownSearch>( + items: purposeList.cast>(), + selectedItem: purposeList.firstWhere( + (item) => item['dropdown_key'] == selectedClass, + orElse: () => {}, ), + itemAsString: (item) => item['dropdown_value'] ?? '', + popupProps: PopupProps.menu( + showSearchBox: false, + fit: FlexFit.loose, + menuProps: const MenuProps(backgroundColor: Colors.white), + itemBuilder: (context, item, isSelected) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 5, + ), + child: Text( + item['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ), + ); + }, + ), + dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 10,vertical: 10,), + ), + ), + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return Text( + "Select ", + style: GoogleFonts.poppins( + color: Colors.grey, + fontSize: 13, + ), + ); + } + return Text( + selectedItem['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ); + }, onChanged: - purposeList.isNotEmpty - ? (newValue) { - setState(() { - selectedClass = newValue; - }); - } - : null, - items: dropdownItems, + purposeList.isNotEmpty + ? (Map? newValue) { + setState(() { + selectedClass= newValue?['dropdown_key']; + print("Selected Purpose: ${selectedClass}"); + }); + } + : null, ), ), ), @@ -584,7 +704,7 @@ class _TrainScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "From", + "From *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, @@ -592,96 +712,158 @@ class _TrainScreenState extends State { ), ), SizedBox(height: 5), + // CustomTextFieldItnerarySubWrapper( + // isFocused: _fromFocus, + // isDesktop: isDesktop, + // child: SizedBox( + // height: 40, + // child: + // isCountryLoading + // ? Center(child: CircularProgressIndicator()) + // : DropdownSearch( + // // selectedItem: selectedFrom != null + // // ? countryMap[selectedFrom] + // // : null, + // selectedItem: + // selectedFrom != null + // ? countryMap[selectedFrom] // get the display value from code + // : null, + // popupProps: PopupProps.menu( + // menuProps: MenuProps(backgroundColor: Colors.white), + // constraints: BoxConstraints(maxHeight: 230), + // showSearchBox: true, + // searchFieldProps: TextFieldProps( + // decoration: InputDecoration( + // hintText: "Search ...", + // contentPadding: EdgeInsets.symmetric( + // horizontal: 10, + // ), + // ), + // ), + // ), + // + // items: countryMap.values.toList(), + // + // dropdownDecoratorProps: DropDownDecoratorProps( + // dropdownSearchDecoration: InputDecoration( + // contentPadding: EdgeInsets.symmetric( + // horizontal: 10, + // vertical: 5, + // ), + // border: InputBorder.none, + // ), + // ), + // dropdownBuilder: + // (context, selectedItem) => Align( + // alignment: Alignment.centerLeft, + // child: Text( + // selectedItem ?? "Select", + // style: TextStyle(fontSize: 12), + // ), + // ), + // + // // onChanged: (String? newValue) { + // // setState(() { + // // // selectedFrom[index] = countryMap.entries + // // // .firstWhere((entry) => entry.value == newValue) + // // // .key; + // // + // // selectedFrom = countryMap.entries + // // .firstWhere((entry) => entry.value == newValue) + // // .key; + // // + // // print(selectedFrom); + // // }); + // // }, + // onChanged: (String? newValue) { + // setState(() { + // selectedFrom = + // countryMap.entries + // .firstWhere( + // (entry) => entry.value == newValue, + // ) + // .key; + // }); + // }, + // ), + // ), + // // child: SizedBox( + // // height: 40, + // // child: TextField( + // // focusNode: _fromFocusNode, + // // controller: _fromController, + // // style: const TextStyle(fontSize: 12), + // // decoration: const InputDecoration( + // // labelText: "From", + // // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + // // floatingLabelBehavior: FloatingLabelBehavior.never, + // // border: InputBorder.none, + // // contentPadding: EdgeInsets.symmetric(vertical: 16), + // // ), + // // ), + // // ), + // ), CustomTextFieldItnerarySubWrapper( - isFocused: _fromFocus, + isFocused: _fromFocus ?? false, isDesktop: isDesktop, child: SizedBox( height: 40, - child: - isCountryLoading - ? Center(child: CircularProgressIndicator()) - : DropdownSearch( - // selectedItem: selectedFrom != null - // ? countryMap[selectedFrom] - // : null, - selectedItem: - selectedFrom != null - ? countryMap[selectedFrom] // get the display value from code - : null, - popupProps: PopupProps.menu( - menuProps: MenuProps(backgroundColor: Colors.white), - constraints: BoxConstraints(maxHeight: 230), - showSearchBox: true, - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - hintText: "Search ...", - contentPadding: EdgeInsets.symmetric( - horizontal: 10, - ), - ), - ), - ), - - items: countryMap.values.toList(), - - dropdownDecoratorProps: DropDownDecoratorProps( - dropdownSearchDecoration: InputDecoration( - contentPadding: EdgeInsets.symmetric( - horizontal: 10, - vertical: 5, - ), - border: InputBorder.none, - ), - ), - dropdownBuilder: - (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select", - style: TextStyle(fontSize: 12), - ), - ), - - // onChanged: (String? newValue) { - // setState(() { - // // selectedFrom[index] = countryMap.entries - // // .firstWhere((entry) => entry.value == newValue) - // // .key; - // - // selectedFrom = countryMap.entries - // .firstWhere((entry) => entry.value == newValue) - // .key; - // - // print(selectedFrom); - // }); - // }, - onChanged: (String? newValue) { - setState(() { - selectedFrom = - countryMap.entries - .firstWhere( - (entry) => entry.value == newValue, - ) - .key; - }); - }, - ), + child: DropdownSearch( + selectedItem: countryMap[selectedFrom], + popupProps: PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: const MenuProps(backgroundColor: Colors.white), + itemBuilder: + (context, item, isSelected) => Container( + color: Colors.white, + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + child: Text( + item, + style: GoogleFonts.poppins(fontSize: 11.5), + ), + ), + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search ...", + contentPadding: EdgeInsets.symmetric(horizontal: 10), + ), + ), + ), + items: countryMap.values.toList(), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), + ), + dropdownBuilder: + (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select ", + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ), + ), + onChanged: (String? newValue) { + setState(() { + selectedFrom = + countryMap.entries + .firstWhere( + (entry) => entry.value == newValue, + ) + .key; + }); + }, + ), ), - // child: SizedBox( - // height: 40, - // child: TextField( - // focusNode: _fromFocusNode, - // controller: _fromController, - // style: const TextStyle(fontSize: 12), - // decoration: const InputDecoration( - // labelText: "From", - // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), - // floatingLabelBehavior: FloatingLabelBehavior.never, - // border: InputBorder.none, - // contentPadding: EdgeInsets.symmetric(vertical: 16), - // ), - // ), - // ), ), if (errorMessages["from_station"] != null) ...[ SizedBox(height: 5), // Space before error message @@ -694,7 +876,7 @@ class _TrainScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "To", + "To *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, @@ -702,63 +884,128 @@ class _TrainScreenState extends State { ), ), SizedBox(height: 5), + // CustomTextFieldItnerarySubWrapper( + // isFocused: _toFocus, + // isDesktop: isDesktop, + // child: SizedBox( + // height: 40, + // child: + // isCountryLoading + // ? Center(child: CircularProgressIndicator()) + // : DropdownSearch( + // selectedItem: + // selectedTo != null + // ? countryMap[selectedTo] // get the display value from code + // : null, + // popupProps: PopupProps.menu( + // menuProps: MenuProps(backgroundColor: Colors.white), + // constraints: BoxConstraints(maxHeight: 230), + // showSearchBox: true, + // searchFieldProps: TextFieldProps( + // decoration: InputDecoration( + // hintText: "Search ...", + // contentPadding: EdgeInsets.symmetric( + // horizontal: 10, + // ), + // ), + // ), + // ), + // items: countryMap.values.toList(), + // dropdownDecoratorProps: DropDownDecoratorProps( + // dropdownSearchDecoration: InputDecoration( + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric(horizontal: 1), + // ), + // ), + // dropdownBuilder: + // (context, selectedItem) => Align( + // alignment: Alignment.centerLeft, + // child: Text( + // selectedItem ?? "Select", + // style: TextStyle(fontSize: 12), + // ), + // ), + // onChanged: (String? newValue) { + // setState(() { + // selectedTo = + // countryMap.entries + // .firstWhere( + // (entry) => entry.value == newValue, + // ) + // .key; + // }); + // }, + // ), + // ), + // ), CustomTextFieldItnerarySubWrapper( - isFocused: _toFocus, + isFocused: _toFocus ?? false, isDesktop: isDesktop, child: SizedBox( height: 40, - child: - isCountryLoading - ? Center(child: CircularProgressIndicator()) - : DropdownSearch( - selectedItem: - selectedTo != null - ? countryMap[selectedTo] // get the display value from code - : null, - popupProps: PopupProps.menu( - menuProps: MenuProps(backgroundColor: Colors.white), - constraints: BoxConstraints(maxHeight: 230), - showSearchBox: true, - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - hintText: "Search ...", - contentPadding: EdgeInsets.symmetric( - horizontal: 10, - ), - ), - ), - ), - items: countryMap.values.toList(), - dropdownDecoratorProps: DropDownDecoratorProps( - dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 1), - ), - ), - dropdownBuilder: - (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select", - style: TextStyle(fontSize: 12), - ), - ), - onChanged: (String? newValue) { - setState(() { - selectedTo = - countryMap.entries - .firstWhere( - (entry) => entry.value == newValue, - ) - .key; - }); - }, - ), + child: DropdownSearch( + selectedItem: countryMap[selectedTo], + popupProps: PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: const MenuProps(backgroundColor: Colors.white), + itemBuilder: + (context, item, isSelected) => Container( + color: Colors.white, + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + child: Text( + item, + style: GoogleFonts.poppins(fontSize: 11.5), + ), + ), + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search ...", + contentPadding: EdgeInsets.symmetric(horizontal: 10), + ), + ), + ), + items: countryMap.values.toList(), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), + ), + dropdownBuilder: + (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select ", + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ), + ), + onChanged: (String? newValue) { + setState(() { + selectedTo = + countryMap.entries + .firstWhere( + (entry) => entry.value == newValue, + ) + .key; + }); + }, + ), ), ), if (errorMessages["to_station"] != null) ...[ SizedBox(height: 5), // Space before error message - Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), + Text( + errorMessages["to_station"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), ], ], ), @@ -767,7 +1014,7 @@ class _TrainScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Date", + "Date *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, @@ -816,7 +1063,7 @@ class _TrainScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Time", + "Time *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, @@ -831,7 +1078,10 @@ class _TrainScreenState extends State { child: SizedBox( height: 40, child: GestureDetector( - onTap: () => _selectCheckOutTime(context), + onTap: () { + _timeController.text = ""; + _selectCheckOutTime(context); + }, child: AbsorbPointer( child: TextField( focusNode: _timeFocusNode, @@ -856,7 +1106,10 @@ class _TrainScreenState extends State { ), if (errorMessages["time"] != null) ...[ SizedBox(height: 5), // Space before error message - Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), + Text( + errorMessages["time"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), ], ], ), diff --git a/lib/Screens/itnerary/visa.dart b/lib/Screens/itnerary/visa.dart index b8de533..5b025e3 100644 --- a/lib/Screens/itnerary/visa.dart +++ b/lib/Screens/itnerary/visa.dart @@ -18,14 +18,15 @@ class VisaScreen extends StatefulWidget { final Map? selectedItem; final String? loginUser; - VisaScreen( - {required this.onClose, - required this.onSaveVisa, - this.apiData, - required this.selectedItem, - required this.apiCountryData, - required this.loginUser, - required this.flightData}); + VisaScreen({ + required this.onClose, + required this.onSaveVisa, + this.apiData, + required this.selectedItem, + required this.apiCountryData, + required this.loginUser, + required this.flightData, + }); @override _VisaScreenState createState() => _VisaScreenState(); @@ -99,14 +100,18 @@ class _VisaScreenState extends State { _addFocusListener(_tripTypeFocusNode, (focus) => _tripTypeFocused = focus); _addFocusListener( - _hotelNameFocusNode, (focus) => _isHotelNameFocused = focus); + _hotelNameFocusNode, + (focus) => _isHotelNameFocused = focus, + ); _addFocusListener(_dateFocusNode, (focus) => _dateFocus = focus); _addFocusListener(_commentsFocusNode, (focus) => _commentsFocus = focus); - _visaCommentsController = - TextEditingController(text: widget.selectedItem?["comments"] ?? ""); - _dateController = - TextEditingController(text: widget.selectedItem?["start_date"] ?? ""); + _visaCommentsController = TextEditingController( + text: widget.selectedItem?["comments"] ?? "", + ); + _dateController = TextEditingController( + text: widget.selectedItem?["start_date"] ?? "", + ); // Set the selected value if available if (widget.selectedItem != null && @@ -130,8 +135,9 @@ class _VisaScreenState extends State { // ✅ Only set controller after value is updated // final parsedDate = // DateTime.tryParse(flightFirstTripDateNotifier.value ?? ''); - final parsedDate = DateFormat("dd-MM-yyyy") - .parse(flightFirstTripDateNotifier.value ?? ''); + final parsedDate = DateFormat( + "dd-MM-yyyy", + ).parse(flightFirstTripDateNotifier.value ?? ''); if (parsedDate != null) { _dateController.text = DateFormat('dd-MM-yyyy').format(parsedDate); } @@ -181,17 +187,16 @@ class _VisaScreenState extends State { } Map getFlightTripDateRange( - List> flightData) { - final allTrips = flightData - .expand((flight) => flight['trips'] ?? []) - .whereType>() - .toList(); + List> flightData, + ) { + final allTrips = + flightData + .expand((flight) => flight['trips'] ?? []) + .whereType>() + .toList(); if (allTrips.isEmpty) { - return { - 'firstTripDate': null, - 'lastTripDate': null, - }; + return {'firstTripDate': null, 'lastTripDate': null}; } allTrips.sort((a, b) { @@ -229,7 +234,7 @@ class _VisaScreenState extends State { List requiredFields = [ "type_of_visa", "country_code", - "start_date" + "start_date", ]; // Check validation for each field @@ -260,29 +265,32 @@ class _VisaScreenState extends State { @override Widget build(BuildContext context) { - return ResponsiveBuilder(builder: (context, sizingInfo) { - bool isMobile = sizingInfo.isMobile; - bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + return ResponsiveBuilder( + builder: (context, sizingInfo) { + bool isMobile = sizingInfo.isMobile; + bool isDesktop = + sizingInfo.deviceScreenType == DeviceScreenType.desktop; - return Container( - child: Form( - key: _formKey, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(28.0), - child: Center( - child: Column(children: _buildAccomadtionForm(isDesktop)), + return Container( + child: Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 30.0), + child: Center( + child: Column(children: _buildAccomadtionForm(isDesktop)), + ), ), - ) - ], + ], + ), ), ), - ), - ); - }); + ); + }, + ); } List _buildAccomadtionForm(bool isDesktop) { @@ -308,14 +316,7 @@ class _VisaScreenState extends State { } List _buildFirstRow(isDesktop) { - return [ - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), - ]; + return [if (isDesktop) Spacer() else SizedBox(height: 8)]; } List _buildTripType(bool isDesktop) { @@ -354,7 +355,7 @@ class _VisaScreenState extends State { // Map country codes to country names countryMap = { for (var item in countryList) - item['country_code'] as String: item['country_name'] as String + item['country_code'] as String: item['country_name'] as String, }; // Extract only country codes for processing @@ -393,6 +394,7 @@ class _VisaScreenState extends State { initialDate: initialDate, firstDate: initialDate, lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); // DateTime? pickedDate = await showDatePicker( @@ -417,19 +419,24 @@ class _VisaScreenState extends State { List purposeList = widget.apiData?['visa_type_of_visa'] ?? []; - List> dropdownItems = purposeList - .map((item) => DropdownMenuItem( - value: item['dropdown_key'], - child: Text(item['dropdown_value']), - )) - .toList(); + List> dropdownItems = + purposeList + .map( + (item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + ), + ) + .toList(); if (dropdownItems.isEmpty) { dropdownItems.add( DropdownMenuItem( value: null, - child: Text("No options available", - style: TextStyle(color: Colors.grey)), + child: Text( + "No options available", + style: TextStyle(color: Colors.grey), + ), ), ); } @@ -442,114 +449,115 @@ class _VisaScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Type of Visa", + "Country *", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), - CustomTextFieldWrapper( - isFocused: _tripTypeFocused, + // CustomTextFieldWrapper( + // isFocused: _isHotelNameFocused, + // isDesktop: isDesktop, + // // width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, + // width: + // isDesktop + // ? MediaQuery.of(context).size.width * 0.34 + // : MediaQuery.of(context).size.width * 0.66, + // child: SizedBox( + // height: 40, + // child: DropdownSearch( + // selectedItem: countryMap[selectedCountry], + // popupProps: PopupProps.menu( + // showSearchBox: true, // Enables search functionality + // searchFieldProps: TextFieldProps( + // decoration: InputDecoration( + // hintText: "Search ...", + // contentPadding: EdgeInsets.symmetric(horizontal: 10), + // ), + // ), + // ), + // items: countryMap.values.toList(), + // dropdownDecoratorProps: DropDownDecoratorProps( + // dropdownSearchDecoration: InputDecoration( + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric(horizontal: 1), + // ), + // ), + // dropdownBuilder: + // (context, selectedItem) => Align( + // // Center-align selected item + // alignment: Alignment.centerLeft, + // child: Text( + // selectedItem ?? "Select ", + // style: TextStyle(fontSize: 12), + // ), + // ), + // onChanged: (String? newValue) { + // setState(() { + // // Find the country_code based on selected country_name + // selectedCountry = + // countryMap.entries + // .firstWhere((entry) => entry.value == newValue) + // .key; + // + // if (selectedCountry!.isNotEmpty) { + // errorMessages.remove("country_code"); + // } + // }); + // }, + // ), + // ), + // ), + CustomTextFieldItnerarySubWrapper( + isFocused: _isHotelNameFocused, isDesktop: isDesktop, width: isDesktop ? MediaQuery.of(context).size.width * 0.34 : MediaQuery.of(context).size.width * 0.66, - child: SizedBox( - height: 40, - child: DropdownButtonFormField( - focusNode: _tripTypeFocusNode, // Assign the correct focus node - value: selectedPurpose, - style: TextStyle(fontSize: 12), - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10), // Proper padding - ), - onChanged: purposeList.isNotEmpty - ? (newValue) { - setState(() { - selectedPurpose = newValue; - }); - print( - "Updating form data: Flight -> trip_type -> ${newValue ?? ""}"); - } - : null, - - items: dropdownItems, - ), - ), - ), - if (errorMessages["type_of_visa"] != null) ...[ - SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), - ], - ], - ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Country", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), - ), - SizedBox(height: 5), - CustomTextFieldWrapper( - isFocused: _isHotelNameFocused, - isDesktop: isDesktop, - width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, child: SizedBox( height: 40, child: DropdownSearch( selectedItem: countryMap[selectedCountry], popupProps: PopupProps.menu( - showSearchBox: true, // Enables search functionality + showSearchBox: true, searchFieldProps: TextFieldProps( decoration: InputDecoration( - hintText: "Search Country...", + hintText: "Search ...", contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), + itemBuilder: (context, item, isSelected) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4), + child: Text( + item, + style: TextStyle(fontSize: 12,color: Colors.black,), + ), + ); + }, ), items: countryMap.values.toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 1, - ), + contentPadding: EdgeInsets.symmetric(horizontal: 1), ), ), dropdownBuilder: (context, selectedItem) => Align( - // Center-align selected item alignment: Alignment.centerLeft, child: Text( - selectedItem ?? "Select Country", + selectedItem ?? "Select", style: TextStyle(fontSize: 12), ), ), onChanged: (String? newValue) { setState(() { - // Find the country_code based on selected country_name selectedCountry = countryMap.entries .firstWhere((entry) => entry.value == newValue) .key; - if (selectedCountry!.isNotEmpty) { - errorMessages.remove("country_code"); - } }); }, ), @@ -557,28 +565,154 @@ class _VisaScreenState extends State { ), if (errorMessages["country_code"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), + + if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Start Date", + "Type of Visa", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), + ), + SizedBox(height: 5), + // CustomTextFieldWrapper( + // isFocused: _tripTypeFocused, + // isDesktop: isDesktop, + // width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, + // // width: isDesktop + // // ? MediaQuery.of(context).size.width * 0.34 + // // : MediaQuery.of(context).size.width * 0.66, + // child: SizedBox( + // height: 40, + // child: DropdownButtonFormField( + // focusNode: _tripTypeFocusNode, // Assign the correct focus node + // value: selectedPurpose, + // style: TextStyle(fontSize: 12), + // decoration: InputDecoration( + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric( + // horizontal: 10, + // ), // Proper padding + // ), + // onChanged: + // purposeList.isNotEmpty + // ? (newValue) { + // setState(() { + // selectedPurpose = newValue; + // }); + // print( + // "Updating form data: Flight -> trip_type -> ${newValue ?? ""}", + // ); + // } + // : null, + // + // items: dropdownItems, + // ), + // ), + // ), + CustomTextFieldWrapper( + isFocused: _tripTypeFocused, + isDesktop: isDesktop, + width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, + child: SizedBox( + height: 40, + width: double.infinity, + child: DropdownSearch>( + items: purposeList.cast>(), + selectedItem: purposeList.firstWhere( + (item) => item['dropdown_key'] == selectedPurpose, + orElse: () => {}, + ), + itemAsString: (item) => item['dropdown_value'] ?? '', + popupProps: PopupProps.menu( + showSearchBox: false, + fit: FlexFit.loose, + menuProps: const MenuProps(backgroundColor: Colors.white), + itemBuilder: (context, item, isSelected) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 5, + ), + child: Text( + item['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ), + ); + }, + ), + dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 10,vertical: 10,), + ), + ), + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return Text( + "Select ", + style: GoogleFonts.poppins( + color: Colors.grey, + fontSize: 13, + ), + ); + } + return Text( + selectedItem['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ); + }, + // focusNode: _tripTypeFocusNode, // Assign the correct focus node + // value: selectedPurpose, + // style: TextStyle(fontSize: 12), + // decoration: InputDecoration( + // border: InputBorder.none, + // contentPadding: + // EdgeInsets.symmetric(horizontal: 10), // Proper padding + // ), + onChanged: + purposeList.isNotEmpty + ? (Map? newValue) { + setState(() { + selectedPurpose= newValue?['dropdown_key']; + print("Selected Purpose: ${selectedPurpose}"); + }); + } + : null, + ), + ), + ), + if (errorMessages["type_of_visa"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), + ], + ], + ), + if (isDesktop) Spacer() else SizedBox(height: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Start Date *", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( @@ -607,8 +741,11 @@ class _VisaScreenState extends State { floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), - suffixIcon: Icon(Icons.calendar_today, - size: 16, color: Colors.grey), + suffixIcon: Icon( + Icons.calendar_today, + size: 16, + color: Colors.grey, + ), ), ), ), @@ -617,10 +754,7 @@ class _VisaScreenState extends State { ), if (errorMessages["start_date"] != null) ...[ SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), ], ], ), @@ -635,17 +769,19 @@ class _VisaScreenState extends State { Text( "Comments", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: _commentsFocus, // Dropdown doesn't use focus isDesktop: isDesktop, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.66, + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, child: TextField( @@ -665,9 +801,7 @@ class _VisaScreenState extends State { ], ), if (isDesktop) Spacer(), - SizedBox( - height: 5, - ), + SizedBox(height: 5), Column( children: [ Row( @@ -688,9 +822,7 @@ class _VisaScreenState extends State { }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( @@ -699,7 +831,6 @@ class _VisaScreenState extends State { ), ), SizedBox(width: 10), // Space between buttons - // Save Changes Button ElevatedButton( onPressed: () { @@ -707,9 +838,7 @@ class _VisaScreenState extends State { }, style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF114D8B), // Primary color for save - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( diff --git a/lib/Screens/itnerary_list/flight_list.dart b/lib/Screens/itnerary_list/flight_list.dart index aaa5b84..23cce10 100644 --- a/lib/Screens/itnerary_list/flight_list.dart +++ b/lib/Screens/itnerary_list/flight_list.dart @@ -398,13 +398,34 @@ class _FlightListWidgetState extends State { ), Expanded( flex: 4, - child: Text( - "$fromPlaceCountry (From) - (To) $toPlaceCountry", - // "${trip["from_place"]?.toString()} - ${trip["to_place"]?.toString()}", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w700, - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "$fromPlaceCountry", + // "${trip["from_place"]?.toString()} - ${trip["to_place"]?.toString()}", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w700, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 10), + child: Image.asset( + 'assets/images/IconsImg/alternate.png', + width: 20, + height: 20, + ), + ), + Text( + "$toPlaceCountry", + // "${trip["from_place"]?.toString()} - ${trip["to_place"]?.toString()}", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w700, + ), + ), + ], ), ), Expanded( diff --git a/lib/Screens/itnerary_list/visa_list.dart b/lib/Screens/itnerary_list/visa_list.dart index 9429a8e..3f70b4a 100644 --- a/lib/Screens/itnerary_list/visa_list.dart +++ b/lib/Screens/itnerary_list/visa_list.dart @@ -334,7 +334,7 @@ class VisaListWidget extends StatelessWidget { Expanded( flex: 2, child: Text( - " Visa Type", + " Country", style: GoogleFonts.poppins( fontSize: 11, ), @@ -342,7 +342,7 @@ class VisaListWidget extends StatelessWidget { Expanded( flex: 2, child: Text( - " Country", + " Visa Type", style: GoogleFonts.poppins( fontSize: 11, ), @@ -372,8 +372,8 @@ class VisaListWidget extends StatelessWidget { Expanded( flex: 2, child: Text( - getRequestForVisa( - item["type_of_visa"]!.toString()), + getRequestForCountry( + item["country_code"]!.toString()), style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, @@ -384,8 +384,8 @@ class VisaListWidget extends StatelessWidget { Expanded( flex: 2, child: Text( - getRequestForCountry( - item["country_code"]!.toString()), + getRequestForVisa( + item["type_of_visa"]!.toString()), style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index b5d9ef3..31fff2a 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -903,10 +903,15 @@ class CreateNewPlansState extends State { setState(() { apiCostData = plansJson; + + print("apiCostData...1"); costCenterMap = { for (var item in apiCostData!) + // item['department_id'].toString(): item['name'].toString(), item['cost_center_id'].toString(): item['name'].toString(), }; + + print("apiCostData...122"); costCenterIds = costCenterMap.keys.toList(); // Optionally auto-select the first item if not already selected @@ -1030,8 +1035,9 @@ class CreateNewPlansState extends State { // Handle Submit bool validateForm() { + print("validateForm"); validationErrors.clear(); // Clear previous errors - + print("validateForm.....1"); // Ensure either "user_id" or "traveller_id" is provided if ((planUsrId == null || planUsrId!.isEmpty) && (planTravlrId == null || planTravlrId!.isEmpty)) { @@ -1040,7 +1046,7 @@ class CreateNewPlansState extends State { validationErrors["traveller_id"] = "Either User ID or Traveller ID is required"; } - + print("validateForm.....2"); final requiredFields = { if (TripPlanAction != "Plan Creation Not Allowed") // "trip_type": _selectedTripType, @@ -1049,14 +1055,14 @@ class CreateNewPlansState extends State { "functional_department": selectedFuncDept, "purpose_of_travel": selectedPurpose, }; - + print("validateForm.....3"); for (var entry in requiredFields.entries) { if (entry.value == null || entry.value!.isEmpty) { validationErrors[entry.key] = "Required"; // "${entry.key.replaceAll('_', ' ').toUpperCase()} Required"; } } - + print("validateForm.....1"); // Validate at least one service is selected final serviceLists = [ flightList, @@ -1179,7 +1185,9 @@ class CreateNewPlansState extends State { void handleSubmit() { setState(() async { + print("Hansle Submit....11"); if (validateForm() && temporaryMessage == null) { + print("Hansle Submit....11222"); // if (selectedPlanId != null && selectedPlanId!.isNotEmpty) { // planData['plan_id'] = selectedPlanId; // Add plan_id for update // } @@ -1251,6 +1259,26 @@ class CreateNewPlansState extends State { } else { print("Failed to submit plan. Status: ${response.statusCode}"); print("Error: ${response.body}"); + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text("Trip Creation Failed"), + content: Text( + "There was a problem submitting your plan. Please try again.", + ), + actions: [ + TextButton( + child: Text("OK"), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); } } catch (e) { print(" Error submitting plan: $e"); @@ -2592,7 +2620,7 @@ class CreateNewPlansState extends State { List> options = [ {"title": "Self", "value": "Option 1"}, {"title": "Other Employee", "value": "Option 2"}, - {"title": "Others", "value": "Option 3"}, + {"title": "Others (Non Employee)", "value": "Option 3"}, ]; print(" layoutColor: ${widget.layoutColor}"); @@ -2908,8 +2936,8 @@ class CreateNewPlansState extends State { // ), // ), CustomTextFieldWrapper( - isFocused: false, - // isFocused: _isdescriptionFocused, + // isFocused: false, + isFocused: _isdescriptionFocused, // width: isDesktop // ? MediaQuery.of(context).size.width * 0.38 // : MediaQuery.of(context).size.width * 0.85, @@ -3017,8 +3045,9 @@ class CreateNewPlansState extends State { labelText: "Trip Name *", labelStyle: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74), + fontWeight: FontWeight.w400, + color: Colors.grey, + // color: Color(0xFF575A74), ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, diff --git a/lib/Screens/plans/dynamic_itinerary_stepper.dart b/lib/Screens/plans/dynamic_itinerary_stepper.dart index fb143f7..3b73751 100644 --- a/lib/Screens/plans/dynamic_itinerary_stepper.dart +++ b/lib/Screens/plans/dynamic_itinerary_stepper.dart @@ -101,6 +101,13 @@ class DynamicItineraryState extends State { super.didChangeDependencies(); if (widget.tripType != _tripType) { + // setState(() { + // selectedOption = ""; + // isSelected = false; + // selectedIndex = null; + // selectedItem = null; + // }); + updateTripType(widget.tripType); } } @@ -160,6 +167,20 @@ class DynamicItineraryState extends State { } List getAllowedServiceNames() { + print( + "DID Updatee changee - $selectedOption $selectedListOption $isSelected $selectedItem $selectedIndex", + ); + setState(() { + selectedOption = ""; + selectedListOption = ""; + isSelected = false; + selectedIndex = null; + selectedItem = null; + }); + print( + "DID Updatee changee - $selectedOption $isSelected $selectedItem $selectedIndex", + ); + if (widget.tripType == "1") { return ["flight", "accomodation", "train", "bus", "taxi"]; } else if (widget.tripType == "2") { diff --git a/lib/Screens/plans/list_plans.dart b/lib/Screens/plans/list_plans.dart index 39b3e23..5880d34 100644 --- a/lib/Screens/plans/list_plans.dart +++ b/lib/Screens/plans/list_plans.dart @@ -968,7 +968,7 @@ class _ListPlansState extends State { height: 15, ), tooltip: - 'Edit Trip/Plans Details', + 'Edit Trip Details', onPressed: () { Navigator.pop( context, @@ -1009,7 +1009,7 @@ class _ListPlansState extends State { size: 18, ), tooltip: - 'Download The Trip Comments', + 'Download The PDF', onPressed: () { Navigator.pop( context, @@ -1222,7 +1222,7 @@ class _ListPlansState extends State { height: 15, ), tooltip: - 'Edit Trip/plan Details', + 'Edit Trip Details', onPressed: () { Navigator.pop( context, @@ -1256,7 +1256,7 @@ class _ListPlansState extends State { size: 18, ), tooltip: - 'Download The Trip Comments', + 'Download The PDF', onPressed: () { Navigator.pop(context); apiService diff --git a/lib/Screens/policy/policy_list.dart b/lib/Screens/policy/policy_list.dart index 6c69199..8b0a94c 100644 --- a/lib/Screens/policy/policy_list.dart +++ b/lib/Screens/policy/policy_list.dart @@ -322,7 +322,8 @@ class _PolicyListState extends State { controller: searchController, onChanged: filterPolicy, decoration: InputDecoration( - hintText: "Search for a Policy", + hintText: "Search ...", + hintStyle: TextStyle( fontSize: 12, color: Color(0xFF9E9DBD), @@ -383,7 +384,7 @@ class _PolicyListState extends State { MainAxisSize.min, // Ensures content fits nicely children: [ Text( - "Add New Policy", + "Add Policy", style: GoogleFonts.poppins( fontSize: isDesktop ? 13 : 11, ), @@ -413,7 +414,7 @@ class _PolicyListState extends State { controller: searchController, onChanged: filterPolicy, decoration: InputDecoration( - hintText: "Search for a Policy", + hintText: "Search ...", hintStyle: TextStyle( fontSize: 12, color: Color(0xFF9E9DBD), diff --git a/lib/Screens/userManagement/create_user/create_user.dart b/lib/Screens/userManagement/create_user/create_user.dart index cff5d90..da6bfb4 100644 --- a/lib/Screens/userManagement/create_user/create_user.dart +++ b/lib/Screens/userManagement/create_user/create_user.dart @@ -42,14 +42,14 @@ class _CreateUserFormDetialsState extends State { final ApiService apiService = ApiService(); final GlobalKey personalDetailsKey = - GlobalKey(); + GlobalKey(); final GlobalKey travellerDetailsKey = - GlobalKey(); + GlobalKey(); // late List?> travelDetailsData; Map? travelDetailsData; Map? travelDetailsDataFromAPI; - + Map errorMessagesTravel = {}; late TabController _tabController; String? userId; @@ -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; }); } @@ -546,6 +546,33 @@ class _CreateUserFormDetialsState extends State { printFormData(); } + void handleBack() async { + print("USR Detail back"); + + final tabs = { + "personal": "Personal Details", + "office": "Office Details", + "travel": "Travel Details", + }; + + final tabKeys = tabs.keys.toList(); // ["personal", "office", "travel"] + + final currentIndex = tabKeys.indexOf(selectedTab ?? "travel"); + + print("currentIndex - $currentIndex"); + + if (currentIndex > 0) { + // Move to next tab + setState(() { + selectedTab = tabKeys[currentIndex - 1]; + }); + } else { + // Final step — submit or show done + print("All tabs completed!"); + // Submit the full form here + } + } + void handleNext() async { print("USR Detail Next"); printFormData(); @@ -553,6 +580,7 @@ class _CreateUserFormDetialsState extends State { // travelDetailsData = [travellerDetailsKey.currentState?.travel_Detials]; travelDetailsData = travellerDetailsKey.currentState?.travel_Detials; + // errorMessagesTravel = travellerDetailsKey.currentState!.errorMessages; print("TRAVEL DETAILS FROM CHILD: $travelDetailsData"); @@ -626,31 +654,106 @@ class _CreateUserFormDetialsState extends State { void handleSubmit() async { print("USR Detail Submit"); // printFormData(); - travelDetailsData = travellerDetailsKey.currentState?.travel_Detials; - print("TRAVEL DETAILS FROM CHILD"); - // print("TRAVEL DETAILS FROM CHILD: $travelDetailsData"); + bool isValid = travellerDetailsKey.currentState?.boolValidation() ?? false; + if (selectedTab == "travel" || + selectedRole == "5" || + setSelectesUserType == true) { + print("NO validation"); - passportFile = travellerDetailsKey.currentState?.passportFile; + Map data = userDetials; - print("passportFile : $passportFile"); + 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(); - Map data = userDetials; - - print("USERDETAILS : $data"); - - // Map data = userDetials; - - if (!isValidData(data) && isValidDataTwo(data)) { - print("USERDETAILS : $userDetials"); - print("Validation Failed: Required fields are missing."); - setState(() {}); - return; // Stop execution if validation fails + createUserData(userDetials); + } } else { - print("USERDETAILS : $userDetials"); - orgId = await getOrgId(); + print("isValid- $isValid"); + if (!isValid) { + print("Validation failed. Please check the inputs."); + setState(() {}); + return; // ❌ STOP execution here if not valid + } - createUserData(userDetials); + travelDetailsData = travellerDetailsKey.currentState?.travel_Detials; + + print("travelDetailsData - $travelDetailsData"); + + print("travelDetailsData - $travelDetailsData"); + // errorMessagesTravel = travellerDetailsKey.currentState!.errorMessages; + + print("TRAVEL DETAILS FROM CHILD"); + // print("TRAVEL DETAILS FROM CHILD: $travelDetailsData"); + + passportFile = travellerDetailsKey.currentState?.passportFile; + + print("passportFile : $passportFile"); + + Map data = userDetials; + + print("USERDETAILS : $data"); + + // block-submit-here + + // 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"; + return ; + } + } catch (e) { // return "valid upto cannot be earlier than valid from"; + return ; + // errorMessages["end_date"] = "Invalid date format"; + } + } + + // Map data = userDetials; + + if (!isValidData(data) && isValidDataTwo(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); + } } } @@ -682,7 +785,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 } } @@ -692,7 +795,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 } } @@ -882,7 +985,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!"); @@ -911,16 +1014,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))], ), @@ -942,9 +1045,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), @@ -956,28 +1059,39 @@ class _CreateUserFormDetialsState extends State { child: Padding( padding: const EdgeInsets.all(8.0), child: - isDesktop - ? Row( - mainAxisAlignment: MainAxisAlignment.end, - children: - (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!), + ), ), ), ], @@ -1022,9 +1136,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, @@ -1127,8 +1241,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, @@ -1187,69 +1301,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(), ); } @@ -1259,17 +1373,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), @@ -1288,17 +1402,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), @@ -1319,28 +1433,28 @@ class _CreateUserFormDetialsState extends State { return [ MouseRegion( cursor: - isViewMode - ? SystemMouseCursors.forbidden - : SystemMouseCursors.click, + isViewMode + ? SystemMouseCursors.forbidden + : SystemMouseCursors.click, child: TextButton( style: ElevatedButton.styleFrom( backgroundColor: - isViewMode ? layoutColor : layoutColor, // Keep original color + isViewMode ? Colors.white : Colors.white, // Keep original color foregroundColor: - isViewMode ? Colors.white : Colors.red, // 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), - side: BorderSide(color: layoutColor, width: 2), + side: BorderSide(color: Colors.white, width: 2), ), - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 18), ), - onPressed: handleNext, + onPressed: handleBack, // onPressed: // isViewMode ? null : handleNext, // Disable when in view mode - child: Text("Next"), + child: Text("Back"), ), ), ]; @@ -1367,19 +1481,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), @@ -1388,7 +1502,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/Screens/userManagement/create_user/office_details.dart b/lib/Screens/userManagement/create_user/office_details.dart index 02f80f7..a015f23 100644 --- a/lib/Screens/userManagement/create_user/office_details.dart +++ b/lib/Screens/userManagement/create_user/office_details.dart @@ -391,7 +391,7 @@ class _OfficeDetailsState extends State { buildDelegationStartDateField(isDesktop), Spacer(), // Space after Last Name buildDelegationEndDateField(isDesktop), - SizedBox(width: 15), + SizedBox(width: 16), buildReset(isDesktop), ], ) @@ -692,6 +692,9 @@ class _OfficeDetailsState extends State { enabled: !widget.isViewMode, popupProps: PopupProps.menu( + menuProps: const MenuProps( + backgroundColor: Colors.white, + ), showSearchBox: true, fit: FlexFit.loose, // Allows flexible height itemBuilder: @@ -1342,6 +1345,7 @@ class _OfficeDetailsState extends State { : today, firstDate: today, lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedCheckOutDate) { @@ -1450,6 +1454,7 @@ class _OfficeDetailsState extends State { : minDate, firstDate: minDate, lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedEndDate) { @@ -1475,7 +1480,7 @@ class _OfficeDetailsState extends State { ), SizedBox(height: 5), CustomTextFieldUserWrapper( - width: isDesktop ? MediaQuery.of(context).size.width * 0.18 : null, + width: isDesktop ? MediaQuery.of(context).size.width * 0.2 : null, isFocused: false, isDesktop: widget.isDesktop, child: SizedBox( diff --git a/lib/Screens/userManagement/create_user/personal_details.dart b/lib/Screens/userManagement/create_user/personal_details.dart index 3472508..6b8bc71 100644 --- a/lib/Screens/userManagement/create_user/personal_details.dart +++ b/lib/Screens/userManagement/create_user/personal_details.dart @@ -639,7 +639,9 @@ class PersonalDetailsState extends State { children: [ if (!widget.apiselectedUser) ...[ buildPassword(), - SizedBox(width: 15), + SizedBox( + width: MediaQuery.of(context).size.width * 0.015, + ), buildRole(), ] else ...[ // TextButton( @@ -736,9 +738,11 @@ class PersonalDetailsState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ buildAddress(), - SizedBox(width: 15), + Spacer(), + // SizedBox(width: 15), buildCountryField(), - SizedBox(width: 15), + Spacer(), + // SizedBox(width: 15), buildPostalCodeField(), ], ) @@ -1043,7 +1047,9 @@ class PersonalDetailsState extends State { ? _selectedDateOfBirth! : today, firstDate: DateTime(1900), - lastDate: DateTime(2100), + // lastDate: DateTime(2100), + lastDate: today, + initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedDateOfBirth) { @@ -1583,6 +1589,7 @@ class PersonalDetailsState extends State { widget.isViewMode ? null : (String? newValue) { + widget.errorMessages.remove("role_id"); if (newValue == null) return; final selectedKey = roleMap.entries diff --git a/lib/Screens/userManagement/create_user/traveller_details.dart b/lib/Screens/userManagement/create_user/traveller_details.dart index ffec240..3781bce 100644 --- a/lib/Screens/userManagement/create_user/traveller_details.dart +++ b/lib/Screens/userManagement/create_user/traveller_details.dart @@ -62,6 +62,7 @@ class TravellerDetailsState extends State { String? selectedFileNames; Uint8List? passportDocumentBytes; String? passportFileUrlFromApi; + String? _selectedTripType; // String? selectedPurpose; // String? selectedCountry; @@ -106,6 +107,7 @@ class TravellerDetailsState extends State { //To Create Controllers List dataHeader = [ "Fname", + "Mname", "Lname", "nationality", "d_meal_pref", @@ -152,6 +154,28 @@ class TravellerDetailsState extends State { }); } + bool boolValidation() { + final passportText = controllers["passportNumber"]?.text ?? ""; + + if (_selectedTripType == "Indian") { + // Match exactly 8 alphanumeric characters + final isValid = RegExp(r'^[a-zA-Z0-9]{8}$').hasMatch(passportText); + + if (!isValid) { + setState(() { + errorMessages["passport_number"] = "Enter 8 characters"; + }); + return false; + } else { + setState(() { + errorMessages.remove("passport_number"); + }); + } + } + + return true; + } + Future apiCheckDuplicate( String label, String field, @@ -308,9 +332,10 @@ class TravellerDetailsState extends State { Map get travel_Detials { Map data = { - // "given_Name": controllers["Fname"]?.text, - // "last_Name": controllers["Lname"]?.text, - // "nationality": controllers["nationality"]?.text, + "passport_firstname": controllers["Fname"]?.text, + "passport_middlename": controllers["Mname"]?.text, + "passport_lastname": controllers["Lname"]?.text, + "nationality": _selectedTripType, "passport_number": controllers["passportNumber"]?.text ?? '', "place_of_issue": controllers["placeOfIssue"]?.text, "date_of_issue": controllers["dateOfIssue"]?.text, @@ -500,6 +525,17 @@ class TravellerDetailsState extends State { print("Updatewidget.travelDetails"); print(widget.travelDetails); + controllers["Fname"]?.text = + widget.travelDetails?["passport_firstname"] ?? ""; + + controllers["Mname"]?.text = + widget.travelDetails?["passport_middlename"] ?? ""; + + controllers["Lname"]?.text = + widget.travelDetails?["passport_lastname"] ?? ""; + + _selectedTripType = widget.travelDetails?["nationality"] ?? ""; + controllers["passportNumber"]?.text = widget.travelDetails?["passport_number"] ?? ""; controllers["placeOfIssue"]?.text = @@ -694,7 +730,8 @@ class TravellerDetailsState extends State { Future loadCountryList() async { final newTripType = "2"; - final result = await apiService.fetchFlightsCountryList(newTripType); + final result = await apiService.fetchAirlineList(); + // final result = await apiService.fetchFlightsCountryList(newTripType); print("resultFlight: $result "); @@ -900,49 +937,52 @@ class TravellerDetailsState extends State { Widget _buildPassportDataRow1(bool isDesktop) { return Container( color: Colors.white, - child: Row( - children: [ - Text( - "Name as per passport : ", - style: GoogleFonts.poppins( - fontSize: 11, - fontStyle: FontStyle.italic, - letterSpacing: 0.5, - ), - ), - Text( - "${widget.fullName} ", - style: GoogleFonts.poppins( - fontSize: 10, - fontWeight: FontWeight.w600, - letterSpacing: 0.5, - color: Colors.black87, - // fontStyle: FontStyle.italic, - ), - ), - ], - ), - // child: widget.isDesktop - // ? Row( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // buildFirstNameField(), - // Spacer(), - // buildLastNameField(), - // Spacer(), // Space after Last Name - // buildNationality(), - // ], - // ) - // : Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // buildFirstNameField(), - // SizedBox(height: 8), // Vertical space - // buildLastNameField(), - // SizedBox(height: 8), - // buildNationality(), - // ], - // ), + // child: Row( + // children: [ + // Text( + // "Name as per passport : ", + // style: GoogleFonts.poppins( + // fontSize: 11, + // fontStyle: FontStyle.italic, + // letterSpacing: 0.5, + // ), + // ), + // Text( + // "${widget.fullName} ", + // style: GoogleFonts.poppins( + // fontSize: 10, + // fontWeight: FontWeight.w600, + // letterSpacing: 0.5, + // color: Colors.black87, + // // fontStyle: FontStyle.italic, + // ), + // ), + // ], + // ), + child: + widget.isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildFirstNameField(), + Spacer(), buildMiddleNameField(), + Spacer(), + buildLastNameField(), + SizedBox(width: 25), // Space after Last Name + buildNationality(), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildFirstNameField(), + SizedBox(height: 8), buildMiddleNameField(), + SizedBox(height: 8), // Vertical space + buildLastNameField(), + SizedBox(height: 8), + buildNationality(), + ], + ), ); } @@ -1039,6 +1079,48 @@ class TravellerDetailsState extends State { ); } + Widget buildMiddleNameField() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Middle Name", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.black, + ), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: controllers["Mname"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("middle_name"); + }, + decoration: InputDecoration( + labelText: "MiddleName", + labelStyle: GoogleFonts.poppins( + fontSize: 12, + color: Colors.grey, + ), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + ], + ); + } + Widget buildLastNameField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1054,6 +1136,10 @@ class TravellerDetailsState extends State { SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( isFocused: false, + width: + widget.isDesktop + ? MediaQuery.of(context).size.width * 0.13 + : null, isDesktop: widget.isDesktop, child: SizedBox( height: 40, @@ -1082,6 +1168,7 @@ class TravellerDetailsState extends State { } Widget buildNationality() { + List nationalityOptions = ["Indian", "International"]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1096,26 +1183,62 @@ class TravellerDetailsState extends State { SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( isFocused: false, + width: + widget.isDesktop + ? MediaQuery.of(context).size.width * 0.13 + : null, isDesktop: widget.isDesktop, child: SizedBox( height: 40, - child: TextField( - style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), - controller: controllers["nationality"], + child: DropdownSearch( + selectedItem: _selectedTripType, enabled: !widget.isViewMode, - onChanged: (value) { - _clearError("last_name"); - }, - decoration: InputDecoration( - labelText: "Nationality", - labelStyle: GoogleFonts.poppins( - fontSize: 12, - color: Colors.grey, - ), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), + + popupProps: PopupProps.menu( + showSearchBox: false, // Set true if you want search + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 200), + itemBuilder: + (context, item, isSelected) => Container( + color: Colors.white, + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + child: Text( + item, + style: GoogleFonts.poppins(fontSize: 11.5), + ), + ), ), + items: nationalityOptions, + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), + ), + dropdownBuilder: + (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select", + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ), + ), + onChanged: + widget.isViewMode + ? null + : (String? newValue) { + setState(() { + _selectedTripType = newValue; + controllers["passportNumber"]?.clear(); + print("_selectedTripType - $_selectedTripType"); + }); + }, ), ), ), @@ -1145,6 +1268,14 @@ class TravellerDetailsState extends State { style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: controllers["passportNumber"], enabled: !widget.isViewMode, + inputFormatters: [ + // limit to 8 characters + FilteringTextInputFormatter.allow( + RegExp(r'[a-zA-Z0-9]'), + ), // allow only letters and digits + if (_selectedTripType == "Indian") + LengthLimitingTextInputFormatter(8), + ], onChanged: (value) { _clearError("passport_number"); apiCheckDuplicate( @@ -1220,8 +1351,10 @@ class TravellerDetailsState extends State { ); } + DateTime? _selectedDateOfIssue; + DateTime? _selectedDateOfExpiry; + Widget buildDateOfIssue() { - DateTime? _selectedDateOfIssue; Future _selectCheckDateOfIssue(BuildContext context) async { DateTime now = DateTime.now(); @@ -1229,21 +1362,26 @@ class TravellerDetailsState extends State { DateTime? pickedDate = await showDatePicker( context: context, - initialDate: - _selectedDateOfIssue != null && _selectedDateOfIssue!.isAfter(today) - ? _selectedDateOfIssue! - : today, - // firstDate: today, + initialDate: _selectedDateOfIssue != null && _selectedDateOfIssue!.isAfter(today) + ? _selectedDateOfIssue! + : today, firstDate: DateTime(1900), lastDate: DateTime(2100), + 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"; + } else { + errorMessages.remove("date_of_expiry"); + } }); } } @@ -1262,25 +1400,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"], @@ -1310,36 +1448,79 @@ class TravellerDetailsState extends State { } Widget buildDateOfExpiry() { - DateTime? _selectedDateOfExpiry; Future _selectCheckDateOfExpiry(BuildContext context) async { + DateTime now = DateTime.now(); DateTime today = DateTime(now.year, now.month, now.day); - // DateTime today = DateTime(now.year now.day, now.month,); + // DateTime firstDate = today; + DateTime? validFromDate; + + try { + String? fromDateText = controllers["dateOfIssue"]?.text.trim(); + print("dateOfIssue From Text: $fromDateText"); + if (fromDateText != null && fromDateText.isNotEmpty) { + validFromDate = DateFormat('dd-MM-yyyy').parseStrict(fromDateText); + print("dateOfIssue Valid From: $validFromDate"); + } + } catch (e) { + print("Error parsing valid from date: $e"); + } + + // Use max(today, validFromDate) as firstDate + // DateTime firstDate = today; + DateTime firstDate = validFromDate ?? today; + + // Parse dateOfIssue safely + // DateTime firstDate; + // try { + // firstDate = DateFormat( + // "yyyy-MM-dd", + // ).parse(controllers["dateOfIssue"]!.text); + // } catch (e) { + // firstDate = today; // fallback if parsing fails + // } DateTime? pickedDate = await showDatePicker( context: context, - initialDate: - _selectedDateOfExpiry != null && - _selectedDateOfExpiry!.isAfter(today) - ? _selectedDateOfExpiry! - : today, - // firstDate: today, - firstDate: DateTime(1900), + + // initialDate: + // _selectedDateOfExpiry != null && + // _selectedDateOfExpiry!.isAfter(firstDate) + // ? _selectedDateOfExpiry! + // : today, + initialDate: firstDate, + firstDate: firstDate, + // firstDate: DateTime(1900), lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); - if (pickedDate != null && pickedDate != _selectedDateOfExpiry) { + if (pickedDate != null) { setState(() { _selectedDateOfExpiry = pickedDate; - controllers["dateOfExpiry"]?.text = DateFormat( - 'dd-MM-yyyy', - ).format(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"; + } else { + errorMessages.remove("date_of_expiry"); + } }); } + + // if (pickedDate != null && pickedDate != _selectedDateOfExpiry) { + // setState(() { + // _selectedDateOfExpiry = pickedDate; + // controllers["dateOfExpiry"]?.text = DateFormat( + // 'dd-MM-yyyy', + // ).format(pickedDate); + // + // // controllers["dateOfExpiry"]?.text = + // // DateFormat('dd-MM-yyyy').format(pickedDate); + // }); + // } } return Column( @@ -1358,23 +1539,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"], @@ -1399,9 +1580,238 @@ class TravellerDetailsState extends State { ), ), ), + if (errorMessages["date_of_expiry"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + 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 + ), + ], ], ); } + // Widget buildDateOfIssue() { + // DateTime? _selectedDateOfIssue; + // + // 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, + // // firstDate: today, + // firstDate: DateTime(1900), + // lastDate: DateTime(2100), + // initialEntryMode: DatePickerEntryMode.calendarOnly, + // ); + // + // if (pickedDate != null && pickedDate != _selectedDateOfIssue) { + // setState(() { + // _selectedDateOfIssue = pickedDate; + // controllers["dateOfIssue"]?.text = DateFormat( + // 'dd-MM-yyyy', + // ).format(pickedDate); + // }); + // } + // } + // + // return Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // "Date of Issue", + // style: GoogleFonts.poppins( + // fontSize: 12, + // fontWeight: FontWeight.w600, + // color: Color(0xFF575A74), + // ), + // ), + // SizedBox(height: 5), + // CustomTextFieldUserTravellerWrapper( + // width: + // 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"); + // }); + // } + // }, + // child: AbsorbPointer( + // child: TextField( + // controller: controllers["dateOfIssue"], + // style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + // decoration: InputDecoration( + // labelText: "Select Date", + // labelStyle: GoogleFonts.poppins( + // fontSize: 12, + // color: Colors.grey, + // ), + // floatingLabelBehavior: FloatingLabelBehavior.never, + // border: InputBorder.none, + // contentPadding: const EdgeInsets.symmetric(vertical: 16), + // suffixIcon: const Icon( + // Icons.calendar_today, + // size: 16, + // color: Color(0xFF8B8FB2), + // ), + // ), + // ), + // ), + // ), + // ), + // ), + // ], + // ); + // } + // + // Widget buildDateOfExpiry() { + // DateTime? _selectedDateOfExpiry; + // + // Future _selectCheckDateOfExpiry(BuildContext context) async { + // DateTime now = DateTime.now(); + // DateTime today = DateTime(now.year, now.month, now.day); + // // DateTime today = DateTime(now.year now.day, now.month,); + // + // DateTime? validFromDate; + // + // try { + // String? fromDateText = controllers["dateOfIssue"]?.text.trim(); + // print("dateOfIssue From Text: $fromDateText"); + // if (fromDateText!.isNotEmpty) { + // validFromDate = DateFormat('dd-MM-yyyy').parseStrict(fromDateText); + // print("dateOfIssue Valid From: $validFromDate"); + // } + // } catch (e) { + // print("Error parsing valid from date: $e"); + // } + // + // // Use max(today, validFromDate) as firstDate + // // DateTime firstDate = today; + // DateTime firstDate = validFromDate ?? today; + // + // // Parse dateOfIssue safely + // // DateTime firstDate; + // // try { + // // firstDate = DateFormat( + // // "yyyy-MM-dd", + // // ).parse(controllers["dateOfIssue"]!.text); + // // } catch (e) { + // // firstDate = today; // fallback if parsing fails + // // } + // + // DateTime? pickedDate = await showDatePicker( + // context: context, + // + // // initialDate: + // // _selectedDateOfExpiry != null && + // // _selectedDateOfExpiry!.isAfter(firstDate) + // // ? _selectedDateOfExpiry! + // // : today, + // initialDate: firstDate, + // firstDate: firstDate, + // // firstDate: DateTime(1900), + // lastDate: DateTime(2100), + // initialEntryMode: DatePickerEntryMode.calendarOnly, + // ); + // + // if (pickedDate != null) { + // _selectedDateOfExpiry = pickedDate; + // controllers["dateOfExpiry"]?.text = DateFormat( + // "dd-MM-yyyy", + // ).format(pickedDate); + // } + // + // // if (pickedDate != null && pickedDate != _selectedDateOfExpiry) { + // // setState(() { + // // _selectedDateOfExpiry = pickedDate; + // // controllers["dateOfExpiry"]?.text = DateFormat( + // // 'dd-MM-yyyy', + // // ).format(pickedDate); + // // + // // // controllers["dateOfExpiry"]?.text = + // // // DateFormat('dd-MM-yyyy').format(pickedDate); + // // }); + // // } + // } + // + // return Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // "Date of Expiry", + // style: GoogleFonts.poppins( + // fontSize: 12, + // fontWeight: FontWeight.w600, + // color: Color(0xFF575A74), + // ), + // ), + // SizedBox(height: 5), + // CustomTextFieldUserTravellerWrapper( + // isFocused: false, + // isDesktop: widget.isDesktop, + // width: + // 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"); + // }); + // } + // }, + // child: AbsorbPointer( + // child: TextField( + // controller: controllers["dateOfExpiry"], + // style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + // decoration: InputDecoration( + // labelText: "Select Date", + // labelStyle: GoogleFonts.poppins( + // fontSize: 12, + // color: Colors.grey, + // ), + // floatingLabelBehavior: FloatingLabelBehavior.never, + // border: InputBorder.none, + // contentPadding: const EdgeInsets.symmetric(vertical: 16), + // suffixIcon: const Icon( + // Icons.calendar_today, + // size: 16, + // color: Color(0xFF8B8FB2), + // ), + // ), + // ), + // ), + // ), + // ), + // ), + // ], + // ); + // } Widget buildPassportDocument() { void pickPDFWeb() { @@ -1719,6 +2129,7 @@ class TravellerDetailsState extends State { dropdownDecoratorProps: const DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, + hintText: "Select Seat", contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 5, @@ -1739,7 +2150,7 @@ class TravellerDetailsState extends State { ); } return Text( - selectedItem['dropdown_value'] ?? '', + selectedItem['dropdown_value'] ?? "Select Seat", style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), ); }, @@ -1889,7 +2300,7 @@ class TravellerDetailsState extends State { vertical: 8, ), child: Text( - item['dropdown_value'] ?? '', + item['dropdown_value'] ?? "Select Seat", style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, @@ -2431,6 +2842,7 @@ class TravellerDetailsState extends State { : today, firstDate: DateTime(1900), lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedExpiryDate) { @@ -2591,10 +3003,18 @@ class TravellerDetailsState extends State { countryList = apiAirlineCountryData!; print("TestcountryList2 - $countryList"); + // countryMap = { + // for (var country in countryList) + // country['Code'] as String: '${country['City']} - ${country['Airport']}', + // }; + countryMap = { for (var country in countryList) - country['Code'] as String: '${country['City']} - ${country['Airport']}', + country['airline'] as String: + '${country['airline_name']} (${country['airline']})', + // country['airline'] as String: '${country['airline_name']}', }; + // airline countryCodes = countryMap.keys.toList(); @@ -2639,10 +3059,10 @@ class TravellerDetailsState extends State { ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( - width: - widget.isDesktop - ? MediaQuery.of(context).size.width * 0.17 - : null, + // width: + // widget.isDesktop + // ? MediaQuery.of(context).size.width * 0.17 + // : null, isFocused: false, isDesktop: widget.isDesktop, child: SizedBox( @@ -2674,17 +3094,56 @@ class TravellerDetailsState extends State { backgroundColor: Colors.white, ), constraints: BoxConstraints(maxHeight: 250), - itemBuilder: - (context, item, isSelected) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 6.0, - ), - child: Text( - item, - style: GoogleFonts.poppins(fontSize: 11.5), - ), + itemBuilder: (context, item, isSelected) { + // Extract airline name and code from "AirlineName (CODE)" + final match = RegExp( + r'^(.*)\s+\(([^)]+)\)$', + ).firstMatch(item); + final name = match?.group(1) ?? ''; + final code = match?.group(2) ?? ''; + + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 6.0, ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + name, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + ), + overflow: TextOverflow.ellipsis, + ), + ), + Text( + code, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.grey, + ), + ), + ], + ), + ); + }, + + // itemBuilder: + // (context, item, isSelected) => Padding( + // padding: const EdgeInsets.symmetric( + // horizontal: 8.0, + // vertical: 6.0, + // ), + // child: Text( + // item, + // style: GoogleFonts.poppins(fontSize: 11.5), + // ), + // ), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search Airline...", @@ -2789,7 +3248,7 @@ class TravellerDetailsState extends State { controller: entry["controller_flier_number"], enabled: !widget.isViewMode, onChanged: (value) { - _clearError("first_name"); + // _clearError("first_name"); }, decoration: InputDecoration( labelText: "Frequent Flyer Information", @@ -3053,7 +3512,7 @@ class TravellerDetailsState extends State { controller: entry["controller_membership"], enabled: !widget.isViewMode, onChanged: (value) { - _clearError("first_name"); + // _clearError("first_name"); }, decoration: InputDecoration( labelText: "Hotel Membership Number", @@ -3591,8 +4050,11 @@ class TravellerDetailsState extends State { ); } + DateTime? _selectedCheckOutDate; + DateTime? _selectedCheckInDate; + Widget buildVisaValidFrom(Map entry) { - DateTime? _selectedCheckOutDate; + TimeOfDay? _selectedCheckOutTime; Future _selectValidFromDate(BuildContext context) async { @@ -3614,16 +4076,21 @@ class TravellerDetailsState extends State { // firstDate: initialDate, firstDate: DateTime(1900), lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedCheckOutDate) { setState(() { - _selectedCheckOutDate = pickedDate; - // _dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate); + _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"; + } else { + errorMessages.remove("valid_upto"); + } }); } } @@ -3642,9 +4109,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( @@ -3681,38 +4148,65 @@ class TravellerDetailsState extends State { } Widget buildVisaValidUpTo(Map entry) { - DateTime? _selectedCheckOutDate; + TimeOfDay? _selectedCheckOutTime; Future _selectValidUpToDate(BuildContext context) async { DateTime now = DateTime.now(); DateTime today = DateTime(now.year, now.month, now.day); + print("buildVisaValidUpTo "); + print("buildVisaValidUpTo - ${entry["controller_valid_from"].text}"); // Parse date from notifier if available, else use today DateTime initialDate = today; // Use previously selected date if valid - if (_selectedCheckOutDate != null && - _selectedCheckOutDate!.isAfter(today)) { - initialDate = _selectedCheckOutDate!; + // if (_selectedCheckOutDate != null && + // _selectedCheckOutDate!.isAfter(today)) { + // initialDate = _selectedCheckOutDate!; + // } + + DateTime? validFromDate; + + try { + String fromDateText = entry["controller_valid_from"].text.trim(); + print("Valid From Text: $fromDateText"); + if (fromDateText.isNotEmpty) { + validFromDate = DateFormat('dd-MM-yyyy').parseStrict(fromDateText); + print("Parsed Valid From: $validFromDate"); + } + } catch (e) { + print("Error parsing valid from date: $e"); } + // Use max(today, validFromDate) as firstDate + // DateTime firstDate = today; + DateTime firstDate = validFromDate ?? today; + if (validFromDate != null && validFromDate.isAfter(today)) { + firstDate = validFromDate; + } final pickedDate = await showDatePicker( context: context, // initialDate: initialDate, // firstDate: initialDate, - firstDate: DateTime(1900), + // firstDate: DateTime(1900), + initialDate: firstDate, + firstDate: firstDate, lastDate: DateTime(2100), + initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedCheckOutDate) { setState(() { _selectedCheckOutDate = pickedDate; - // _dateController.text = DateFormat('yyyy-MM-dd').format(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"; + } else { + errorMessages.remove("valid_upto"); + } }); } } @@ -3731,9 +4225,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( @@ -3765,10 +4259,222 @@ class TravellerDetailsState extends State { ), ), ), + if (errorMessages["valid_upto"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + 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 + ), + ], ], ); } + // Widget buildVisaValidFrom(Map entry) { + // DateTime? _selectedCheckOutDate; + // TimeOfDay? _selectedCheckOutTime; + // + // Future _selectValidFromDate(BuildContext context) async { + // DateTime now = DateTime.now(); + // DateTime today = DateTime(now.year, now.month, now.day); + // + // // Parse date from notifier if available, else use today + // DateTime initialDate = today; + // + // // Use previously selected date if valid + // if (_selectedCheckOutDate != null && + // _selectedCheckOutDate!.isAfter(today)) { + // initialDate = _selectedCheckOutDate!; + // } + // + // final pickedDate = await showDatePicker( + // context: context, + // // initialDate: initialDate, + // // firstDate: initialDate, + // firstDate: DateTime(1900), + // lastDate: DateTime(2100), + // initialEntryMode: DatePickerEntryMode.calendarOnly, + // ); + // + // if (pickedDate != null && pickedDate != _selectedCheckOutDate) { + // setState(() { + // _selectedCheckOutDate = pickedDate; + // // _dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate); + // + // entry["controller_valid_from"].text = DateFormat( + // 'dd-MM-yyyy', + // ).format(pickedDate); + // }); + // } + // } + // + // return Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // "Valid From", + // style: GoogleFonts.poppins( + // fontSize: 12, + // fontWeight: FontWeight.w600, + // color: Color(0xFF575A74), + // ), + // ), + // SizedBox(height: 5), + // CustomTextFieldUserTravellerWrapper( + // width: + // widget.isDesktop + // ? MediaQuery.of(context).size.width * 0.15 + // : null, + // isFocused: false, + // isDesktop: widget.isDesktop, + // child: SizedBox( + // height: 40, + // child: GestureDetector( + // onTap: () async { + // await _selectValidFromDate(context); + // }, + // child: AbsorbPointer( + // child: TextField( + // // focusNode: _dateFocusNode, + // controller: entry["controller_valid_from"], + // + // style: const TextStyle(fontSize: 12), + // decoration: const InputDecoration( + // labelText: "Select Date", + // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + // floatingLabelBehavior: FloatingLabelBehavior.never, + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric(vertical: 16), + // suffixIcon: Icon( + // Icons.calendar_today, + // size: 16, + // color: Colors.grey, + // ), + // ), + // ), + // ), + // ), + // ), + // ), + // ], + // ); + // } + // + // Widget buildVisaValidUpTo(Map entry) { + // DateTime? _selectedCheckOutDate; + // TimeOfDay? _selectedCheckOutTime; + // + // Future _selectValidUpToDate(BuildContext context) async { + // DateTime now = DateTime.now(); + // DateTime today = DateTime(now.year, now.month, now.day); + // print("buildVisaValidUpTo "); + // print("buildVisaValidUpTo - ${entry["controller_valid_from"].text}"); + // + // // Parse date from notifier if available, else use today + // DateTime initialDate = today; + // + // // Use previously selected date if valid + // // if (_selectedCheckOutDate != null && + // // _selectedCheckOutDate!.isAfter(today)) { + // // initialDate = _selectedCheckOutDate!; + // // } + // + // DateTime? validFromDate; + // + // try { + // String fromDateText = entry["controller_valid_from"].text.trim(); + // print("Valid From Text: $fromDateText"); + // if (fromDateText.isNotEmpty) { + // validFromDate = DateFormat('dd-MM-yyyy').parseStrict(fromDateText); + // print("Parsed Valid From: $validFromDate"); + // } + // } catch (e) { + // print("Error parsing valid from date: $e"); + // } + // + // // Use max(today, validFromDate) as firstDate + // // DateTime firstDate = today; + // DateTime firstDate = validFromDate ?? today; + // if (validFromDate != null && validFromDate.isAfter(today)) { + // firstDate = validFromDate; + // } + // final pickedDate = await showDatePicker( + // context: context, + // // initialDate: initialDate, + // // firstDate: initialDate, + // // firstDate: DateTime(1900), + // initialDate: firstDate, + // firstDate: firstDate, + // lastDate: DateTime(2100), + // initialEntryMode: DatePickerEntryMode.calendarOnly, + // ); + // + // if (pickedDate != null && pickedDate != _selectedCheckOutDate) { + // setState(() { + // _selectedCheckOutDate = pickedDate; + // // _dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate); + // + // entry["controller_valid_upto"].text = DateFormat( + // 'dd-MM-yyyy', + // ).format(pickedDate); + // }); + // } + // } + // + // return Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // "Valid To", + // style: GoogleFonts.poppins( + // fontSize: 12, + // fontWeight: FontWeight.w600, + // color: Color(0xFF575A74), + // ), + // ), + // SizedBox(height: 5), + // CustomTextFieldUserTravellerWrapper( + // width: + // widget.isDesktop + // ? MediaQuery.of(context).size.width * 0.15 + // : null, + // isFocused: false, + // isDesktop: widget.isDesktop, + // child: SizedBox( + // height: 40, + // child: GestureDetector( + // onTap: () async { + // await _selectValidUpToDate(context); + // }, + // child: AbsorbPointer( + // child: TextField( + // // focusNode: _dateFocusNode, + // // controller: _dateController, + // controller: entry["controller_valid_upto"], + // style: const TextStyle(fontSize: 12), + // decoration: const InputDecoration( + // labelText: "Select Date", + // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + // floatingLabelBehavior: FloatingLabelBehavior.never, + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric(vertical: 16), + // suffixIcon: Icon( + // Icons.calendar_today, + // size: 16, + // color: Colors.grey, + // ), + // ), + // ), + // ), + // ), + // ), + // ), + // ], + // ); + // } + Widget IconForVisa() { return Container( height: 60, // must define height diff --git a/lib/Screens/userManagement/user_List.dart b/lib/Screens/userManagement/user_List.dart index c4097ff..ab9b547 100644 --- a/lib/Screens/userManagement/user_List.dart +++ b/lib/Screens/userManagement/user_List.dart @@ -1137,15 +1137,54 @@ class _UserListScreenState extends State { child: Column( // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + // Expanded( + // child: + // isDesktop + // ? SingleChildScrollView( + // scrollDirection: Axis.vertical, + // child: table, // <-- your existing table + // ) + // : buildMobileCardView(paginatedUser), + // ), + Expanded( child: - isDesktop - ? SingleChildScrollView( - scrollDirection: Axis.vertical, - child: table, // <-- your existing table - ) - : buildMobileCardView(paginatedUser), + isDesktop + ? (searchController.text.isNotEmpty && + filteredUsers.isEmpty + ? Center( + child: Text( + "No users found", + style: GoogleFonts.poppins( + fontSize: 14, + color: Colors.grey, + ), + ), + ) + : SingleChildScrollView( + scrollDirection: Axis.vertical, + child: table, + )) + : (searchController.text.isNotEmpty && + filteredUsers.isEmpty + ? Center( + child: Text( + "No users found", + style: GoogleFonts.poppins( + fontSize: 14, + color: Colors.grey, + ), + ), + ) + : buildMobileCardView(paginatedUser)), + // child: isDesktop + // ? SingleChildScrollView( + // scrollDirection: Axis.vertical, + // child: table, // <-- your existing table + // ) + // : buildMobileCardView(paginatedPlans), ), + PaginationControls( currentPage: currentPage, itemsPerPage: itemsPerPage, diff --git a/lib/routes/custom_appBar.dart b/lib/routes/custom_appBar.dart index 6b5ac2f..3498243 100644 --- a/lib/routes/custom_appBar.dart +++ b/lib/routes/custom_appBar.dart @@ -284,297 +284,298 @@ class _CustomAppBarState extends State { @override Widget build(BuildContext context) { - 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 - ), + return 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( - 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( + title: + !widget.isDesktop + ? Text('') + : 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'); + Padding( + padding: const EdgeInsets.all(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, + // 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, + child: Icon( + Icons.add_a_photo, + size: 10, + color: Colors.grey, ), - ), - const Divider(), // 👈 Divider after role - ], + // backgroundColor: Colors.redAccent, + // child: Icon(Icons.error, size: 10), + ); + }, ), ), - ...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 CircleAvatar( + radius: 20, + // backgroundColor: Colors.white, + child: Icon( + Icons.add_a_photo, + size: 10, + color: Colors.grey, + ), ), - ), - ), ), - const SizedBox(width: 8), + SizedBox(width: MediaQuery.of(context).size.width * 0.18), + // Spacer(), + 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(), ], ), ), - ], - 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 + 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 + ), + ), + ); } @override @@ -701,4 +702,4 @@ Widget buildNavItem( ), ), ); -} +} \ No newline at end of file diff --git a/lib/routes/organizationSetting.dart b/lib/routes/organizationSetting.dart index c06b3d9..ec624fa 100644 --- a/lib/routes/organizationSetting.dart +++ b/lib/routes/organizationSetting.dart @@ -112,9 +112,9 @@ class OrganizationSettingState extends State { }, { 'value': '/forexTexmplate', - 'icon': Icons.attach_money, - 'label': 'Forex Template', - 'description': 'Edit Template', + 'icon': Icons.credit_card, + 'label': 'Forex Documentation', + 'description': 'Edit Document', }, { 'value': '/templateList', diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index 1b1155f..272aaa5 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -102,6 +102,41 @@ class ApiService { throw Exception('Failed to load country list'); } } + Future> fetchAirlineList() async { + final String apiUrldata = '$apiUrl/api/getAirlineMaster'; + final token = await getToken(); + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + try { + final data = json.decode(response.body); + print("Airline - $data"); + + if (!data.containsKey('data') || data['data'] is! List) { + throw Exception( + "Invalid response format: 'data' field is missing or not a List", + ); + } + + return data['data']; + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + throw Exception('Failed to load country list'); + } + } Future> fetchUsers() async { String? ordId = await getOrgId(); diff --git a/lib/widgets/popup_userList_action.dart b/lib/widgets/popup_userList_action.dart index aa66b34..5ff7a85 100644 --- a/lib/widgets/popup_userList_action.dart +++ b/lib/widgets/popup_userList_action.dart @@ -42,7 +42,7 @@ class UserActionsMenu extends StatelessWidget { children: [ GestureDetector( child: Tooltip( - message: 'View User Details', + message: 'View Details', child: Icon(Icons.remove_red_eye, color: Color(0xFF475569), size: 18) ), @@ -61,7 +61,7 @@ class UserActionsMenu extends StatelessWidget { SizedBox(width: 8), GestureDetector( child: Tooltip( - message: 'Edit User Details', + message: 'Edit Details', child: Image.asset( 'assets/images/IconsImg/edit.png', width: 20, diff --git a/pubspec.yaml b/pubspec.yaml index 24d8c13..58aee17 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: -# sdk: ^3.6.1 + # sdk: ^3.6.1 sdk: ^3.7.2 # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -99,6 +99,8 @@ flutter: - assets/images/login/VectorG.png - assets/images/login/logoNew.jpg - assets/images/login/microsoft.png + - assets/images/IconsImg/path.png + - assets/images/IconsImg/alternate.png - assets/images/IconsImg/delete.png - assets/images/IconsImg/edit.png - assets/images/IconsImg/planPdf_icon.png @@ -134,4 +136,4 @@ flutter: # weight: 700 # # For details regarding fonts from package dependencies, - # see https://flutter.dev/to/font-from-package + # see https://flutter.dev/to/font-from-packagej \ No newline at end of file