From db7df6b7de6b8f2c528d028c3ef48aea9ff43fb9 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Thu, 15 May 2025 19:11:21 +0530 Subject: [PATCH] user management - remarks list --- lib/Screens/allTrips/list_all_plans.dart | 17 +- lib/Screens/allTrips/remarks_list.dart | 197 +++++ lib/Screens/approvals/approval_list.dart | 29 +- lib/Screens/itnerary/accomodations.dart | 6 +- lib/Screens/itnerary/flights.dart | 4 +- lib/Screens/itnerary/train.dart | 4 +- lib/Screens/itnerary_list/flight_list.dart | 2 +- .../plans/dynamic_itinerary_stepper.dart | 1 + lib/Screens/plans/list_plans.dart | 29 +- .../create_user/create_user.dart | 140 +++- .../create_user/personal_details.dart | 7 +- .../create_user/traveller_details.dart | 680 ++++++++++++++---- lib/routes/custom_appBar.dart | 4 +- lib/services/apiService.dart | 6 +- lib/utils/auth_utils.dart | 2 + lib/utils/travelAgent_remarks.dart | 77 +- lib/widgets/custom_user_travel.dart | 2 +- 17 files changed, 978 insertions(+), 229 deletions(-) create mode 100644 lib/Screens/allTrips/remarks_list.dart diff --git a/lib/Screens/allTrips/list_all_plans.dart b/lib/Screens/allTrips/list_all_plans.dart index f5fedae..546276a 100644 --- a/lib/Screens/allTrips/list_all_plans.dart +++ b/lib/Screens/allTrips/list_all_plans.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:core'; +import 'package:frontend/Screens/allTrips/remarks_list.dart'; import 'package:frontend/data/models/plan.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -782,12 +783,14 @@ class _ListAllPlansState extends State { onPressed: () { showDialog( context: context, - builder: (context) => CommentModal( - // planId: plan.planId, - planId: plan.planId.toString(), - layoutColorForUser: - layoutColor!, - role: "Admin"), + builder: (context) => + CommentModalList( + // planId: plan.planId, + planId: + plan.planId.toString(), + layoutColorForUser: + layoutColor!, + role: "Admin"), ); }), ], @@ -869,7 +872,7 @@ class _ListAllPlansState extends State { showDialog( context: context, builder: (context) => - CommentModal( + CommentModalList( // planId: plan.planId, planId: plan.planId .toString(), diff --git a/lib/Screens/allTrips/remarks_list.dart b/lib/Screens/allTrips/remarks_list.dart new file mode 100644 index 0000000..3bf5e3c --- /dev/null +++ b/lib/Screens/allTrips/remarks_list.dart @@ -0,0 +1,197 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:intl/intl.dart'; + +import '../../config/apiUrl.dart'; +import '../../utils/auth_utils.dart'; + +class CommentModalList extends StatefulWidget { + final String planId; + final Color layoutColorForUser; + final String role; + + const CommentModalList({ + Key? key, + required this.planId, + required this.layoutColorForUser, + required this.role, + }) : super(key: key); + + @override + _CommentModalListState createState() => _CommentModalListState(); +} + +class _CommentModalListState extends State { + late Future>> _commentsFuture; + + Future>> fetchComments1() async { + final response = await http.get( + Uri.parse( + '$apiUrl/api/plans/getRemarksByPlanId?plan_id=${widget.planId}'), + ); + + if (response.statusCode == 200) { + final jsonData = json.decode(response.body); + final List dataList = jsonData['data']; + return dataList.cast>(); + } else { + throw Exception('Failed to load comments'); + } + } + + Future>> fetchComments() async { + // final String apiUrldata = + // '$apiUrl/getRemarksByPlanId?plan_id=${widget.planId}&user_id=$userId'; + + final String apiUrldata = + '$apiUrl/api/plans/getRemarksByPlanId?plan_id=${widget.planId}'; + + 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) { + final jsonData = json.decode(response.body); + final List dataList = jsonData['data']; + return dataList.cast>(); + } else { + throw Exception('Failed to load comments'); + } + } + + @override + void initState() { + super.initState(); + _commentsFuture = fetchComments(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: Colors.white, + title: Text( + 'Comments', + style: GoogleFonts.poppins(color: Colors.black), + ), + content: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 500, // ✅ You can adjust this width + maxHeight: + 400, // ✅ Optional: limit height to make it scrollable vertically + ), + child: FutureBuilder>>( + future: _commentsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError) { + return Text( + 'No Comments For This Plan', + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + ); + } + + if (!snapshot.hasData || snapshot.data!.isEmpty) { + return Text( + 'No comments found.', + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + ); + } + + final comments = snapshot.data!; + + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: DataTable( + columns: [ + DataColumn( + label: Text( + 'Name', + style: + GoogleFonts.poppins(fontSize: 11, color: Colors.black), + )), + DataColumn( + label: Text( + 'Comment', + style: + GoogleFonts.poppins(fontSize: 11, color: Colors.black), + )), + DataColumn( + label: Text( + 'Updated On', + style: + GoogleFonts.poppins(fontSize: 11, color: Colors.black), + )), + ], + rows: comments.map((comment) { + final name = comment['created_by_name'] ?? 'Unknown'; + final remark = comment['remarks'] ?? ''; + final rawDateStr = comment['updated_on']; + String updatedOn = ''; + + if (rawDateStr != null && rawDateStr.isNotEmpty) { + try { + final parsedDate = DateTime.parse(rawDateStr); + updatedOn = DateFormat('d MMM yyyy') + .format(parsedDate); // e.g., 15 May 2025 + } catch (e) { + updatedOn = rawDateStr.split(' ').first; // fallback + } + } + + return DataRow(cells: [ + DataCell(Text( + name, + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + fontWeight: FontWeight.w500), + )), + DataCell(Text( + remark, + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + fontWeight: FontWeight.w500), + )), + DataCell(Text( + updatedOn, + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + fontWeight: FontWeight.w500), + )), + ]); + }).toList(), + ), + ); + }, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text('Close', + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w500, + color: widget.layoutColorForUser)), + ), + ], + ); + } +} diff --git a/lib/Screens/approvals/approval_list.dart b/lib/Screens/approvals/approval_list.dart index 896e7f5..a6d84a6 100644 --- a/lib/Screens/approvals/approval_list.dart +++ b/lib/Screens/approvals/approval_list.dart @@ -18,6 +18,7 @@ import '../../utils/pagination.dart'; import '../../utils/travelAgent_remarks.dart'; import '../../widgets/custom_popup.dart'; import '../../widgets/popup_listPlan_action.dart'; +import '../allTrips/remarks_list.dart'; class ApprovalList extends StatefulWidget { const ApprovalList({super.key}); @@ -756,12 +757,14 @@ class _ApprovalListState extends State { onPressed: () { showDialog( context: context, - builder: (context) => CommentModal( - // planId: plan.planId, - planId: plan.planId.toString(), - layoutColorForUser: - layoutColor!, - role: "Approver"), + builder: (context) => + CommentModalList( + // planId: plan.planId, + planId: + plan.planId.toString(), + layoutColorForUser: + layoutColor!, + role: "Approver"), ); }), ], @@ -946,12 +949,14 @@ class _ApprovalListState extends State { onPressed: () { showDialog( context: context, - builder: (context) => CommentModal( - // planId: plan.planId, - planId: plan.planId.toString(), - layoutColorForUser: - layoutColor!, - role: "Approver"), + builder: (context) => + CommentModalList( + // planId: plan.planId, + planId: + plan.planId.toString(), + layoutColorForUser: + layoutColor!, + role: "Approver"), ); }), ], diff --git a/lib/Screens/itnerary/accomodations.dart b/lib/Screens/itnerary/accomodations.dart index f69890f..b931c87 100644 --- a/lib/Screens/itnerary/accomodations.dart +++ b/lib/Screens/itnerary/accomodations.dart @@ -13,13 +13,15 @@ class AccomodationScreen extends StatefulWidget { final Function(Map) onSaveAccomadation; final Map? selectedItem; final String? loginUser; + final String? tripType; AccomodationScreen( {required this.onClose, required this.onSaveAccomadation, required this.selectedItem, required this.loginUser, - required this.flightData}); + required this.flightData, + this.tripType}); @override _AccomodationScreenState createState() => _AccomodationScreenState(); @@ -293,7 +295,7 @@ class _AccomodationScreenState extends State { } Future loadCountryList() async { - final result = await apiService.fetchFlightsCountryList(); + final result = await apiService.fetchFlightsCountryList(widget.tripType); print("ResultCountry : $result"); diff --git a/lib/Screens/itnerary/flights.dart b/lib/Screens/itnerary/flights.dart index 3e72ee8..7db00f3 100644 --- a/lib/Screens/itnerary/flights.dart +++ b/lib/Screens/itnerary/flights.dart @@ -84,6 +84,8 @@ class _FlightScreenState extends State { void initState() { super.initState(); + print("TripType - ${widget.tripType}"); + // _initializeRows(); // List purposeList = widget.apiData?['flight_trip_type'] ?? []; // selectedTripType ??= purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null; @@ -206,7 +208,7 @@ class _FlightScreenState extends State { isCountryLoading = true; }); - final result = await apiService.fetchFlightsCountryList(); + final result = await apiService.fetchFlightsCountryList(widget.tripType); print("ResultCountry : $result"); diff --git a/lib/Screens/itnerary/train.dart b/lib/Screens/itnerary/train.dart index dba5e9b..5bb5f40 100644 --- a/lib/Screens/itnerary/train.dart +++ b/lib/Screens/itnerary/train.dart @@ -15,6 +15,7 @@ class TrainScreen extends StatefulWidget { final Function(bool) onClose; final Map? selectedItem; final String? loginUser; + final String? tripType; TrainScreen( {required this.onClose, @@ -22,7 +23,8 @@ class TrainScreen extends StatefulWidget { required this.onSavetrain, required this.selectedItem, required this.loginUser, - this.apiDataForClass}); + this.apiDataForClass, + this.tripType}); @override _TrainScreenState createState() => _TrainScreenState(); diff --git a/lib/Screens/itnerary_list/flight_list.dart b/lib/Screens/itnerary_list/flight_list.dart index 550d1ce..845ef9d 100644 --- a/lib/Screens/itnerary_list/flight_list.dart +++ b/lib/Screens/itnerary_list/flight_list.dart @@ -43,7 +43,7 @@ class _FlightListWidgetState extends State { } Future loadCountryList() async { - final result = await apiService.fetchFlightsCountryList(); + final result = await apiService.fetchFlightsCountryList(widget.tripType); print("ResultCountry : $result"); diff --git a/lib/Screens/plans/dynamic_itinerary_stepper.dart b/lib/Screens/plans/dynamic_itinerary_stepper.dart index 9520eeb..cf8bfc2 100644 --- a/lib/Screens/plans/dynamic_itinerary_stepper.dart +++ b/lib/Screens/plans/dynamic_itinerary_stepper.dart @@ -616,6 +616,7 @@ class DynamicItineraryState extends State { apiDataForClass: widget.apiDataForClass, loginUser: widget.loginUser, onSavetrain: (data) => handleItineraryUpdate("Train", data), + tripType: widget.tripType, selectedItem: selectedItem); break; case "Taxi": diff --git a/lib/Screens/plans/list_plans.dart b/lib/Screens/plans/list_plans.dart index a5b4a26..23f34f8 100644 --- a/lib/Screens/plans/list_plans.dart +++ b/lib/Screens/plans/list_plans.dart @@ -18,6 +18,7 @@ import '../../utils/pagination.dart'; import '../../utils/travelAgent_remarks.dart'; import '../../widgets/custom_popup.dart'; import '../../widgets/popup_listPlan_action.dart'; +import '../allTrips/remarks_list.dart'; class ListPlans extends StatefulWidget { const ListPlans({super.key}); @@ -822,12 +823,14 @@ class _ListPlansState extends State { onPressed: () { showDialog( context: context, - builder: (context) => CommentModal( - // planId: plan.planId, - planId: plan.planId.toString(), - layoutColorForUser: - layoutColor!, - role: "User"), + builder: (context) => + CommentModalList( + // planId: plan.planId, + planId: + plan.planId.toString(), + layoutColorForUser: + layoutColor!, + role: "User"), ); }), ], @@ -942,12 +945,14 @@ class _ListPlansState extends State { onPressed: () { showDialog( context: context, - builder: (context) => CommentModal( - // planId: plan.planId, - planId: plan.planId.toString(), - layoutColorForUser: - layoutColor!, - role: "User"), + builder: (context) => + CommentModalList( + // planId: plan.planId, + planId: + plan.planId.toString(), + layoutColorForUser: + layoutColor!, + role: "User"), ); }), ], diff --git a/lib/Screens/userManagement/create_user/create_user.dart b/lib/Screens/userManagement/create_user/create_user.dart index c664468..6d54a6d 100644 --- a/lib/Screens/userManagement/create_user/create_user.dart +++ b/lib/Screens/userManagement/create_user/create_user.dart @@ -31,6 +31,8 @@ import '../../../widgets/custom_text_forex.dart'; import '../../../widgets/custom_user_form.dart'; import 'office_details.dart'; +import 'package:universal_html/html.dart' as html; + class CreateUserFormDetials extends StatefulWidget { @override _CreateUserFormDetialsState createState() => _CreateUserFormDetialsState(); @@ -44,7 +46,9 @@ class _CreateUserFormDetialsState extends State { final GlobalKey travellerDetailsKey = GlobalKey(); + // late List?> travelDetailsData; Map? travelDetailsData; + Map? travelDetailsDataFromAPI; late TabController _tabController; @@ -172,6 +176,8 @@ class _CreateUserFormDetialsState extends State { } void handleUserTypeChange(bool isTravelAgent) { + print("handleUserTypeChange- $isTravelAgent"); + setState(() { setSelectesUserType = isTravelAgent; }); @@ -186,6 +192,8 @@ class _CreateUserFormDetialsState extends State { if (apiselectedUser != null && apiselectedUser!.isNotEmpty) { isapiselectedUser = true; + + print("API Selected User Has Data - $apiselectedUser"); } controllers["Fname"]?.text = apiselectedUser?["first_name"] ?? ""; @@ -265,18 +273,17 @@ class _CreateUserFormDetialsState extends State { print("Updated selectedGender: $selectedGender"); // Debugging - // ✅ Load passport document from API - String? apiDocPath = apiselectedUser?["passport_document"]; - if (apiDocPath != null && apiDocPath.isNotEmpty) { - passportFileUrlFromApi = apiDocPath; - selectedFileNames = - apiDocPath.split('/').last; // Extract filename from path - passportFile = null; // No local file selected yet - } else { - passportFileUrlFromApi = null; - selectedFileNames = null; - passportFile = null; - } + travelDetailsDataFromAPI = apiselectedUser?["travel_details"]; + + print("travelDetailsDataFromAPI00 - $travelDetailsDataFromAPI"); + // if (travelDetails != null) { + // WidgetsBinding.instance.addPostFrameCallback((_) { + // // Safe to call updateTravel now + // travellerDetailsKey.currentState?.updateTravel(); + // }); + // } + + // Optionally load other travel-related fields like meal/seat preferences, etc. }); } else { print("API Selected User Has Data - No data available yet"); @@ -288,15 +295,13 @@ class _CreateUserFormDetialsState extends State { super.initState(); selectedTab = "personal"; - WidgetsFlutterBinding.ensureInitialized(); + // WidgetsFlutterBinding.ensureInitialized(); // Step 1: Set 'reloaded' flag before page unload - html.window.onBeforeUnload.listen((event) { - html.window.localStorage['reloaded'] = 'true'; - }); + // html.window.onBeforeUnload.listen((event) { + // html.window.localStorage['reloaded'] = 'true'; + // }); - // apiCountryData = extraData['apiCountryData']; // Extract apiCountryData - // futureUsers = extraData['apiUserData']; // Extract futureUsers (Future>) apiCountryData = null; apiUserData = null; // apiselectedUser = null; @@ -306,12 +311,12 @@ class _CreateUserFormDetialsState extends State { // Initialize controllers for each field WidgetsBinding.instance.addPostFrameCallback((_) async { - final wasReloaded = html.window.localStorage['reloaded'] == 'true'; - - if (wasReloaded) { - html.window.localStorage.remove('reloaded'); // Clear it - context.go('/listUser'); // Navigate using go_router - } + // final wasReloaded = html.window.localStorage['reloaded'] == 'true'; + // + // if (wasReloaded) { + // html.window.localStorage.remove('reloaded'); // Clear it + // context.go('/listUser'); // Navigate using go_router + // } final extraData = GoRouterState.of(context).extra as Map?; @@ -484,9 +489,11 @@ class _CreateUserFormDetialsState extends State { } void handleNext() async { - print("USR Detail Submit"); + print("USR Detail Next"); printFormData(); + // travelDetailsData = [travellerDetailsKey.currentState?.travel_Detials]; + travelDetailsData = travellerDetailsKey.currentState?.travel_Detials; print("TRAVEL DETAILS FROM CHILD: $travelDetailsData"); @@ -528,11 +535,15 @@ class _CreateUserFormDetialsState extends State { void handleSubmit() async { print("USR Detail Submit"); - printFormData(); - + // printFormData(); travelDetailsData = travellerDetailsKey.currentState?.travel_Detials; - print("TRAVEL DETAILS FROM CHILD: $travelDetailsData"); + print("TRAVEL DETAILS FROM CHILD"); + // print("TRAVEL DETAILS FROM CHILD: $travelDetailsData"); + + passportFile = travellerDetailsKey.currentState?.passportFile; + + print("passportFile : $passportFile"); Map data = userDetials; @@ -665,9 +676,21 @@ class _CreateUserFormDetialsState extends State { } // Add all non-null and non-empty user data fields + // userData.forEach((key, value) { + // if (value != null && value.toString().trim().isNotEmpty) { + // request.fields[key] = value.toString(); + // } + // }); + userData.forEach((key, value) { if (value != null && value.toString().trim().isNotEmpty) { - request.fields[key] = value.toString(); + if (key == 'travel_details' && value is Map) { + // ✅ Encode travel_details as a proper JSON string + request.fields[key] = jsonEncode(value); + print("✅ Encoded travel_details: ${request.fields[key]}"); + } else { + request.fields[key] = value.toString(); + } } }); @@ -801,6 +824,8 @@ class _CreateUserFormDetialsState extends State { ? Row( mainAxisAlignment: MainAxisAlignment.end, children: (selectedTab == "travel" || + selectedRole == "5" || + isEditProfile || setSelectesUserType == true) ? _buildSubmit(isDesktop, layoutColor!) : _buildNext(isDesktop, layoutColor!), @@ -808,6 +833,7 @@ class _CreateUserFormDetialsState extends State { : Row( mainAxisAlignment: MainAxisAlignment.center, children: (selectedTab == "travel" || + selectedRole == "5" || setSelectesUserType == true) ? _buildSubmit(isDesktop, layoutColor!) : _buildNext(isDesktop, layoutColor!), @@ -827,16 +853,32 @@ class _CreateUserFormDetialsState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - apiselectedUser != null ? "Profile" : "Create New User", - style: GoogleFonts.poppins( - fontSize: isDesktop ? 15 : 12, - fontWeight: FontWeight.w600, - color: Colors.black, - ), + Row( + children: [ + Text( + apiselectedUser != null ? "Profile" : "Create New User", + style: GoogleFonts.poppins( + fontSize: isDesktop ? 15 : 12, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + if (isEditProfile) + IconButton( + icon: Icon( + Icons.edit, + color: Color(0xFF114D8B), + size: 14, + ), + onPressed: () { + setState(() { + isViewMode = !isViewMode; + }); + }) + ], ), SizedBox( - height: 20, + height: 18, ), isDesktop ? buildTabsForUser() @@ -935,6 +977,8 @@ class _CreateUserFormDetialsState extends State { isViewMode: isViewMode, controllers: controllers, errorMessages: errorMessages, + travelDetails: travelDetailsDataFromAPI, // 👈 Pass this down + passportFileUrlFromApi: passportFileUrlFromApi, ); default: return PersonalDetails( @@ -952,12 +996,30 @@ class _CreateUserFormDetialsState extends State { } Widget buildTabsForUser() { - final tabs = { + // final tabs = { + // "personal": "Personal Details", + // "office": "Office Details", + // "travel": "Travel Details", + // }; + + final Map allTabs = { "personal": "Personal Details", "office": "Office Details", "travel": "Travel Details", }; + Map getTabs(bool setSelectesUserType) { + if (setSelectesUserType || selectedRole == "5") { + return { + "personal": "Personal Details", + }; + } else { + return allTabs; + } + } + + final tabs = getTabs(setSelectesUserType); + return Row( crossAxisAlignment: CrossAxisAlignment.end, // important children: tabs.entries.map((entry) { @@ -1042,7 +1104,7 @@ class _CreateUserFormDetialsState extends State { onPressed: () { isEditProfile ? context.go('/listPlan') : context.go('/listUser'); }, - child: isViewMode ? Text("Back") : Text("Cancel")), + child: Text("Cancel")), SizedBox( width: 20, ), diff --git a/lib/Screens/userManagement/create_user/personal_details.dart b/lib/Screens/userManagement/create_user/personal_details.dart index 5e7ae03..eec4137 100644 --- a/lib/Screens/userManagement/create_user/personal_details.dart +++ b/lib/Screens/userManagement/create_user/personal_details.dart @@ -150,6 +150,8 @@ class PersonalDetailsState extends State { if (response is Map && response.containsKey("role")) { List roleList = response["role"]; // Extract the list + + if (!mounted) return; setState(() { apiRoleData = roleList; }); @@ -1109,7 +1111,8 @@ class PersonalDetailsState extends State { child: SizedBox( height: 45, // Set appropriate height child: DropdownButtonFormField( - value: widget.isViewMode ? null : selectedRole, + // value: widget.isViewMode ? null : selectedRole, + value: selectedRole, style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), decoration: InputDecoration( border: InputBorder.none, @@ -1121,7 +1124,7 @@ class PersonalDetailsState extends State { : (newValue) { setState(() { selectedRole = newValue; - isTravelAgent = selectedRole == "Travel Agent"; + isTravelAgent = selectedRole == "5"; // Pass the result back to parent widget.onUserTypeChanged?.call(isTravelAgent); }); diff --git a/lib/Screens/userManagement/create_user/traveller_details.dart b/lib/Screens/userManagement/create_user/traveller_details.dart index 7d481a6..400a152 100644 --- a/lib/Screens/userManagement/create_user/traveller_details.dart +++ b/lib/Screens/userManagement/create_user/traveller_details.dart @@ -5,6 +5,7 @@ import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:frontend/utils/auth_utils.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:universal_html/html.dart' as html; @@ -15,6 +16,8 @@ import 'dart:typed_data'; // Import for Uint8List import 'package:flutter/foundation.dart'; +import '../../../config/apiUrl.dart'; +import '../../../services/apiService.dart'; import '../../../widgets/custom_user_form.dart'; import '../../../widgets/custom_user_form.dart'; @@ -26,6 +29,8 @@ class TravellerDetails extends StatefulWidget { final Map errorMessages; final bool isViewMode; + final Map? travelDetails; + final String? passportFileUrlFromApi; const TravellerDetails({ Key? key, @@ -33,22 +38,26 @@ class TravellerDetails extends StatefulWidget { required this.errorMessages, required this.isDesktop, required this.isViewMode, + this.travelDetails, + this.passportFileUrlFromApi, }) : super(key: key); @override TravellerDetailsState createState() => TravellerDetailsState(); } class TravellerDetailsState extends State { + final ApiService apiService = ApiService(); html.File? passportFile; late List isExpandedList; int? expandedIndex; + late final userId; String? selectedFileNames; Uint8List? passportDocumentBytes; String? passportFileUrlFromApi; - String? selectedPurpose; - String? selectedCountry; + // String? selectedPurpose; + // String? selectedCountry; String? selectedLocalIdType; List countryList = []; @@ -56,14 +65,21 @@ class TravellerDetailsState extends State { String? selectedIntenationalSeat; String? SelectedIdType; - List frequentFlierRowIds = []; - int _rowFlierCounter = 1; - List hotelLoyalityRowIds = []; + Map airlineControllers = {}; + Map flierNumberControllers = {}; + + List> hotelLoyaltyEntries = []; int _rowhotelCounter = 1; - List visaRowIds = []; + + List> frequentFlierEntries = []; + int _rowFlierCounter = 1; + + List> visaEntries = []; int _rowVisaCounter = 1; Map countryMap = {}; + late List? apiCountryData; + Map? apiData; final Map controllers = {}; final List headers = [ @@ -101,16 +117,23 @@ class TravellerDetailsState extends State { void initState() { super.initState(); + // userId = getUserId(); + + apiCountryData = null; + apiData = null; for (var field in dataHeader) { controllers[field] = TextEditingController(); } + fetchCountries(); + fetchApiData(); // Delay adding the row until after the first frame WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { - frequentFlierRowIds.add(_rowFlierCounter++); - hotelLoyalityRowIds.add(_rowhotelCounter++); - visaRowIds.add(_rowVisaCounter++); + addHotelLoyaltyEntry(); + addFrequentFlierEntry(); + addVisaEntry(); }); + updateTravel(); }); } @@ -120,34 +143,360 @@ class TravellerDetailsState extends State { }); } +//-------------------------Hotel Loyalty -------------------------------- + void addHotelLoyaltyEntry() { + final localId = _rowhotelCounter++; + hotelLoyaltyEntries.add({ + "local_id": localId, + "id": null, // to be set when backend responds + "controller_hotel": TextEditingController(), + "controller_membership": TextEditingController(), + "is_active": "1" + }); + setState(() {}); + } + + void removeHotelLoyaltyEntry(Map entry) { + print("removeHotelLoyaltyEntry- $entry"); + + setState(() { + entry["is_active"] = "0"; + }); + } +//-------------------------Frequent Flier-------------------------------- + + void addFrequentFlierEntry() { + final localId = _rowFlierCounter++; + frequentFlierEntries.add({ + "local_id": localId, + "id": null, // to be set when backend responds + "controller_airline": TextEditingController(), + "controller_flier_number": TextEditingController(), + "is_active": "1" + }); + setState(() {}); + } + + void removeFrequentFlierEntry(Map entry) { + print("removeFrequentFlierEntrydd- $entry"); + + setState(() { + entry["is_active"] = "0"; + }); + } + + //-------------------------Visa -------------------------------- + + void addVisaEntry() { + final localId = _rowVisaCounter++; + visaEntries.add({ + "local_id": localId, + "id": null, // to be set when backend responds + "country_code": null, + "visa_type_id": null, + "controller_valid_from": TextEditingController(), + "controller_valid_upto": TextEditingController(), + + "is_active": "1" + }); + setState(() {}); + } + + void removeVisaEntry(Map entry) { + print("removeFrequentFlierEntrydd- $entry"); + + setState(() { + entry["is_active"] = "0"; + }); + } + + @override + void dispose() { + for (var entry in hotelLoyaltyEntries) { + entry["controller_hotel"].dispose(); + entry["controller_membership"].dispose(); + } + + for (var entry in frequentFlierEntries) { + entry["controller_airline"].dispose(); + entry["controller_flier_number"].dispose(); + } + + for (var entry in visaEntries) { + entry["controller_valid_from"].dispose(); + entry["controller_valid_upto"].dispose(); + } + super.dispose(); + } + Map get travel_Detials { Map data = { - "given_Name": controllers["Fname"]?.text, - "last_Name": controllers["Lname"]?.text, - "nationality": controllers["nationality"]?.text, - "passport_number": controllers["passportNumber"]?.text, + // "given_Name": controllers["Fname"]?.text, + // "last_Name": controllers["Lname"]?.text, + // "nationality": controllers["nationality"]?.text, + "passport_number": controllers["passportNumber"]?.text ?? '', "place_of_issue": controllers["placeOfIssue"]?.text, - "passport_document": passportFile, "date_of_issue": controllers["dateOfIssue"]?.text, "date_of_expiry": controllers["dateOfExpiry"]?.text, "d_meal_preference": controllers["d_meal_pref"]?.text, "d_seat_preference": selectedDomesticSeat, - "d_additonalInfo": controllers["d_additonal_Info"]?.text, + // "d_additonalInfo": controllers["d_additonal_Info"]?.text, "i_meal_preference": controllers["i_meal_pref"]?.text, "i_seat_preference": selectedIntenationalSeat, - "i_additonalInfo": controllers["i_additonal_Info"]?.text, + // "i_additonalInfo": controllers["i_additonal_Info"]?.text, "emergency_contact_number": controllers["emergency_contact"]?.text, "forex_pre_paid_card_number": controllers["forex_card_num"]?.text, - "frequent_flier_information": [], - "visa_details": [], - "hotel_membership": [], - "local_id_type": selectedLocalIdType, - "local_id_num": controllers["local_id_num"]?.text, - "full_name_as_id": controllers["full_name_as_id"]?.text, + // "frequent_flier_information": [], + "frequent_flier_information": frequentFlierList, + "hotel_membership": hotelMembershipList, + "visa_details": visaDetailsList, + // "local_id_type": selectedLocalIdType, + // "local_id_num": controllers["local_id_num"]?.text, + // "full_name_as_id": controllers["full_name_as_id"]?.text, }; return data; } + List> get hotelMembershipList { + return hotelLoyaltyEntries.map((entry) { + return { + "id": entry["id"], + "hotel_name": entry["controller_hotel"].text ?? '', + "membership_number": entry["controller_membership"].text ?? '', + "created_by": null, + "updated_by": null, + "is_active": entry["is_active"] ?? "1", + }; + }).toList(); + } + + List> get frequentFlierList { + return frequentFlierEntries + // .where((entry) => entry["is_active"] != "0") // ✅ Only active entries + .map((entry) { + return { + "id": entry["id"], + "airline": entry["controller_airline"].text, + "frequent_flier_number": entry["controller_flier_number"].text, + "created_by": null, + "updated_by": null, + "is_active": entry["is_active"] ?? "1", + }; + }).toList(); + } + + // List> get visaDetailsList { + // return visaEntries + // // .where((entry) => entry["is_active"] != "0") // ✅ Only active entries + // .map((entry) { + // return { + // "id": entry["id"], + // "country_code": entry["country_code"], + // "visa_type_id": entry["visa_type_id"], + // "valid_from": entry["controller_valid_from"].text, + // "valid_upto": entry["controller_valid_upto"].text, + // // "valid_from": entry["controller_valid_from"] != null + // // ? entry["controller_valid_from"].text + // // : "", + // // "valid_upto": entry["controller_valid_upto"] != null + // // ? entry["controller_valid_upto"].text + // // : "", + // "created_by": null, + // "updated_by": null, + // "is_active": entry["is_active"] ?? "1", + // }; + // }).toList(); + // } + + List> get visaDetailsList { + return visaEntries.map((entry) { + final fromController = entry["controller_valid_from"]; + final uptoController = entry["controller_valid_upto"]; + + return { + "id": entry["id"], + "country_code": entry["country_code"], // ✅ string value + "visa_type_id": entry["visa_type_id"], // ✅ string value + "valid_from": + fromController is TextEditingController ? fromController.text : "", + "valid_upto": + uptoController is TextEditingController ? uptoController.text : "", + "created_by": null, + "updated_by": null, + "is_active": entry["is_active"] ?? "1", + }; + }).toList(); + } + + void updateTravel() { + print("widget.travelDetails"); + print(widget.travelDetails); + if (widget.travelDetails != null) { + print("Updatewidget.travelDetails"); + print(widget.travelDetails); + + controllers["passportNumber"]?.text = + widget.travelDetails?["passport_number"] ?? ""; + controllers["placeOfIssue"]?.text = + widget.travelDetails?["place_of_issue"] ?? ""; + controllers["dateOfIssue"]?.text = + widget.travelDetails?["date_of_issue"] ?? ""; + controllers["dateOfExpiry"]?.text = + widget.travelDetails?["date_of_expiry"] ?? ""; + + controllers["d_meal_pref"]?.text = + widget.travelDetails?["d_meal_preference"] ?? ""; + + controllers["i_meal_pref"]?.text = + widget.travelDetails?["i_meal_preference"] ?? ""; + + selectedIntenationalSeat = + widget.travelDetails?["i_seat_preference"] ?? ""; + selectedDomesticSeat = widget.travelDetails?["d_seat_preference"] ?? ""; + + controllers["emergency_contact"]?.text = + widget.travelDetails?["emergency_contact_number"] ?? ""; + + controllers["forex_card_num"]?.text = + widget.travelDetails?["forex_pre_paid_card_number"] ?? ""; + + String? apiDocPath = widget.travelDetails?["passport_document"]; + if (apiDocPath != null && apiDocPath.isNotEmpty) { + passportFileUrlFromApi = apiDocPath; + selectedFileNames = apiDocPath.split('/').last; + passportFile = null; + } else { + passportFileUrlFromApi = null; + selectedFileNames = null; + passportFile = null; + } + + //---------------------------------------------------- + + // 🚨 Hotel Membership Logic (pre-fill for edit) + for (var entry in hotelLoyaltyEntries) { + entry["controller_hotel"].dispose(); + entry["controller_membership"].dispose(); + } + hotelLoyaltyEntries.clear(); + + final hotelMembershipList = widget.travelDetails?['hotel_membership']; + if (hotelMembershipList != null && hotelMembershipList is List) { + for (var item in hotelMembershipList) { + if (item["is_active"]?.toString() == "0") continue; + + final localId = _rowhotelCounter++; + hotelLoyaltyEntries.add({ + "local_id": localId, + "id": item["id"], + "controller_hotel": + TextEditingController(text: item["hotel_name"] ?? ""), + "controller_membership": + TextEditingController(text: item["membership_number"] ?? ""), + }); + } + + if (hotelLoyaltyEntries.isEmpty) { + addHotelLoyaltyEntry(); + } + } + + //---------------------------------------------------- + + // 🚨 Frequent Flier Logic (pre-fill for edit) + for (var entry in frequentFlierEntries) { + entry["controller_airline"].dispose(); + entry["controller_flier_number"].dispose(); + } + frequentFlierEntries.clear(); + + final frequentFlierList = + widget.travelDetails?['frequent_flier_information']; + if (frequentFlierList != null && frequentFlierList is List) { + for (var item in frequentFlierList) { + if (item["is_active"]?.toString() == "0") continue; + + final localId = _rowFlierCounter++; + frequentFlierEntries.add({ + "local_id": localId, + "id": item["id"], + "controller_airline": + TextEditingController(text: item["airline"] ?? ""), + "controller_flier_number": TextEditingController( + text: item["frequent_flier_number"] ?? ""), + }); + } + + if (frequentFlierEntries.isEmpty) { + addFrequentFlierEntry(); + } + } + + //---------------------------------------------------- + + // 🚨 Visa Logic (pre-fill for edit) + for (var entry in visaEntries) { + entry["controller_valid_from"]?.dispose(); + entry["controller_valid_upto"]?.dispose(); + } + visaEntries.clear(); + + final visaDetailsList = widget.travelDetails?['visa_details']; + if (visaDetailsList != null && visaDetailsList is List) { + for (var item in visaDetailsList) { + if (item["is_active"]?.toString() == "0") continue; + + final localId = _rowVisaCounter++; + visaEntries.add({ + "local_id": localId, + "id": item["id"], + "country_code": item["country_code"], + "visa_type_id": item["visa_type_id"], + "controller_valid_from": + TextEditingController(text: item["valid_from"] ?? ""), + "controller_valid_upto": + TextEditingController(text: item["valid_upto"] ?? ""), + }); + } + + if (visaEntries.isEmpty) { + addVisaEntry(); + } + } + + setState(() {}); + } + } + + Future fetchCountries() async { + try { + List countries = await apiService.fetchCountryList(); + setState(() { + apiCountryData = countries; + }); + } catch (e) { + print('Error fetching country list: $e'); + } + } + + Future fetchApiData() async { + try { + final data = await apiService.fetchMasterDropdown(); + + print("fetchApiData - $data"); + + if (data is! Map) { + throw Exception("Invalid response format: expected a Map"); + } + if (!mounted) return; + setState(() { + apiData = data; + }); + } catch (e) { + print('Error fetching role list: $e'); + } + } + @override Widget build(BuildContext context) { return Container( @@ -627,7 +976,7 @@ class TravellerDetailsState extends State { SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( width: widget.isDesktop - ? MediaQuery.of(context).size.width * 0.11 + ? MediaQuery.of(context).size.width * 0.13 : null, isFocused: false, isDesktop: widget.isDesktop, @@ -673,6 +1022,7 @@ class TravellerDetailsState extends State { 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? pickedDate = await showDatePicker( context: context, @@ -689,6 +1039,9 @@ class TravellerDetailsState extends State { _selectedDateOfExpiry = pickedDate; controllers["dateOfExpiry"]?.text = DateFormat('yyyy-MM-dd').format(pickedDate); + + // controllers["dateOfExpiry"]?.text = + // DateFormat('dd-MM-yyyy').format(pickedDate); }); } } @@ -706,7 +1059,7 @@ class TravellerDetailsState extends State { isFocused: false, isDesktop: widget.isDesktop, width: widget.isDesktop - ? MediaQuery.of(context).size.width * 0.11 + ? MediaQuery.of(context).size.width * 0.13 : null, child: SizedBox( height: 40, @@ -824,24 +1177,39 @@ class TravellerDetailsState extends State { if (passportFile != null) { try { // ✅ Step 1: Create a Blob directly from the file - final blob = - html.Blob([passportFile!], 'application/pdf'); + // final blob = + // html.Blob([passportFile!], 'application/pdf'); + // + // // ✅ Step 2: Generate a download URL from the Blob + // final url = html.Url.createObjectUrlFromBlob(blob); + // + // // ✅ Step 3: Create an invisible anchor to trigger download + // final anchor = html.AnchorElement(href: url) + // ..setAttribute( + // "download", selectedFileNames ?? "document.pdf") + // ..style.display = "none"; + // + // // ✅ Step 4: Add anchor to DOM and click it + // html.document.body!.append(anchor); + // anchor.click(); + // + // // ✅ Step 5: Clean up + // anchor.remove(); + // html.Url.revokeObjectUrl(url); - // ✅ Step 2: Generate a download URL from the Blob + // Create a blob from the response body + final blob = html.Blob([passportFile!]); + +// Generate a download URL for the blob final url = html.Url.createObjectUrlFromBlob(blob); - // ✅ Step 3: Create an invisible anchor to trigger download + // Create a link element to trigger the download final anchor = html.AnchorElement(href: url) ..setAttribute( - "download", selectedFileNames ?? "document.pdf") - ..style.display = "none"; + 'download', selectedFileNames ?? "document.pdf") + ..click(); - // ✅ Step 4: Add anchor to DOM and click it - html.document.body!.append(anchor); - anchor.click(); - - // ✅ Step 5: Clean up - anchor.remove(); + // Revoke the download URL to free up resources html.Url.revokeObjectUrl(url); print("Download triggered successfully!"); @@ -849,12 +1217,23 @@ class TravellerDetailsState extends State { print("Error during download: $e"); } } else if (passportFileUrlFromApi != null) { - // Trigger file download from the server path - final anchor = - html.AnchorElement(href: passportFileUrlFromApi!) - ..target = 'blank' - ..download = selectedFileNames ?? "document.pdf" - ..click(); + print("Raw file path: $passportFileUrlFromApi"); + + // Extract public part of the path starting from "/assets" + String cleanedPath = passportFileUrlFromApi!; + final index = passportFileUrlFromApi!.indexOf("/assets"); + if (index != -1) { + cleanedPath = passportFileUrlFromApi!.substring(index); + } + + final fullUrl = + 'https://apitest.tripapprovaltool.com$cleanedPath'; + print("Final download URL: $fullUrl"); + + final anchor = html.AnchorElement(href: fullUrl) + ..target = '_blank' + ..download = selectedFileNames ?? "document.pdf" + ..click(); } else { print("No file available to download."); } @@ -1118,8 +1497,8 @@ class TravellerDetailsState extends State { buildDomesticSeatType(), SizedBox(width: 15), buildDomesticMealPref(), - SizedBox(width: 15), - buildDomesticAddtnlPref(), + // SizedBox(width: 15), + // buildDomesticAddtnlPref(), ], ) : Column( @@ -1128,9 +1507,9 @@ class TravellerDetailsState extends State { buildDomesticSeatType(), SizedBox(height: 8), buildDomesticMealPref(), - SizedBox(height: 8), // Vertical space - - buildDomesticAddtnlPref(), + // SizedBox(height: 8), // Vertical space + // + // buildDomesticAddtnlPref(), ], ), ); @@ -1323,8 +1702,8 @@ class TravellerDetailsState extends State { buildInternationalSeatType(), SizedBox(width: 15), buildInternationalMealPref(), - SizedBox(width: 15), - buildInternationalAddtnlPref(), + // SizedBox(width: 15), + // buildInternationalAddtnlPref(), ], ) : Column( @@ -1333,9 +1712,9 @@ class TravellerDetailsState extends State { buildInternationalSeatType(), SizedBox(height: 8), buildInternationalMealPref(), - SizedBox(height: 8), // Vertical space - - buildInternationalAddtnlPref(), + // SizedBox(height: 8), // Vertical space + // + // buildInternationalAddtnlPref(), ], ), ); @@ -1616,15 +1995,17 @@ class TravellerDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - ...frequentFlierRowIds.map( - (id) => _buildFrequentAirlineDataRow1(id, isDesktop), - ), + ...frequentFlierEntries + .where((e) => e["is_active"] != "0") + .map((e) => _buildFrequentAirlineDataRow1(e, isDesktop)), IconForAirlineMem(), ], ); } - Widget _buildFrequentAirlineDataRow1(int id, bool isDesktop) { + Widget _buildFrequentAirlineDataRow1( + Map entry, bool isDesktop) { + int localId = entry["local_id"]; return Container( color: Colors.white, padding: const EdgeInsets.only(top: 8.0), @@ -1632,9 +2013,9 @@ class TravellerDetailsState extends State { ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - buildAirline(), + buildAirline(entry), SizedBox(width: 25), - buildFrequentFlierInformation(), + buildFrequentFlierInformation(entry), SizedBox(width: 15), IconButton( icon: Icon( @@ -1644,7 +2025,7 @@ class TravellerDetailsState extends State { ), onPressed: () { setState(() { - frequentFlierRowIds.remove(id); + removeFrequentFlierEntry(entry); }); }, ), @@ -1653,9 +2034,9 @@ class TravellerDetailsState extends State { : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - buildAirline(), + buildAirline(entry), SizedBox(height: 8), - buildFrequentFlierInformation(), + buildFrequentFlierInformation(entry), SizedBox(width: 15), IconButton( icon: Icon( @@ -1665,7 +2046,7 @@ class TravellerDetailsState extends State { ), onPressed: () { setState(() { - frequentFlierRowIds.remove(id); + removeFrequentFlierEntry(entry); }); }, ), @@ -1674,7 +2055,7 @@ class TravellerDetailsState extends State { ); } - Widget buildAirline() { + Widget buildAirline(Map entry) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1694,7 +2075,7 @@ class TravellerDetailsState extends State { height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), - controller: widget.controllers["Fname"], + controller: entry["controller_airline"], enabled: !widget.isViewMode, onChanged: (value) { _clearError("first_name"); @@ -1714,7 +2095,9 @@ class TravellerDetailsState extends State { ); } - Widget buildFrequentFlierInformation() { + Widget buildFrequentFlierInformation( + Map entry, + ) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1731,7 +2114,7 @@ class TravellerDetailsState extends State { height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), - controller: widget.controllers["Fname"], + controller: entry["controller_flier_number"], enabled: !widget.isViewMode, onChanged: (value) { _clearError("first_name"); @@ -1761,31 +2144,33 @@ class TravellerDetailsState extends State { color: Color(0xFF114D8B), size: 30, ), - onPressed: () { - setState(() { - frequentFlierRowIds.add(_rowFlierCounter++); - }); - }, - tooltip: "Hotel Loyalty Membership", + onPressed: () => addFrequentFlierEntry(), + tooltip: "Frequent Flier Information", ), ); } -//-------------------------Frequent Loyalty MemberShip ------------------------------- +//-------------------------Frequent Loyalty MemberShip Hotels ------------------------------- Widget _buildHotelLoyalty(bool isDesktop) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - ...hotelLoyalityRowIds.map( - (id) => _buildFrequentLoyaltyDataRow1(id, isDesktop), - ), + // ...hotelLoyaltyEntries.map((entry) { + // return _buildFrequentLoyaltyDataRow1(entry, isDesktop); + // }), + ...hotelLoyaltyEntries + .where((e) => e["is_active"] != "0") + .map((e) => _buildFrequentLoyaltyDataRow1(e, isDesktop)), + IconForLoyaltyMem(), ], ); } - Widget _buildFrequentLoyaltyDataRow1(int id, bool isDesktop) { + Widget _buildFrequentLoyaltyDataRow1( + Map entry, bool isDesktop) { + int localId = entry["local_id"]; return Container( padding: const EdgeInsets.only(top: 8.0), color: Colors.white, @@ -1793,9 +2178,9 @@ class TravellerDetailsState extends State { ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - buildHotel(), + buildHotel(entry), SizedBox(width: 25), - buildHotelMembershipNum(), + buildHotelMembershipNum(entry), IconButton( icon: Icon( Icons.cancel, @@ -1804,7 +2189,8 @@ class TravellerDetailsState extends State { ), onPressed: () { setState(() { - hotelLoyalityRowIds.remove(id); + // removeHotelLoyaltyEntry(localId); + removeHotelLoyaltyEntry(entry); }); }, ), @@ -1813,9 +2199,9 @@ class TravellerDetailsState extends State { : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - buildHotel(), + buildHotel(entry), SizedBox(height: 8), - buildHotelMembershipNum(), + buildHotelMembershipNum(entry), IconButton( icon: Icon( Icons.cancel, @@ -1824,7 +2210,8 @@ class TravellerDetailsState extends State { ), onPressed: () { setState(() { - hotelLoyalityRowIds.remove(id); + // removeHotelLoyaltyEntry(localId); + removeHotelLoyaltyEntry(entry); }); }, ), @@ -1833,7 +2220,7 @@ class TravellerDetailsState extends State { ); } - Widget buildHotel() { + Widget buildHotel(Map entry) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1853,7 +2240,7 @@ class TravellerDetailsState extends State { height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), - controller: widget.controllers["Fname"], + controller: entry["controller_hotel"], enabled: !widget.isViewMode, onChanged: (value) { _clearError("first_name"); @@ -1873,7 +2260,7 @@ class TravellerDetailsState extends State { ); } - Widget buildHotelMembershipNum() { + Widget buildHotelMembershipNum(Map entry) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1890,7 +2277,7 @@ class TravellerDetailsState extends State { height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), - controller: widget.controllers["Fname"], + controller: entry["controller_membership"], enabled: !widget.isViewMode, onChanged: (value) { _clearError("first_name"); @@ -1920,12 +2307,8 @@ class TravellerDetailsState extends State { color: Color(0xFF114D8B), size: 30, ), - onPressed: () { - setState(() { - hotelLoyalityRowIds.add(_rowhotelCounter++); - }); - }, - tooltip: "Hotel Loyalty Membership", + onPressed: () => addHotelLoyaltyEntry(), + tooltip: "Add Hotel Loyalty Membership", ), ); } @@ -1939,15 +2322,18 @@ class TravellerDetailsState extends State { SizedBox( height: 10, ), - ...visaRowIds.map( - (id) => _buildVisaDataRow1(id, isDesktop), - ), + + ...visaEntries + .where((e) => e["is_active"] != "0") + .map((e) => _buildVisaDataRow1(e, isDesktop)), + + // _buildVisaDataRow1(isDesktop), IconForVisa() ], ); } - Widget _buildVisaDataRow1(int id, bool isDesktop) { + Widget _buildVisaDataRow1(Map entry, bool isDesktop) { return Container( padding: const EdgeInsets.only(top: 8), color: Colors.white, @@ -1955,13 +2341,13 @@ class TravellerDetailsState extends State { ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - buildVisaCountry(), + buildVisaCountry(entry), SizedBox(width: 25), - buildVisaType(), + buildVisaType(entry), SizedBox(width: 15), - buildVisaValidFrom(), + buildVisaValidFrom(entry), SizedBox(width: 15), - buildVisaValidUpTo(), + buildVisaValidUpTo(entry), IconButton( icon: Icon( Icons.cancel, @@ -1970,7 +2356,7 @@ class TravellerDetailsState extends State { ), onPressed: () { setState(() { - visaRowIds.remove(id); + removeVisaEntry(entry); }); }, ), @@ -1979,13 +2365,13 @@ class TravellerDetailsState extends State { : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - buildVisaCountry(), + buildVisaCountry(entry), SizedBox(height: 8), - buildVisaType(), + buildVisaType(entry), SizedBox(height: 8), - buildVisaValidFrom(), + buildVisaValidFrom(entry), SizedBox(height: 8), - buildVisaValidUpTo(), + buildVisaValidUpTo(entry), IconButton( icon: Icon( Icons.cancel, @@ -1994,7 +2380,8 @@ class TravellerDetailsState extends State { ), onPressed: () { setState(() { - visaRowIds.remove(id); + // visaRowIds.remove(id); + removeVisaEntry(entry); }); }, ), @@ -2003,12 +2390,12 @@ class TravellerDetailsState extends State { ); } - Widget buildVisaCountry() { + Widget buildVisaCountry(entry) { late Map countryMap; // Mapping country_code -> country_name late List countryCodes; // List of country codes - countryList = []; - // countryList = apiCountryData ?? []; + // countryList = []; + countryList = apiCountryData ?? []; // Map country codes to country names countryMap = { @@ -2019,7 +2406,9 @@ class TravellerDetailsState extends State { // Extract only country codes for processing countryCodes = countryMap.keys.toList(); - selectedCountry ??= null; + // selectedCountry ??= null; + + String? selectedCountry = entry['country_code']; return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -2032,7 +2421,7 @@ class TravellerDetailsState extends State { SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( width: widget.isDesktop - ? MediaQuery.of(context).size.width * 0.15 + ? MediaQuery.of(context).size.width * 0.17 : null, isFocused: false, isDesktop: widget.isDesktop, @@ -2069,9 +2458,14 @@ class TravellerDetailsState extends State { onChanged: (String? newValue) { setState(() { // Find the country_code based on selected country_name - selectedCountry = countryMap.entries + // selectedCountry = countryMap.entries + // .firstWhere((entry) => entry.value == newValue) + // .key; + + final selectedCode = countryMap.entries .firstWhere((entry) => entry.value == newValue) .key; + entry['country_code'] = selectedCode; }); }, ), @@ -2081,9 +2475,19 @@ class TravellerDetailsState extends State { ); } - Widget buildVisaType() { - // List purposeList = widget.apiData?['visa_type_of_visa'] ?? []; - List purposeList = []; + Widget buildVisaType(entry) { + if (apiData == null || apiData?['visa_type_of_visa'] == null) { + return Center( + child: Transform.scale( + scale: 0.5, + child: CircularProgressIndicator(), + ), + ); + } + + // List purposeList = apiData?['visa_type_of_visa'] ?? []; + // List purposeList = []; + List purposeList = apiData?['visa_type_of_visa']; List> dropdownItems = purposeList .map((item) => DropdownMenuItem( @@ -2102,9 +2506,19 @@ class TravellerDetailsState extends State { ); } - // Default selected value - selectedPurpose ??= - dropdownItems.isNotEmpty ? dropdownItems.first.value : null; + // // Default selected value + // selectedPurpose ??= + // dropdownItems.isNotEmpty ? dropdownItems.first.value : null; + + // String? selectedPurpose = entry['visa_type_id']; + + // 3. Selected value + String? selectedPurpose = entry['visa_type_id']?.toString(); + + // 4. Avoid crash if selected value doesn't match any item + if (!dropdownItems.any((item) => item.value == selectedPurpose)) { + selectedPurpose = null; + } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -2116,7 +2530,7 @@ class TravellerDetailsState extends State { SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( width: widget.isDesktop - ? MediaQuery.of(context).size.width * 0.15 + ? MediaQuery.of(context).size.width * 0.17 : null, isFocused: false, isDesktop: widget.isDesktop, @@ -2130,10 +2544,12 @@ class TravellerDetailsState extends State { contentPadding: EdgeInsets.symmetric(horizontal: 10), // Proper padding ), + hint: Text("Select"), onChanged: purposeList.isNotEmpty ? (newValue) { setState(() { - selectedPurpose = newValue; + // selectedPurpose = newValue; + entry['visa_type_id'] = newValue; }); print( "Updating form data: Flight -> trip_type -> ${newValue ?? ""}"); @@ -2147,7 +2563,7 @@ class TravellerDetailsState extends State { ); } - Widget buildVisaValidFrom() { + Widget buildVisaValidFrom(Map entry) { DateTime? _selectedCheckOutDate; TimeOfDay? _selectedCheckOutTime; @@ -2175,6 +2591,9 @@ class TravellerDetailsState extends State { setState(() { _selectedCheckOutDate = pickedDate; // _dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate); + + entry["controller_valid_from"].text = + DateFormat('yyyy-MM-dd').format(pickedDate); }); } } @@ -2203,7 +2622,8 @@ class TravellerDetailsState extends State { child: AbsorbPointer( child: TextField( // focusNode: _dateFocusNode, - // controller: _dateController, + controller: entry["controller_valid_from"], + style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Select Date", @@ -2223,7 +2643,7 @@ class TravellerDetailsState extends State { ); } - Widget buildVisaValidUpTo() { + Widget buildVisaValidUpTo(Map entry) { DateTime? _selectedCheckOutDate; TimeOfDay? _selectedCheckOutTime; @@ -2251,6 +2671,9 @@ class TravellerDetailsState extends State { setState(() { _selectedCheckOutDate = pickedDate; // _dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate); + + entry["controller_valid_upto"].text = + DateFormat('yyyy-MM-dd').format(pickedDate); }); } } @@ -2280,6 +2703,7 @@ class TravellerDetailsState extends State { child: TextField( // focusNode: _dateFocusNode, // controller: _dateController, + controller: entry["controller_valid_upto"], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Select Date", @@ -2309,12 +2733,8 @@ class TravellerDetailsState extends State { color: Color(0xFF114D8B), size: 30, ), - onPressed: () { - setState(() { - visaRowIds.add(_rowVisaCounter++); - }); - }, - tooltip: "Hotel Loyalty Membership", + onPressed: () => addVisaEntry(), + tooltip: "Add Visa Details", ), ); } diff --git a/lib/routes/custom_appBar.dart b/lib/routes/custom_appBar.dart index 1902194..c4c207a 100644 --- a/lib/routes/custom_appBar.dart +++ b/lib/routes/custom_appBar.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:shared_preferences/shared_preferences.dart'; // don't forget import '../services/apiService.dart'; +import '../utils/auth_utils.dart'; enum TabSelection { allTrips, myTrips, myApprovals } @@ -481,7 +482,8 @@ class _CustomAppBarState extends State { extra: { "selectedUser": userDetails, "isEditProfile": true, - "isViewMode": true + "isEditProfile": true, + "isViewMode": false, }, ); case '/logout': diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index 82a0688..7a70f31 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -489,9 +489,11 @@ class ApiService { // Flight From - To - Future> fetchFlightsCountryList() async { + Future> fetchFlightsCountryList(String? tripType) async { + print("FlightTripType- $tripType"); + final String apiUrldata = - '$apiUrl/api/getAirportCodeMaster?limit=1000&offset=0'; + '$apiUrl/api/getAirportCodeMaster?trip_type=$tripType'; final token = await getToken(); diff --git a/lib/utils/auth_utils.dart b/lib/utils/auth_utils.dart index f4cf5d6..5c55f6c 100644 --- a/lib/utils/auth_utils.dart +++ b/lib/utils/auth_utils.dart @@ -51,6 +51,8 @@ Future getRoleUser() async { final prefs = await SharedPreferences.getInstance(); final String? userDataString = prefs.getString('user_data'); + // print("UserDataSTr - $userDataString"); + if (userDataString != null) { try { final Map userData = jsonDecode(userDataString); diff --git a/lib/utils/travelAgent_remarks.dart b/lib/utils/travelAgent_remarks.dart index 0c3e7fa..2c73a0a 100644 --- a/lib/utils/travelAgent_remarks.dart +++ b/lib/utils/travelAgent_remarks.dart @@ -26,23 +26,35 @@ class CommentModalState extends State { Map? remarksData; int? remarksId; dynamic userId; + late final role; bool isLoading = true; String? errorMessage; bool editRemarks = false; TextEditingController commentController = TextEditingController(); - Map getData() { + // Map getData() { + // final map = { + // "plan_id": int.parse(widget.planId), + // "remarks": commentController.text, + // "created_by": userId, + // "is_active": 1, + // }; + // + // if (remarksId != null) { + // map["id"] = remarksId; // Add 'id' only if available + // } + // + // return map; + // } + + Map getData({int isActive = 1}) { final map = { "plan_id": int.parse(widget.planId), "remarks": commentController.text, - "created_by": userId, - "is_active": 1, + // "created_by": userId, + "is_active": isActive, }; - if (remarksId != null) { - map["id"] = remarksId; // Add 'id' only if available - } - return map; } @@ -59,6 +71,10 @@ class CommentModalState extends State { throw Exception('User ID not found.'); } + role = await getRoleUser(); + + print("Role - $role"); + userId = int.tryParse(userIdString); if (userId == null) { throw Exception('Invalid user ID format.'); @@ -85,8 +101,15 @@ class CommentModalState extends State { // final String apiUrldata = // '$apiUrl/getRemarksByPlanId?plan_id=${widget.planId}&user_id=$userId'; - final String apiUrldata = - '$apiUrl/api/plans/getRemarksByPlanId?plan_id=${widget.planId}&user_id=$userId'; + final String apiUrldata; + + if (role == "Travel Agent") { + apiUrldata = + '$apiUrl/api/plans/getRemarksByPlanId?plan_id=${widget.planId}&user_id=$userId'; + } else { + apiUrldata = + '$apiUrl/api/plans/getRemarksByPlanId?plan_id=${widget.planId}'; + } final token = await getToken(); @@ -128,8 +151,19 @@ class CommentModalState extends State { } } - Future postRemarksData() async { - final remarksData = getData(); + Future postRemarksData({int isActive = 1}) async { + // final remarksData = getData(); + + final remarksData = getData(isActive: isActive); + + print("remarksId - $remarksId"); + + if (remarksId != null) { + remarksData["id"] = remarksId; + remarksData["updated_by"] = userId; + } else { + remarksData["created_by"] = userId; + } print("Remarks Data - remarksData"); @@ -172,12 +206,16 @@ class CommentModalState extends State { if (data['data'] != null && data['data'].isNotEmpty) { final remarksData = data['data'][0]; // Take the first item from the list + print("remarksData - $remarksData"); // Now update your local fields commentController.text = remarksData['remarks'] ?? ''; // If you need to update other fields like created_by, you can do that too userId = remarksData['created_by'] ?? userId; - remarksId = remarksData['id']; + // remarksId = remarksData['id']; + remarksId = int.tryParse(remarksData['id'].toString()); + + print("remarksId1- $remarksId"); // If plan_id needs to be updated (usually it doesn't change), you can do it too // widget.planId = remarksData['plan_id'].toString(); // If widget.planId is mutable @@ -211,12 +249,15 @@ class CommentModalState extends State { }); }, ), - IconButton( - icon: const Icon(Icons.delete, size: 20), - onPressed: () { - // Handle delete pressed - }, - ), + if (widget.role == "Travel Agent") + IconButton( + icon: const Icon(Icons.delete, size: 20), + onPressed: () async { + await postRemarksData( + isActive: 0); // Marks the remark as deleted + Navigator.of(context).pop(); + }, + ), ], ), const SizedBox(height: 10), diff --git a/lib/widgets/custom_user_travel.dart b/lib/widgets/custom_user_travel.dart index d01f6c1..da9291b 100644 --- a/lib/widgets/custom_user_travel.dart +++ b/lib/widgets/custom_user_travel.dart @@ -32,7 +32,7 @@ class _CustomTextFieldUserTravellerWrapperState return Container( width: widget.width ?? // Use custom width if provided, else default (widget.isDesktop - ? MediaQuery.of(context).size.width * 0.24 + ? MediaQuery.of(context).size.width * 0.22 : MediaQuery.of(context).size.width * 0.8), padding: widget.padding, decoration: BoxDecoration(