import 'dart:convert'; import 'dart:async'; import 'dart:typed_data'; import 'package:dropdown_search/dropdown_search.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:super_tooltip/super_tooltip.dart'; import 'package:web/web.dart' as web; import 'package:flutter/material.dart'; import 'package:frontend/Screens/plans/dynamic_itinerary_stepper.dart'; import 'package:frontend/utils/auth_utils.dart'; import 'package:go_router/go_router.dart'; import 'package:responsive_builder/responsive_builder.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; import 'package:universal_html/html.dart' as html; import '../../config/apiUrl.dart'; import '../../data/models/plan.dart'; import '../../routes/custom_appBar.dart'; import '../../routes/custom_drawer.dart'; import '../../services/apiService.dart'; import '../../widgets/custom_radio_button.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/saving_loader.dart'; import '../approvals/approval_dialogs.dart'; import '../dialog/user_selection_dialog.dart'; import '../itnerary/flights.dart'; class CreatePlan extends StatefulWidget { CreatePlan({super.key}); @override _CreatePlansState createState() => _CreatePlansState(); } class _CreatePlansState extends State { final GlobalKey _createPlanKey = GlobalKey(); Color layoutColor = Colors.redAccent; Color bodyColor = Colors.white; final FocusNode focusNode = FocusNode(); bool isFocused = false; @override void initState() { super.initState(); _checkAuthAndLoadData(); // loadInitialData(); } void _checkAuthAndLoadData() async { final String? token = await getToken(); // Your async function to get token if (token == null || token.isEmpty) { // Token doesn't exist → redirect to login context.go( "/", ); // or use: router.go("/") if you're using `GoRouter` directly return; } loadInitialData(); } void loadInitialData() async { String? layoutString = await getLayoutColor(); String? bodyStringColor = await getBodyColor(); setState(() { layoutColor = layoutString != null ? Color(int.parse(layoutString)) : Colors.redAccent; bodyColor = bodyStringColor != null ? Color(int.parse(bodyStringColor)) : Colors.white; }); } @override Widget build(BuildContext context) { return ResponsiveBuilder( builder: (context, sizingInfo) { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Scaffold( // backgroundColor: Colors.white, backgroundColor: Color(0xFFf5f5f5), // backgroundColor: Color(0xFFFCFCFC), 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(0), child: Row( children: [ // if (isDesktop) CustomDrawer(isDesktop: true), Expanded( child: buildUserTable( isDesktop, context, bodyColor, layoutColor, ), ), ], ), ), ); }, ); } Widget buildUserTable( bool isDesktop, context, Color? bodyColor, Color layoutColor, ) { final args = GoRouterState.of(context).extra as Map? ?? {}; // final planData = args?['planData']; final bool isViewMode = args?['isViewMode'] ?? false; final bool isApprover = args?['isApprover'] ?? false; final String approverId = args['approverId'] ?? ""; final String delegaterId = args['delegaterId'] ?? ""; final String approverStatus = args['approver_status'] ?? ""; final Map planData = args['planData'] as Map? ?? {}; // print("isViewMode: $isViewMode"); // final bool isViewMode = true; // final planData = GoRouterState.of(context).extra as Map? ?? {}; print("RECived palndata"); print("RECived approverId - $approverId"); print("RECived delegateId - $delegaterId"); return Container( margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null, padding: const EdgeInsets.all(8), decoration: BoxDecoration( // color: Colors.amber, // color: bodyColor, color: isDesktop ? Colors.white : Color(0xFFFCFCFC), // border: Border.all( // color: Colors.white, // // color: Color(0xFFF7F7FB), // // width: 3.5) ), child: Column( children: [ // Container( // color: Color(0xFFF4F4FB), // padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16), // child: Row(children: [ // Row( // children: [ // Padding( // padding: const EdgeInsets.all(8.0), // child: Icon( // Icons.create_new_folder_outlined, // color: Color(0xFF84869A), // size: 23, // ), // ), // Text( // isViewMode // ? "View Plan" // : (planData.isNotEmpty ? "Update Plan" : "New Plan"), // style: TextStyle(fontSize: 18), // ), // ], // ), // Spacer(), // ]), // ), Expanded( child: Container( // margin: isDesktop // ? EdgeInsets.all(10.0) // : EdgeInsets.only( // left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), // padding: const EdgeInsets.all(10), height: isDesktop ? MediaQuery.of(context).size.height * 0.98 : MediaQuery.of(context).size.height, // decoration: BoxDecoration( // border: isDesktop // ? Border.all( // width: 2, // color: Colors.white, // // color: Color(0xFFF7F7FB), // ) // : null, // color: Colors.white, // // color: Color(0xFFF7F7FB), // // // color: Colors.amber, // ), // color: bodyColor, child: SingleChildScrollView( child: Padding( padding: EdgeInsets.all(10.0), child: CreateNewPlan( key: _createPlanKey, bodyColor: bodyColor, layoutColor: layoutColor, isDesktop: isDesktop, selectedPlanData: planData, isViewMode: isViewMode, isApprover: isApprover, approverId: approverId, delegaterId: delegaterId, approverStatus: approverStatus, ), ), ), ), ), Container( padding: const EdgeInsets.all(10), color: Colors.white, child: isDesktop ? Row( mainAxisAlignment: MainAxisAlignment.end, // children: [Text("Button")], children: _buildSubmit( isDesktop, isViewMode, layoutColor, isApprover, ), ) : Row( mainAxisAlignment: MainAxisAlignment.center, children: _buildSubmit( isDesktop, isViewMode, layoutColor, isApprover, ), ), ), ], ), ); } List _buildSubmit( isDesktop, bool isViewMode, Color layoutColor, bool isApprover, ) { return [ ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: layoutColor ?? Colors.blueAccent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: layoutColor, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: () { final currentUri = GoRouterState.of( context, ).uri.toString(); // ✅ safer than `.location` print("currentUri - $currentUri"); if (currentUri == "/allTrips/trips") { context.go('/listAllPlan'); } else if (currentUri == "/createPlan") { context.go('/listPlan'); } else if (currentUri == "/approver/plans") { context.go('/approvallist'); } else if (currentUri == "/travelagent/trips") { context.go('/listTravelAgentPlan'); } else { isApprover ? context.go('/approvallist') : context.go('/listPlan'); } }, child: Text("Cancel"), ), SizedBox(width: 20), MouseRegion( cursor: isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: isViewMode ? layoutColor : layoutColor, // Keep original color foregroundColor: isViewMode ? Colors.white : Colors.white, // Keep original color disabledBackgroundColor: layoutColor, // Ensure color remains when disabled disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: layoutColor, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: isViewMode ? null : () { _createPlanKey.currentState?.handleSubmit(); }, // Disable when in view mode child: Text("Submit"), ), ), ]; } } class CreateNewPlan extends StatefulWidget { final bool isDesktop; final bool isViewMode; final bool isApprover; final String? approverStatus; final Color? bodyColor; final Color? layoutColor; final Map selectedPlanData; final String approverId; final String delegaterId; const CreateNewPlan({ super.key, required this.isDesktop, required this.bodyColor, required this.layoutColor, required this.selectedPlanData, required this.isViewMode, required this.isApprover, required this.approverStatus, required this.approverId, required this.delegaterId, }); @override CreateNewPlansState createState() => CreateNewPlansState(); } class CreateNewPlansState extends State { final GlobalKey dynamicItineraryKey = GlobalKey(); final GlobalKey flightScreenKey = GlobalKey(); late final ValueNotifier flightTripTypeNotifier; final ApiService apiService = ApiService(); final TextEditingController _tripTitleController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController(); final TextEditingController _soNumberController = TextEditingController(); final TextEditingController _excepntldescriptionController = TextEditingController(); final TextEditingController _remarksController = TextEditingController(); // final FocusNode _tripTitleFocusNode = FocusNode(); // final FocusNode _descriptionFocusNode = FocusNode(); // Declare FocusNode // bool _isdescriptionFocused = false; // bool _isTripTitleFocused = false; Color? layoutColor; late String _selectedOption = "Option 1"; // late String? _selectedIsBillable = "Billable"; bool isStatusExpanded = false; bool isShowApprovalAction = false; String? selectedPlanId; String? approverStatus; String? userDetails; String? userName; String? selfId; String? otherUserName; String? selectedplanUserId; bool? selectedIstravelUser; late Color layoutColorForUser; Map? apiData; // Store API response here Map? apiDataForClass; // Store API response here List? apiCountryData; List? apiCostData; // Store API response here bool isLoading = true; // Track loading state String? TripPlanAction; bool showDomestic = false; bool showInternational = false; bool hasAction = true; bool hasSoNumber = false; bool hasExceptionalClass = false; late Map costCenterMap; List costCenterIds = []; // List apiCostData = []; // if you’re not already using this late Map purposeMap; List purposeKeys = []; // Declare tooltip controller late SuperTooltip tooltip; String? orgId; String? planUsrId; String? planTravlrId; String? statusValue; String? _selectedTripType; String? selectedCostCenterId; String? _selectedIsBillable; String? selectedFuncDept; String? selectedPurpose; Map validationErrors = {}; List> miscellaneousList = []; // List> miscellaneousList = [{"special_request": 1, "comments": "posta", "indx": 1}]; List> visaList = []; List> insuranceList = []; List> accommodationList = []; List> trainList = []; List> flightList = []; List> busList = []; List> taxiList = []; List> forexList = []; List> planStatusList = []; Map focusNodes = {}; Map focusStates = {}; late bool isApproverApproved = false; late bool isApproverRejected = false; String? temporaryMessage; //Getter Method Map get planData => { "org_id": orgId, "user_id": planUsrId, "traveller_id": planTravlrId, "trip_title": _tripTitleController.text, "trip_type": _selectedTripType, "cost_center_id": selectedCostCenterId, "is_billable": _selectedIsBillable, "purpose_of_travel": selectedPurpose, "description": _descriptionController.text, "exceptional_plan_reason": _excepntldescriptionController.text, "functional_department": selectedFuncDept, "so_number": _soNumberController.text, "created_by": selfId, "updated_by": selfId, "is_active": "1", "flight": flightList, "accomodation": accommodationList, "bus": busList, "taxi": taxiList, "train": trainList, "visa": visaList, "forex": forexList, "insurance": insuranceList, "miscellaneous": miscellaneousList, // "status_value": statusValue, }; List dataHeader = [ "trip_planned", "trip_type", "cost_center_id", "is_billable", "purpose_of_travel", "description", "excepntldescription", "functional_department", "so_number", ]; void handleItineraryUpdate(String type, List> newList) { setState(() { switch (type) { case "Miscellaneous": miscellaneousList = newList; break; case "Visa": visaList = newList; break; case "Insurance": insuranceList = newList; break; case "Train": trainList = newList; break; case "Bus": busList = newList; break; case "Taxi": taxiList = newList; break; case "Forex": forexList = newList; break; case "Flight": flightList = newList; break; case "Accomodation": accommodationList = newList; break; default: print("Unknown itinerary type: $type"); } }); print("Updated $type List: $newList"); } void handleExcentionalClass(bool val) { print("handleExcentionalClass - $val"); setState(() { hasExceptionalClass = val; }); } @override void initState() { super.initState(); print("approverStatus - ${widget.approverStatus}"); WidgetsBinding.instance.addPostFrameCallback((_) { loadInitialData(); }); if (widget.approverStatus == "Approval pending") { print("Status approver - Approval Pending"); } flightTripTypeNotifier = ValueNotifier(null); for (var field in dataHeader) { focusNodes["${field}FocusNode"] = FocusNode(); focusStates["${field}Focused"] = false; } print("Focus Nodes KeysII: ${focusNodes.keys.toList()}"); print("Focus States KeysII: ${focusStates.keys.toList()}"); for (var key in focusNodes.keys) { _addFocusListener(focusNodes[key]!, (focus) { setState(() { focusStates[key.replaceFirst("FocusNode", "Focused")] = focus; }); }); } fetchUserDetails(); fetchPlans(); fetchCostCenter(); fetchCountryList(); // _tripTitleFocusNode.addListener(() { // setState(() { // _isTripTitleFocused = _tripTitleFocusNode.hasFocus; // }); // }); // // _descriptionFocusNode.addListener(() { // setState(() { // _isdescriptionFocused = _descriptionFocusNode.hasFocus; // }); // }); handleUpdateData(); } void loadInitialData() async { String? layoutString = await getLayoutColor(); setState(() { layoutColor = layoutString != null ? Color(int.parse(layoutString)) : Colors.redAccent; }); } @override void dispose() { for (var node in focusNodes.values) { node.dispose(); } // _tripTitleFocusNode.dispose(); // _descriptionFocusNode.dispose(); super.dispose(); } void _addFocusListener(FocusNode node, Function(bool) updateState) { node.addListener(() { setState(() { updateState(node.hasFocus); }); }); } void handleSelectedUser() { if (widget.selectedPlanData != null) { setState(() { String userId = widget.selectedPlanData['user_id'] ?? ''; String userName = widget.selectedPlanData['user_name'] ?? ''; String travellerId = widget.selectedPlanData['traveller_id'] ?? ''; String travellerName = widget.selectedPlanData['traveller_name'] ?? ''; print("TestplanUsrId $userId --- $travellerId"); if (travellerId.isNotEmpty && travellerId != "0") { print("TestplanUsrId1: $travellerId"); planTravlrId = travellerId; _selectedOption = "Option 3"; otherUserName = travellerName; } else if (userId.isNotEmpty && userId != "0") { print("TestplanUsrId : $userId"); print("TestplanUsrIdSelf : $selfId"); _selectedOption = (selfId != userId) ? "Option 2" : "Option 1"; otherUserName = userName; } }); } } void handleUpdateData() { if (widget.selectedPlanData != null) { setState(() { statusValue = widget.selectedPlanData['status_value'] ?? ''; print("STATUS____ : $statusValue"); planUsrId = widget.selectedPlanData['user_id'] ?? ''; _tripTitleController.text = widget.selectedPlanData['trip_title'] ?? ''; _descriptionController.text = widget.selectedPlanData['description'] ?? ''; _soNumberController.text = widget.selectedPlanData['so_number'] ?? ''; if (_soNumberController.text != "") { hasSoNumber = true; } _excepntldescriptionController.text = widget.selectedPlanData['exceptional_plan_reason'] ?? ''; if (_excepntldescriptionController.text != "") { hasExceptionalClass = true; } _selectedTripType = widget.selectedPlanData['trip_type']; flightTripTypeNotifier.value = widget.selectedPlanData['trip_type']; _selectedIsBillable = widget.selectedPlanData['is_billable'] == "1" ? "1" : "2"; // selectedCostCenterId = widget.selectedPlanData['cost_center_id']?.toString() ; // selectedPurpose = widget.selectedPlanData['purpose_of_travel']?.toString(); // selectedFuncDept =widget.selectedPlanData['functional_department']?.toString(); if (widget.selectedPlanData!["cost_center_id"] != null) { selectedCostCenterId = widget.selectedPlanData!["cost_center_id"].toString(); } if (widget.selectedPlanData['plan_status'] != null) { planStatusList = List>.from( widget.selectedPlanData['plan_status'] ?? [], ); } // if (widget.selectedPlanData!["purpose_of_travel"] != null) { selectedPurpose = widget.selectedPlanData!["purpose_of_travel"].toString(); } if (widget.selectedPlanData!["functional_department"] != null) { // selectedFuncDept = widget.selectedPlanData!["functional_department"].toString(); selectedFuncDept = widget.selectedPlanData!["functional_department"]; } // Assign lists from selectedPlanData, ensuring they are properly formatted flightList = List>.from( widget.selectedPlanData['flight'] ?? [], ); accommodationList = List>.from( widget.selectedPlanData['accomodation'] ?? [], ); busList = List>.from( widget.selectedPlanData['bus'] ?? [], ); taxiList = List>.from( widget.selectedPlanData['taxi'] ?? [], ); trainList = List>.from( widget.selectedPlanData['train'] ?? [], ); visaList = List>.from( widget.selectedPlanData['visa'] ?? [], ); forexList = List>.from( widget.selectedPlanData['forex'] ?? [], ); insuranceList = List>.from( widget.selectedPlanData['insurance'] ?? [], ); miscellaneousList = List>.from( widget.selectedPlanData['miscellaneous'] ?? [], ); }); if (widget.selectedPlanData['trip_type'] != null) { int? tripId = int.tryParse( widget.selectedPlanData['trip_type'].toString(), ); fetchTrainFlightClass(tripId!); } if (widget.selectedPlanData.containsKey('plan_id') && widget.selectedPlanData['plan_id'] != null) { print("Plan ID exists: ${widget.selectedPlanData['plan_id']}"); selectedPlanId = widget.selectedPlanData['plan_id']?.toString(); } else { print("Plan ID is missing or null"); } print("updatedPlanDAta - $planData"); } } void setTripPlanAction() { setState(() { if (TripPlanAction == "Plan Creation Not Allowed") { showDomestic = false; showInternational = false; hasAction = false; } else if (TripPlanAction == "Only Domestic Plan Creation Allowed") { showDomestic = true; showInternational = false; hasAction = true; } else if (TripPlanAction == "Only International Plan Creation Allowed") { showDomestic = false; showInternational = true; hasAction = true; } else if (TripPlanAction == "Both Type Plan Creation Allowed") { showDomestic = true; showInternational = true; hasAction = true; } }); } Future getPdfDownload() async { final String apiUrldata = '$apiUrl/api/plans/download?plan_id=$selectedPlanId'; // final String apiUrldata = '$apiUrl/auth/googlelogin'; 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 { print("PDf Dowloaded"); // Create a blob from the response body final blob = html.Blob([response.bodyBytes]); // Generate a download URL for the blob final url = html.Url.createObjectUrlFromBlob(blob); // Create a link element to trigger the download final anchor = html.AnchorElement(href: url) ..setAttribute('download', 'trip_plan_$selectedPlanId.pdf') ..click(); // Revoke the download URL to free up resources html.Url.revokeObjectUrl(url); } catch (e) { throw Exception('Error parsing response: $e'); } } else if (response.statusCode == 404) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text('File not found.'), // content: Text('File not found.'), actions: [ TextButton( child: Text('OK'), onPressed: () { Navigator.of(context).pop(); // Close the dialog }, ), ], ); }, ); } else { throw Exception('Failed to load plans'); } } Future getSelectedPlanFor() async { var userTripId; // if (!mounted) return; print("getSelectedPlanFor"); setState(() { if (selectedplanUserId != null) { print("Is Not USER ID - $planUsrId "); if (selectedIstravelUser!) { planUsrId = ""; planTravlrId = selectedplanUserId; userTripId = selectedplanUserId; } else { planUsrId = selectedplanUserId; planTravlrId = ""; userTripId = selectedplanUserId; } } else { print("Is USER ID - $planUsrId "); planUsrId = selfId; planTravlrId = ""; _selectedOption = "Option 1"; userTripId = selfId; } }); final prefs = await SharedPreferences.getInstance(); await prefs.setString('trip_planned_user', userTripId); print("USER ID - $planUsrId , TRAVELLER ID - $planTravlrId"); } void fetchUserDetails() async { final details = await getUserDetails(); TripPlanAction = await getTripPlanAction(); print("TripPlanAction- $TripPlanAction"); print("details- $details"); if (details != null) { setState(() { userDetails = details.toString(); // Store the full Map userName = details['name']; // Extract the name selfId = details['user_id']; }); } orgId = await getOrgId(); print("userDetails - $selfId"); // handleSelectedUser(); getSelectedPlanFor(); setTripPlanAction(); handleSelectedUser(); // handleUpdateData(); } Future getToken() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('auth_token'); } Future getUserId() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('userId'); } Future?> getUserDetails() async { final prefs = await SharedPreferences.getInstance(); final userData = prefs.getString('user_data'); if (userData != null) { final decodedData = jsonDecode(userData); return { 'user_id': decodedData['user_id'].toString(), 'name': "${decodedData['first_name']} ${decodedData['last_name']}", }; } return null; } Future fetchPlans() async { final String apiUrldata = '$apiUrl/api/getDropdownMaster'; 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(data); if (!data.containsKey('data') || data['data'] is! Map) { throw Exception( "Invalid response format: 'data' field is missing or not a Map", ); } Map plansJson = data['data']; // 'data' is a Map, not a List setState(() { apiData = plansJson; // Store API response in state isLoading = false; }); } catch (e) { throw Exception('Error parsing response: $e'); } } else { throw Exception('Failed to load plans'); } } Future fetchCostCenter() async { final String apiUrldata = '$apiUrl/api/getCostCenterMaster'; final token = await getToken(); final userId = await getUserId(); // print("SUSRTRT- $userId"); // 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("CostCenterdropdoen - $data"); if (!data.containsKey('data') || data['data'] is! List) { throw Exception( "Invalid response format: 'data' field is missing or not a List", ); } List plansJson = data['data']; // 'data' is a Map, not a List // setState(() { // apiCostData = plansJson; // Store API response in state // // if(apiCostData!.isNotEmpty){ // // selectedCostCenterId =apiCostData?.first['department_id']; // // } // // if (apiCostData != null && apiCostData!.isNotEmpty) { // selectedCostCenterId ??= apiCostData!.first['name']?.toString(); // } // }); 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 selectedCostCenterId ??= costCenterIds.isNotEmpty ? costCenterIds.first : null; }); print('plansJSON'); } catch (e) { throw Exception('Error parsing response: $e'); } } else { throw Exception('Failed to load plans'); } } Future fetchCountryList() async { final String apiUrldata = '$apiUrl/api/getcountryMaster'; final token = await getToken(); final userId = await getUserId(); // print("SUSRTRT- $userId"); // 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("Country - $data"); if (!data.containsKey('data') || data['data'] is! List) { throw Exception( "Invalid response format: 'data' field is missing or not a List", ); } List plansJson = data['data']; // 'data' is a Map, not a List if (data['data'] is List) { List plansJson = data['data']; print("plansJson.length - ${plansJson.length}"); } else { print("The 'data' key does not contain a list."); } setState(() { apiCountryData = plansJson; // Store API response in state }); print('plansJSONContry - $plansJson'); } catch (e) { throw Exception('Error parsing response: $e'); } } else { throw Exception('Failed to load plans'); } } Future fetchTrainFlightClass(int tripId) async { final userId = (planUsrId?.toString().isNotEmpty == true) ? planUsrId.toString() : (planTravlrId?.toString().isNotEmpty == true) ? planTravlrId.toString() : ''; // final String apiUrldata = '$apiUrl/api/getDropdownMaster'; final String apiUrldata = '$apiUrl/api/getFlightAndTrainClass?user_id=$userId&trip_type=$tripId'; 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(data); if (!data.containsKey('data') || data['data'] is! Map) { throw Exception( "Invalid response format: 'data' field is missing or not a Map", ); } Map plansJson = data['data']; // 'data' is a Map, not a List setState(() { apiDataForClass = plansJson; // Store API response in state isLoading = false; }); } catch (e) { throw Exception('Error parsing response: $e'); } } else { throw Exception('Failed to load plans'); } } // 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)) { validationErrors["user_id"] = "Either User ID or Traveller ID is required"; 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, "trip_title": _tripTitleController.text, "cost_center_id": selectedCostCenterId, "functional_department": selectedFuncDept, "purpose_of_travel": selectedPurpose, if (hasExceptionalClass) "exceptional_plan_reason": _excepntldescriptionController.text, if (hasSoNumber) "so_number": _soNumberController.text, }; 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, accommodationList, busList, taxiList, trainList, visaList, forexList, insuranceList, miscellaneousList, ]; // bool anyServiceSelected = serviceLists.any( // (list) => list != null && list.isNotEmpty, // ); bool anyServiceSelected = serviceLists.any((list) { return list != null && list.any((entry) => entry['is_active'].toString() == "1"); }); if (!anyServiceSelected) { validationErrors["services"] = "Please select at least one service"; setState(() { temporaryMessage = "Please select at least one service"; }); // Clear message after 3 seconds Future.delayed(Duration(seconds: 3), () { if (mounted) { setState(() { temporaryMessage = null; }); } }); } return validationErrors.isEmpty; // Returns true if no errors } Future callApproveAPI(String planId, String userId) async { await postToAPI( endpoint: '/api/plans/approvePlan', data: { "plan_id": planId, "user_id": widget.approverId, "delegation_user_id": widget.delegaterId, }, methodName: 'Plan Approval', ); } Future callRejectAPI( String planId, String userId, String remarks, ) async { await postToAPI( endpoint: '/api/plans/rejectPlan', data: { "plan_id": planId, "user_id": widget.approverId, "delegation_user_id": widget.delegaterId, "reason": remarks, }, methodName: 'Plan Rejection', ); } Future postToAPI({ required String endpoint, required Map data, String methodName = '', }) async { final token = await getToken(); if (token == null) { throw Exception('Token not found. Please log in.'); } try { final response = await http.post( Uri.parse('$apiUrl$endpoint'), headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', }, body: jsonEncode(data), ); if (response.statusCode == 200) { print("$methodName successful!"); print("Response: ${response.body}"); // await apiService.getViewPlan( // data['planId'],planData // ); print("ViewAAA - $planData"); print("Call viewPlanForApprover"); // ApiService.viewPlanForApprover( // context, // data['plan_id'], // data['approverId'], // data['delegaterId'], // data['approver_status'], // // isViewMode: false, // isApprover: true, // ); // Close loading dialog (ONLY if still mounted) if (mounted) Navigator.of(context, rootNavigator: true).pop(); context.go('/approvallist'); } else { print("$methodName failed. Status: ${response.statusCode}"); print("Error: ${response.body}"); } } catch (e) { print("Error in $methodName: $e"); } } Future checkExceptionalClass() async { // pretend we fetch something await Future.delayed(Duration(seconds: 1)); // Example async operation int exceptionalCount = 0; bool hasExceptional(List> list) { return list.any( (item) => item['is_this_exceptional']?.toString() == '1' && item['is_active']?.toString() == '1', ); } bool hasExceptionalInFlights(List> flights) { for (var flight in flights) { if (flight['is_active']?.toString() == '1') { final trips = flight['trips']; if (trips is List) { final exceptionalTrip = trips.any( (trip) => trip['is_this_exceptional']?.toString() == '1', ); if (exceptionalTrip) return true; } } } return false; } if (hasExceptionalInFlights(flightList)) exceptionalCount++; if (hasExceptional(trainList)) exceptionalCount++; if (hasExceptional(accommodationList)) exceptionalCount++; setState(() { hasExceptionalClass = exceptionalCount >= 1; if (!hasExceptionalClass) { _excepntldescriptionController.text = ''; } }); print("hasExceptionalClass: $hasExceptionalClass"); } void handleSubmit() { setState(() async { await checkExceptionalClass(); print("Handle Submit....after check"); 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 // } // // print("Form submitted successfully:" // " ${_remarksController.text}, ${planData['user_id']}, ${planData['traveller_id']}, " // "${planData['traveller_id']}," // " ${selectedPlanId}, " // " "); print("Start loader at: ${DateTime.now()}"); showDialog( context: context, barrierDismissible: false, builder: (context) => const SavingLoader(), ); postPlanData(planData); // await postPlanData(planData); print("Hide loader at: ${DateTime.now()}"); // Close loading dialog (ONLY if still mounted) Future.delayed(Duration(seconds: 12), () { if (mounted) { Navigator.of(context, rootNavigator: true).pop(); final currentUri = GoRouterState.of( context, ).uri.toString(); // ✅ safer than `.location` print("currentUri - $currentUri"); if (currentUri == "/allTrips/trips") { context.go('/listAllPlan'); } else if (currentUri == "/createPlan") { context.go('/listPlan'); } else { widget.isApprover ? context.go('/approvallist') : context.go('/listPlan'); } } }); // if (mounted) Navigator.of(context, rootNavigator: true).pop(); } }); } Future postPlanData(planData) async { final String apiUrldata = '$apiUrl/api/plans/createOrEditPlan'; final token = await getToken(); // Fetch token if (token == null) { throw Exception('Token not found. Please log in.'); } if (selectedPlanId != null && selectedPlanId!.isNotEmpty) { planData['plan_id'] = selectedPlanId; // Add plan_id for update } print("POSTPlanTesting------- $planData}"); try { final response = await http.post( Uri.parse(apiUrldata), headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', }, body: jsonEncode(planData), // Convert map to JSON ); if (response.statusCode == 200) { print("Plan submitted successfully!"); print("Response: ${response.body}"); // // final currentUri = // GoRouterState.of( // context, // ).uri.toString(); // ✅ safer than `.location` // print("currentUri - $currentUri"); // // if (currentUri == "/allTrips/trips") { // context.go('/listAllPlan'); // } else if (currentUri == "/createPlan") { // context.go('/listPlan'); // } else { // widget.isApprover // ? context.go('/approvallist') // : context.go('/listPlan'); // } } 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"); } } Widget build(BuildContext context) { bool hasApprovals = planStatusList.any( (item) => item.entries.any( (entry) => entry.key.contains('status') && entry.value != null && entry.value.toString().isNotEmpty, ), ); return FocusTraversalGroup( policy: OrderedTraversalPolicy(), // 👈 more predictable tab order descendantsAreFocusable: true, child: ResponsiveBuilder( builder: (context, sizingInfo) { bool isMobile = sizingInfo.isMobile; bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ResponsiveBuilder( builder: (context, sizingInfo) { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Container( padding: const EdgeInsets.all(5), child: isDesktop ? Row( crossAxisAlignment: CrossAxisAlignment.start, // mainAxisAlignment: MainAxisAlignment.end, children: [ _buildTripName(isDesktop), if (isDesktop) Spacer(), ..._buildApproverControls(isDesktop), // Text( // widget.isViewMode // ? "View Plan" // : (selectedPlanId != null && // selectedPlanId!.isNotEmpty // ? "Update Plan" // : "New Plan"), // style: TextStyle(fontSize: 18), // ), // Spacer(), if (statusValue != "") ..._buildPlanPdf(isDesktop), ], ) : Column( children: [ Row( // crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.end, children: [ ..._buildApproverControls( isDesktop, ), if (statusValue != "") ..._buildPlanPdf(isDesktop), ], ), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildTripName(isDesktop), ], ), ], ), ); }, ), // if (isStatusExpanded) // Container( // margin: isDesktop // ? const EdgeInsets.only(left: 0, top: 0) // : const EdgeInsets.only(left: 5, top: 2), // padding: const EdgeInsets.all(12), // width: isDesktop // ? MediaQuery.of(context).size.width * 0.2 // : MediaQuery.of(context).size.width, // decoration: BoxDecoration( // color: Color(0xFFF5F5F5), // border: Border.all( // // color: Colors.grey.shade300, // color: Colors.white, // width: 0.2), // borderRadius: BorderRadius.circular(8), // // boxShadow: [ // // BoxShadow( // // // color: Colors.grey.withAlpha(20), // // color: Colors.grey.withAlpha(20), // // spreadRadius: 1.5, // // blurRadius: 7, // // offset: Offset(0, 4), // shadow direction: bottom // // ), // // ], // ), // child: Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // if (!hasApprovals) // Center( // child: Text( // "--- No Approvals ---", // style: TextStyle( // fontFamily: "Archivo", // fontSize: 11, // fontWeight: FontWeight.w500, // color: Colors.black87, // ), // )), // for (int i = 0; i < planStatusList.length; i++) ...[ // if (planStatusList[i].entries.any((entry) => // entry.key.contains('status') && // entry.value != null && // entry.value.toString().isNotEmpty)) ...[ // _buildApprovalItem( // "Approver ${i + 1}", // planStatusList[i] // .entries // .firstWhere( // (entry) => entry.key.contains('status'), // orElse: () => MapEntry('', ''), // ) // .value // .toString(), // ), // SizedBox(height: 6), // ], // ], // ], // ), // ), if (isApproverRejected) Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.only(top: 4, left: 8), child: Text( "Remarks : ", style: TextStyle( fontFamily: "Archivo", fontWeight: FontWeight.w600, fontSize: 11, ), ), ), Expanded( child: Padding( padding: const EdgeInsets.only( top: 0, ), // tweak if needed child: TextFormField( controller: _remarksController, style: TextStyle( fontFamily: "Archivo", fontWeight: FontWeight.w600, fontSize: 11, ), decoration: InputDecoration( hintText: "Please enter remarks...", hintStyle: GoogleFonts.poppins( fontSize: 13, color: Colors.grey, ), border: InputBorder.none, isDense: true, ), maxLines: null, ), ), ), ], ), // if (widget.isApprover || isStatusExpanded) Divider(thickness: 0.1, color: Colors.blueGrey), if (widget.isApprover) SizedBox(height: 5), Text( "Planning This Trip For*", // Your label style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 6), isDesktop ? SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row(children: _buildPlanTrip(isDesktop)), ) : Row(children: _buildPlanTrip(isDesktop)), SizedBox(height: 7), // Text( // otherUserName ?? userName ?? " ", // Your label // // style: GoogleFonts.poppins( // fontSize: 11, // fontWeight: FontWeight.w400, // color: widget.layoutColor, // ), // ), Text.rich( TextSpan( text: "Trip Planned User : ", // Static text style: GoogleFonts.poppins( fontSize: 11, fontWeight: FontWeight.w400, color: Color(0xFF212121), // color: Color(0xFF575A74), // Default color ), children: [ TextSpan( text: otherUserName ?? userName ?? " ", // Dynamic username style: GoogleFonts.poppins( fontSize: 11, fontWeight: FontWeight.w400, color: widget .layoutColor, // Change this to any color // color: Colors.blueAccent, // Change this to any color ), ), ], ), ), SizedBox(height: 5), ], ), ), ], ), // Padding( // padding: const EdgeInsets.all(8.0), // child: Divider( // color: Color(0xFFE6E7F5), // Change color // thickness: 0.5, // ), // ), SizedBox(height: 10), // Row( // children: [ // Expanded( // child: // ), // ], // ), isDesktop ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: _buildTripRow(isMobile), ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: _buildTripRow(isMobile), ), SizedBox(height: 15), isDesktop ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: _buildCostCenter(isDesktop), ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: _buildCostCenter(isDesktop), ), SizedBox(height: 8), isDesktop ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // _buildDescriptionColumn(isDesktop), SizedBox(width: 25), if (hasExceptionalClass) _buildNonDescriptionColumn(), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildDescriptionColumn(isDesktop), SizedBox(height: 15), if (hasExceptionalClass) _buildNonDescriptionColumn(), ], ), // Padding( // padding: const EdgeInsets.all(8.0), // child: Divider( // color: Color(0xFFE6E7F5), // Change color // thickness: 0.5, // ), // ), SizedBox(height: 20), if (temporaryMessage != null) Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( // validationErrors["services"]!, temporaryMessage!, style: GoogleFonts.poppins( color: Colors.red, fontSize: 12, fontWeight: FontWeight.w400, ), ), ], ), if (temporaryMessage != null) SizedBox(height: 10), Row( children: [ Expanded( child: DynamicItinerary( key: dynamicItineraryKey, flightScreenKey: flightScreenKey, tripTypeNotifier: flightTripTypeNotifier, hasAction: hasAction, tripType: _selectedTripType, apiData: apiData, apiDataForClass: apiDataForClass, apiCountryData: apiCountryData, onItineraryUpdate: handleItineraryUpdate, onExeptionalClass: handleExcentionalClass, loginUser: selfId, selectedPlanData: planData, isViewMode: widget.isViewMode, ), ), // Wrap with Expanded if needed ], ), SizedBox(height: 15), ], ); // Row( // children: [ // isDesktop // ? Row( // mainAxisAlignment: MainAxisAlignment.end, // children: _buildSubmit(isDesktop), // ) // : Row( // mainAxisAlignment: MainAxisAlignment.center, // children: _buildSubmit(isDesktop), // ) // ], // ) }, ), // replace with your full form column ); } // String? getSelectedCostCenterName() { // if (selectedCostCenterId == null || apiCostData == null) return null; // return apiCostData!.firstWhere( // (item) => item['department_id'] == selectedCostCenterId, // orElse: () => null, // )?['name']; // } final List> allTripTypes = [ {"dropdown_key": "1", "dropdown_value": "Domestic"}, {"dropdown_key": "2", "dropdown_value": "International"}, ]; List> getFilteredTripTypes() { if (!showDomestic && !showInternational) return []; return allTripTypes.where((item) { if (item['dropdown_key'] == "1" && showDomestic) return true; if (item['dropdown_key'] == "2" && showInternational) return true; return false; }).toList(); } List _buildTripRow(bool isMobile) { List purposeList = apiData?['plan_is_billable'] ?? []; List> tripTypeList = [ {"dropdown_key": "1", "dropdown_value": "Domestic"}, {"dropdown_key": "2", "dropdown_value": "International"}, ]; return [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Trip Type *", // Your label style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: false, padding: const EdgeInsets.symmetric(horizontal: 0), // isFocused: focusStates["trip_typeFocused"] ?? false, width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.32 : MediaQuery.of(context).size.width * 0.88, isDesktop: widget.isDesktop, child: SizedBox( height: 35, // width: double.infinity, width: double.infinity, child: Focus( focusNode: focusNodes["trip_typeFocusNode"], onFocusChange: (hasFocus) { setState(() { focusStates["trip_typeFocused"] = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps focusNodes["trip_typeFocusNode"]?.requestFocus(); }, child: DropdownSearch>( popupProps: PopupProps.menu( showSearchBox: false, // Optionally enable search 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, // 👈 Smaller font size color: Colors.black, ), ), ); }, ), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( // border: InputBorder.none, // No underline border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: (focusStates["trip_typeFocused"] ?? false) ? layoutColor! : Colors.white, width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (focusStates["trip_typeFocused"] ?? false) ? layoutColor! : Colors.white, // : const Color(0xFFD6D5E6), width: 0.5, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: layoutColor!, width: 1), ), contentPadding: const EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), ), ), dropdownBuilder: (context, selectedItem) { if (selectedItem == null || selectedItem.isEmpty) { return Text( "Select Trip Type", style: GoogleFonts.poppins( color: Colors.grey, fontSize: 13, ), ); } return Text( selectedItem['dropdown_value'] ?? '', style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ); }, selectedItem: tripTypeList.firstWhere( (item) => item['dropdown_key'] == _selectedTripType, orElse: () => {}, ), itemAsString: (item) => item['dropdown_value'] ?? '', items: getFilteredTripTypes(), // items: // [ // {"dropdown_key": "1", "dropdown_value": "Domestic"}, // {"dropdown_key": "2", "dropdown_value": "International"}, // ].map>((item) { // return Map.from(item); // }).toList(), onChanged: widget.isViewMode ? null : (Map? newItem) { if (newItem != null) { setState(() { _selectedTripType = newItem['dropdown_key'].toString(); // Notify FlightScreen flightTripTypeNotifier.value = _selectedTripType; print( "flightTripTypeNotifier- ${flightTripTypeNotifier.value}", ); fetchTrainFlightClass( int.parse(_selectedTripType!), ); dynamicItineraryKey.currentState ?.updateSelectedServices(); }); } }, ), ), ), ), ), // isMobile // ? SingleChildScrollView( // scrollDirection: Axis.horizontal, // child: Row( // children: _buildTripType(isMobile), // ), // ) // : Row( // children: _buildTripType(isMobile), // ), if (validationErrors["trip_type"] != null) Padding( padding: EdgeInsets.only(top: 4), child: Text( validationErrors["trip_type"]!, style: GoogleFonts.poppins(fontSize: 12, color: Colors.red), ), ), ], ), SizedBox(width: 25, height: 5), // SizedBox( // width: 25, // height: 5, // ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Is Billable ", // Your label style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldWrapper( // isFocused: focusStates["is_billableFocused"] ?? false, isFocused: false, padding: const EdgeInsets.symmetric(horizontal: 0), width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.2 : null, isDesktop: widget.isDesktop, child: SizedBox( height: 35, width: double.infinity, child: Focus( focusNode: focusNodes["is_billableFocusNode"], onFocusChange: (hasFocus) { setState(() { focusStates["is_billableFocused"] = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps focusNodes["is_billableFocusNode"]?.requestFocus(); }, child: DropdownSearch>( 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: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: (focusStates["is_billableFocused"] ?? false) ? layoutColor! : Colors.white, width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (focusStates["is_billableFocused"] ?? false) ? layoutColor! : Colors.white, // : const Color(0xFFD6D5E6), width: 0.5, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: layoutColor!, width: 1), ), contentPadding: const EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), ), ), selectedItem: purposeList .cast>() .firstWhere( (item) => item['dropdown_key'] == _selectedIsBillable, orElse: () => {}, ), dropdownButtonProps: const DropdownButtonProps( icon: Icon(Icons.arrow_drop_down), ), // itemAsString: (item) => item['dropdown_value'] ?? '', // items: purposeList.cast>(), dropdownBuilder: (context, selectedItem) { if (selectedItem == null || selectedItem.isEmpty) { return Text( "Select Billable", // fallback text style: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), ); } return Text( selectedItem['dropdown_value'] ?? '', style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ); }, items: purposeList.map>((item) { return Map.from( item, ); // Ensuring each item is properly cast }).toList(), onChanged: widget.isViewMode ? null : (Map? newItem) { if (newItem != null) { setState(() { _selectedIsBillable = newItem['dropdown_key'].toString(); }); } }, ), ), ), ), ), // Row( // children: purposeList.map((item) { // final isSelected = // _selectedIsBillable == item['dropdown_key'].toString(); // // return Padding( // padding: const EdgeInsets.symmetric(horizontal: 5), // child: CustomTextFieldWrapper( // color: Color(0xFFF5F5F5), // padding: // const EdgeInsets.symmetric(horizontal: 10, vertical: 4), // layoutColor: widget.layoutColor, // width: item['dropdown_key'] == 'some_key' ? 185 : 125, // borderRadius: BorderRadius.circular(25), // isFocused: isSelected, // isDesktop: widget.isDesktop, // child: GestureDetector( // onTap: widget.isViewMode // ? null // : () { // setState(() { // _selectedIsBillable = // item['dropdown_key'].toString(); // }); // print("SELECBILL - $_selectedIsBillable"); // }, // child: Row( // mainAxisSize: MainAxisSize.min, // mainAxisAlignment: MainAxisAlignment.spaceBetween, // children: [ // Text( // item['dropdown_value'] ?? '', // style: TextStyle( // color: isSelected ? Colors.white : Colors.black, // fontWeight: isSelected ? FontWeight.w500 : null, // fontSize: 13), // ), // const SizedBox(width: 8), // Container( // width: 16, // height: 16, // decoration: BoxDecoration( // borderRadius: BorderRadius.circular(4), // border: Border.all( // color: isSelected ? Colors.white : Colors.black, // width: isSelected ? 2 : 1, // ), // ), // child: isSelected // ? Icon(Icons.rectangle, // size: 8, color: Colors.white) // : null, // ), // ], // ), // ), // ), // ); // }).toList(), // ), ], ), ]; } List _buildCostCenter(bool isDesktop) { // if (apiData == null) { // return Center(child: CircularProgressIndicator()); // Show loading indicator // } // 'plan_purpose_of_travel' Starts ------------------------------------------------------ // List purposeList = apiData?['plan_purpose_of_travel'] ?? []; List> purposeList = List>.from( apiData?['plan_purpose_of_travel'] ?? [], ); List> dropdownItems = purposeList .map( (item) => DropdownMenuItem( // value: item['dropdown_key'], value: item['dropdown_key']?.toString(), // value: item['dropdown_key'].toString(), child: Text(item['dropdown_value']), ), ) .toList(); if (dropdownItems.isEmpty) { dropdownItems.add( DropdownMenuItem( // value: null, value: "1", child: Text( "No options available", style: GoogleFonts.poppins(color: Colors.grey), ), ), ); } selectedPurpose ??= dropdownItems.isNotEmpty ? dropdownItems.first.value.toString() : "No options"; print( "Dropdown Purpose List: ${dropdownItems.map((e) => e.value).toList()}", ); print("Selected Purpose: $selectedPurpose"); // 'plan_functional_department' Starts --------------------------------------------- // List funcDeptList = apiData?['plan_functional_department'] ?? []; List> funcDeptList = List>.from( apiData?['plan_functional_department'] ?? [], ); List> dropdownFuncDeptItems = funcDeptList .map( (item) => DropdownMenuItem( // value: item['dropdown_key'], value: item['dropdown_key']?.toString(), child: Text(item['dropdown_value']), ), ) .toList(); if (dropdownFuncDeptItems.isEmpty) { dropdownFuncDeptItems.add( DropdownMenuItem( // value: null, value: "1", child: Text( "No options available", style: TextStyle(color: Colors.grey), ), ), ); } // selectedFuncDept ??= dropdownFuncDeptItems.isNotEmpty ? dropdownFuncDeptItems.first.value.toString() : null; selectedFuncDept ??= dropdownFuncDeptItems.isNotEmpty ? dropdownFuncDeptItems.first.value.toString() : "No options"; print( "Dropdown Functional Department List: ${dropdownFuncDeptItems.map((e) => e.value).toList()}", ); print("Selected Functional Department: $selectedFuncDept"); return [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Cost Center *", // Your label style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldWrapper( width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, // isFocused: focusStates["cost_center_idFocused"] ?? isFocused: false, padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, child: SizedBox( height: 35, width: double.infinity, child: apiCostData == null ? Center( child: Transform.scale( scale: 0.5, child: CircularProgressIndicator(), ), ) : Focus( focusNode: focusNodes["cost_center_idFocusNode"], onFocusChange: (hasFocus) { setState(() { focusStates["cost_center_idFocused"] = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps focusNodes["cost_center_idFocusNode"] ?.requestFocus(); }, child: DropdownSearch( selectedItem: costCenterMap[selectedCostCenterId], enabled: !widget.isViewMode, popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search Cost Center...", // hintStyle: TextStyle(fontSize: 13, color: Colors.grey), hintStyle: GoogleFonts.poppins( fontSize: 13, color: Colors.grey, ), contentPadding: EdgeInsets.symmetric( horizontal: 10, ), ), style: GoogleFonts.poppins(fontSize: 13), ), menuProps: MenuProps( backgroundColor: Colors.white, ), itemBuilder: (context, item, isSelected) => Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 6.0, ), child: Text( item, style: GoogleFonts.poppins( fontSize: 13, ), // 👈 Set your desired text size here ), ), ), items: costCenterMap.values.toList(), // just names dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: (focusStates["cost_center_idFocused"] ?? false) ? layoutColor! : Colors.white, width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (focusStates["cost_center_idFocused"] ?? false) ? layoutColor! : Colors.white, // : const Color(0xFFD6D5E6), width: 0.5, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: layoutColor!, width: 1, ), ), // contentPadding: EdgeInsets.symmetric(horizontal: 1), contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), ), ), dropdownBuilder: (context, selectedItem) { if (selectedItem == null || selectedItem.isEmpty) { return Text( "Select Purpose", // fallback text style: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), ); } return Text( selectedItem ?? '', style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ); }, // dropdownBuilder: (context, selectedItem) => Align( // alignment: Alignment.centerLeft, // child: Text( // selectedItem ?? "Select", // style: TextStyle(fontSize: 12), // ), // ), onChanged: (String? newValue) { setState(() { selectedCostCenterId = costCenterMap.entries .firstWhere( (entry) => entry.value == newValue, ) .key; }); }, ), ), ), ), // child: SizedBox( // height: 45, // Set appropriate height // child: DropdownButtonFormField( // value: selectedCostCenterId, // style: TextStyle(fontSize: 12), // decoration: InputDecoration( // border: InputBorder.none, // contentPadding: // EdgeInsets.symmetric(horizontal: 10), // Proper padding // ), // onChanged: widget.isViewMode // ? null // : (newValue) { // setState(() { // selectedCostCenterId = newValue; // }); // }, // items: apiCostData?.map>((item) { // return DropdownMenuItem( // value: item['department_id'], // ID as value // child: Text(item['name'] ?? "Unknown"), // ); // }).toList(), // ), // ), ), if (validationErrors["cost_center_id"] != null) Padding( padding: EdgeInsets.only(top: 4), child: Text( validationErrors["cost_center_id"]!, style: GoogleFonts.poppins(fontSize: 12, color: Colors.red), ), ), ], ), SizedBox(width: 25, height: 5), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Purpose of Trip *", // Your label style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldWrapper( width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : MediaQuery.of(context).size.width * 0.88, // isFocused: focusStates["purpose_of_travelFocused"] ?? false, isFocused: false, padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, child: SizedBox( height: 35, // Set appropriate height child: apiData == null ? Center( child: CircularProgressIndicator(), ) // Show loading inside dropdown : Focus( focusNode: focusNodes["purpose_of_travelFocusNode"], onFocusChange: (hasFocus) { setState(() { focusStates["purpose_of_travelFocused"] = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps focusNodes["purpose_of_travelFocusNode"] ?.requestFocus(); }, child: DropdownSearch>( selectedItem: purposeList.firstWhere( (item) => item['dropdown_key'].toString() == selectedPurpose, orElse: () => {}, // ✅ Safe fallback ), items: purposeList, itemAsString: (Map item) => item['dropdown_value'], popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search Purpose...", hintStyle: GoogleFonts.poppins( fontSize: 13, color: Colors.grey, ), contentPadding: EdgeInsets.symmetric( horizontal: 10, ), ), style: GoogleFonts.poppins(fontSize: 13), ), menuProps: MenuProps( backgroundColor: Colors.white, ), itemBuilder: (context, item, isSelected) => Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 6.0, ), child: Text( item['dropdown_value'], style: GoogleFonts.poppins( fontSize: 13, ), // 👈 Set your desired text size here ), ), ), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: (focusStates["purpose_of_travelFocused"] ?? false) ? layoutColor! : Colors.white, width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (focusStates["purpose_of_travelFocused"] ?? false) ? layoutColor! : Colors.white, // : const Color(0xFFD6D5E6), width: 0.5, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: layoutColor!, width: 1, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), ), ), onChanged: widget.isViewMode ? null : (Map? newValue) { setState(() { selectedPurpose = newValue?['dropdown_key'] .toString(); }); print( "selectedPurpose - $selectedPurpose", ); }, dropdownBuilder: (context, selectedItem) { if (selectedItem == null || selectedItem.isEmpty) { return Text( "Select Purpose", // fallback text style: GoogleFonts.poppins( fontSize: 13, color: Colors.grey, ), ); } return Text( selectedItem['dropdown_value'] ?? '', style: GoogleFonts.poppins( fontSize: 12, // 👈 Small font size for selected item color: Colors.black, ), ); }, // dropdownBuilder: (context, selectedItem) => Align( // alignment: Alignment.centerLeft, // child: Text( // selectedItem?['dropdown_value'] ?? '', // style: TextStyle(fontSize: 12), // ), // ), ), ), ), ), ), if (validationErrors["purpose_of_travel"] != null) Padding( padding: EdgeInsets.only(top: 4), child: Text( validationErrors["purpose_of_travel"]!, style: GoogleFonts.poppins(fontSize: 12, color: Colors.red), ), ), // CustomTextFieldWrapper( // isFocused: false, // Dropdown doesn't use focus // isDesktop: widget.isDesktop, // child: SizedBox( // height: 45, // Set appropriate height // child: apiData == null // ? Center( // child: // CircularProgressIndicator()) // Show loading inside dropdown // : DropdownButtonFormField( // value: selectedPurpose, // style: TextStyle(fontSize: 12), // decoration: InputDecoration( // border: InputBorder.none, // contentPadding: EdgeInsets.symmetric( // horizontal: 10), // Proper padding // ), // onChanged: widget.isViewMode // ? null // : purposeList.isNotEmpty // ? (newValue) { // setState(() { // selectedPurpose = newValue; // }); // print( // "selectedPurpose - $selectedPurpose"); // } // : null, // items: dropdownItems, // ), // ), // ), ], ), SizedBox(width: 25, height: 5), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Functional Department", // Your label style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldWrapper( // isFocused: focusStates["functional_departmentFocused"] ?? false, isFocused: false, padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, width: isDesktop ? MediaQuery.of(context).size.width * 0.2 : MediaQuery.of(context).size.width * 0.88, // width: MediaQuery.of(context).size.width * 0.2, child: SizedBox( height: 35, // Set appropriate height child: apiData == null ? Center( child: CircularProgressIndicator(), ) // Show loading inside dropdown : Focus( focusNode: focusNodes["functional_departmentFocusNode"], onFocusChange: (hasFocus) { setState(() { focusStates["functional_departmentFocused"] = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps focusNodes["functional_departmentFocusNode"] ?.requestFocus(); }, child: DropdownSearch>( selectedItem: funcDeptList.firstWhere( (item) => item['dropdown_key'].toString() == selectedFuncDept, orElse: () => {}, // ✅ Safe fallback ), items: funcDeptList, itemAsString: (Map item) => item['dropdown_value'], popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search department...", hintStyle: GoogleFonts.poppins( fontSize: 13, color: Colors.grey, ), contentPadding: EdgeInsets.symmetric( horizontal: 10, ), ), style: GoogleFonts.poppins( fontSize: 13, // 👈 Small font size for selected item ), ), menuProps: MenuProps( backgroundColor: Colors.white, ), itemBuilder: (context, item, isSelected) => Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 6.0, ), child: Text( item['dropdown_value'], style: GoogleFonts.poppins( fontSize: 13, // 👈 Small font size for selected item ), // 👈 Set your desired text size here ), ), ), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: (focusStates["functional_departmentFocused"] ?? false) ? layoutColor! : Colors.white, width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (focusStates["functional_departmentFocused"] ?? false) ? layoutColor! : Colors.white, // : const Color(0xFFD6D5E6), width: 0.5, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: layoutColor!, width: 1, ), ), ), ), onChanged: widget.isViewMode ? null : (Map? newValue) { dynamic val = newValue?['dropdown_value'] .toString(); setState(() { selectedFuncDept = newValue?['dropdown_key'] .toString(); if (val == " Others (Kindly enter SO number )") { hasSoNumber = true; } else { hasSoNumber = false; _soNumberController.text = ""; } }); print( "selectedFuncDept1 - ${newValue?['dropdown_value'].toString()}", ); print( "selectedFuncDept - $selectedFuncDept", ); }, dropdownBuilder: (context, selectedItem) { if (selectedItem == null || selectedItem.isEmpty) { return const Text( "Select Purpose", // fallback text style: TextStyle( fontSize: 12, color: Colors.grey, ), ); } return Text( selectedItem['dropdown_value'] ?? '', style: GoogleFonts.poppins( fontSize: 12, // 👈 Small font size for selected item color: Colors.black, ), ); }, // dropdownBuilder: (context, selectedItem) => Align( // alignment: Alignment.centerLeft, // child: Text( // selectedItem?['dropdown_value'] ?? '', // style: TextStyle(fontSize: 12), // ), // ), ), ), ), ), ), if (validationErrors["functional_department"] != null) Padding( padding: EdgeInsets.only(top: 4), child: Text( validationErrors["functional_department"]!, style: GoogleFonts.poppins(fontSize: 12, color: Colors.red), ), ), ], ), SizedBox(width: 25, height: 5), hasSoNumber ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "SO Number *", // Your label style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: focusStates["so_numberFocused"] ?? false, // isFocused: _isdescriptionFocused, width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.2 : null, isDesktop: widget.isDesktop, child: SizedBox( height: 35, child: TextField( focusNode: focusNodes["so_numberFocusNode"], controller: _soNumberController, style: TextStyle(fontSize: 12), decoration: InputDecoration( labelText: "SO Number", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (validationErrors["so_number"] != null) Padding( padding: EdgeInsets.only(top: 4), child: Text( validationErrors["so_number"]!, style: GoogleFonts.poppins(fontSize: 12, color: Colors.red), ), ), ], ) : SizedBox.shrink(), ]; } List _buildPlanTrip(bool isDesktop) { List> options = [ {"title": "Self", "value": "Option 1"}, {"title": "Other Employee", "value": "Option 2"}, {"title": "Others (Non Employee)", "value": "Option 3"}, ]; return [ CustomTextFieldWrapper( // isFocused: focusStates["trip_plannedFocused"] ?? false, width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.32 : MediaQuery.of(context).size.width * 0.88, isFocused: false, padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: isDesktop, layoutColor: widget.layoutColor, child: SizedBox( height: 35, width: double.infinity, child: Focus( focusNode: focusNodes["trip_plannedFocusNode"], onFocusChange: (hasFocus) { setState(() { focusStates["trip_plannedFocused"] = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps focusNodes["trip_plannedFocusNode"]?.requestFocus(); }, child: DropdownSearch( selectedItem: options.firstWhere( (opt) => opt['value'] == _selectedOption, )['title'], enabled: !widget.isViewMode, items: options.map((opt) => opt['title']!).toList(), popupProps: PopupProps.menu( showSearchBox: false, menuProps: const MenuProps(backgroundColor: Colors.white), constraints: BoxConstraints(maxHeight: 100), itemBuilder: (context, item, isSelected) => Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 6.0, ), child: Text( item, style: GoogleFonts.poppins(fontSize: 13), ), ), ), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: (focusStates["trip_typeFocused"] ?? false) ? layoutColor! : Colors.white, width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (focusStates["trip_plannedFocused"] ?? false) ? layoutColor! : Colors.white, // : const Color(0xFFD6D5E6), width: 0.5, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: layoutColor!, width: 1), ), contentPadding: const EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), ), ), dropdownBuilder: (context, selectedItem) { return Text( selectedItem ?? "Select Purpose", style: GoogleFonts.poppins( fontSize: 12, color: selectedItem == null ? Colors.grey : Colors.black, ), ); }, onChanged: (String? newTitle) { if (newTitle == null) return; final selected = options.firstWhere( (opt) => opt["title"] == newTitle, ); setState(() { _selectedOption = selected["value"]!; if (_selectedOption == "Option 2" || _selectedOption == "Option 3") { _showInputDialog(selected["title"]!); } else if (_selectedOption == "Option 1") { otherUserName = userName; } }); }, ), ), ), ), ), ]; } List _buildPlanTrip1(bool isDesktop) { List> options = [ {"title": "Self", "value": "Option 1"}, {"title": "Other Employee", "value": "Option 2"}, {"title": "Others (Non Employee)", "value": "Option 3"}, ]; print(" layoutColor: ${widget.layoutColor}"); return options.map((option) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 0), child: CustomTextFieldWrapper( // color: Color(0xFFF4F4FB), color: Color(0xFFF5F5F5), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), layoutColor: widget.layoutColor, width: option["value"] == "Option 2" ? 185 : 125, // Adjust width conditionally borderRadius: BorderRadius.circular(10), isFocused: _selectedOption == option["value"], isDesktop: isDesktop, child: GestureDetector( onTap: widget.isViewMode ? null : () { setState(() { _selectedOption = option["value"]!; if (option["value"] == 'Option 2' || option["value"] == 'Option 3') { _showInputDialog(option["title"]!); } }); }, child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( option["title"]!, style: TextStyle( fontSize: 13, color: _selectedOption == option["value"] ? Colors.white : Colors.black, fontWeight: _selectedOption == option["value"] ? FontWeight.w500 : null, ), ), Container( width: 15, height: 15, decoration: BoxDecoration( shape: BoxShape.rectangle, // color: _selectedOption == option["value"] // ? Colors.blueAccent // : Colors.transparent, borderRadius: BorderRadius.circular(4), // Rounded rectangle border: Border.all( color: _selectedOption == option["value"] ? Colors.white : Colors.black, width: _selectedOption == option["value"] ? 2 : 1, ), ), child: _selectedOption == option["value"] ? Icon(Icons.rectangle, size: 8, color: Colors.white) : null, // Add checkmark if selected ), ], ), ), ), ); }).toList(); } List _buildTripType(bool isMobile) { return [ if (showDomestic == true) GestureDetector( onTap: widget.isViewMode ? null : () { setState(() { _selectedTripType = "1"; fetchTrainFlightClass(1); dynamicItineraryKey.currentState ?.updateSelectedServices(); }); }, child: CustomTextFieldWrapper( color: Color(0xFFF4F4FB), layoutColor: widget.layoutColor, borderRadius: BorderRadius.circular(25), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), width: 130, isFocused: _selectedTripType == "1", isDesktop: widget.isDesktop, child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Domestic", style: TextStyle( color: _selectedTripType == "1" ? Colors.white : Colors.black, fontWeight: _selectedTripType == "1" ? FontWeight.w500 : null, fontSize: 13, ), ), Container( width: 15, height: 15, decoration: BoxDecoration( shape: BoxShape.rectangle, // color: _selectedOption == option["value"] // ? Colors.blueAccent // : Colors.transparent, borderRadius: BorderRadius.circular(4), // Rounded rectangle border: Border.all( color: _selectedTripType == "1" ? Colors.white : Colors.black, width: _selectedTripType == "1" ? 2 : 1, ), ), child: _selectedTripType == "1" ? Icon(Icons.rectangle, size: 8, color: Colors.white) : null, // Add checkmark if selected ), ], ), ), ), SizedBox(width: 20), if (showInternational) GestureDetector( onTap: widget.isViewMode ? null : () { setState(() { _selectedTripType = "2"; fetchTrainFlightClass(2); dynamicItineraryKey.currentState ?.updateSelectedServices(); }); }, child: CustomTextFieldWrapper( color: Color(0xFFF4F4FB), layoutColor: widget.layoutColor, borderRadius: BorderRadius.circular(25), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), width: 150, // padding: EdgeInsets.symmetric(horizontal: 5, vertical: 2), isFocused: _selectedTripType == "2", isDesktop: widget.isDesktop, child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "International", style: TextStyle( fontSize: 13, color: _selectedTripType == "2" ? Colors.white : Colors.black, fontWeight: _selectedTripType == "2" ? FontWeight.w500 : null, ), ), Container( width: 15, height: 15, decoration: BoxDecoration( shape: BoxShape.rectangle, // color: _selectedOption == option["value"] // ? Colors.blueAccent // : Colors.transparent, borderRadius: BorderRadius.circular(4), // Rounded rectangle border: Border.all( color: _selectedTripType == "2" ? Colors.white : Colors.black, width: _selectedTripType == "2" ? 2 : 1, ), ), child: _selectedTripType == "2" ? Icon(Icons.rectangle, size: 8, color: Colors.white) : null, // Add checkmark if selected ), ], ), // RadioListTile( // activeColor: Colors.blueAccent, // contentPadding: EdgeInsets.zero, // dense: true, // title: Text("International"), // value: "2", // groupValue: _selectedTripType, // onChanged: widget.isViewMode // ? null // : (value) { // setState(() { // _selectedTripType = value!; // }); // }, // ), ), ), ]; } Widget _buildNonDescriptionColumn() { return Column( children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Reason for travel policy exception*", // Your label style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: focusStates["excepntldescriptionFocused"] ?? false, // isFocused: _isdescriptionFocused, width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.42 : null, isDesktop: widget.isDesktop, hasError: validationErrors["exceptional_plan_reason"] != null, child: SizedBox( height: 55, child: TextField( focusNode: focusNodes["excepntldescriptionFocusNode"], controller: _excepntldescriptionController, maxLines: 2, keyboardType: TextInputType.multiline, style: TextStyle(fontSize: 12), enabled: !widget.isViewMode, decoration: InputDecoration( labelText: "Reason for exception ", labelStyle: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 1), ), ), ), ), if (validationErrors["exceptional_plan_reason"] != null) Padding( padding: EdgeInsets.only(top: 4), child: Text( validationErrors["exceptional_plan_reason"]!, style: GoogleFonts.poppins( fontSize: 12, color: Colors.red, ), ), ), ], ), ], ), ], ); } Widget _buildDescriptionColumn(isDesktop) { return Column( children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Description", // Your label style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), // CustomTextFieldWrapper( // isFocused: false, // // isFocused: _isdescriptionFocused, // isDesktop: widget.isDesktop, // width: isDesktop // ? MediaQuery.of(context).size.width * 0.4 // : MediaQuery.of(context).size.width * 0.85, // child: SizedBox( // height: 35, // child: TextField( // // focusNode: _descriptionFocusNode, // controller: _descriptionController, // style: TextStyle(fontSize: 12), // enabled: !widget.isViewMode, // decoration: InputDecoration( // labelText: "Description", // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), // floatingLabelBehavior: FloatingLabelBehavior.never, // border: InputBorder.none, // contentPadding: EdgeInsets.symmetric(vertical: 16), // ), // ), // ), // ), CustomTextFieldWrapper( isFocused: focusStates["descriptionFocused"] ?? false, // isFocused: _isdescriptionFocused, width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.32 : MediaQuery.of(context).size.width * 0.88, isDesktop: widget.isDesktop, child: SizedBox( height: 55, child: TextField( focusNode: focusNodes["descriptionFocusNode"], controller: _descriptionController, maxLines: 2, keyboardType: TextInputType.multiline, style: TextStyle(fontSize: 12), enabled: !widget.isViewMode, decoration: InputDecoration( labelText: "Description", labelStyle: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 1), ), ), ), ), ], ), ], ), ], ); } List _buildSubmit1(isDesktop) { return [ ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: Colors.blueAccent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: Colors.blueAccent, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: () { widget.isApprover ? context.go('/approvallist') : context.go('/listPlan'); }, child: Text("Cancel"), ), SizedBox(width: 20), MouseRegion( cursor: widget.isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: widget.isViewMode ? Colors.blueAccent : Colors.blueAccent, // Keep original color foregroundColor: widget.isViewMode ? Colors.white : Colors.white, // Keep original color disabledBackgroundColor: Colors.blueAccent, // Ensure color remains when disabled disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: Colors.blueAccent, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: widget.isViewMode ? null : handleSubmit, // Disable when in view mode child: Text("Submit"), ), ), ]; } Widget _buildTripName(bool isDesktop) { return SizedBox( width: isDesktop ? MediaQuery.of(context).size.width * 0.2 : 180, height: 35, child: TextField( // focusNode: _tripTitleFocusNode, controller: _tripTitleController, // style: TextStyle(fontSize: 12), style: GoogleFonts.poppins( fontSize: isDesktop ? 16 : 14, fontWeight: FontWeight.w600, color: Colors.black, ), enabled: !widget.isViewMode, decoration: InputDecoration( labelText: "Trip Name *", labelStyle: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w400, color: Colors.grey, // color: Color(0xFF575A74), ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), errorText: validationErrors["trip_title"], // <--- add this line errorStyle: GoogleFonts.poppins(fontSize: 12, color: Colors.red), ), ), ); } Widget _buildApprovalItem(String title, String name) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "$title : ", style: TextStyle( fontFamily: "Archivo", fontSize: 11, fontWeight: FontWeight.w500, color: Colors.black87, ), ), Expanded( child: Text( name, style: TextStyle( fontFamily: "Archivo", fontSize: 11, fontWeight: FontWeight.bold, color: Colors.black87, ), ), ), ], ); } Color getStatusColor(String status) { if (status == "Partially Approved") return Colors.yellow; if (status == "Approved") return Colors.green; if (status == "Completed") return Colors.green; if (status == "Rejected") return Colors.red; return Colors.grey; } List _buildApproverControls(bool isDesktop) { final statusText = isApproverApproved ? "Approved" : isApproverRejected ? "Rejected" : (statusValue ?? ""); bool hasApprovals = planStatusList.any( (item) => item.entries.any( (entry) => entry.key.contains('status') && entry.value != null && entry.value.toString().isNotEmpty, ), ); return [ // Row( // mainAxisSize: MainAxisSize.min, // children: [ // Text( // isApproverApproved // ? "Approved" // : isApproverRejected // ? "Rejected" // : (statusValue ?? ""), // style: TextStyle( // fontFamily: "Archivo", // fontWeight: FontWeight.bold, // color: widget.layoutColor ?? Colors.grey, // ), // ), // SizedBox(width: 5), // MouseRegion( // onEnter: (_) => setState(() => isStatusExpanded = true), // onExit: (_) => setState(() => isStatusExpanded = false), // child: Icon( // Icons.approval_outlined, // size: 18, // color: isStatusExpanded ? Colors.green : Colors.grey, // ), // ), // ], // ), const SizedBox(height: 10, width: 10), if (widget.isApprover && (widget.approverStatus == "Approval pending")) GestureDetector( // onTap: () { // setState(() { // isShowApprovalAction = !isShowApprovalAction; // // }); // }, onTap: () async { final result = await showApprovalDialog( context, widget.layoutColor ?? Colors.grey, ); if (result == 'approved') { // User approved setState(() { isApproverApproved = true; isApproverRejected = false; statusValue = "Approved"; }); callApproveAPI(selectedPlanId!, planData['user_id']); showDialog( context: context, barrierDismissible: false, builder: (context) => const SavingLoader(), ); } else if (result is String) { // User rejected with remarks _remarksController.text = result; setState(() { isApproverApproved = false; isApproverRejected = true; statusValue = "Rejected"; }); callRejectAPI(selectedPlanId!, planData['user_id'], result); } }, child: Icon( Icons.edit, size: 18, color: isShowApprovalAction ? Colors.green : Colors.grey, ), ), // const SizedBox(height: 10, width: 4), // if (isShowApprovalAction) // Row( // mainAxisSize: MainAxisSize.min, // children: [ // MouseRegion( // cursor: widget.isViewMode // ? SystemMouseCursors.forbidden // : SystemMouseCursors.click, // child: ElevatedButton( // style: ElevatedButton.styleFrom( // disabledBackgroundColor: // statusValue == "Approved" ? Colors.green.shade100 : null, // disabledForegroundColor: // statusValue == "Approved" ? Colors.white : Colors.black, // backgroundColor: isApproverApproved || // (!isApproverApproved && // !isApproverRejected && // statusValue == "Approved") // ? Colors.green // : Colors.grey.shade100, // foregroundColor: isApproverApproved || // (!isApproverApproved && // !isApproverRejected && // statusValue == "Approved") // ? Colors.white // : Colors.black, // shape: RoundedRectangleBorder( // borderRadius: BorderRadius.circular(8), // ), // padding: EdgeInsets.symmetric(horizontal: 18, vertical: 10), // ), // onPressed: widget.isViewMode // ? null // : () async { // final confirmed = await showApproveDialog( // context, widget.layoutColor ?? Colors.grey); // if (confirmed == true) { // setState(() { // isApproverApproved = true; // isApproverRejected = false; // statusValue = "Approved"; // }); // callApproveAPI( // selectedPlanId!, // planData['user_id'], // ); // } // }, // child: Text( // isApproverApproved || statusValue == "Approved" // ? "Approved" // : "Approve", // style: TextStyle(fontSize: 12), // ), // ), // ), // const SizedBox(width: 10), // MouseRegion( // cursor: widget.isViewMode // ? SystemMouseCursors.forbidden // : SystemMouseCursors.click, // child: ElevatedButton( // style: ElevatedButton.styleFrom( // disabledBackgroundColor: statusValue == "Rejected" // ? Colors.redAccent.shade100 // : null, // disabledForegroundColor: // statusValue == "Rejected" ? Colors.white : Colors.black, // backgroundColor: isApproverRejected || // (!isApproverApproved && // !isApproverRejected && // statusValue == "Rejected") // ? Colors.redAccent // : Colors.grey.shade100, // foregroundColor: isApproverRejected || // (!isApproverApproved && // !isApproverRejected && // statusValue == "Rejected") // ? Colors.white // : Colors.black, // shape: RoundedRectangleBorder( // borderRadius: BorderRadius.circular(8), // ), // padding: EdgeInsets.symmetric(horizontal: 18, vertical: 10), // ), // onPressed: widget.isViewMode // ? null // : () async { // final remarks = await showRejectDialog( // context, widget.layoutColor ?? Colors.grey); // if (remarks != null) { // _remarksController.text = remarks; // setState(() { // isApproverApproved = false; // isApproverRejected = true; // statusValue = "Rejected"; // }); // callRejectAPI( // selectedPlanId!, planData['user_id'], remarks); // } // }, // child: Text( // isApproverRejected || statusValue == "Rejected" // ? "Rejected" // : "Reject", // style: TextStyle(fontSize: 12), // ), // ), // ), // ], // ), const SizedBox(height: 10, width: 10), Stack( clipBehavior: Clip.none, // allow tooltip to overflow children: [ if (statusValue != "") MouseRegion( onEnter: (_) => setState(() => isStatusExpanded = true), onExit: (_) => setState(() => isStatusExpanded = false), child: Container( decoration: BoxDecoration( border: Border.all(color: getStatusColor(statusText)), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.only( top: 6.0, bottom: 6.0, left: 15, right: 15, ), child: Text( statusText, style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w400, color: getStatusColor( isApproverApproved ? "Approved" : isApproverRejected ? "Rejected" : (statusValue ?? ""), ), ), // style: TextStyle( // fontFamily: "Roboto", // fontWeight: FontWeight.w400, // fontSize: 12, // // ), ), ), ], ), ), ), // Tooltip if (isStatusExpanded) Positioned( right: 0, top: 45, // Ensure the tooltip is above the container // left: // MediaQuery.of(context).size.width * 0.06, // align with label child: Material( // important: avoid clipping, give elevation elevation: 4, borderRadius: BorderRadius.circular(8), child: SizedBox( child: Container( width: isDesktop ? MediaQuery.of(context).size.width * 0.25 : MediaQuery.of(context).size.width, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, // color: Colors.transparent, border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (!hasApprovals) Center( child: Text( "--- No Approvals ---", style: TextStyle( fontFamily: "Archivo", fontSize: 11, fontWeight: FontWeight.w500, color: Colors.black87, ), ), ), for (int i = 0; i < planStatusList.length; i++) ...[ if (planStatusList[i].entries.any( (entry) => entry.key.contains('status') && entry.value != null && entry.value.toString().isNotEmpty, )) ...[ _buildApprovalItem( "Approver ${i + 1}", planStatusList[i].entries .firstWhere( (entry) => entry.key.contains('status'), orElse: () => MapEntry('', ''), ) .value .toString(), ), SizedBox(height: 6), ], ], ], ), ), ), ), ), // if (isStatusExpanded) // Positioned( // top: 20, // adjust how much above you want // left: 100, // child: Container( // margin: isDesktop // ? const EdgeInsets.only(left: 0, top: 0) // : const EdgeInsets.only(left: 5, top: 2), // padding: const EdgeInsets.all(12), // width: isDesktop // ? MediaQuery.of(context).size.width * 0.2 // : MediaQuery.of(context).size.width, // decoration: BoxDecoration( // // color: Color(0xFFF5F5F5), // color: Colors.white, // border: Border.all(color: Colors.white, width: 0.2), // borderRadius: BorderRadius.circular(8), // ), // child: Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // if (!hasApprovals) // Center( // child: Text( // "--- No Approvals ---", // style: TextStyle( // fontFamily: "Archivo", // fontSize: 11, // fontWeight: FontWeight.w500, // color: Colors.black87, // ), // )), // for (int i = 0; i < planStatusList.length; i++) ...[ // if (planStatusList[i].entries.any((entry) => // entry.key.contains('status') && // entry.value != null && // entry.value.toString().isNotEmpty)) ...[ // _buildApprovalItem( // "Approver ${i + 1}", // planStatusList[i] // .entries // .firstWhere( // (entry) => entry.key.contains('status'), // orElse: () => MapEntry('', ''), // ) // .value // .toString(), // ), // SizedBox(height: 6), // ], // ], // ], // ), // ), // ), ], ), const SizedBox(height: 10, width: 10), ]; } List _buildPlanPdf(isDesktop) { return [ Column( children: [ ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF114D8B), foregroundColor: Colors.white, disabledBackgroundColor: Color(0xFF114D8B), disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: Color(0xFF114D8B), width: 2), ), padding: isDesktop ? EdgeInsets.only( top: 6.0, bottom: 6.0, left: 15, right: 15, ) : EdgeInsets.symmetric(horizontal: 15, vertical: 10), ), onPressed: () { getPdfDownload(); }, child: Row( mainAxisSize: MainAxisSize.min, // Ensures content fits nicely children: [ if (isDesktop) Text( "Download PDF", style: GoogleFonts.poppins( fontSize: isDesktop ? 13 : 11, // fontWeight: // FontWeight.w500, ), // style: TextStyle(fontSize: isDesktop ? 13 : 11), ), if (isDesktop) SizedBox(width: 8), // spacing between icon and text Icon(Icons.download_rounded, size: 15, color: Colors.white), ], ), ), // InkWell( // hoverColor: Colors.white, // onTap: () { // print('📄 PDF icon clicked!'); // getPdfDownload(); // }, // child: Row( // children: [ // Text("Download PDF"), // SizedBox( // width: 10, // ), // Transform.scale( // scale: 1.5, // 1.0 = normal, 1.5 = 50% bigger // child: Image.asset( // 'assets/images/IconsImg/planPdf_icon.png', // width: 25, // height: 25, // keep the real height small // ), // ), // ], // )), // SizedBox(height: 10), // small spacing ], ), ]; } void _showInputDialog(String title) { showDialog( context: context, builder: (BuildContext context) { return UserSelectionDialog( title: title, onSubmit: (input, userId, isTraveller) { setState(() { otherUserName = input; selectedplanUserId = userId; selectedIstravelUser = isTraveller; }); print("USer entered : $otherUserName $userId $isTraveller"); getSelectedPlanFor(); }, onClose: () { print("Choosede Clsoes"); fetchUserDetails(); }, layoutColorForUser: widget.layoutColor!, currentUser: selfId, ); }, ); } }