diff --git a/lib/Screens/allTrips/list_all_plans.dart b/lib/Screens/allTrips/list_all_plans.dart index fb4d67f..04b11e1 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/plan_info_mdl.dart'; import 'package:frontend/Screens/allTrips/remarks_list.dart'; import 'package:frontend/data/models/plan.dart'; import 'package:go_router/go_router.dart'; @@ -1196,6 +1197,34 @@ class _ListAllPlansState extends State { ); }, ), + IconButton( + icon: const Icon( + Icons + .info_outlined, + color: Color( + 0xFF475569, + ), + size: 18, + ), + tooltip: + 'Trip Info', + onPressed: () { + showDialog( + context: + context, + builder: + ( + context, + ) => TripInformation( + // planId: plan.planId, + planId: + plan.planId.toString(), + layoutColorForUser: + layoutColor!, + ), + ); + }, + ), ], ), ), @@ -1490,6 +1519,34 @@ class _ListAllPlansState extends State { ); }, ), + IconButton( + icon: const Icon( + Icons + .info_outline_rounded, + color: Color( + 0xFF475569, + ), + size: 20, + ), + tooltip: + 'Trip Info', + onPressed: () { + showDialog( + context: + context, + builder: + ( + context, + ) => TripInformation( + // planId: plan.planId, + planId: + plan.planId.toString(), + layoutColorForUser: + layoutColor!, + ), + ); + }, + ), ], ), ), diff --git a/lib/Screens/allTrips/plan_info_mdl.dart b/lib/Screens/allTrips/plan_info_mdl.dart new file mode 100644 index 0000000..b960e10 --- /dev/null +++ b/lib/Screens/allTrips/plan_info_mdl.dart @@ -0,0 +1,208 @@ +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 TripInformation extends StatefulWidget { + final String planId; + final Color layoutColorForUser; + + const TripInformation({ + Key? key, + required this.planId, + required this.layoutColorForUser, + }) : super(key: key); + + @override + _TripInformationState createState() => _TripInformationState(); +} + +class _TripInformationState extends State { + late Future> _tripInfoFuture; + + Future> fetchComments() async { + final String apiUrldata = + '$apiUrl/api/plans/planInfo?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', + 'app-signature': 'ts-traveltool-2025-signature-123456', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + final jsonData = json.decode(response.body); + final Map dataMap = + jsonData['data'] as Map; + return dataMap; + } else { + throw Exception('Failed to load comments'); + } + } + + @override + void initState() { + super.initState(); + _tripInfoFuture = fetchComments(); + + print('_tripInfoFuture : $_tripInfoFuture'); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: Colors.white, + title: Text( + 'Trip Information', + 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: _tripInfoFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError) { + return Text( + 'Error: ${snapshot.error}', + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + ); + } + + if (!snapshot.hasData) { + return Text( + 'No data found.', + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + ); + } + + final data = snapshot.data!; + + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + keyValueColumn("Traveller", data["traveller"] ?? ""), + keyValueColumn("Group Name", data["group_name"] ?? ""), + keyValueColumn( + "Allowed Plan Type", + data["allowed_plan_type"] ?? "", + ), + keyValueColumn( + "Policy Action Flow", + data["policy_action_flow"] ?? "", + ), + keyValueColumn("Policy Type", data["policy_type"] ?? ""), + + const SizedBox(height: 12), + Text( + "Approval Criteria:", + style: GoogleFonts.poppins( + fontWeight: FontWeight.w600, + fontSize: 13, + ), + ), + + ...List.generate((data["approval_criteria"] as List).length, ( + index, + ) { + final item = data["approval_criteria"][index]; + return Card( + elevation: 1, + color: Colors.white, + margin: const EdgeInsets.symmetric(vertical: 4), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + keyValueRow("Action", item["action"]), + keyValueRow("Approver", item["approver"]), + keyValueRow( + "Is Action Done", + item["is_action_done"], + ), + keyValueRow("Action On", item["action_on"]), + keyValueRow("Email Status", item["email_status"]), + ], + ), + ), + ); + }), + ], + ), + ); + }, + ), + ), + actions: [], + ); + } + + Widget keyValueColumn(String key, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "$key ", + style: GoogleFonts.poppins( + fontWeight: FontWeight.w600, + fontSize: 13, + ), + ), + SizedBox(height: 5), + Text(value, style: GoogleFonts.poppins(fontSize: 13)), + // Expanded( + // child: Text(value, style: GoogleFonts.poppins(fontSize: 13)), + // ), + ], + ), + ); + } + + Widget keyValueRow(String key, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + "$key ", + style: GoogleFonts.poppins( + fontWeight: FontWeight.w600, + fontSize: 12, + ), + ), + ), + SizedBox(height: 5), + Expanded( + child: Text(value, style: GoogleFonts.poppins(fontSize: 12)), + ), + ], + ), + ); + } +} diff --git a/lib/Screens/allTrips/travel_agent_list.dart b/lib/Screens/allTrips/travel_agent_list.dart index 72da57b..d4903e7 100644 --- a/lib/Screens/allTrips/travel_agent_list.dart +++ b/lib/Screens/allTrips/travel_agent_list.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:core'; +import 'package:frontend/Screens/allTrips/plan_info_mdl.dart'; import 'package:frontend/data/models/plan.dart'; import 'package:frontend/utils/travelAgent_remarks.dart'; import 'package:go_router/go_router.dart'; @@ -947,6 +948,35 @@ class _TravelAgentListPlansState extends State { ); }, ), + IconButton( + icon: const Icon( + Icons + .info_outlined, + color: Color( + 0xFF475569, + ), + size: 18, + ), + tooltip: + 'Trip Info', + onPressed: () { + showDialog( + context: + context, + builder: + ( + context, + ) => TripInformation( + // planId: plan.planId, + planId: + plan.planId + .toString(), + layoutColorForUser: + layoutColor!, + ), + ); + }, + ), ], ), ), diff --git a/lib/Screens/itnerary/forex.dart b/lib/Screens/itnerary/forex.dart index 0f8cdd9..27ecf49 100644 --- a/lib/Screens/itnerary/forex.dart +++ b/lib/Screens/itnerary/forex.dart @@ -635,6 +635,7 @@ class _ForexScreenState extends State { if (_isForexDataComplete()) { if (tripuserId != null) { + print('tripuserId - $tripuserId'); postgetForexData(getForexData); } else { print("tripuserId is null"); @@ -686,6 +687,7 @@ class _ForexScreenState extends State { }); if (_isForexDataComplete()) { + print('_isForexDataComplete'); postgetForexData(getForexData); } } diff --git a/lib/Screens/itnerary_list/accomodation_list.dart b/lib/Screens/itnerary_list/accomodation_list.dart index 45d38b5..1b45e0d 100644 --- a/lib/Screens/itnerary_list/accomodation_list.dart +++ b/lib/Screens/itnerary_list/accomodation_list.dart @@ -29,10 +29,13 @@ class _AccomodationListWidgetState extends State { Color? secondColor; Color? thridColor; + bool orgHasService = false; + @override void initState() { super.initState(); loadInitialData(); + getOrgServices(); } void loadInitialData() async { @@ -64,6 +67,22 @@ class _AccomodationListWidgetState extends State { }); } + void getOrgServices() async { + final services = await getOrgServicesName(); + print("getOrgServices storage: $services"); + + // just names + final names = services.map((s) => s['name']).toList(); + print("Names only: $names"); + final hasFlight = names.contains("Accomodation"); + + setState(() { + orgHasService = hasFlight; + }); + + print("orgHasService: $orgHasService"); + } + @override Widget build(BuildContext context) { final isDesktop = MediaQuery.of(context).size.width > 1024; @@ -88,73 +107,73 @@ class _AccomodationListWidgetState extends State { fontWeight: FontWeight.bold, ), ), - - MouseRegion( - cursor: - widget.isViewMode - ? SystemMouseCursors.forbidden - : SystemMouseCursors.click, - - child: GestureDetector( - onTap: + if (widget.accommodationList.isNotEmpty && orgHasService) + MouseRegion( + cursor: widget.isViewMode - ? null - : () { - print("New data"); - widget.onAddNew("Accomodation", true); - }, - child: Row( - mainAxisSize: - MainAxisSize.min, // Ensures content fits nicely - children: [ - // Text( - // "Add New", - // style: TextStyle(fontSize: 13), - // ), - // SizedBox(width: 8), // spacing between icon and text - Icon( - Icons.add_circle_sharp, - size: 30, - color: Color(0xFF114D8B), - ), - // Container( - // decoration: BoxDecoration( - // shape: BoxShape.circle, - // border: Border.all( - // color: Color(0xFF114D8B), // Outline color - // width: 2, // Outline thickness - // ), - // ), - // height: 30, - // width: 30, - // child: Center( - // child: Icon( - // Icons.add, - // size: 20, - // color: Colors.black, - // ), - // ), - // ), - ], + ? SystemMouseCursors.forbidden + : SystemMouseCursors.click, + + child: GestureDetector( + onTap: + widget.isViewMode + ? null + : () { + print("New data"); + widget.onAddNew("Accomodation", true); + }, + child: Row( + mainAxisSize: + MainAxisSize.min, // Ensures content fits nicely + children: [ + // Text( + // "Add New", + // style: TextStyle(fontSize: 13), + // ), + // SizedBox(width: 8), // spacing between icon and text + Icon( + Icons.add_circle_sharp, + size: 30, + color: Color(0xFF114D8B), + ), + // Container( + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // border: Border.all( + // color: Color(0xFF114D8B), // Outline color + // width: 2, // Outline thickness + // ), + // ), + // height: 30, + // width: 30, + // child: Center( + // child: Icon( + // Icons.add, + // size: 20, + // color: Colors.black, + // ), + // ), + // ), + ], + ), ), + // onPressed: isViewMode + // ? null + // : () { + // print("New data"); + // + // }, + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Icon( + // Icons.add_circle_sharp, + // size: 30, + // color: Color(0xFF114D8B), + // ) + // ], + // ), ), - // onPressed: isViewMode - // ? null - // : () { - // print("New data"); - // - // }, - // child: Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // Icon( - // Icons.add_circle_sharp, - // size: 30, - // color: Color(0xFF114D8B), - // ) - // ], - // ), - ), // MouseRegion( // cursor: isViewMode // ? SystemMouseCursors.forbidden @@ -328,31 +347,34 @@ class _AccomodationListWidgetState extends State { ), ), Spacer(), - GestureDetector( - onTap: () => widget.onOpen(true, item, "Accomodation"), - child: Tooltip( - message: 'Edit Accomodation Details', - child: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15, - color: isDesktop ? Color(0xFF114D8B) : Colors.black87, + if (orgHasService) ...[ + GestureDetector( + onTap: () => widget.onOpen(true, item, "Accomodation"), + child: Tooltip( + message: 'Edit Accomodation Details', + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15, + color: + isDesktop ? Color(0xFF114D8B) : Colors.black87, + ), ), ), - ), - SizedBox(width: 10), - GestureDetector( - onTap: () => widget.onDeleteAccommodation(item), - child: Tooltip( - message: 'Delete Accommodation Details', - child: Image.asset( - 'assets/images/IconsImg/delete.png', - width: 20, - height: 15, - color: Colors.red, + SizedBox(width: 10), + GestureDetector( + onTap: () => widget.onDeleteAccommodation(item), + child: Tooltip( + message: 'Delete Accommodation Details', + child: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15, + color: Colors.red, + ), ), ), - ), + ], ], ), // Divider(color: Colors.blueGrey.shade50), diff --git a/lib/Screens/itnerary_list/bus_list.dart b/lib/Screens/itnerary_list/bus_list.dart index 5b5b380..899dc58 100644 --- a/lib/Screens/itnerary_list/bus_list.dart +++ b/lib/Screens/itnerary_list/bus_list.dart @@ -35,11 +35,13 @@ class _BusListWidgetState extends State { Color? layoutColor; Color? secondColor; Color? thridColor; + bool orgHasService = false; @override void initState() { super.initState(); loadInitialData(); + getOrgServices(); } void loadInitialData() async { @@ -71,6 +73,22 @@ class _BusListWidgetState extends State { }); } + void getOrgServices() async { + final services = await getOrgServicesName(); + print("getOrgServices storage: $services"); + + // just names + final names = services.map((s) => s['name']).toList(); + print("Names only: $names"); + final hasFlight = names.contains("Bus"); + + setState(() { + orgHasService = hasFlight; + }); + + print("orgHasService: $orgHasService"); + } + @override Widget build(BuildContext context) { final isDesktop = MediaQuery.of(context).size.width > 1024; @@ -95,87 +113,87 @@ class _BusListWidgetState extends State { fontWeight: FontWeight.bold, ), ), - - MouseRegion( - cursor: - widget.isViewMode - ? SystemMouseCursors.forbidden - : SystemMouseCursors.click, - child: GestureDetector( - onTap: + if (widget.busList.isNotEmpty && orgHasService) + MouseRegion( + cursor: widget.isViewMode - ? null - : () { - print("New data"); - widget.onAddNew("Bus", true); - }, - child: Row( - mainAxisSize: - MainAxisSize.min, // Ensures content fits nicely - children: [ - // Text( - // "Add New", - // style: TextStyle(fontSize: 13), - // ), - // SizedBox(width: 8), // spacing between icon and text - Icon( - Icons.add_circle_sharp, - size: 30, - color: Color(0xFF114D8B), - ), - // Container( - // decoration: BoxDecoration( - // shape: BoxShape.circle, - // border: Border.all( - // color: Color(0xFF114D8B), // Outline color - // width: 2, // Outline thickness - // ), - // ), - // height: 30, - // width: 30, - // child: Center( - // child: Icon( - // Icons.add, - // size: 20, - // color: Colors.black, - // ), - // ), - // ), - ], + ? SystemMouseCursors.forbidden + : SystemMouseCursors.click, + child: GestureDetector( + onTap: + widget.isViewMode + ? null + : () { + print("New data"); + widget.onAddNew("Bus", true); + }, + child: Row( + mainAxisSize: + MainAxisSize.min, // Ensures content fits nicely + children: [ + // Text( + // "Add New", + // style: TextStyle(fontSize: 13), + // ), + // SizedBox(width: 8), // spacing between icon and text + Icon( + Icons.add_circle_sharp, + size: 30, + color: Color(0xFF114D8B), + ), + // Container( + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // border: Border.all( + // color: Color(0xFF114D8B), // Outline color + // width: 2, // Outline thickness + // ), + // ), + // height: 30, + // width: 30, + // child: Center( + // child: Icon( + // Icons.add, + // size: 20, + // color: Colors.black, + // ), + // ), + // ), + ], + ), ), + // child: 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: + // EdgeInsets.symmetric(horizontal: 20, vertical: 12), + // ), + // onPressed: isViewMode + // ? null + // : () { + // print("New data"); + // onAddNew("Bus", true); + // }, + // child: Row( + // mainAxisSize: + // MainAxisSize.min, // Ensures content fits nicely + // children: [ + // Icon( + // Icons.add_circle_sharp, + // size: 30, + // color: Color(0xFF114D8B), + // ) + // ], + // ), + // ), ), - // child: 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: - // EdgeInsets.symmetric(horizontal: 20, vertical: 12), - // ), - // onPressed: isViewMode - // ? null - // : () { - // print("New data"); - // onAddNew("Bus", true); - // }, - // child: Row( - // mainAxisSize: - // MainAxisSize.min, // Ensures content fits nicely - // children: [ - // Icon( - // Icons.add_circle_sharp, - // size: 30, - // color: Color(0xFF114D8B), - // ) - // ], - // ), - // ), - ), ], ), const SizedBox(height: 16), @@ -306,32 +324,36 @@ class _BusListWidgetState extends State { ), ), Spacer(), - GestureDetector( - onTap: () => widget.onOpen(true, item, "Bus"), - child: Tooltip( - message: 'Edit Bus Details', - child: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15, - // color: Color(0xFF114D8B), - color: !isDesktop ? Colors.black : Color(0xFF575A74), + + if (widget.busList.isNotEmpty && orgHasService) ...[ + GestureDetector( + onTap: () => widget.onOpen(true, item, "Bus"), + child: Tooltip( + message: 'Edit Bus Details', + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15, + // color: Color(0xFF114D8B), + color: + !isDesktop ? Colors.black : Color(0xFF575A74), + ), ), ), - ), - SizedBox(width: 10), - GestureDetector( - onTap: () => widget.onDeleteBus(item), - child: Tooltip( - message: 'Delete Bus Details', - child: Image.asset( - 'assets/images/IconsImg/delete.png', - width: 20, - height: 15, - color: Colors.red, + SizedBox(width: 10), + GestureDetector( + onTap: () => widget.onDeleteBus(item), + child: Tooltip( + message: 'Delete Bus Details', + child: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15, + color: Colors.red, + ), ), ), - ), + ], ], ), // Divider(color: Colors.blueGrey.shade50), diff --git a/lib/Screens/itnerary_list/flight_list.dart b/lib/Screens/itnerary_list/flight_list.dart index 3c4606b..807b04d 100644 --- a/lib/Screens/itnerary_list/flight_list.dart +++ b/lib/Screens/itnerary_list/flight_list.dart @@ -37,6 +37,8 @@ class _FlightListWidgetState extends State { Color? secondColor; Color? thridColor; + bool orgHasService = false; + // late Map countryMap; Map countryMap = {}; @@ -45,6 +47,7 @@ class _FlightListWidgetState extends State { super.initState(); loadCountryList(); // Call your method here loadInitialData(); + getOrgServices(); } void loadInitialData() async { @@ -76,6 +79,22 @@ class _FlightListWidgetState extends State { }); } + void getOrgServices() async { + final services = await getOrgServicesName(); + print("getOrgServices storage: $services"); + + // just names + final names = services.map((s) => s['name']).toList(); + print("Names only: $names"); + final hasFlight = names.contains("Flight"); + + setState(() { + orgHasService = hasFlight; + }); + + print("orgHasService: $orgHasService"); + } + Future loadCountryList() async { final result = await apiService.fetchFlightsCountryList(widget.tripType); @@ -225,7 +244,7 @@ class _FlightListWidgetState extends State { ), // Right side: either "Add" icon or nothing - if (widget.flightList.isNotEmpty) + if (widget.flightList.isNotEmpty && orgHasService) MouseRegion( cursor: widget.isViewMode @@ -247,23 +266,24 @@ class _FlightListWidgetState extends State { ), ) else if (!isDesktop) - // Centered "No Data Found" message for mobile and empty list - Expanded( - child: Center( - child: Text( - "No Data Found", - textAlign: TextAlign.center, - style: GoogleFonts.poppins( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Colors.grey, - ), - ), - ), - ), + SizedBox.shrink(), + // Centered "No Data Found" message for mobile and empty list + // Expanded( + // child: Center( + // child: Text( + // "No Data Found", + // textAlign: TextAlign.center, + // style: GoogleFonts.poppins( + // fontSize: 18, + // fontWeight: FontWeight.w500, + // color: Colors.grey, + // ), + // ), + // ), + // ), ], ), - const SizedBox(height: 16), + isDesktop ? SizedBox(height: 16) : SizedBox(height: 5), // Actual flight data UI _buildData(context, isDesktop), @@ -430,32 +450,37 @@ class _FlightListWidgetState extends State { ), ), Spacer(), - GestureDetector( - onTap: () => widget.onOpen(true, item, "Flight"), - child: Tooltip( - message: 'Edit Flight Details', - child: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15, - color: - isDesktop ? Color(0xFF114D8B) : Colors.black87, + + if (orgHasService) ...[ + GestureDetector( + onTap: () => widget.onOpen(true, item, "Flight"), + child: Tooltip( + message: 'Edit Flight Details', + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15, + color: + isDesktop + ? Color(0xFF114D8B) + : Colors.black87, + ), ), ), - ), - SizedBox(width: 10), - GestureDetector( - onTap: () => widget.onDeleteFlight(item), - child: Tooltip( - message: 'Delete Flight Details', - child: Image.asset( - 'assets/images/IconsImg/delete.png', - width: 20, - height: 15, - color: Colors.red, + SizedBox(width: 10), + GestureDetector( + onTap: () => widget.onDeleteFlight(item), + child: Tooltip( + message: 'Delete Flight Details', + child: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15, + color: Colors.red, + ), ), ), - ), + ], ], ), ), diff --git a/lib/Screens/itnerary_list/forex_list.dart b/lib/Screens/itnerary_list/forex_list.dart index 0f52467..2083813 100644 --- a/lib/Screens/itnerary_list/forex_list.dart +++ b/lib/Screens/itnerary_list/forex_list.dart @@ -36,6 +36,7 @@ class _ForexListWidgetState extends State { Color? layoutColor; Color? secondColor; Color? thridColor; + bool orgHasService = false; @override void initState() { @@ -72,6 +73,22 @@ class _ForexListWidgetState extends State { }); } + void getOrgServices() async { + final services = await getOrgServicesName(); + print("getOrgServices storage: $services"); + + // just names + final names = services.map((s) => s['name']).toList(); + print("Names only: $names"); + final hasFlight = names.contains("Forex"); + + setState(() { + orgHasService = hasFlight; + }); + + print("orgHasService: $orgHasService"); + } + @override Widget build(BuildContext context) { final isDesktop = MediaQuery.of(context).size.width > 1024; @@ -334,32 +351,37 @@ class _ForexListWidgetState extends State { ), ), Spacer(), - GestureDetector( - onTap: () => widget.onOpen(true, item, "Forex"), - child: Tooltip( - message: 'Edit Forex Details', - child: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15, - // color: Color(0xFF114D8B), - color: !isDesktop ? Colors.black : Color(0xFF575A74), + + if (orgHasService) ...[ + GestureDetector( + onTap: () => widget.onOpen(true, item, "Forex"), + child: Tooltip( + message: 'Edit Forex Details', + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15, + // color: Color(0xFF114D8B), + color: + !isDesktop ? Colors.black : Color(0xFF575A74), + ), ), ), - ), - SizedBox(width: 10), - GestureDetector( - onTap: () => widget.onDeleteForex(item), - child: Tooltip( - message: 'Delete Forex Details', - child: Image.asset( - 'assets/images/IconsImg/delete.png', - width: 20, - height: 15, - color: Colors.red, + SizedBox(width: 10), + GestureDetector( + onTap: () => widget.onDeleteForex(item), + child: Tooltip( + message: 'Delete Forex Details', + child: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15, + color: Colors.red, + ), ), ), - ), + ], + item['forex_id'] != null ? IconButton( icon: Icon( diff --git a/lib/Screens/itnerary_list/insurance_list.dart b/lib/Screens/itnerary_list/insurance_list.dart index f5f9b37..73ca742 100644 --- a/lib/Screens/itnerary_list/insurance_list.dart +++ b/lib/Screens/itnerary_list/insurance_list.dart @@ -33,11 +33,29 @@ class _InsuranceListWidgetState extends State { Color? layoutColor; Color? secondColor; Color? thridColor; + bool orgHasService = false; @override void initState() { super.initState(); loadInitialData(); + getOrgServices(); + } + + void getOrgServices() async { + final services = await getOrgServicesName(); + print("getOrgServices storage: $services"); + + // just names + final names = services.map((s) => s['name']).toList(); + print("Names only: $names"); + final hasFlight = names.contains("Insurance"); + + setState(() { + orgHasService = hasFlight; + }); + + print("orgHasService: $orgHasService"); } void loadInitialData() async { @@ -267,31 +285,33 @@ class _InsuranceListWidgetState extends State { ), ), Spacer(), - GestureDetector( - onTap: () => widget.onOpen(true, item, "Insurance"), - child: Tooltip( - message: 'Edit Insurance Details', - child: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15, - color: isDesktop ? Color(0xFF114D8B) : Colors.black, + if (orgHasService) ...[ + GestureDetector( + onTap: () => widget.onOpen(true, item, "Insurance"), + child: Tooltip( + message: 'Edit Insurance Details', + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15, + color: isDesktop ? Color(0xFF114D8B) : Colors.black, + ), ), ), - ), - SizedBox(width: 10), - GestureDetector( - onTap: () => widget.onDeleteInsurance(item), - child: Tooltip( - message: 'Delete Insurance Details', - child: Image.asset( - 'assets/images/IconsImg/delete.png', - width: 20, - height: 15, - color: Colors.red, + SizedBox(width: 10), + GestureDetector( + onTap: () => widget.onDeleteInsurance(item), + child: Tooltip( + message: 'Delete Insurance Details', + child: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15, + color: Colors.red, + ), ), ), - ), + ], ], ), SizedBox(height: 4), diff --git a/lib/Screens/itnerary_list/miscellaneous_list.dart b/lib/Screens/itnerary_list/miscellaneous_list.dart index 50277f4..7b45255 100644 --- a/lib/Screens/itnerary_list/miscellaneous_list.dart +++ b/lib/Screens/itnerary_list/miscellaneous_list.dart @@ -30,11 +30,29 @@ class _MiscellaneousListWidgetState extends State { Color? layoutColor; Color? secondColor; Color? thridColor; + bool orgHasService = false; @override void initState() { super.initState(); loadInitialData(); + getOrgServices(); + } + + void getOrgServices() async { + final services = await getOrgServicesName(); + print("getOrgServices storage: $services"); + + // just names + final names = services.map((s) => s['name']).toList(); + print("Names only: $names"); + final hasFlight = names.contains("Miscellaneous"); + + setState(() { + orgHasService = hasFlight; + }); + + print("orgHasService: $orgHasService"); } void loadInitialData() async { @@ -90,84 +108,86 @@ class _MiscellaneousListWidgetState extends State { fontWeight: FontWeight.bold, ), ), - MouseRegion( - cursor: - widget.isViewMode - ? SystemMouseCursors.forbidden - : SystemMouseCursors.click, - child: GestureDetector( - onTap: + + if (orgHasService && widget.miscellaneousList.isNotEmpty) + MouseRegion( + cursor: widget.isViewMode - ? null - : () { - print("New data"); - widget.onAddNew("Miscellaneous", true); - }, - child: Row( - mainAxisSize: - MainAxisSize.min, // Ensures content fits nicely - children: [ - // Text( - // "Add New", - // style: TextStyle(fontSize: 13), - // ), - // SizedBox(width: 8), // spacing between icon and text - Icon( - Icons.add_circle_sharp, - size: 30, - color: Color(0xFF114D8B), - ), - // Container( - // decoration: BoxDecoration( - // shape: BoxShape.circle, - // border: Border.all( - // color: Color(0xFF114D8B), // Outline color - // width: 2, // Outline thickness - // ), - // ), - // height: 30, - // width: 30, - // child: Center( - // child: Icon( - // Icons.add, - // size: 20, - // color: Colors.black, - // ), - // ), - // ), - ], + ? SystemMouseCursors.forbidden + : SystemMouseCursors.click, + child: GestureDetector( + onTap: + widget.isViewMode + ? null + : () { + print("New data"); + widget.onAddNew("Miscellaneous", true); + }, + child: Row( + mainAxisSize: + MainAxisSize.min, // Ensures content fits nicely + children: [ + // Text( + // "Add New", + // style: TextStyle(fontSize: 13), + // ), + // SizedBox(width: 8), // spacing between icon and text + Icon( + Icons.add_circle_sharp, + size: 30, + color: Color(0xFF114D8B), + ), + // Container( + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // border: Border.all( + // color: Color(0xFF114D8B), // Outline color + // width: 2, // Outline thickness + // ), + // ), + // height: 30, + // width: 30, + // child: Center( + // child: Icon( + // Icons.add, + // size: 20, + // color: Colors.black, + // ), + // ), + // ), + ], + ), ), + // child: 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: + // EdgeInsets.symmetric(horizontal: 20, vertical: 12), + // ), + // onPressed: isViewMode + // ? null + // : () { + // onAddNew("Miscellaneous", true); + // }, + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Icon( + // Icons.add_circle_sharp, + // size: 30, + // color: Color(0xFF114D8B), + // ) + // ], + // ), + // ), ), - // child: 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: - // EdgeInsets.symmetric(horizontal: 20, vertical: 12), - // ), - // onPressed: isViewMode - // ? null - // : () { - // onAddNew("Miscellaneous", true); - // }, - // child: Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // Icon( - // Icons.add_circle_sharp, - // size: 30, - // color: Color(0xFF114D8B), - // ) - // ], - // ), - // ), - ), ], ), const SizedBox(height: 16), @@ -244,32 +264,34 @@ class _MiscellaneousListWidgetState extends State { ), ), Spacer(), - GestureDetector( - onTap: () => widget.onOpen(true, item, "Miscellaneous"), - child: Tooltip( - message: 'Edit Miscellaneous Details', - child: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15, - color: isDesktop ? Color(0xFF114D8B) : Colors.black, + if (orgHasService) ...[ + GestureDetector( + onTap: () => widget.onOpen(true, item, "Miscellaneous"), + child: Tooltip( + message: 'Edit Miscellaneous Details', + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15, + color: isDesktop ? Color(0xFF114D8B) : Colors.black, + ), ), ), - ), - SizedBox(width: 10), - GestureDetector( - // onTap: () => onOpen(true, item, "Miscellaneous"), - onTap: () => widget.onDeleteMiscellaneous(item), - child: Tooltip( - message: 'Delete Miscellaneous Details', - child: Image.asset( - 'assets/images/IconsImg/delete.png', - width: 20, - height: 15, - color: Colors.red, + SizedBox(width: 10), + GestureDetector( + // onTap: () => onOpen(true, item, "Miscellaneous"), + onTap: () => widget.onDeleteMiscellaneous(item), + child: Tooltip( + message: 'Delete Miscellaneous Details', + child: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15, + color: Colors.red, + ), ), ), - ), + ], ], ), // Divider(color: Colors.blueGrey.shade50), diff --git a/lib/Screens/itnerary_list/taxi_list.dart b/lib/Screens/itnerary_list/taxi_list.dart index a306df9..269d5b1 100644 --- a/lib/Screens/itnerary_list/taxi_list.dart +++ b/lib/Screens/itnerary_list/taxi_list.dart @@ -31,11 +31,29 @@ class _TaxiListWidgetState extends State { Color? layoutColor; Color? secondColor; Color? thridColor; + bool orgHasService = false; @override void initState() { super.initState(); loadInitialData(); + getOrgServices(); + } + + void getOrgServices() async { + final services = await getOrgServicesName(); + print("getOrgServices storage: $services"); + + // just names + final names = services.map((s) => s['name']).toList(); + print("Names only: $names"); + final hasFlight = names.contains("Taxi"); + + setState(() { + orgHasService = hasFlight; + }); + + print("orgHasService: $orgHasService"); } void loadInitialData() async { @@ -91,69 +109,71 @@ class _TaxiListWidgetState extends State { fontWeight: FontWeight.bold, ), ), - MouseRegion( - cursor: - widget.isViewMode - ? SystemMouseCursors.forbidden - : SystemMouseCursors.click, - child: GestureDetector( - onTap: + if (orgHasService && widget.taxiList.isNotEmpty) + MouseRegion( + cursor: widget.isViewMode - ? null - : () { - print("New data"); - widget.onAddNew("Taxi", true); - }, - child: Row( - mainAxisSize: - MainAxisSize.min, // Ensures content fits nicely - children: [ - // Text( - // "Add New", - // style: TextStyle(fontSize: 13), - // ), - // SizedBox(width: 8), // spacing between icon and text - Icon( - Icons.add_circle_sharp, - size: 30, - color: Color(0xFF114D8B), - ), - ], + ? SystemMouseCursors.forbidden + : SystemMouseCursors.click, + + child: GestureDetector( + onTap: + widget.isViewMode + ? null + : () { + print("New data"); + widget.onAddNew("Taxi", true); + }, + child: Row( + mainAxisSize: + MainAxisSize.min, // Ensures content fits nicely + children: [ + // Text( + // "Add New", + // style: TextStyle(fontSize: 13), + // ), + // SizedBox(width: 8), // spacing between icon and text + Icon( + Icons.add_circle_sharp, + size: 30, + color: Color(0xFF114D8B), + ), + ], + ), ), + // child: 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: + // EdgeInsets.symmetric(horizontal: 20, vertical: 12), + // ), + // onPressed: isViewMode + // ? null + // : () { + // print("New data"); + // onAddNew("Taxi", true); + // }, + // child: Row( + // mainAxisSize: + // MainAxisSize.min, // Ensures content fits nicely + // children: [ + // Icon( + // Icons.add_circle_sharp, + // size: 30, + // color: Color(0xFF114D8B), + // ) + // ], + // ), + // ), ), - // child: 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: - // EdgeInsets.symmetric(horizontal: 20, vertical: 12), - // ), - // onPressed: isViewMode - // ? null - // : () { - // print("New data"); - // onAddNew("Taxi", true); - // }, - // child: Row( - // mainAxisSize: - // MainAxisSize.min, // Ensures content fits nicely - // children: [ - // Icon( - // Icons.add_circle_sharp, - // size: 30, - // color: Color(0xFF114D8B), - // ) - // ], - // ), - // ), - ), ], ), const SizedBox(height: 16), @@ -315,31 +335,33 @@ class _TaxiListWidgetState extends State { ), ), Spacer(), - GestureDetector( - onTap: () => widget.onOpen(true, item, "Taxi"), - child: Tooltip( - message: 'Edit Taxi Details', - child: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15, - color: isDesktop ? Color(0xFF114D8B) : Colors.black, + if (orgHasService) ...[ + GestureDetector( + onTap: () => widget.onOpen(true, item, "Taxi"), + child: Tooltip( + message: 'Edit Taxi Details', + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15, + color: isDesktop ? Color(0xFF114D8B) : Colors.black, + ), ), ), - ), - SizedBox(width: 10), - GestureDetector( - onTap: () => widget.onDeleteTaxi(item), - child: Tooltip( - message: 'Delete Taxi Details', - child: Image.asset( - 'assets/images/IconsImg/delete.png', - width: 20, - height: 15, - color: Colors.red, + SizedBox(width: 10), + GestureDetector( + onTap: () => widget.onDeleteTaxi(item), + child: Tooltip( + message: 'Delete Taxi Details', + child: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15, + color: Colors.red, + ), ), ), - ), + ], ], ), // Divider(color: Colors.blueGrey.shade50), diff --git a/lib/Screens/itnerary_list/train_list.dart b/lib/Screens/itnerary_list/train_list.dart index 5c422ee..9f3e351 100644 --- a/lib/Screens/itnerary_list/train_list.dart +++ b/lib/Screens/itnerary_list/train_list.dart @@ -40,12 +40,30 @@ class _TrainListWidgetState extends State { Color? layoutColor; Color? secondColor; Color? thridColor; + bool orgHasService = false; @override void initState() { super.initState(); loadInitialData(); loadCountryList(); // Call your method here + getOrgServices(); + } + + void getOrgServices() async { + final services = await getOrgServicesName(); + print("getOrgServices storage: $services"); + + // just names + final names = services.map((s) => s['name']).toList(); + print("Names only: $names"); + final hasFlight = names.contains("Train"); + + setState(() { + orgHasService = hasFlight; + }); + + print("orgHasService: $orgHasService"); } void loadInitialData() async { @@ -145,7 +163,7 @@ class _TrainListWidgetState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if ((!isDesktop)) + if ((!isDesktop) && widget.trainList.isNotEmpty) Text( " ", style: const TextStyle( @@ -153,91 +171,93 @@ class _TrainListWidgetState extends State { fontWeight: FontWeight.bold, ), ), - MouseRegion( - cursor: - widget.isViewMode - ? SystemMouseCursors.forbidden - : SystemMouseCursors.click, - child: MouseRegion( + + if (orgHasService && widget.trainList.isNotEmpty) + MouseRegion( cursor: widget.isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click, - - child: GestureDetector( - onTap: + child: MouseRegion( + cursor: widget.isViewMode - ? null - : () { - print("New data"); - checkClass(); - }, - child: Row( - mainAxisSize: - MainAxisSize.min, // Ensures content fits nicely - children: [ - // Text( - // "Add New", - // style: TextStyle(fontSize: 13), - // ), - // SizedBox(width: 8), // spacing between icon and text - Icon( - Icons.add_circle_sharp, - size: 30, - color: Color(0xFF114D8B), - ), - ], + ? SystemMouseCursors.forbidden + : SystemMouseCursors.click, + + child: GestureDetector( + onTap: + widget.isViewMode + ? null + : () { + print("New data"); + checkClass(); + }, + child: Row( + mainAxisSize: + MainAxisSize.min, // Ensures content fits nicely + children: [ + // Text( + // "Add New", + // style: TextStyle(fontSize: 13), + // ), + // SizedBox(width: 8), // spacing between icon and text + Icon( + Icons.add_circle_sharp, + size: 30, + color: Color(0xFF114D8B), + ), + ], + ), ), + // onPressed: isViewMode + // ? null + // : () { + // print("New data"); + // + // }, + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Icon( + // Icons.add_circle_sharp, + // size: 30, + // color: Color(0xFF114D8B), + // ) + // ], + // ), ), - // onPressed: isViewMode - // ? null - // : () { - // print("New data"); - // - // }, - // child: Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // Icon( - // Icons.add_circle_sharp, - // size: 30, - // color: Color(0xFF114D8B), - // ) - // ], + // child: 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: + // EdgeInsets.symmetric(horizontal: 20, vertical: 12), + // ), + // onPressed: isViewMode + // ? null + // : () { + // print("New data"); + // onAddNew("Train", true); + // }, + // child: Row( + // mainAxisSize: + // MainAxisSize.min, // Ensures content fits nicely + // children: [ + // Icon( + // Icons.add_circle_sharp, + // size: 30, + // color: Color(0xFF114D8B), + // ) + // ], + // ), // ), ), - // child: 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: - // EdgeInsets.symmetric(horizontal: 20, vertical: 12), - // ), - // onPressed: isViewMode - // ? null - // : () { - // print("New data"); - // onAddNew("Train", true); - // }, - // child: Row( - // mainAxisSize: - // MainAxisSize.min, // Ensures content fits nicely - // children: [ - // Icon( - // Icons.add_circle_sharp, - // size: 30, - // color: Color(0xFF114D8B), - // ) - // ], - // ), - // ), - ), ], ), const SizedBox(height: 16), @@ -387,31 +407,34 @@ class _TrainListWidgetState extends State { ), ), Spacer(), - GestureDetector( - onTap: () => widget.onOpen(true, item, "Train"), - child: Tooltip( - message: 'Edit Train Details', - child: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15, - color: isDesktop ? Color(0xFF114D8B) : Colors.black, + + if (orgHasService) ...[ + GestureDetector( + onTap: () => widget.onOpen(true, item, "Train"), + child: Tooltip( + message: 'Edit Train Details', + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15, + color: isDesktop ? Color(0xFF114D8B) : Colors.black, + ), ), ), - ), - SizedBox(width: 10), - GestureDetector( - onTap: () => widget.onDeleteTrain(item), - child: Tooltip( - message: 'Delete Train Details', - child: Image.asset( - 'assets/images/IconsImg/delete.png', - width: 20, - height: 15, - color: Colors.red, + SizedBox(width: 10), + GestureDetector( + onTap: () => widget.onDeleteTrain(item), + child: Tooltip( + message: 'Delete Train Details', + child: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15, + color: Colors.red, + ), ), ), - ), + ], ], ), // Divider(color: Colors.blueGrey.shade50), diff --git a/lib/Screens/itnerary_list/visa_list.dart b/lib/Screens/itnerary_list/visa_list.dart index d575628..55aee40 100644 --- a/lib/Screens/itnerary_list/visa_list.dart +++ b/lib/Screens/itnerary_list/visa_list.dart @@ -33,10 +33,13 @@ class _VisaListWidgetState extends State { Color? secondColor; Color? thridColor; + bool orgHasService = false; + @override void initState() { super.initState(); loadInitialData(); + getOrgServices(); } void loadInitialData() async { @@ -68,6 +71,22 @@ class _VisaListWidgetState extends State { }); } + void getOrgServices() async { + final services = await getOrgServicesName(); + print("getOrgServices storage: $services"); + + // just names + final names = services.map((s) => s['name']).toList(); + print("Names only: $names"); + final hasFlight = names.contains("Visa"); + + setState(() { + orgHasService = hasFlight; + }); + + print("orgHasService: $orgHasService"); + } + @override Widget build(BuildContext context) { final isDesktop = MediaQuery.of(context).size.width > 1024; @@ -364,31 +383,34 @@ class _VisaListWidgetState extends State { ), ), Spacer(), - GestureDetector( - onTap: () => widget.onOpen(true, item, "Visa"), - child: Tooltip( - message: 'Edit Visa Details', - child: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15, - color: isDesktop ? Color(0xFF114D8B) : Colors.black, + + if (orgHasService) ...[ + GestureDetector( + onTap: () => widget.onOpen(true, item, "Visa"), + child: Tooltip( + message: 'Edit Visa Details', + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15, + color: isDesktop ? Color(0xFF114D8B) : Colors.black, + ), ), ), - ), - SizedBox(width: 10), - GestureDetector( - onTap: () => widget.onDeleteMiscellaneous(item), - child: Tooltip( - message: 'Delete Visa Details', - child: Image.asset( - 'assets/images/IconsImg/delete.png', - width: 20, - height: 15, - color: Colors.red, + SizedBox(width: 10), + GestureDetector( + onTap: () => widget.onDeleteMiscellaneous(item), + child: Tooltip( + message: 'Delete Visa Details', + child: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15, + color: Colors.red, + ), ), ), - ), + ], ], ), // Divider(color: Colors.blueGrey.shade50), diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index c0f7fb8..25cb1cc 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -485,8 +485,8 @@ class CreateNewPlansState extends State { "exceptional_plan_reason": _excepntldescriptionController.text, "functional_department": selectedFuncDept, "so_number": _soNumberController.text, - "created_by": selfId, - "updated_by": selfId, + // "created_by": selfId, + // "updated_by": selfId, "is_active": "1", "flight": flightList, "accomodation": accommodationList, @@ -562,6 +562,7 @@ class CreateNewPlansState extends State { print("approverStatus - ${widget.approverStatus}"); WidgetsBinding.instance.addPostFrameCallback((_) { loadInitialData(); + dynamicItineraryKey.currentState?.updateSelectedServices(); }); // if (widget.selectedPlanData != null) { @@ -876,7 +877,7 @@ class CreateNewPlansState extends State { var userTripId; // if (!mounted) return; print("getSelectedPlanFor"); - setState(() { + setState(() async { if (selectedplanUserId != null) { print("Is Not USER ID - $planUsrId "); if (selectedIstravelUser!) { @@ -904,6 +905,7 @@ class CreateNewPlansState extends State { } void fetchUserDetails() async { + print('FetchHandleUSe'); final details = await getUserDetails(); TripPlanAction = await getTripPlanAction(); print("TripPlanAction- $TripPlanAction"); @@ -926,6 +928,30 @@ class CreateNewPlansState extends State { // handleUpdateData(); } + void fetchUsrDtlFromSelectedTripUser() async { + // final details = await getUserDetails(); + + TripPlanAction = await getTripPlanActionFromSelectedUsr(); + 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'); @@ -1496,6 +1522,9 @@ class CreateNewPlansState extends State { if (selectedPlanId != null && selectedPlanId!.isNotEmpty) { planData['plan_id'] = selectedPlanId; // Add plan_id for update + planData['updated_by'] = selfId; // Add plan_id for update + } else { + planData['created_by'] = selfId; // Add plan_id for update } print("POSTPlanTesting------- $planData}"); @@ -3288,13 +3317,21 @@ class CreateNewPlansState extends State { final selected = options.firstWhere( (opt) => opt["title"] == newTitle, ); - setState(() { + setState(() async { _selectedOption = selected["value"]!; if (_selectedOption == "Option 2" || _selectedOption == "Option 3") { + _selectedTripType = null; _showInputDialog(selected["title"]!); } else if (_selectedOption == "Option 1") { otherUserName = userName; + await apiService.handleTripWiseToken(selfId!); + fetchUserDetails(); + _selectedTripType = null; + dynamicItineraryKey.currentState + ?.loadOrgSelectedAlServices(); + dynamicItineraryKey.currentState + ?.updateSelectedServices(); } }); }, @@ -3306,237 +3343,6 @@ class CreateNewPlansState extends State { ]; } - 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!; - // }); - // }, - // ), - ), - ), - ]; - } - void _showExceptionalReasonModal(BuildContext context) { showDialog( context: context, @@ -4424,7 +4230,7 @@ class CreateNewPlansState extends State { builder: (BuildContext context) { return UserSelectionDialog( title: title, - onSubmit: (input, userId, isTraveller) { + onSubmit: (input, userId, isTraveller) async { setState(() { otherUserName = input; selectedplanUserId = userId; @@ -4432,9 +4238,25 @@ class CreateNewPlansState extends State { }); print("USer entered : $otherUserName $userId $isTraveller"); getSelectedPlanFor(); + + if (isTraveller) { + await apiService.handleTripWiseToken( + selfId!, + ); // since userId is for traveller + } else { + await apiService.handleTripWiseToken( + userId, + ); // fallback to selfId + } + + fetchUsrDtlFromSelectedTripUser(); + + dynamicItineraryKey.currentState?.loadOrgSelectedAlServices(); + dynamicItineraryKey.currentState?.updateSelectedServices(); }, - onClose: () { + onClose: () async { print("Choosede Clsoes"); + await apiService.handleTripWiseToken(selfId!); fetchUserDetails(); }, layoutColorForUser: widget.layoutColor!, diff --git a/lib/Screens/plans/create_plans_token_bfr_17_Sep.dart b/lib/Screens/plans/create_plans_token_bfr_17_Sep.dart new file mode 100644 index 0000000..5407394 --- /dev/null +++ b/lib/Screens/plans/create_plans_token_bfr_17_Sep.dart @@ -0,0 +1,4218 @@ +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; + late FocusNode _tripTitleFocusNode; + + @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; + bool hasExceptionalClassInUpdate = 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; + bool hasUpdateVal = false; + + Map validationErrors = {}; + + // List> miscellaneousList = [{"special_request": 1, "comments": "posta", "indx": 1}]; + List> miscellaneousList = []; + 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.selectedPlanData != null) { + // hasUpdateVal = true; + // } + + 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; + } + + // trip_titleFocusNode.addListener(() { + // setState(() {}); // Rebuild when focus changes + // }); + + 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(); + + print("hasExceptionalClass - $hasExceptionalClass"); + print("hasExceptionalClassInUpdate - $hasExceptionalClassInUpdate"); + + // _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; + + hasUpdateVal = true; + } else if (userId.isNotEmpty && userId != "0") { + print("TestplanUsrId : $userId"); + print("TestplanUsrIdSelf : $selfId"); + planUsrId = userId; + print("TestplanUsrId2 : $planUsrId"); + _selectedOption = (selfId != userId) ? "Option 2" : "Option 1"; + otherUserName = userName; + hasUpdateVal = true; + } + }); + } + } + + 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 != "" && + _excepntldescriptionController.text != null) { + hasExceptionalClassInUpdate = true; + print("hasExceptionalClass1 - $hasExceptionalClass"); + print("hasExceptionalClassInUpdate1 - $hasExceptionalClassInUpdate"); + } + + _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', + 'app-signature': 'ts-traveltool-2025-signature-123456', + }, + ); + + 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(() async { + 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', + 'app-signature': 'ts-traveltool-2025-signature-123456', + }, + ); + + 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', + 'app-signature': 'ts-traveltool-2025-signature-123456', + }, + ); + + 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', + 'app-signature': 'ts-traveltool-2025-signature-123456', + }, + ); + + 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', + 'app-signature': 'ts-traveltool-2025-signature-123456', + }, + ); + + 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', + 'app-signature': 'ts-traveltool-2025-signature-123456', + }, + 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 serviceHasData() async { + return miscellaneousList.isNotEmpty || + visaList.isNotEmpty || + insuranceList.isNotEmpty || + accommodationList.isNotEmpty || + trainList.isNotEmpty || + flightList.isNotEmpty || + busList.isNotEmpty || + taxiList.isNotEmpty || + forexList.isNotEmpty; + } + + void clearAllServiceLists() async { + setState(() { + miscellaneousList = []; + visaList = []; + insuranceList = []; + accommodationList = []; + trainList = []; + flightList = []; + busList = []; + taxiList = []; + forexList = []; + + planData['miscellaneous'] = []; + planData['visa'] = []; + planData['insurance'] = []; + planData['accommodation'] = []; + planData['train'] = []; + planData['flight'] = []; + planData['bus'] = []; + planData['taxi'] = []; + planData['forex'] = []; + }); + + // Debugging output + print("After clearing:"); + print("After clearing: $planData"); + + print("miscellaneousList: ${miscellaneousList.length}"); + print("visaList: ${visaList.length}"); + print("insuranceList: ${insuranceList.length}"); + print("accommodationList: ${accommodationList.length}"); + print("trainList: ${trainList.length}"); + print("flightList: ${flightList.length}"); + print("busList: ${busList.length}"); + print("taxiList: ${taxiList.length}"); + print("forexList: ${forexList.length}"); + } + + 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.trim().isEmpty) { + _showExceptionalReasonModal(context); + } + if (!hasExceptionalClass) { + _excepntldescriptionController.text = ""; + } + }); + + print("hasExceptionalClass: $hasExceptionalClass"); + } + + void handleSubmit() async { + await checkExceptionalClass(); // Do async work first + setState(() { + // 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 + planData['updated_by'] = selfId; // Add plan_id for update + } else { + planData['created_by'] = selfId; // 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', + 'app-signature': 'ts-traveltool-2025-signature-123456', + }, + 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 (hasExceptionalClassInUpdate || + _excepntldescriptionController.text != "") + _buildNonDescriptionColumn(), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildDescriptionColumn(isDesktop), + SizedBox(height: 15), + if (hasExceptionalClassInUpdate) + _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: () { + print("TRIP CLICKESS1"); + // Request focus when user taps + focusNodes["trip_typeFocusNode"]?.requestFocus(); + print("TRIP CLICKESS15"); + }, + child: DropdownSearch>( + onBeforePopupOpening: (selectedItem) async { + bool hasData = await serviceHasData(); + if (hasData) { + bool? shouldProceed = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text( + "Changing trip type will remove added services", + ), + content: Text("Do you wish to proceed?"), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); // Cancel + }, + child: Text("Cancel"), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(true); // Continue + }, + child: Text("Continue"), + ), + ], + ); + }, + ); + + return shouldProceed ?? false; + } + return true; + }, + enabled: !hasUpdateVal, + 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) { + final newTripType = + newItem?['dropdown_key'].toString(); + print("newTripType - $newTripType"); + print("_selectedTripType- $_selectedTripType"); + if (newTripType != _selectedTripType) { + print("Type changed"); + clearAllServiceLists(); + dynamicItineraryKey.currentState + ?.clearAllServiceLists(); + } + 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"}, + ]; + + print("isViewModeYY: ${widget.isViewMode}, hasUpdateVal: $hasUpdateVal"); + 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: !hasUpdateVal, + // enabled: || !hasUpdateVal, + 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; + } + }); + }, + ), + ), + ), + ), + ), + ]; + } + + void _showExceptionalReasonModal(BuildContext context) { + showDialog( + context: context, + barrierDismissible: false, // prevent closing by tapping outside + builder: (context) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + child: Container( + padding: const EdgeInsets.all(16), + // decoration: BoxDecoration( + // border: Border.all( + // color: Colors.grey, // or any color you prefer + // width: 1.0, + // ), + // borderRadius: BorderRadius.circular(10), + // ), + color: Colors.white, + width: 400, + // width: MediaQuery.of(context).size.width * 0.5, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Reason for travel policy exception", + style: GoogleFonts.poppins( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 16), + TextField( + controller: _excepntldescriptionController, + maxLines: 3, + decoration: InputDecoration( + hintText: "Enter reason...", + hintStyle: GoogleFonts.poppins(fontSize: 10), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular( + 10, + ), // 👈 Rounded border + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: layoutColor!), // optional + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: layoutColor!), // optional + ), + ), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); // Cancel + }, + child: Text( + "Cancel", + style: GoogleFonts.poppins(fontSize: 12), + ), + ), + const SizedBox(width: 8), + ElevatedButton( + onPressed: () { + // Use the entered text + String reason = + _excepntldescriptionController.text.trim(); + if (reason.isNotEmpty) { + print("Reason entered: $reason"); + setState(() { + hasExceptionalClassInUpdate = true; + validationErrors.remove("exceptional_plan_reason"); + }); + + Navigator.of(context).pop(); // Close dialog + } else { + // Optional: show a validation message + } + }, + child: Text( + "OK", + style: GoogleFonts.poppins(fontSize: 12), + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + } + + 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, + onChanged: (value) { + validationErrors.remove("exceptional_plan_reason"); + }, + 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 _buildTN(bool isDesktop) { + return SizedBox( + width: isDesktop ? MediaQuery.of(context).size.width * 0.7443333 : 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 _buildTripName(bool isDesktop) { + return SizedBox( + width: isDesktop ? MediaQuery.of(context).size.width * 0.5 : 180, + height: 35, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + reverse: false, // show right-end on overflow + physics: BouncingScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: isDesktop ? MediaQuery.of(context).size.width * 0.5 : 180, + ), + child: IntrinsicWidth( + child: TextField( + controller: _tripTitleController, + maxLines: 1, + 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, + ), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 1, + vertical: 16, + ), + errorText: validationErrors["trip_title"], + errorStyle: GoogleFonts.poppins( + fontSize: 12, + color: Colors.red, + ), + ), + keyboardType: TextInputType.text, + scrollPhysics: BouncingScrollPhysics(), + ), + ), + ), + ), + ); + } + + 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, + ); + }, + ); + } +} diff --git a/lib/Screens/plans/dynamic_itinerary_stepper.dart b/lib/Screens/plans/dynamic_itinerary_stepper.dart index bb058a4..4f24bd1 100644 --- a/lib/Screens/plans/dynamic_itinerary_stepper.dart +++ b/lib/Screens/plans/dynamic_itinerary_stepper.dart @@ -11,8 +11,10 @@ import 'package:frontend/Screens/itnerary_list/train_list.dart'; import 'package:frontend/utils/auth_utils.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:responsive_builder/responsive_builder.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import '../../services/apiService.dart'; +import '../../utils/auth_utils.dart'; import '../itnerary/accomodations.dart'; import '../itnerary/bus.dart'; import '../itnerary/flights.dart'; @@ -84,7 +86,8 @@ class DynamicItineraryState extends State { int? selectedIndex; List? selectedAllServices; - List> selectedOrgServiceIds = []; + // List> selectedOrgServiceIds = []; + List> selectedOrgServiceIds = []; List? ServicesChoosed; List filledItineraryKeys = []; @@ -206,7 +209,9 @@ class DynamicItineraryState extends State { } } + /// Org token based services Future loadOrgSelectedAlServices() async { + print('loadOrgSelectedAlServices'); try { final result = await apiService.fetchOrganization(); @@ -239,6 +244,39 @@ class DynamicItineraryState extends State { } } + Future saveSelectedOrgServices( + List> services, + ) async { + final prefs = await SharedPreferences.getInstance(); + final jsonString = jsonEncode(services); + await prefs.setString("selectedOrgServiceName", jsonString); + } + + Future OrgServices() async { + print('loadOrgSelectedAlServices'); + try { + List> formattedName = + selectedOrgServiceIds.map((e) { + final id = e['service_id'].toString(); + + // find matching service from master list + final match = selectedAllServices?.firstWhere( + (service) => service['service_id'].toString() == id, + orElse: () => {}, + ); + + return { + "service_id": id, + "name": match.isNotEmpty ? match['name'].toString() : "", + }; + }).toList(); + await saveSelectedOrgServices(formattedName); + print("ServiceformattedName: $formattedName"); + } catch (e) { + print('Error fetching role list: $e'); + } + } + List getAllowedServiceNames() { print( "DID Updatee changee - $selectedOption $selectedListOption $isSelected $selectedItem $selectedIndex", @@ -336,6 +374,7 @@ class DynamicItineraryState extends State { Future updateSelectedServices() async { await loadAllServices(); await loadOrgSelectedAlServices(); + await OrgServices(); final allowedServiceNames = getAllowedServiceNames(); diff --git a/lib/Screens/plans/list_plans.dart b/lib/Screens/plans/list_plans.dart index 0dc7761..7a52972 100644 --- a/lib/Screens/plans/list_plans.dart +++ b/lib/Screens/plans/list_plans.dart @@ -22,6 +22,7 @@ import '../../utils/pagination.dart'; import '../../utils/travelAgent_remarks.dart'; import '../../widgets/custom_popup.dart'; import '../../widgets/popup_listPlan_action.dart'; +import '../allTrips/plan_info_mdl.dart'; import '../allTrips/remarks_list.dart'; class ListPlans extends StatefulWidget { @@ -1315,6 +1316,34 @@ class _ListPlansState extends State { ); }, ), + IconButton( + icon: const Icon( + Icons + .info_outlined, + color: Color( + 0xFF475569, + ), + size: 18, + ), + tooltip: + 'Trip Info', + onPressed: () { + showDialog( + context: + context, + builder: + ( + context, + ) => TripInformation( + // planId: plan.planId, + planId: + plan.planId.toString(), + layoutColorForUser: + layoutColor!, + ), + ); + }, + ), ], ), ), @@ -1632,6 +1661,35 @@ class _ListPlansState extends State { ); }, ), + + IconButton( + icon: const Icon( + Icons + .info_outlined, + color: Color( + 0xFF475569, + ), + size: 18, + ), + tooltip: + 'Trip Info', + onPressed: () { + showDialog( + context: + context, + builder: + ( + context, + ) => TripInformation( + // planId: plan.planId, + planId: + plan.planId.toString(), + layoutColorForUser: + layoutColor!, + ), + ); + }, + ), ], ), ), diff --git a/lib/Screens/policy/policy.dart b/lib/Screens/policy/policy.dart index 3cf3326..18a17cc 100644 --- a/lib/Screens/policy/policy.dart +++ b/lib/Screens/policy/policy.dart @@ -44,7 +44,18 @@ class _PolicyState extends State { final ApiService apiService = ApiService(); - // List services = []; + List definedServices = [ + 'Flight', + 'Accomodation', + 'Forex', + 'Insurance', + 'Visa', + 'Miscellaneous', + 'Taxi', + 'Bus', + 'Train', + ]; + List> services = []; String? servicesJson; Color? layoutColor; @@ -297,16 +308,18 @@ class _PolicyState extends State { ); print("exisitingService ${exisitingService}"); - final filtered = - exisitingService! - .where( - (service) => - selectedIds.contains(service['service_id'].toString()), - ) - .toList(); + // final filtered = + // exisitingService! + // .where( + // (service) => + // selectedIds.contains(service['service_id'].toString()), + // ) + // .toList(); setState(() { - ServicesChoosed = filtered; + // ServicesChoosed = exisitingService; + ServicesChoosed = selectedAllServices; + // ServicesChoosed = filtered; }); print("298 ServicesChoosed ${ServicesChoosed}"); @@ -357,17 +370,18 @@ class _PolicyState extends State { print("Filtered Selected Services Added to Policy: $ServicesChoosed"); } else { - final filtered = - selectedAllServices! - .where( - (service) => - selectedIds.contains(service['service_id'].toString()), - ) - .toList(); + // final filtered = + // selectedAllServices! + // .where( + // (service) => + // selectedIds.contains(service['service_id'].toString()), + // ) + // .toList(); print("ServicesChoosedYY: $ServicesChoosed"); setState(() { - ServicesChoosed = filtered; + ServicesChoosed = selectedAllServices; + // ServicesChoosed = filtered; // services = ServicesChoosed! // .map((service) => service['name'].toString()) // .toList(); diff --git a/lib/config/apiUrl.dart b/lib/config/apiUrl.dart index 9ca7783..d036aaf 100644 --- a/lib/config/apiUrl.dart +++ b/lib/config/apiUrl.dart @@ -5,7 +5,7 @@ * File : web/index.html - change below * (or) replaced to * Check File : App.dart -> line 37 need to uncomment it "SemanticsBinding" **/ -// const String apiUrl = 'https://apitest.tripapprovaltool.com/tstat_be'; +const String apiUrl = 'https://apitest.tripapprovaltool.com/tstat_be'; /** Note : TSTAT UAT BE URL * incase "adfactor" or "aujas" href means changed to "tstat" @@ -33,4 +33,4 @@ * incase "tstat" or "adfactor" href means changed to "aujas" * File : web/index.html - change below * (or) replaced to **/ -const String apiUrl = 'https://tripapprovaltool.com/aujas_be'; +// const String apiUrl = 'https://tripapprovaltool.com/aujas_be'; diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index 17eef42..652a70b 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -38,8 +38,44 @@ class ApiService { // userRole = userData['role']; - print("userData - $userData"); - print("userData11 - ${userData['role']}"); + print("store1userData - $userData"); + print("store2userData11 - ${userData['role']}"); + // print("userData12 - $userRole"); + } + + await getOrganizationData(); + } catch (e) { + print('Error decoding token: $e'); + } + } + + Future storeTripUserDetails(String token) async { + try { + final parts = token.split('.'); + if (parts.length != 3) throw Exception('Invalid token format'); + + final payload = json.decode( + utf8.decode(base64Url.decode(base64Url.normalize(parts[1]))), + ); + + final userData = payload['data']; + + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('trip_auth_token', token); + await prefs.setString( + 'trip_user_data', + jsonEncode(userData), + ); // Store full user data + + if (userData != null) { + final pref = await SharedPreferences.getInstance(); + await pref.setString('trip_auth_token', token); + await pref.setString('trip_user_data', jsonEncode(userData)); + + // userRole = userData['role']; + + print("storetrip_userData - $userData"); + print("storetrip_2userData11 - ${userData['role']}"); // print("userData12 - $userRole"); } @@ -677,6 +713,37 @@ class ApiService { } } + Future handleTripWiseToken(String userId) async { + final String apiUrldata = + '$apiUrl/api/user/refreshUserToken?user_id=$userId'; + print("API URL: $userId"); + // final token = await getToken(); + + final token = await getToken(); + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + 'app-signature': 'ts-traveltool-2025-signature-123456', + }, + ); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + print("data- $data"); + + final token = data['token']; // Assuming the token is in response + // final userId = data['user_id'].toString(); + + print("Token - $token"); + await storeTripUserDetails(token); + } else { + throw Exception('Failed to load plans'); + } + } + static Future viewPlan( BuildContext context, String planId, { diff --git a/lib/utils/auth_utils.dart b/lib/utils/auth_utils.dart index d085727..86bd69f 100644 --- a/lib/utils/auth_utils.dart +++ b/lib/utils/auth_utils.dart @@ -79,6 +79,16 @@ Future getRoleUser() async { return null; } +Future>> getOrgServicesName() async { + final prefs = await SharedPreferences.getInstance(); + final jsonString = prefs.getString("selectedOrgServiceName"); + + if (jsonString != null) { + return List>.from(jsonDecode(jsonString)); + } + return []; +} + Future getForexCardNumber() async { final prefs = await SharedPreferences.getInstance(); final String? userDataString = prefs.getString('user_data'); @@ -110,6 +120,23 @@ Future getTripPlanAction() async { return null; } +Future getTripPlanActionFromSelectedUsr() async { + final prefs = await SharedPreferences.getInstance(); + // final String? userDataString = prefs.getString('user_data'); + final String? userDataString = prefs.getString('trip_user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + print("TRIPUSRDATAT - $userData"); + return userData["plan_action"]?.toString(); + } catch (e) { + return null; + } + } + return null; +} + Future>?> getUserServices() async { final prefs = await SharedPreferences.getInstance(); final String? userDataString = prefs.getString('user_data'); @@ -128,3 +155,22 @@ Future>?> getUserServices() async { } return null; } + +Future?> fetchTripUserServices() async { + String? orgId = await getOrgId(); + + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('trip_user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + print("TRIPUSRSERVICE - $userData"); + return userData["service"]; // this is a List + } catch (e) { + print("Error decoding trip_user_data: $e"); + return null; + } + } + return null; +} diff --git a/lib/widgets/popup_listPlan_action.dart b/lib/widgets/popup_listPlan_action.dart index 20d987d..1503714 100644 --- a/lib/widgets/popup_listPlan_action.dart +++ b/lib/widgets/popup_listPlan_action.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import '../Screens/allTrips/plan_info_mdl.dart'; import '../Screens/allTrips/remarks_list.dart'; import '../data/models/plan.dart'; import '../services/apiService.dart'; @@ -133,6 +134,26 @@ class PlanPopupMenu extends StatelessWidget { ); }, ), + + IconButton( + icon: const Icon( + Icons.info_outlined, + color: Color(0xFF475569), + size: 18, + ), + tooltip: 'Trip Info', + onPressed: () { + showDialog( + context: context, + builder: + (context) => TripInformation( + // planId: plan.planId, + planId: plan.planId.toString(), + layoutColorForUser: layoutColor!, + ), + ); + }, + ), ], ), ), diff --git a/pubspec.lock b/pubspec.lock index 0c5d513..52f3fa5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,22 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + url: "https://pub.dev" + source: hosted + version: "85.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" + url: "https://pub.dev" + source: hosted + version: "7.7.1" archive: dependency: transitive description: @@ -65,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" cli_util: dependency: transitive description: @@ -89,6 +113,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + url: "https://pub.dev" + source: hosted + version: "1.15.0" cross_file: dependency: transitive description: @@ -355,6 +395,14 @@ packages: url: "https://pub.dev" source: hosted version: "11.0.0" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: ca2480512a8e840291325249f4857e363ffa5d1b77b132e189c9313a9d9fb9e0 + url: "https://pub.dev" + source: hosted + version: "3.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -373,6 +421,22 @@ packages: url: "https://pub.dev" source: hosted version: "8.2.12" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" go_router: dependency: "direct main" description: @@ -421,6 +485,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" http_parser: dependency: "direct main" description: @@ -509,6 +581,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" json_annotation: dependency: transitive description: @@ -621,6 +709,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" path: dependency: transitive description: @@ -709,6 +813,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" posix: dependency: transitive description: @@ -725,6 +837,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" quill_native_bridge: dependency: transitive description: @@ -813,6 +933,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.1" + riverpod: + dependency: transitive + description: + name: riverpod + sha256: "135723ec44dfba141bc4696224048a408336e794228a0117439e7ad0a8be6d05" + url: "https://pub.dev" + source: hosted + version: "3.0.0" shared_preferences: dependency: "direct main" description: @@ -869,11 +997,59 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" source_span: dependency: transitive description: @@ -890,6 +1066,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" stream_channel: dependency: transitive description: @@ -922,6 +1106,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" + url: "https://pub.dev" + source: hosted + version: "1.25.15" test_api: dependency: transitive description: @@ -930,6 +1122,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + test_core: + dependency: transitive + description: + name: test_core + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" + url: "https://pub.dev" + source: hosted + version: "0.6.8" typed_data: dependency: transitive description: @@ -1082,6 +1282,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + watcher: + dependency: transitive + description: + name: watcher + sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c" + url: "https://pub.dev" + source: hosted + version: "1.1.3" web: dependency: "direct main" description: @@ -1090,6 +1298,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 02e915d..ce4f83b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,6 +64,7 @@ dependencies: flutter_quill_delta_from_html: ^1.5.2 flutter_launcher_icons: ^0.14.4 dotted_border: ^3.1.0 + flutter_riverpod: ^3.0.0 diff --git a/web/index.html b/web/index.html index 830c30a..eea2d20 100644 --- a/web/index.html +++ b/web/index.html @@ -14,7 +14,7 @@ This is a placeholder for base href that will be replaced by the value of the `--base-href` argument provided to `flutter build`. --> - +