From 52fa34bd0d4c55470bce0c35ba3478afccfae3f4 Mon Sep 17 00:00:00 2001 From: VE10-Sanjeev Date: Wed, 28 May 2025 13:21:42 +0000 Subject: [PATCH 01/29] minor fixes --- lib/Screens/allTrips/travel_agent_list.dart | 24 +- lib/Screens/approvals/approval_list.dart | 7 +- lib/Screens/forex/forexDetails.dart | 4 +- lib/Screens/group/groupList.dart | 227 +++++++++--------- lib/Screens/itnerary_list/bus_list.dart | 8 + lib/Screens/itnerary_list/insurance_list.dart | 8 + .../itnerary_list/miscellaneous_list.dart | 8 + lib/Screens/itnerary_list/visa_list.dart | 8 + lib/Screens/plans/create_plans.dart | 10 + lib/widgets/popup_listPlan_action.dart | 21 +- 10 files changed, 185 insertions(+), 140 deletions(-) diff --git a/lib/Screens/allTrips/travel_agent_list.dart b/lib/Screens/allTrips/travel_agent_list.dart index 721efe8..39523d1 100644 --- a/lib/Screens/allTrips/travel_agent_list.dart +++ b/lib/Screens/allTrips/travel_agent_list.dart @@ -706,18 +706,18 @@ class _TravelAgentListPlansState extends State { isViewMode: true); }, ), - IconButton( - icon: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15), - onPressed: () { - Navigator.pop(context); - ApiService.viewPlan( - context, plan.planId, - isViewMode: false); - }, - ), + // IconButton( + // icon: Image.asset( + // 'assets/images/IconsImg/edit.png', + // width: 20, + // height: 15), + // onPressed: () { + // Navigator.pop(context); + // ApiService.viewPlan( + // context, plan.planId, + // isViewMode: false); + // }, + // ), IconButton( icon: Icon( Icons.cancel_rounded, diff --git a/lib/Screens/approvals/approval_list.dart b/lib/Screens/approvals/approval_list.dart index 0cda2f5..7fa0ef4 100644 --- a/lib/Screens/approvals/approval_list.dart +++ b/lib/Screens/approvals/approval_list.dart @@ -55,6 +55,8 @@ class _ApprovalListState extends State { loadInitialData(); }); + print("i am here ........................."); + // futurePlans = fetchPlans(); } @@ -245,7 +247,7 @@ class _ApprovalListState extends State { if (confirmed) { try { Map planData = await ApiService.getViewPlan(planId); - print("ViewAAA - $planData"); + print("ViewABC - $planData"); refresh(); // postPlanData(planData, planId); } catch (e) { @@ -286,8 +288,7 @@ class _ApprovalListState extends State { {bool isViewMode = false, bool isApprover = true}) async { try { Map planData = await getViewPlan(planId); - - print("ViewAAA - $planData"); + print("viewPlanforApprover.....$planData"); context.go('/createPlan', extra: { 'planData': planData, 'isViewMode': isViewMode, diff --git a/lib/Screens/forex/forexDetails.dart b/lib/Screens/forex/forexDetails.dart index 45230f8..9839ec7 100644 --- a/lib/Screens/forex/forexDetails.dart +++ b/lib/Screens/forex/forexDetails.dart @@ -493,7 +493,7 @@ class ForexDataState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Cash", + "Cash (%)", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, @@ -540,7 +540,7 @@ class ForexDataState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Card", + "Card (%)", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, diff --git a/lib/Screens/group/groupList.dart b/lib/Screens/group/groupList.dart index e7591c0..ab9ef8d 100644 --- a/lib/Screens/group/groupList.dart +++ b/lib/Screens/group/groupList.dart @@ -276,7 +276,7 @@ class _GroupListState extends State { children: [ Expanded( child: Container( - // height: MediaQuery.of(context).size.height * 0.89, + height: MediaQuery.of(context).size.height * 0.75, padding: const EdgeInsets.all(10), // margin: const EdgeInsets.only(bottom: 10), color: Colors.white, @@ -309,125 +309,124 @@ class _GroupListState extends State { itemCount: apiAllGroups!.length, itemBuilder: (context, index) { final group = apiAllGroups![index]; - return Card( - // color: bodyColor, - // color: Color(0xFFF5F5F5), - color: Colors.white, - margin: EdgeInsets.symmetric(vertical: 6, horizontal: 10), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Text("Group Name", - style: GoogleFonts.poppins( - fontSize: 11.5, fontWeight: FontWeight.w400)), - ), - Expanded( - child: Text("Domestic Policy", - style: GoogleFonts.poppins( - fontSize: 11.5, fontWeight: FontWeight.w400)), - ), - Expanded( - child: Text("International Policy", - style: GoogleFonts.poppins( - fontSize: 11.5, fontWeight: FontWeight.w400)), - ), - Expanded( - child: Text("Description", - style: GoogleFonts.poppins( - fontSize: 11.5, fontWeight: FontWeight.w400)), - ), - ], - ), - SizedBox(height: 4), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text("${group['name']}", - style: GoogleFonts.poppins( - fontSize: 13, fontWeight: FontWeight.w600)), - ), - Expanded( - child: Text("${group['domestic_policy_name'] ?? 'N/A'}", - style: GoogleFonts.poppins( - fontSize: 13, fontWeight: FontWeight.w600)), - ), - Expanded( - child: Text("${group['international_policy_name']}", + return Card( + // color: bodyColor, + // color: Color(0xFFF5F5F5), + color: Colors.white, + margin: EdgeInsets.symmetric(vertical: 6, horizontal: 10), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text("Group Name", style: GoogleFonts.poppins( - fontSize: 13, fontWeight: FontWeight.w600))), - Expanded( - child: Text("${group['description'] ?? 'N/A'}", + fontSize: 11.5, fontWeight: FontWeight.w400)), + ), + Expanded( + child: Text("Domestic Policy", style: GoogleFonts.poppins( - fontSize: 13, fontWeight: FontWeight.w600))), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - GestureDetector( - onTap: () async { - - if (group['group_id'] != null) { - final newGroupID = int.tryParse(group['group_id'].toString()); - if (newGroupID != null) { - final data = await apiService.getGroupDetailsFind(newGroupID); // ✅ Always an int - showDialog( - context: context, - builder: (context) => GroupData( - isDesktop: isDesktop, - groupId: newGroupID, // Pass the ID - groupData: data, - layoutColor: layoutColor!, - fetchGetGroup: refreshData - ), - ); - - - + fontSize: 11.5, fontWeight: FontWeight.w400)), + ), + Expanded( + child: Text("International Policy", + style: GoogleFonts.poppins( + fontSize: 11.5, fontWeight: FontWeight.w400)), + ), + Expanded( + child: Text("Description", + style: GoogleFonts.poppins( + fontSize: 11.5, fontWeight: FontWeight.w400)), + ), + ], + ), + SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text("${group['name']}", + style: GoogleFonts.poppins( + fontSize: 13, fontWeight: FontWeight.w600)), + ), + Expanded( + child: Text("${group['domestic_policy_name'] ?? 'N/A'}", + style: GoogleFonts.poppins( + fontSize: 13, fontWeight: FontWeight.w600)), + ), + Expanded( + child: Text("${group['international_policy_name']}", + style: GoogleFonts.poppins( + fontSize: 13, fontWeight: FontWeight.w600))), + Expanded( + child: Text("${group['description'] ?? 'N/A'}", + style: GoogleFonts.poppins( + fontSize: 13, fontWeight: FontWeight.w600))), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + GestureDetector( + onTap: () async { + if (group['group_id'] != null) { + final newGroupID = int.tryParse( + group['group_id'].toString()); + if (newGroupID != null) { + final data = await apiService.getGroupDetailsFind( + newGroupID); // ✅ Always an int + showDialog( + context: context, + builder: (context) => + GroupData( + isDesktop: isDesktop, + groupId: newGroupID, + // Pass the ID + groupData: data, + layoutColor: layoutColor!, + fetchGetGroup: refreshData + ), + ); + } + } else { + print("something went wrong check properly"); } + }, - } else { - print("something went wrong check properly"); - } - }, + // onTap: () { + // context.go("/CreateGroup", extra: group); + // }, + child: Image.asset('assets/images/IconsImg/edit.png', + width: 20, height: 15), + ), + SizedBox( + width: 5, + ), + GestureDetector( + onTap: () { + final idStr = group['group_id']; + final id = int.tryParse(idStr.toString()); - // onTap: () { - // context.go("/CreateGroup", extra: group); - // }, - child: Image.asset('assets/images/IconsImg/edit.png', - width: 20, height: 15), - ), - SizedBox( - width: 5, - ), - GestureDetector( - onTap: () { - final idStr = group['group_id']; - final id = int.tryParse(idStr.toString()); - - if (id == null) { - print("group_id is null"); - return; - } - final status = group['is_active']; - // print("GroupId : ${group['group_id']} "); - deleteGroup(group, id, status); - }, - child: Image.asset('assets/images/IconsImg/delete.png', - width: 20, height: 15), - ), - ], - ), - ], + if (id == null) { + print("group_id is null"); + return; + } + final status = group['is_active']; + // print("GroupId : ${group['group_id']} "); + deleteGroup(group, id, status); + }, + child: Image.asset('assets/images/IconsImg/delete.png', + width: 20, height: 15), + ), + ], + ), + ], + ), ), - ), - ); + ); }, ); } diff --git a/lib/Screens/itnerary_list/bus_list.dart b/lib/Screens/itnerary_list/bus_list.dart index 4e20724..07795fa 100644 --- a/lib/Screens/itnerary_list/bus_list.dart +++ b/lib/Screens/itnerary_list/bus_list.dart @@ -214,6 +214,14 @@ class BusListWidget extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.end, children: [ + Text( + "${item["from"]} - ${item["to"]}", + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w800, + ), + ), + Spacer(), GestureDetector( onTap: () => onOpen(true, item, "Bus"), child: Image.asset('assets/images/IconsImg/edit.png', diff --git a/lib/Screens/itnerary_list/insurance_list.dart b/lib/Screens/itnerary_list/insurance_list.dart index e512edb..5c88d65 100644 --- a/lib/Screens/itnerary_list/insurance_list.dart +++ b/lib/Screens/itnerary_list/insurance_list.dart @@ -192,6 +192,14 @@ class InsuranceListWidget extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.end, children: [ + Text( + getRequestForInsuranceType(item["type_of_insurance"]!.toString()), + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w800, + ), + ), + Spacer(), GestureDetector( onTap: () => onOpen(true, item, "Insurance"), child: Image.asset('assets/images/IconsImg/edit.png', diff --git a/lib/Screens/itnerary_list/miscellaneous_list.dart b/lib/Screens/itnerary_list/miscellaneous_list.dart index ecd1f1d..30e288b 100644 --- a/lib/Screens/itnerary_list/miscellaneous_list.dart +++ b/lib/Screens/itnerary_list/miscellaneous_list.dart @@ -152,6 +152,14 @@ class MiscellaneousListWidget extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.end, children: [ + Text( + getRequestValue(item["special_request"]?.toString()), + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w800, + ), + ), + Spacer(), GestureDetector( onTap: () => onOpen(true, item, "Miscellaneous"), child: Image.asset('assets/images/IconsImg/edit.png', diff --git a/lib/Screens/itnerary_list/visa_list.dart b/lib/Screens/itnerary_list/visa_list.dart index cd7a441..589af86 100644 --- a/lib/Screens/itnerary_list/visa_list.dart +++ b/lib/Screens/itnerary_list/visa_list.dart @@ -291,6 +291,14 @@ class VisaListWidget extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.end, children: [ + Text( + getRequestForVisa(item["type_of_visa"]!.toString()), + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w800, + ), + ), + Spacer(), GestureDetector( onTap: () => onOpen(true, item, "Visa"), child: Image.asset('assets/images/IconsImg/edit.png', diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index 52ee358..1a9391c 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -43,6 +43,8 @@ class _CreatePlansState extends State { final FocusNode focusNode = FocusNode(); bool isFocused = false; + late String approverId; + late String delegaterId; @override void initState() { @@ -104,6 +106,14 @@ class _CreatePlansState extends State { // final planData = args?['planData']; final bool isViewMode = args?['isViewMode'] ?? false; final bool isApprover = args?['isApprover'] ?? false; + String approverId = args?['approver_id'] ?? ''; + String delegaterId = args?['delegater_id'] ?? ''; + + print("args.......................$args"); + print(approverId); + print(delegaterId); + + final Map planData = args['planData'] as Map? ?? {}; diff --git a/lib/widgets/popup_listPlan_action.dart b/lib/widgets/popup_listPlan_action.dart index 16a6f7f..b839220 100644 --- a/lib/widgets/popup_listPlan_action.dart +++ b/lib/widgets/popup_listPlan_action.dart @@ -21,6 +21,7 @@ class PlanPopupMenu extends StatelessWidget { required this.layoutColor, }) : super(key: key); + @override Widget build(BuildContext context) { return Align( @@ -51,15 +52,17 @@ class PlanPopupMenu extends StatelessWidget { isViewMode: true); }, ), - IconButton( - icon: Image.asset('assets/images/IconsImg/edit.png', - width: 20, height: 15), - onPressed: () { - Navigator.pop(context); - ApiService.viewPlan(context, plan.planId, - isViewMode: false); - }, - ), + + // IconButton( + // icon: Image.asset('assets/images/IconsImg/edit.png', + // width: 20, height: 15), + // onPressed: () { + // Navigator.pop(context); + // ApiService.viewPlan(context, plan.planId, + // isViewMode: false); + // }, + // ), + IconButton( icon: Icon(Icons.cancel_rounded, size: 18), onPressed: () { From 33185ab3b7243a705d43295d0240f58f35a5f741 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Thu, 29 May 2025 09:50:33 +0530 Subject: [PATCH 02/29] Travel Policy BugFix --- lib/Screens/approvals/approval_list.dart | 8 +++++ lib/Screens/plans/create_plans.dart | 37 ++++++++++++++++-------- lib/Screens/policy/policy.dart | 15 ++++++---- lib/Screens/policy/policyCriteria.dart | 11 +++++-- lib/data/models/plan.dart | 7 +++++ lib/routes/custom_appBar.dart | 3 +- lib/routes/custom_router.dart | 4 +++ lib/services/apiService.dart | 7 +++-- pubspec.lock | 14 +++++---- pubspec.yaml | 3 +- 10 files changed, 78 insertions(+), 31 deletions(-) diff --git a/lib/Screens/approvals/approval_list.dart b/lib/Screens/approvals/approval_list.dart index 0cda2f5..9a1a7f0 100644 --- a/lib/Screens/approvals/approval_list.dart +++ b/lib/Screens/approvals/approval_list.dart @@ -761,11 +761,17 @@ class _ApprovalListState extends State { width: 20, height: 15), onPressed: () { + print( + "Approver Edit : ${plan.approverId}"); + print( + "Approver Edit2 : ${plan.delegaterId}"); Navigator.pop(context); ApiService .viewPlanForApprover( context, plan.planId, + plan.approverId, + plan.delegaterId, isViewMode: false, isApprover: true); }, @@ -959,6 +965,8 @@ class _ApprovalListState extends State { .viewPlanForApprover( context, plan.planId, + plan.approverId, + plan.delegaterId, isViewMode: false, isApprover: true); }, diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index 52ee358..82c324c 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -104,6 +104,8 @@ class _CreatePlansState extends State { // 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 Map planData = args['planData'] as Map? ?? {}; @@ -114,7 +116,8 @@ class _CreatePlansState extends State { // final planData = GoRouterState.of(context).extra as Map? ?? {}; print("RECived palndata"); - // print("RECived palndata - ${planData}"); + print("RECived approverId - $approverId"); + print("RECived delegateId - $delegaterId"); return Container( margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null, @@ -185,14 +188,15 @@ class _CreatePlansState extends State { child: Padding( padding: EdgeInsets.all(10.0), child: CreateNewPlan( - key: _createPlanKey, - bodyColor: bodyColor, - layoutColor: layoutColor, - isDesktop: isDesktop, - selectedPlanData: planData, - isViewMode: isViewMode, - isApprover: isApprover, - ), + key: _createPlanKey, + bodyColor: bodyColor, + layoutColor: layoutColor, + isDesktop: isDesktop, + selectedPlanData: planData, + isViewMode: isViewMode, + isApprover: isApprover, + approverId: approverId, + delegaterId: delegaterId), ), ), ), @@ -291,6 +295,10 @@ class CreateNewPlan extends StatefulWidget { final Color? layoutColor; final Map selectedPlanData; + + final String approverId; + + final String delegaterId; const CreateNewPlan({ super.key, required this.isDesktop, @@ -299,6 +307,8 @@ class CreateNewPlan extends StatefulWidget { required this.selectedPlanData, required this.isViewMode, required this.isApprover, + required this.approverId, + required this.delegaterId, }); @override @@ -1036,7 +1046,8 @@ class CreateNewPlansState extends State { endpoint: '/api/plans/approvePlan', data: { "plan_id": planId, - "user_id": userId, + "user_id": widget.approverId, + "delegater_id": widget.delegaterId, }, methodName: 'Plan Approval', ); @@ -1048,7 +1059,8 @@ class CreateNewPlansState extends State { endpoint: '/api/plans/rejectPlan', data: { "plan_id": planId, - "user_id": userId, + "user_id": widget.approverId, + "delegater_id": widget.delegaterId, "reason": remarks, }, methodName: 'Plan Rejection', @@ -1086,7 +1098,8 @@ class CreateNewPlansState extends State { print("ViewAAA - $planData"); print("Call viewPlanForApprover"); - ApiService.viewPlanForApprover(context, data['plan_id'], + ApiService.viewPlanForApprover( + context, data['plan_id'], data['approverId'], data['delegaterId'], isViewMode: false, isApprover: true); } else { print("$methodName failed. Status: ${response.statusCode}"); diff --git a/lib/Screens/policy/policy.dart b/lib/Screens/policy/policy.dart index 96e51f7..844ca65 100644 --- a/lib/Screens/policy/policy.dart +++ b/lib/Screens/policy/policy.dart @@ -52,6 +52,7 @@ class _PolicyState extends State { late String policyType = "domestic"; // int? selectedServiceIndex = 1; ValueNotifier selectedServiceIndex = ValueNotifier("1"); + ValueNotifier selectedServicePriorityIndex = ValueNotifier("1"); String selectedService = "train"; // ValueNotifier selectedService = ValueNotifier("Train"); bool isViewMode = false; @@ -295,7 +296,7 @@ class _PolicyState extends State { if (SelectedDomestic == "1") { _selectedTripType = "1"; } else if (SelectedInternational == "1") { - _selectedTripType = "1"; + _selectedTripType = "2"; } /// ✅ Load policy_details list safely @@ -621,7 +622,7 @@ class _PolicyState extends State { SizedBox(height: 5), // Space before error message Text( errorMessages["policy_details"]!, - style: TextStyle(color: Colors.red, fontSize: 12), + style: GoogleFonts.poppins(color: Colors.red, fontSize: 10), ), ], isDesktop @@ -747,7 +748,7 @@ class _PolicyState extends State { if (errorMessages["name"] != null) ...[ SizedBox(height: 5), Text(errorMessages["name"]!, - style: TextStyle(color: Colors.red, fontSize: 12)), + style: GoogleFonts.poppins(color: Colors.red, fontSize: 10)), ], ], ); @@ -770,7 +771,7 @@ class _PolicyState extends State { if (errorMessages["trip_type"] != null) ...[ SizedBox(height: 5), Text(errorMessages["trip_type"]!, - style: TextStyle(color: Colors.red, fontSize: 12)), + style: GoogleFonts.poppins(color: Colors.red, fontSize: 10)), ], ], ); @@ -876,13 +877,15 @@ class _PolicyState extends State { } Widget _buildServiceTile(Map service, String index) { - bool isSelected = selectedServiceIndex.value == index; + // bool isSelected = selectedServiceIndex.value == index; + bool isSelected = selectedServicePriorityIndex.value == index; String name = service['name']; // or 'service_id', as needed return GestureDetector( onTap: () { setState(() { - selectedServiceIndex.value = index; + // selectedServiceIndex.value = index; + selectedServicePriorityIndex.value = index; }); }, child: Container( diff --git a/lib/Screens/policy/policyCriteria.dart b/lib/Screens/policy/policyCriteria.dart index c3eddac..eb81378 100644 --- a/lib/Screens/policy/policyCriteria.dart +++ b/lib/Screens/policy/policyCriteria.dart @@ -172,8 +172,11 @@ class PolicyCriteriaState extends State { final a2 = SecondApproverAction[serviceId]; final a3 = ThirdApproverAction[serviceId]; - bool hasValue = cost.trim().isNotEmpty; - // bool hasValue = cost.trim().isNotEmpty || travelClass.trim().isNotEmpty; + // bool hasValue = cost.trim().isNotEmpty; + // bool hasValue = cost.trim().isNotEmpty || travelClass!.isNotEmpty; + bool hasValue = + cost.trim().isNotEmpty || (travelClass?.isNotEmpty ?? false); + bool allActionsNull = a1 == null && a2 == null && a3 == null; bool someActionsMissing = [a1, a2, a3].where((a) => a != null).length > 0 && [a1, a2, a3].where((a) => a == null).length > 0; @@ -380,7 +383,7 @@ class PolicyCriteriaState extends State { Row( children: [ Text(validationErrors[ServiceId]!, - style: TextStyle( + style: GoogleFonts.poppins( color: Colors.red, fontSize: 10, fontWeight: FontWeight.bold)) @@ -961,6 +964,8 @@ class PolicyCriteriaState extends State { style: GoogleFonts.poppins(fontSize: 12), decoration: InputDecoration( + hintText: "Select", + hintStyle: GoogleFonts.poppins(fontSize: 12), border: InputBorder.none, contentPadding: EdgeInsets.symmetric( horizontal: 10), // Proper padding diff --git a/lib/data/models/plan.dart b/lib/data/models/plan.dart index 8ac57f5..4dc036d 100644 --- a/lib/data/models/plan.dart +++ b/lib/data/models/plan.dart @@ -1,5 +1,6 @@ class Plan { final String planId; + final String? employeeCode; final String tripTitle; final String tripType; @@ -15,6 +16,8 @@ class Plan { final String travellerName; final String statusValue; final String createdOn; + final String? approverId; + final String? delegaterId; Plan({ required this.planId, @@ -32,6 +35,8 @@ class Plan { required this.travellerName, required this.statusValue, required this.createdOn, + this.approverId, + this.delegaterId, }); factory Plan.fromJson(Map json) { @@ -52,6 +57,8 @@ class Plan { travellerName: json["traveller_name"] ?? '', statusValue: json["status_value"] ?? '', createdOn: json["created_on"] ?? '', + approverId: json['approver_id'], + delegaterId: json['delegater_id'], ); } } diff --git a/lib/routes/custom_appBar.dart b/lib/routes/custom_appBar.dart index a4e592a..fc5b47f 100644 --- a/lib/routes/custom_appBar.dart +++ b/lib/routes/custom_appBar.dart @@ -203,7 +203,8 @@ class _CustomAppBarState extends State { location.contains('/createPlan') || location.contains('/listTravelAgentPlan')) { selectedTab = TabSelection.myTrips; - } else if (location.contains('/ApprovalList')) { + } else if (location.contains('/ApprovalList') || + location.contains('/approver/plans')) { selectedTab = TabSelection.myApprovals; } else { selectedTab = TabSelection.allMenu; diff --git a/lib/routes/custom_router.dart b/lib/routes/custom_router.dart index e7d3c0c..487ff68 100644 --- a/lib/routes/custom_router.dart +++ b/lib/routes/custom_router.dart @@ -66,6 +66,10 @@ final GoRouter router = GoRouter( path: '/allTrips/trips', builder: (context, state) => CreatePlan(), ), + GoRoute( + path: '/approver/plans', + builder: (context, state) => CreatePlan(), + ), GoRoute( path: '/listUser', builder: (context, state) => UserListScreen(), diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index 52747ae..d391549 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -487,15 +487,18 @@ class ApiService { } static Future viewPlanForApprover(BuildContext context, String planId, + String? approverId, String? delegaterId, {bool isViewMode = false, bool isApprover = true}) async { try { Map planData = await getViewPlanEdit(planId); print("ViewAAA - $planData"); - context.replace('/createPlan', extra: { + context.replace('/approver/plans', extra: { 'planData': planData, + 'approverId': approverId, + 'delegaterId': delegaterId, 'isViewMode': isViewMode, - 'isApprover': isApprover + 'isApprover': isApprover, }); } catch (e) { print("Error fetching plan: $e"); diff --git a/pubspec.lock b/pubspec.lock index e0c379b..4e5e26c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -270,11 +270,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.2" - flutter_quill: - git: - url: https://github.com/singerdmx/flutter-quill.git - ref: v11.4.1 - flutter_localizations: dependency: transitive description: flutter @@ -288,7 +283,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.27" - + flutter_quill: + dependency: "direct main" + description: + name: flutter_quill + sha256: "7e60963632bbc8615627f0bae8e178515f69ecb378ad49fa68c43c2aabf33e21" + url: "https://pub.dev" + source: hosted + version: "11.4.1" flutter_quill_delta_from_html: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7429217..6ef992f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,7 +54,8 @@ dependencies: flutter_quill_extensions: ^11.0.0 flutter_localization: ^0.3.2 reorderables: ^0.6.0 - html_editor_enhanced: ^1.8.0 + + dev_dependencies: From dbe248195de7ba69d99150b6e88ac664ce269ea9 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Thu, 29 May 2025 10:30:27 +0530 Subject: [PATCH 03/29] Merge --- lib/Screens/plans/create_plans.dart | 4 +- .../create_user/office_details.dart | 2 +- lib/services/apiService.dart | 54 +++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index 82c324c..7c7ee2b 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -1047,7 +1047,7 @@ class CreateNewPlansState extends State { data: { "plan_id": planId, "user_id": widget.approverId, - "delegater_id": widget.delegaterId, + "delegation_user_id": widget.delegaterId, }, methodName: 'Plan Approval', ); @@ -1060,7 +1060,7 @@ class CreateNewPlansState extends State { data: { "plan_id": planId, "user_id": widget.approverId, - "delegater_id": widget.delegaterId, + "delegation_user_id": widget.delegaterId, "reason": remarks, }, methodName: 'Plan Rejection', diff --git a/lib/Screens/userManagement/create_user/office_details.dart b/lib/Screens/userManagement/create_user/office_details.dart index 2ca78e3..4b176d7 100644 --- a/lib/Screens/userManagement/create_user/office_details.dart +++ b/lib/Screens/userManagement/create_user/office_details.dart @@ -217,7 +217,7 @@ class _OfficeDetailsState extends State { Future fetchDepartment() async { try { - List department = await apiService.fetchCostCenter(); + List department = await apiService.fetchDepartmentCostCenter(); setState(() { apiCostData = department; }); diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index d391549..5d60c78 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -162,6 +162,60 @@ class ApiService { } } + Future fetchDepartmentCostCenter() async { + final String apiUrldata = '$apiUrl/api/getDepartmentList'; + + final token = await getToken(); + + final userId = await getUserId(); + + // print("SUSRTRT- $userId"); + // + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + try { + final data = json.decode(response.body); + print(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['department_id']?.toString(); + // } + // }); + + print('plansJSON'); + + return plansJson; + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + throw Exception('Failed to load plans'); + } + } + Future> fetchMasterDropdown() async { final String apiUrldata = '$apiUrl/api/getDropdownMaster'; From e981a27b0c71ea86382729b4049a058b3b02f313 Mon Sep 17 00:00:00 2001 From: VE10-Sanjeev Date: Thu, 29 May 2025 14:28:11 +0000 Subject: [PATCH 04/29] alignment --- lib/Screens/dashboard/status_dashboard.dart | 125 +++++---- lib/Screens/plans/create_plans.dart | 14 +- .../create_user/office_details.dart | 70 +++-- .../create_user/personal_details.dart | 94 ++++--- lib/routes/custom_drawer.dart | 5 + lib/routes/organizationSetting.dart | 245 +++++++++--------- 6 files changed, 307 insertions(+), 246 deletions(-) diff --git a/lib/Screens/dashboard/status_dashboard.dart b/lib/Screens/dashboard/status_dashboard.dart index 23227f1..9ec8522 100644 --- a/lib/Screens/dashboard/status_dashboard.dart +++ b/lib/Screens/dashboard/status_dashboard.dart @@ -195,65 +195,78 @@ class StatusDashboardState extends State { 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: 10, // 5% of screen height as vertical padding - ) - : EdgeInsets.all(0), - child:Container( - // padding: const EdgeInsets.all(10.0), - decoration: BoxDecoration( - color: isDesktop ? Colors.white : const Color(0xFFFCFCFC), - borderRadius: BorderRadius.circular(12), // 👈 Set your desired radius - ), - // color: isDesktop ? Colors.white : Color(0xFFFCFCFC), - child: Row( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.all(30.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Wrap( - spacing: 10, - runSpacing: 10, - children: typeBasedCount.map((item) { - double cardWidth = isDesktop - ? (MediaQuery.of(context).size.width * 0.75 - 10) / 2 // 80% width padding adjusted - : MediaQuery.of(context).size.width - 24; // full width with padding + padding: isDesktop + ? EdgeInsets.symmetric( + horizontal: MediaQuery.of(context).size.width * 0.1, // 30% of screen width as horizontal padding + vertical: 10, // 5% of screen height as vertical padding + ) + : EdgeInsets.all(0), + child : LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( // Only needed if child layout depends on height + child: Container( + decoration: BoxDecoration( + color: isDesktop ? Colors.white : const Color(0xFFFCFCFC), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(30.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Wrap( + spacing: 10, + runSpacing: 10, + children: typeBasedCount.map((item) { + double cardWidth = isDesktop + ? (MediaQuery.of(context).size.width * 0.75 - 10) / 2 // 80% width padding adjusted + : MediaQuery.of(context).size.width - 24; // full width with padding - return SizedBox( - width: cardWidth, - child: buildInfoCard(item['value'], item['count'], cardWidth), - ); - }).toList(), + return SizedBox( + width: cardWidth, + child: buildInfoCard(item['value'], item['count'], cardWidth), + ); + }).toList(), + ), + const SizedBox(height: 20), + Wrap( + spacing: 10, + runSpacing: 10, + children: statusBasedCount.map((item) { + double cardWidth = isDesktop + ? (MediaQuery.of(context).size.width * 0.90 - 50) / 6 // desktop layout: 6 cards per row + : MediaQuery.of(context).size.width - 24; // mobile: full width + + return SizedBox( + width: cardWidth, + child: buildInfoCard(item['value'], item['count'], cardWidth), + ); + }).toList(), + ), + + ], + ), + ), + ), + ], + ), ), - const SizedBox(height: 20), - Wrap( - spacing: 10, - runSpacing: 10, - children: statusBasedCount.map((item) { - double cardWidth = isDesktop - ? (MediaQuery.of(context).size.width * 0.90 - 50) / 6 // desktop layout: 6 cards per row - : MediaQuery.of(context).size.width - 24; // mobile: full width - - return SizedBox( - width: cardWidth, - child: buildInfoCard(item['value'], item['count'], cardWidth), - ); - }).toList(), - ), - - ], + ), ), - ), - ), - ], - ), - ) + ); + }, + ) + + + ), ); }); diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index 7c7ee2b..3323f17 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -2594,10 +2594,10 @@ class CreateNewPlansState extends State { children: [ Text( "Trip Name", // Your label - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldWrapper( @@ -2774,8 +2774,10 @@ class CreateNewPlansState extends State { enabled: !widget.isViewMode, decoration: InputDecoration( labelText: "Trip Name *", - - labelStyle: GoogleFonts.poppins(fontSize: 14, color: Colors.black54), + labelStyle: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), diff --git a/lib/Screens/userManagement/create_user/office_details.dart b/lib/Screens/userManagement/create_user/office_details.dart index 4b176d7..4a6c71f 100644 --- a/lib/Screens/userManagement/create_user/office_details.dart +++ b/lib/Screens/userManagement/create_user/office_details.dart @@ -395,8 +395,10 @@ class _OfficeDetailsState extends State { children: [ Text( "Employee Code", - style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -439,8 +441,10 @@ class _OfficeDetailsState extends State { children: [ Text( "Department", - style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -489,8 +493,10 @@ class _OfficeDetailsState extends State { children: [ Text( "Group", - style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), // CustomTextFieldUserWrapper( @@ -627,10 +633,11 @@ class _OfficeDetailsState extends State { children: [ Text( "First Approver", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) + ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -739,10 +746,11 @@ class _OfficeDetailsState extends State { children: [ Text( "Second Approver", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) + ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -850,10 +858,11 @@ class _OfficeDetailsState extends State { children: [ Text( "Third Approver", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) + ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -960,10 +969,11 @@ class _OfficeDetailsState extends State { children: [ Text( "Delegate To", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) + ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -1107,10 +1117,11 @@ class _OfficeDetailsState extends State { children: [ Text( "Start Date", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) + ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -1201,8 +1212,11 @@ class _OfficeDetailsState extends State { children: [ Text( "End Date", - style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) + ), SizedBox(height: 5), CustomTextFieldUserWrapper( diff --git a/lib/Screens/userManagement/create_user/personal_details.dart b/lib/Screens/userManagement/create_user/personal_details.dart index 032ac46..c07af96 100644 --- a/lib/Screens/userManagement/create_user/personal_details.dart +++ b/lib/Screens/userManagement/create_user/personal_details.dart @@ -515,11 +515,11 @@ class PersonalDetailsState extends State { : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - buildEmailField(), - SizedBox(height: 8), // Vertical space buildMobileField(), - SizedBox(height: 8), + SizedBox(height: 8), // Vertical space buildAlternateMobileField(), + SizedBox(height: 8), + buildEmailField(), ], ), ); @@ -532,19 +532,21 @@ class PersonalDetailsState extends State { ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - buildRole(), - SizedBox( - width: 15, - ), - if (!widget.apiselectedUser) buildPassword() + if (!widget.apiselectedUser) ...[ + buildPassword(), + SizedBox(width: 15), + ], + buildRole() ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - buildRole(), - SizedBox(height: 8), // - if (!widget.apiselectedUser) buildPassword() + if (!widget.apiselectedUser) ...[ + buildPassword(), + SizedBox(height: 8)], // + buildRole() + ], ), ); @@ -587,8 +589,10 @@ class PersonalDetailsState extends State { children: [ Text( "First Name", - style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -632,10 +636,10 @@ class PersonalDetailsState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Last Name", - style: TextStyle( + style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + fontWeight: FontWeight.w600, + color: Color(0xFF575A74))), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -677,7 +681,9 @@ class PersonalDetailsState extends State { Text( "Gender", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -794,8 +800,10 @@ class PersonalDetailsState extends State { children: [ Text( "Date of Birth", - style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -848,8 +856,10 @@ class PersonalDetailsState extends State { children: [ Text( "Email", - style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -892,8 +902,10 @@ class PersonalDetailsState extends State { children: [ Text( "Mobile Number", - style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -941,9 +953,9 @@ class PersonalDetailsState extends State { children: [ Text("Alternate Mobile Number", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74))), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -1004,8 +1016,10 @@ class PersonalDetailsState extends State { children: [ Text( "Country", - style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -1089,8 +1103,10 @@ class PersonalDetailsState extends State { children: [ Text( "Postal Code", - style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -1130,8 +1146,10 @@ class PersonalDetailsState extends State { children: [ Text( "Role ", - style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -1182,8 +1200,10 @@ class PersonalDetailsState extends State { children: [ Text( "Address", - style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -1219,8 +1239,10 @@ class PersonalDetailsState extends State { children: [ Text( "Password", - style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserWrapper( diff --git a/lib/routes/custom_drawer.dart b/lib/routes/custom_drawer.dart index 94bccea..786b5cf 100644 --- a/lib/routes/custom_drawer.dart +++ b/lib/routes/custom_drawer.dart @@ -171,6 +171,11 @@ class _CustomDrawerState extends State { ), // _buildDrawerItem(context, Icons.home_outlined, 'Home', '/home'), + if (userData?["role"] == "Org Admin" || + userData?["role"] == "Travel Admin") + _buildDrawerItem(context, Icons.dashboard, 'Dashboard', + '/StatusDashboard'), + if (userData?["role"] == "Org Admin" || userData?["role"] == "Travel Admin") _buildDrawerItem(context, Icons.insights_outlined, 'All Trips', diff --git a/lib/routes/organizationSetting.dart b/lib/routes/organizationSetting.dart index bc50865..432ac13 100644 --- a/lib/routes/organizationSetting.dart +++ b/lib/routes/organizationSetting.dart @@ -102,77 +102,77 @@ class OrganizationSettingState extends State { }, ]; - List rows = []; - for (int i = 0; i < menuItems.length; i += cardsPerRow) { - List cards = []; - for (int j = i; j < i + cardsPerRow && j < menuItems.length; j++) { - final item = menuItems[j]; - - cards.add( - Expanded( - child: Card( - margin: EdgeInsets.all(8), - color: Colors.white, - child: InkWell( - onTap: () { - // Navigate using go_router - final route = item['value'] as String; - switch (route) { - case '/OrganizationSetup': - case '/group': - case '/department': - case '/PolicyList': - case '/getPerdiem': - case '/templateList': - case '/costcenter': - case '/hotels': - case '/groupDetails' : - context.go(route); - break; - default: - // Handle unknown routes or do nothing - print('Unknown route: $route'); - } - }, - child: Padding( - padding: EdgeInsets.all(12), - child: Row( - children: [ - Icon(item['icon'], size: 40, color: Color(0xFF114D8B)), - SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - item['label'], - style: GoogleFonts.poppins( - fontSize: 14, fontWeight: FontWeight.w500), - ), - SizedBox(height: 4), - Text( - item['description'], - style: TextStyle( - fontSize: 14, color: Colors.grey[700]), - ), - ], - ), - ), - ], - ), - ), - ), - ), - ), - ); - } - - rows.add(Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: cards, - )); - } + // List rows = []; + // for (int i = 0; i < menuItems.length; i += cardsPerRow) { + // List cards = []; + // for (int j = i; j < i + cardsPerRow && j < menuItems.length; j++) { + // final item = menuItems[j]; + // + // cards.add( + // Expanded( + // child: Card( + // margin: EdgeInsets.all(8), + // color: Colors.white, + // child: InkWell( + // onTap: () { + // // Navigate using go_router + // final route = item['value'] as String; + // switch (route) { + // case '/OrganizationSetup': + // case '/group': + // case '/department': + // case '/PolicyList': + // case '/getPerdiem': + // case '/templateList': + // case '/costcenter': + // case '/hotels': + // case '/groupDetails' : + // context.go(route); + // break; + // default: + // // Handle unknown routes or do nothing + // print('Unknown route: $route'); + // } + // }, + // child: Padding( + // padding: EdgeInsets.all(12), + // child: Row( + // children: [ + // Icon(item['icon'], size: 40, color: Color(0xFF114D8B)), + // SizedBox(width: 16), + // Expanded( + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // mainAxisSize: MainAxisSize.min, + // children: [ + // Text( + // item['label'], + // style: GoogleFonts.poppins( + // fontSize: 14, fontWeight: FontWeight.w500), + // ), + // SizedBox(height: 4), + // Text( + // item['description'], + // style: TextStyle( + // fontSize: 14, color: Colors.grey[700]), + // ), + // ], + // ), + // ), + // ], + // ), + // ), + // ), + // ), + // ), + // ); + // } + // + // rows.add(Row( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: cards, + // )); + // } // return Container( // // color: Colors.white, @@ -196,7 +196,6 @@ class OrganizationSettingState extends State { // ), // ); - return Container( padding: EdgeInsets.symmetric(vertical: 16, horizontal: 12), child: Column( @@ -213,58 +212,64 @@ class OrganizationSettingState extends State { SizedBox(height: 8), Divider(thickness: 0.2, color: Colors.blueGrey.shade100), SizedBox(height: 12), - Wrap( - spacing: 16, // horizontal space between cards - runSpacing: 16, // vertical space between rows - children: menuItems.map((item) { - return SizedBox( - width: isDesktop ? 325 : double.infinity, - child: Card( - color: Colors.white, - child: InkWell( - onTap: () { - final route = item['value'] as String; - context.go(route); - }, - child: Padding( - padding: EdgeInsets.all(12), - child: Row( - children: [ - Icon(item['icon'], size: 40, color: Color(0xFF114D8B)), - SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - item['label'], - style: GoogleFonts.poppins( - fontSize: 14, - fontWeight: FontWeight.w500, + Expanded( + child: SingleChildScrollView( + child: Center( + child: Wrap( + spacing: 16, + runSpacing: 16, + children: menuItems.map((item) { + return SizedBox( + width: isDesktop ? 325 : double.infinity, + child: Card( + color: Colors.white, + child: InkWell( + onTap: () { + final route = item['value'] as String; + context.go(route); + }, + child: Padding( + padding: EdgeInsets.all(12), + child: Row( + children: [ + Icon(item['icon'], size: 40, color: Color(0xFF114D8B)), + SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + item['label'], + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: 4), + Text( + item['description'], + maxLines: 1, + overflow: TextOverflow.ellipsis, + softWrap: false, + style: TextStyle( + fontSize: 14, + color: Colors.grey[700], + ), + ), + ], + ), ), - ), - SizedBox(height: 4), - Text( - item['description'], - maxLines: 1, - overflow: TextOverflow.ellipsis, - softWrap: false, - style: TextStyle( - fontSize: 14, - color: Colors.grey[700], - ), - ), - ], + ], + ), ), ), - ], - ), - ), - ), + ), + ); + }).toList(), ), - ); - }).toList(), + ), + ), ), ], ), From 057da000785ba90669f70528fc9cf2a783a44b26 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Thu, 29 May 2025 20:00:56 +0530 Subject: [PATCH 05/29] Travel Policy --- lib/Screens/myTemplates/templateTest.dart | 36 +++++++++++++ lib/Screens/policy/policy.dart | 10 +++- lib/Screens/policy/policyCriteria.dart | 61 ++++++++++++++++++++--- pubspec.lock | 50 +++++++++---------- 4 files changed, 125 insertions(+), 32 deletions(-) create mode 100644 lib/Screens/myTemplates/templateTest.dart diff --git a/lib/Screens/myTemplates/templateTest.dart b/lib/Screens/myTemplates/templateTest.dart new file mode 100644 index 0000000..baaad4a --- /dev/null +++ b/lib/Screens/myTemplates/templateTest.dart @@ -0,0 +1,36 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_quill/flutter_quill.dart'; + +class MyHomePage extends StatefulWidget { + const MyHomePage({Key? key, required String title}) : super(key: key); + + @override + MyHomePageState createState() => MyHomePageState(); +} + +class MyHomePageState extends State { + QuillController _controller = QuillController.basic(); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + Text("data"), + QuillSimpleToolbar( + controller: _controller, + config: const QuillSimpleToolbarConfig(), + ), + Expanded( + child: QuillEditor.basic( + controller: _controller, + config: const QuillEditorConfig(), + ), + ), + ], + ), + ); + } +} diff --git a/lib/Screens/policy/policy.dart b/lib/Screens/policy/policy.dart index 844ca65..90b531a 100644 --- a/lib/Screens/policy/policy.dart +++ b/lib/Screens/policy/policy.dart @@ -311,7 +311,7 @@ class _PolicyState extends State { print("Services - $services"); print("USR Detail Submit - $policyData"); - policyCriteriaKey.currentState?.saveCurrentPolicy(); + policyCriteriaKey.currentState?.saveCurrentPolicy(services); // Now the full data is ready in policyDataFromChild print("Submitting full policyData: $policyData"); @@ -1006,6 +1006,7 @@ class _PolicyState extends State { children: services.asMap().entries.map((entry) { int index = entry.key + 1; String service = entry.value; + String serviceId = index.toString(); bool isSelected = selectedServiceIndex.value == index.toString(); return SizedBox( @@ -1022,11 +1023,18 @@ class _PolicyState extends State { selectedServiceIndex.value = index.toString(); selectedService = service; + print( + " selectedServiceIndex.value - ${selectedServiceIndex.value}"); + + // policyCriteriaKey.currentState?.fieldForPolicy(); + // policyCriteriaKey.currentState + // ?.addOrUpdatePolicy(selectedServiceIndex.value); if (selectedService == "Flight" || selectedService == "Train") { showClass = true; showCost = true; int serviceCode = selectedService == "Flight" ? 1 : 2; + policyCriteriaKey.currentState?.fetchTrainFlightClass(); } else if (selectedService == "Accommodation") { showClass = true; diff --git a/lib/Screens/policy/policyCriteria.dart b/lib/Screens/policy/policyCriteria.dart index eb81378..3cec293 100644 --- a/lib/Screens/policy/policyCriteria.dart +++ b/lib/Screens/policy/policyCriteria.dart @@ -87,6 +87,7 @@ class PolicyCriteriaState extends State { void initState() { super.initState(); fieldForPolicy(); + widget.selectedTabNotifier.addListener(() { print("selectedTab changed: ${widget.selectedTabNotifier.value}"); fieldForPolicy(); @@ -99,11 +100,57 @@ class PolicyCriteriaState extends State { userId = await getUserId(); } - void saveCurrentPolicy() { + // void saveCurrentPolicy(List> services) { + // print("saveCurrentPolicy- $services"); + // if (ServiceId != null) { + // print("Saving curremt Add or Update"); + // addOrUpdatePolicy(ServiceId!); + // } + // } + + void saveCurrentPolicy(List> services) { + print("saveCurrentPolicy- $services"); + + // Step 1: Save currently selected policy first (if not already saved) if (ServiceId != null) { - print("Saving curremt Add or Update"); + print("Saving current Add or Update"); addOrUpdatePolicy(ServiceId!); } + + // Step 2: Collect existing service_ids from policyData + final existingServiceIds = + policyData?.map((e) => e['service_id'].toString()).toSet(); + + // Step 3: Loop through all service definitions + for (var service in services) { + String id = service['service_id'].toString(); + + // Skip if already present + if (existingServiceIds!.contains(id)) continue; + + // Step 4: Initialize any missing controllers or data + costController.putIfAbsent(id, () => TextEditingController()); + classAction.putIfAbsent(id, () => "1"); + FirstApproverAction.putIfAbsent(id, () => "None"); + SecondApproverAction.putIfAbsent(id, () => "None"); + ThirdApproverAction.putIfAbsent(id, () => "None"); + SelectedParallelProcess.putIfAbsent(id, () => "3"); + + // Step 5: Add default policy entry + policyData?.add({ + "service_id": int.parse(id), + "cost": "", + "class": classAction[id], + "a1_action": FirstApproverAction[id], + "a2_action": SecondApproverAction[id], + "a3_action": ThirdApproverAction[id], + "parallel_process_from": SelectedParallelProcess[id], + "created_by": widget.userId, + }); + } + + // Step 6: Emit updated policy data + widget.onPolicyDataChanged(policyData); } // To set the data (update) @@ -148,6 +195,7 @@ class PolicyCriteriaState extends State { } void addOrUpdatePolicy(String serviceId) { + print("addOrUpdatePolicyserviceId - $serviceId"); // 1. First, find existing item if any final existingIndex = policyData!.indexWhere((item) => item["service_id"] == serviceId); @@ -297,6 +345,7 @@ class PolicyCriteriaState extends State { // Save current input to policyData before switching if (ServiceId != null) { + print("Calling addOrUpdatePolicy"); addOrUpdatePolicy( ServiceId!); // 👈 Save current values for existing service } @@ -304,12 +353,12 @@ class PolicyCriteriaState extends State { ServiceId = widget.selectedTabNotifier.value ?? "1"; // Initialize controllers and variables if not present costController.putIfAbsent(ServiceId!, () => TextEditingController()); - classAction.putIfAbsent(ServiceId!, () => null); + classAction.putIfAbsent(ServiceId!, () => "1"); // classController.putIfAbsent(ServiceId!, () => TextEditingController()); - FirstApproverAction.putIfAbsent(ServiceId!, () => null); - SecondApproverAction.putIfAbsent(ServiceId!, () => null); - ThirdApproverAction.putIfAbsent(ServiceId!, () => null); + FirstApproverAction.putIfAbsent(ServiceId!, () => "None"); + SecondApproverAction.putIfAbsent(ServiceId!, () => "None"); + ThirdApproverAction.putIfAbsent(ServiceId!, () => "None"); SelectedParallelProcess.putIfAbsent(ServiceId!, () => "3"); }); diff --git a/pubspec.lock b/pubspec.lock index 4e5e26c..9ab84f6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: archive - sha256: "0c64e928dcbefddecd234205422bcfc2b5e6d31be0b86fef0d0dd48d7b4c9742" + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" url: "https://pub.dev" source: hosted - version: "4.0.4" + version: "4.0.7" args: dependency: transitive description: @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" file: dependency: transitive description: @@ -165,10 +165,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "36a1652d99cb6bf8ccc8b9f43aded1fd60b234d23ce78af422c07f950a436ef7" + sha256: "77f8e81d22d2a07d0dee2c62e1dda71dc1da73bf43bb2d45af09727406167964" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.1.9" file_selector_linux: dependency: transitive description: @@ -181,10 +181,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711" url: "https://pub.dev" source: hosted - version: "0.9.4+2" + version: "0.9.4+3" file_selector_platform_interface: dependency: transitive description: @@ -279,10 +279,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "5a1e6fb2c0561958d7e4c33574674bda7b77caaca7a33b758876956f2902eea3" + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e url: "https://pub.dev" source: hosted - version: "2.0.27" + version: "2.0.28" flutter_quill: dependency: "direct main" description: @@ -353,10 +353,10 @@ packages: dependency: "direct main" description: name: http - sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" http_parser: dependency: "direct main" description: @@ -377,10 +377,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "8bd392ba8b0c8957a157ae0dc9fcf48c58e6c20908d5880aea1d79734df090e9" + sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" url: "https://pub.dev" source: hosted - version: "0.8.12+22" + version: "0.8.12+23" image_picker_for_web: dependency: transitive description: @@ -617,18 +617,18 @@ packages: dependency: transitive description: name: posix - sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "6.0.2" provider: dependency: transitive description: name: provider - sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.5" quill_native_bridge: dependency: transitive description: @@ -729,10 +729,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad" + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.10" shared_preferences_foundation: dependency: transitive description: @@ -942,10 +942,10 @@ packages: dependency: transitive description: name: video_player_android - sha256: "28dcc4122079f40f93a0965b3679aff1a5f4251cf79611bd8011f937eb6b69de" + sha256: "4a5135754a62dbc827a64a42ef1f8ed72c962e191c97e2d48744225c2b9ebb73" url: "https://pub.dev" source: hosted - version: "2.8.4" + version: "2.8.7" video_player_avfoundation: dependency: transitive description: @@ -990,10 +990,10 @@ packages: dependency: transitive description: name: win32 - sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f + sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" url: "https://pub.dev" source: hosted - version: "5.12.0" + version: "5.13.0" xdg_directories: dependency: transitive description: @@ -1004,4 +1004,4 @@ packages: version: "1.1.0" sdks: dart: ">=3.7.0 <4.0.0" - flutter: ">=3.27.0" + flutter: ">=3.29.0" From 954fc5873ee7f892453f5081c2940b4a68da32ec Mon Sep 17 00:00:00 2001 From: venbaittech Date: Thu, 29 May 2025 20:17:14 +0530 Subject: [PATCH 06/29] merge --- lib/config/apiUrl.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/config/apiUrl.dart b/lib/config/apiUrl.dart index 5963b6f..bc1e6df 100644 --- a/lib/config/apiUrl.dart +++ b/lib/config/apiUrl.dart @@ -1,4 +1,3 @@ //api url -const String apiUrl = 'http://apitest.tripapprovaltool.com'; - - +// const String apiUrl = 'http://apitest.tripapprovaltool.com'; +const String apiUrl = 'https://uat.tripapprovaltool.com'; From 3b242280806d1072cf019149a4a4dea9c0b6117c Mon Sep 17 00:00:00 2001 From: VE10-Sanjeev Date: Fri, 30 May 2025 15:27:40 +0530 Subject: [PATCH 07/29] traveller --- lib/Screens/traveller/travellerDetails.dart | 529 +++++++++++ lib/Screens/traveller/travellerList.dart | 869 ++++++++++++++++++ .../create_user/traveller_details.dart | 102 +- lib/routes/custom_router.dart | 5 + lib/routes/organizationSetting.dart | 7 + lib/services/apiService.dart | 44 + 6 files changed, 1530 insertions(+), 26 deletions(-) create mode 100644 lib/Screens/traveller/travellerDetails.dart create mode 100644 lib/Screens/traveller/travellerList.dart diff --git a/lib/Screens/traveller/travellerDetails.dart b/lib/Screens/traveller/travellerDetails.dart new file mode 100644 index 0000000..1cf933b --- /dev/null +++ b/lib/Screens/traveller/travellerDetails.dart @@ -0,0 +1,529 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:http/http.dart' as http; + +import '../../config/apiUrl.dart'; +import '../../services/apiService.dart'; +import '../../utils/auth_utils.dart'; +import '../../widgets/custom_text_forex.dart'; +import 'travellerList.dart'; + +class TravellerData extends StatefulWidget { + final Future> Function() fetchGetTraveller; + final bool isDesktop; + final Color? layoutColor; + + final int? travellerId; // <-- Add this + final Map? travellerData; + + const TravellerData( + {super.key, + required this.isDesktop, + this.layoutColor, + required this.fetchGetTraveller, + this.travellerId, + this.travellerData}); + + @override + TravellerDataState createState() => TravellerDataState(); +} + +class TravellerDataState extends State { + final ApiService apiService = ApiService(); + Map? apiData; + + final Map focusNodes = { + "name": FocusNode(), + "description": FocusNode(), + }; + + final Map controllers = {}; + Map errorMessages = {}; + + String? selectedName; + String? selectedDescription; + String? userId; + int? travellerDataId; + late String isActive = "1"; + + List dataHeader = [ + "first_name", + "last_name", + "email", + "mobile", + ]; + + Map travellerDetails() { + final data = { + // "traveller_id": int.parse(travellerId), + "first_name": controllers["first_name"]?.text, + "last_name": controllers["last_name"]?.text, + "email": controllers["email"]?.text, + "mobile": controllers["mobile"]?.text, + "is_active": isActive, + }; + return data; + } + + @override + void initState() { + super.initState(); + + + apiData = null; + for (var field in dataHeader) { + controllers[field] = TextEditingController(); + } + + if (widget.travellerId != null) { + print('Editing D ID: ${widget.travellerId}'); + updateTravellerDetails(); + } + } + + void _clearError() { + setState(() { + errorMessages.clear(); + }); + } + + @override + void dispose() { + for (var controller in controllers.values) { + controller.dispose(); + } + super.dispose(); + } + + void updateTravellerDetails() { + print("Inside Update Function - ${widget.travellerData}"); + + final data = widget.travellerData; + + if (data == null) return; + setState(() { + controllers['first_name']?.text = data['first_name'] ?? ''; + controllers['last_name']?.text = data['last_name'] ?? ''; + controllers['email']?.text = data['email'].toString(); + controllers['mobile']?.text = data['mobile'].toString(); + isActive = data["is_active"]; + final travellerId = int.tryParse(data['traveller_id'].toString()); + travellerDataId = travellerId; + }); + } + + + void toggleStatus() { + setState(() { + isActive = isActive == "1" ? "0" : "1"; + }); + } + + bool validateData() { + errorMessages.clear(); + + final data = { + "first_name": controllers["first_name"]?.text, + "last_name": controllers["last_name"]?.text, + "email": controllers["email"]?.text, + "mobile": controllers["mobile"]?.text, + }; + + final requiredFields = ["first_name","last_name","email","mobile"]; + bool hasFocused = false; + + // Check validation for each field + for (String field in requiredFields) { + if (data[field] == null || data[field]!.trim().isEmpty) { + errorMessages[field] = "Required"; + + if (!hasFocused) { + focusNodes[field]?.requestFocus(); + hasFocused = true; + } + } + } + + if (data["mobile"] != null && data["mobile"].toString().isNotEmpty) { + if (!RegExp(r"^\d{10}$").hasMatch(data["mobile"].toString())) { + errorMessages["mobile"] = + "Enter 10 digits"; // Invalid mobile number format + } + } + + if (data["email"] != null && data["email"].toString().isNotEmpty) { + if (!RegExp(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") + .hasMatch(data["email"].toString())) { + errorMessages["email"] = "Invalid email format"; // Invalid email format + } + } + + return errorMessages.isEmpty; + } + + Future handleSubmit() async { + userId = await getUserId(); + + setState(() { + // This triggers UI rebuild with error messages + if (validateData()) { + postTravellerData(); + } + }); + + final travellerData1 = travellerDetails(); + print("submit data - $travellerData1"); + } + + Future postTravellerData({int isActive = 1}) async { + // final remarksData = getData(); + + final travellerData = travellerDetails(); + + print("initially value of the Traveller - $travellerData"); + // static here + final orgId = await getOrgId(); + + final String apiUrldata; + travellerData["org_id"] = orgId; + + if (travellerDataId != null) { + print("for edit traveller id - $travellerDataId"); + apiUrldata = '$apiUrl/api/travellers/update/$travellerDataId'; + travellerData["traveller_id"] = travellerDataId.toString(); + travellerData["updated_by"] = userId; + (travellerData.containsKey("created_by")) ? travellerData.remove("created_by") : '' ; + } else { + print("for add Traveller id - null"); + apiUrldata = '$apiUrl/api/travellers/create'; + print("called apiUrl - $apiUrldata"); + travellerData["created_by"] = userId; + } + print("recently Traveller data - $travellerData"); + final token = await getToken(); // Fetch token + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + try { + final uri = Uri.parse(apiUrldata); + final headers = { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }; + final body = jsonEncode(travellerData); + + final response = travellerDataId != null + ? await http.put(uri, headers: headers, body: body) + : await http.post(uri, headers: headers, body: body); + + + switch (response.statusCode) { + case 200: + print("Update - Response: ${response.body}"); + _clearError(); + widget.fetchGetTraveller(); + Navigator.of(context).pop(); + break; + + case 201: + print("Save - Response: ${response.body}"); + _clearError(); + await widget.fetchGetTraveller(); + Navigator.of(context).pop(); + break; + + default: + print("Failed to submit traveller. Status: ${response.statusCode}"); + print("Error: ${response.body}"); + } + + } catch (e) { + print(" Error submitting plan: $e"); + } + } + + @override + Widget build(BuildContext context) { + + return AlertDialog( + backgroundColor: Colors.white, + contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30), + // contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + content: SizedBox( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Row 1: Title + Edit + Delete buttons + Row( + children: [ + Text( + (travellerDataId != null) ? 'Edit Traveller' : 'Create Traveller', + style: GoogleFonts.poppins(fontSize: 15, color: Colors.black), + ), + const Spacer(), + ], + ), + const SizedBox(height: 2), + Divider( + thickness: 0.2, + color: Colors.blueGrey.shade100, + ), + const SizedBox(height: 5), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "First Name", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldForexWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + color: Colors.transparent, + child: SizedBox( + height: 40, + child: TextField( + controller: controllers["first_name"], + focusNode: focusNodes["first_name"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "First Name", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + )), + ), + if (errorMessages["first_name"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["first_name"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + SizedBox( + height: 15, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Last Name", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldForexWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + color: Colors.transparent, + child: SizedBox( + height: 40, + child: TextField( + controller: controllers["last_name"], + focusNode: focusNodes["last_name"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Last Name", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + )), + ), + if (errorMessages["last_name"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["last_name"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + SizedBox( + height: 15, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Email", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldForexWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + color: Colors.transparent, + child: SizedBox( + height: 40, + child: TextField( + controller: controllers["email"], + focusNode: focusNodes["email"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Email", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + )), + ), + if (errorMessages["email"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["email"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + SizedBox( + height: 15, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Mobile", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldForexWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + color: Colors.transparent, + child: SizedBox( + height: 40, + child: TextField( + controller: controllers["mobile"], + focusNode: focusNodes["mobile"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Mobile", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + )), + ), + if (errorMessages["mobile"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["mobile"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + SizedBox( + height: 15, + ), + if (travellerDataId != null) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Change Status ", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + Tooltip( + message: + isActive == "1" ? "Tap to deactivate" : "Tap to activate", + child: GestureDetector( + onTap: toggleStatus, + child: Text( + isActive == "1" ? "Active" : "Inactive", + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + color: isActive == "1" ? Colors.green : Colors.red, + ), + ), + ), + ) + ], + ), + if (travellerDataId != null) + SizedBox( + height: 15, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + // SizedBox( + // child: ElevatedButton( + // onPressed: () { + // // You can get text from commentController.text + // Navigator.of(context).pop(); // Close the modal + // }, + // style: ElevatedButton.styleFrom( + // backgroundColor: widget.layoutColor, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(8), + // ), + // ), + // child: Text('Cancel', + // style: GoogleFonts.poppins( + // fontSize: 13, color: Colors.white)), + // ), + // ), + // SizedBox( + // width: 10, + // ), + SizedBox( + child: ElevatedButton( + onPressed: () { + handleSubmit(); + // You can get text from commentController.text + // Navigator.of(context).pop(); // Close the modal + }, + style: ElevatedButton.styleFrom( + backgroundColor: widget.layoutColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Text('Save', + style: GoogleFonts.poppins( + fontSize: 11, color: Colors.white)), + ), + ), + ], + ) + // : SizedBox.shrink(), + ], + ), + ) + ) + ); + } +} \ No newline at end of file diff --git a/lib/Screens/traveller/travellerList.dart b/lib/Screens/traveller/travellerList.dart new file mode 100644 index 0000000..1076c5a --- /dev/null +++ b/lib/Screens/traveller/travellerList.dart @@ -0,0 +1,869 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:http/http.dart' as http; +import 'package:responsive_builder/responsive_builder.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../config/apiUrl.dart'; +import '../../routes/custom_appBar.dart'; +import '../../routes/custom_drawer.dart'; +import '../../services/apiService.dart'; +import '../../utils/auth_utils.dart'; +import '../../utils/pagination.dart'; +import 'travellerDetails.dart'; + +class TravellerList extends StatefulWidget { + const TravellerList({super.key}); + + @override + TravellerListState createState() => TravellerListState(); +} + +class TravellerListState extends State { + final GlobalKey travellerListKey = + GlobalKey(); + + final ApiService apiService = ApiService(); + late Future> futureTraveller; + + late Map depSingleData; + String? selectedTravellerId; + String? orgId; + + Color? layoutColor; + Color? bodyColor; + + List allTraveller = []; + List filteredTraveller = []; + TextEditingController searchController = TextEditingController(); + + int currentPage = 0; + int itemsPerPage = 10; + + @override + void initState() { + super.initState(); + futureTraveller = fetchGetTraveller(); + + futureTraveller.then((object) { + setState(() { + allTraveller = object; + }); + }); + + WidgetsBinding.instance.addPostFrameCallback((_) { + loadInitialData(); + }); + + // futurePlans = fetchPlans(); + } + + 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; + }); + } + + Future getToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('auth_token'); + } + + Future> refreshData() { + print("Calling Refresh Data"); + + futureTraveller = fetchGetTraveller(); + + return futureTraveller.then((object) { + print("Calling Refresh Data $object"); + setState(() { + allTraveller = object; + }); + return object; + }); + } + + Future> fetchGetTraveller() async { + String? ordId = await getOrgId(); + final String apiUrlData = '$apiUrl/api/travellers?org_id=$ordId'; + + + final String? token = await getToken(); + + print("Fetch Traveller"); + print("2KN Here : $token"); + + 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', + }, + ); + print("called api : $apiUrlData"); + if (response.statusCode == 200) { + final data = json.decode(response.body); + return data['data']; // Returning raw JSON list + } else { + throw Exception('Failed to load users'); + } + } + + void filterTraveller(String query) { + // print("all before filtering: $query"); + // final lowerQuery = query.toLowerCase(); + // setState(() { + // filteredTraveller = allTraveller.where((object) { + // return (object['traveller_id']?.toLowerCase().contains(lowerQuery) ?? + // false) || + // (object['description']?.toLowerCase().contains(lowerQuery) ?? false) || + // (object['user']?.toLowerCase().contains(lowerQuery) ?? false) || + // (object['is_active']?.toLowerCase().contains(lowerQuery) ?? false); + // }).toList(); + // }); + // print("filteredPlans: $filteredTraveller"); + + print("all before filtering: $query"); + final lowerQuery = query.toLowerCase(); + setState(() { + filteredTraveller = allTraveller.where((object) { + final isActiveStatus = + object['is_active'] == "1" ? "active" : "inactive"; + return (object['traveller_id']?.toLowerCase().contains(lowerQuery) ?? + false) || + (object['name']?.toLowerCase().contains(lowerQuery) ?? false) || + (object['description']?.toLowerCase().contains(lowerQuery) ?? + false) || + (isActiveStatus.contains(lowerQuery)); + }).toList(); + }); + print("filteredTraveller: $filteredTraveller"); + } + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder(builder: (context, sizingInfo) { + bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + + return Scaffold( + backgroundColor: Color(0xFFf5f5f5), + // appBar: isDesktop ? null : const CustomAppBar(title: 'User Management'), + // drawer: isDesktop ? null : CustomDrawer(isDesktop: false), + 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), + // const Expanded(child: Center(child: Text("User Page Content"))), + Expanded(child: buildGroupList(isDesktop)), + ], + ), + ), + ); + }); + } + + Widget buildGroupList(bool isDesktop) { + return Container( + margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null, + padding: const EdgeInsets.all(1), + decoration: BoxDecoration( + color: isDesktop ? Colors.white : Color(0xFFFCFCFC), + ), + // decoration: BoxDecoration( + // // color: Colors.amber, + // // color: bodyColor, + // color: Color(0xFFE1F5FE), + // border: Border.all( + // color: Colors.white, + // // color: Color(0xFFF7F7FB), + // width: 3.5)), + child: buildUserTable(isDesktop), + ); + } + + Widget buildUserTable(bool isDesktop) { + return 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, + + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Container( + color: Colors.white, + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Divider( + // thickness: 0.2, // how "thick" the line is + // color: Colors.grey, // optional + // ), + Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + 'Traveller Details', + style: GoogleFonts.poppins( + fontSize: isDesktop ? 16 : 14, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + ], + ), + if (isDesktop) + SizedBox( + width: MediaQuery.of(context).size.width * 0.16, + ), + + if (isDesktop) + Container( + width: MediaQuery.of(context).size.width * 0.2, + height: 40, + child: TextField( + controller: searchController, + onChanged: filterTraveller, + decoration: InputDecoration( + hintText: "Search ...", + hintStyle: TextStyle( + fontSize: 12, color: Color(0xFF9E9DBD)), + prefixIcon: Icon( + Icons.search, + color: Color(0xFF9E9DBD), + size: 18, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade200, width: 0.5), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, width: 1), + ), + ), + style: GoogleFonts.poppins( + fontSize: 12, + ), + ), + ), + // SizedBox(width: 16), + Spacer(), + + 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: () async { + showDialog( + context: context, + builder: (context) => TravellerData( + isDesktop: isDesktop, + layoutColor: layoutColor!, + fetchGetTraveller: refreshData, + + // role: + // "Travel Agent" + ), + ); + }, + child: Row( + mainAxisSize: + MainAxisSize.min, // Ensures content fits nicely + children: [ + Text( + "Add Traveller", + style: GoogleFonts.poppins( + fontSize: isDesktop ? 13 : 11, + ), + ), + SizedBox(width: 8), // spacing between icon and text + Icon( + Icons.add_circle_outline_rounded, + size: 15, + color: Colors.white, + ), + ], + ), + ), + ], + ), + + if (!isDesktop) + SizedBox( + height: 5, + ), + isDesktop + ? SizedBox.shrink() + : Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: MediaQuery.of(context).size.width * 0.8, + height: 35, + child: TextField( + controller: searchController, + onChanged: filterTraveller, + decoration: InputDecoration( + hintText: "Search ...", + hintStyle: TextStyle( + fontSize: 12, color: Color(0xFF9E9DBD)), + prefixIcon: Icon( + Icons.search, + color: Color(0xFF9E9DBD), + size: 18, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade200, + width: 0.5), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, width: 1), + ), + ), + style: GoogleFonts.poppins( + fontSize: 12, + ), + ), + ), + // SizedBox(width: 16), + ], + ), + const SizedBox(height: 10), + FutureBuilder>( + future: futureTraveller, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError || + !snapshot.hasData || + snapshot.data!.isEmpty) { + return Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // const Icon(Icons.error_outline, + // color: Colors.redAccent, size: 60), + // const SizedBox(height: 16), + // Text( + // "Oops!", + // style: GoogleFonts.poppins( + // fontSize: 20, + // fontWeight: FontWeight.bold, + // color: Colors.redAccent), + // ), + const SizedBox(height: 8), + Text( + "No Traveller Available ", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.grey), + ), + const SizedBox(height: 20), + Text( + "Please Create Traveller Details", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 16, color: Colors.grey), + ), + const SizedBox(height: 20), + ], + ), + ), + ); + } + /* Here collect the list to displayed the data in table or card Used */ + List object = filteredTraveller.isNotEmpty + ? filteredTraveller + : allTraveller; + + /* List is Sorting here */ + object.sort((a, b) { + DateTime dateA = DateTime.parse(a['created_on']); + DateTime dateB = DateTime.parse(b['created_on']); + + return dateB + .compareTo(dateA); // Descending: newest first + }); + + /* For pagination for list ... */ + List paginatedTraveller = object + .skip(currentPage * itemsPerPage) + .take(itemsPerPage) + .toList(); + + /* Table ... */ + Widget table = LayoutBuilder( + builder: (context, constraints) { + double minWidth = + isDesktop ? constraints.maxWidth : 1300; + + return ConstrainedBox( + constraints: BoxConstraints(minWidth: minWidth), + child: DataTable( + dividerThickness: 0.5, + columnSpacing: isDesktop ? 24.0 : 16.0, + border: TableBorder( + horizontalInside: BorderSide( + width: 0.5, color: Colors.grey.shade200), + ), + columns: [ + DataColumn( + label: Text( + 'Name', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600), + )), + DataColumn( + label: Text( + 'Email', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600), + )), + DataColumn( + label: Text( + 'Mobile', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600), + )), + DataColumn( + label: Text( + 'Status', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600), + )), + DataColumn( + label: Text( + 'Actions', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600), + )), + ], + rows: paginatedTraveller.map((tableObject) { + String fullName = '${tableObject['first_name'] ?? ''} ${tableObject['last_name'] ?? ''}'; + String travellerId = + tableObject['traveller_id'] + .toString(); // Get user ID + bool isSelected = + selectedTravellerId == travellerId; + + return DataRow(cells: [ + DataCell(Text(fullName ?? 'N/A', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ))), + DataCell( + Text(tableObject['email'] ?? 'N/A', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ), + softWrap: true, + overflow: TextOverflow.ellipsis)), + DataCell( + Text(tableObject['mobile'] ?? 'N/A', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ), + softWrap: true, + overflow: TextOverflow.ellipsis)), + DataCell( + Text( + tableObject['is_active'] == "1" + ? 'Active' + : 'Inactive', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + color: tableObject['is_active'] == "1" + ? Colors.green + : Colors.red, + ), + softWrap: true, + overflow: TextOverflow.ellipsis, + ), + ), + DataCell( + // UserActionsMenu( + // user: forex, + // getUserDetails: (id) => + // apiService.getSingleUser(id), + // ), + GestureDetector( + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15), + onTap: () async { + // final userId = getUserId(user['user_id']); + // final usersData = await getUserDetails(userId); + // + final travellerId = int.tryParse( + tableObject['traveller_id'] + .toString()); + + if (travellerId != null) { + print( + "Table cell - traveller Id -- $travellerId"); + final data = await apiService + .getTravellerDetailsFind( + travellerId); + print("TravellerId -- $data"); + + showDialog( + context: context, + builder: (context) => + TravellerData( + isDesktop: isDesktop, + travellerId: + travellerId, // Pass the ID + travellerData: data, + layoutColor: layoutColor!, + // fetchGetForex: fetchGetForex, + fetchGetTraveller: refreshData, + // role: + // "Travel Agent" + ), + ); + } else { + print("Invalid ID"); + } + }, + ), + ), + ]); + }).toList(), + ), + ); + }, + ); + + /* Card ... */ + Widget buildMobileCardView(List paginatedUser) { + return ListView.builder( + itemCount: paginatedUser.length, + itemBuilder: (context, index) { + final cardObject = paginatedUser[index]; + String fullName = '${cardObject['first_name'] ?? ''} ${cardObject['last_name'] ?? ''}'; + return Card( + color: Colors.white, + margin: EdgeInsets.symmetric( + horizontal: 12, vertical: 6), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status and Employee Code + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + fullName ?? 'N/A', + style: GoogleFonts.poppins( + fontSize: 10, + color: Colors.black87, + fontWeight: FontWeight.w700), + ), + + GestureDetector( + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15), + onTap: () async { + // final userId = getUserId(user['user_id']); + // final usersData = await getUserDetails(userId); + // + final travellerId = int.tryParse( + cardObject['traveller_id'] + .toString()); + + if (travellerId != null) { + print( + "travellerId -- $travellerId"); + final data = await apiService + .getTravellerDetailsFind( + travellerId); + print("TravellerId -- $data"); + + showDialog( + context: context, + builder: (context) => + TravellerData( + isDesktop: isDesktop, + travellerId: + travellerId, // Pass the ID + travellerData: data, + layoutColor: layoutColor!, + // fetchGetTraveller: fetchGetTraveller, + fetchGetTraveller: + refreshData, + // role: + // "Travel Agent" + ), + ); + } else { + print("Invalid ID"); + } + }, + ), + // PopupMenuButton( + // color: Colors.white, + // padding: EdgeInsets.zero, + // offset: Offset(0, 30), + // icon: Icon( + // Icons.more_vert, + // color: Color(0xFF475569), + // size: 14, + // ), + // itemBuilder: (context) => [ + // CustomPopupMenuEntry( + // child: Container( + // padding: EdgeInsets.symmetric( + // horizontal: 8, vertical: 8), + // child: Row( + // mainAxisSize: + // MainAxisSize.min, + // mainAxisAlignment: + // MainAxisAlignment.center, + // children: [ + // IconButton( + // icon: Icon( + // Icons + // .remove_red_eye, + // color: Color( + // 0xFF475569), + // size: 18), + // onPressed: () { + // print( + // "USerDAta - $user"); + // // dynamic usersData = apiService + // // .getSingleUser(user[ + // // 'user_id'] + // // is String + // // ? int.parse(user[ + // // 'user_id']) + // // : user[ + // // 'user_id']); + // // + // // print( + // // "USerDAta - $usersData"); + // + // context.go( + // "/CreateUserDetails", + // extra: { + // "selectedUser": + // user, + // "isViewMode": true + // }, + // ); + // }), + // IconButton( + // icon: Image.asset( + // 'assets/images/IconsImg/edit.png', + // width: 20, + // height: 15), + // onPressed: () { + // context.go( + // "/CreateUserDetails", + // extra: { + // "selectedUser": + // user, + // "isViewMode": false + // }, + // ); + // }, + // ), + // ], + // ), + // ), + // ), + // ], + // ), + ], + ), + + SizedBox(height: 2), + // Trip Id and Trip Name + // Name + Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + cardObject['email'] ?? '', + style: GoogleFonts.poppins( + fontSize: 10, + color: Colors.black87), + ), + ], + ), + SizedBox( + width: 10, + ), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + cardObject['mobile'] ?? '', + style: GoogleFonts.poppins( + fontSize: 10, + color: Colors.black87), + ), + ], + ), + ], + ), + // Actions + // Actions + ], + ), + ), + ); + }, + ); + } + + return Expanded( + child: Column( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: isDesktop + ? (searchController.text.isNotEmpty && + filteredTraveller.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, + color: Colors.grey), + ), + ) + : SingleChildScrollView( + scrollDirection: Axis.vertical, + child: table, + )) + : (searchController.text.isNotEmpty && + filteredTraveller.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, + color: Colors.grey), + ), + ) + : buildMobileCardView( + paginatedTraveller)), + ), + // Expanded( + // child: isDesktop + // ? SingleChildScrollView( + // scrollDirection: Axis.vertical, + // child: table, // <-- your existing table + // ) + // : buildMobileCardView(paginatedTraveller), + // ), + PaginationControls( + currentPage: currentPage, + itemsPerPage: itemsPerPage, + totalItems: object.length, + activeColor: layoutColor, // your theme color + onPageChanged: (page) { + setState(() { + currentPage = page; + }); + }, + onItemsPerPageChanged: (items) { + setState(() { + itemsPerPage = items; + currentPage = 0; + }); + }, + ), + ], + ), + ); + }, + ) + ]), + )), + ); + } +} \ No newline at end of file diff --git a/lib/Screens/userManagement/create_user/traveller_details.dart b/lib/Screens/userManagement/create_user/traveller_details.dart index 4ff5d0d..6d737ce 100644 --- a/lib/Screens/userManagement/create_user/traveller_details.dart +++ b/lib/Screens/userManagement/create_user/traveller_details.dart @@ -1035,7 +1035,9 @@ class TravellerDetailsState extends State { Text( "Passport Number", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -1072,7 +1074,9 @@ class TravellerDetailsState extends State { Text( "Place of Issue", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -1134,7 +1138,9 @@ class TravellerDetailsState extends State { Text( "Date of Issue", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -1215,7 +1221,9 @@ class TravellerDetailsState extends State { Text( "Date of Expiry", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -1298,7 +1306,9 @@ class TravellerDetailsState extends State { Text( "Passport Document", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -1487,7 +1497,9 @@ class TravellerDetailsState extends State { Text( "Id Number", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -1531,7 +1543,9 @@ class TravellerDetailsState extends State { Text( "Id Type", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -1606,7 +1620,9 @@ class TravellerDetailsState extends State { Text( "Full Name As ID", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -1691,7 +1707,9 @@ class TravellerDetailsState extends State { Text( "Seat Preference", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -1769,7 +1787,9 @@ class TravellerDetailsState extends State { Text( "Meal Preference", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -1809,7 +1829,9 @@ class TravellerDetailsState extends State { Text( "Additional Information", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -1896,7 +1918,9 @@ class TravellerDetailsState extends State { Text( "Seat Preference", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -1979,7 +2003,9 @@ class TravellerDetailsState extends State { Text( "Meal Preference", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -2019,7 +2045,9 @@ class TravellerDetailsState extends State { Text( "Additional Information", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -2072,7 +2100,9 @@ class TravellerDetailsState extends State { Text( "Emergency Contact Number", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -2150,7 +2180,9 @@ class TravellerDetailsState extends State { Text( "Forex Pre-Paid Card Number", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -2214,7 +2246,9 @@ class TravellerDetailsState extends State { Text( "Forex Expiry Date", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -2370,7 +2404,9 @@ class TravellerDetailsState extends State { Text( "Airline", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -2500,7 +2536,9 @@ class TravellerDetailsState extends State { Text( "Frequent Flyer Information", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -2643,7 +2681,9 @@ class TravellerDetailsState extends State { Text( "Hotel", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -2735,8 +2775,10 @@ class TravellerDetailsState extends State { children: [ Text( "Hotel Membership Number", - style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + style:GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -2887,7 +2929,9 @@ class TravellerDetailsState extends State { Text( "Country", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -3009,7 +3053,9 @@ class TravellerDetailsState extends State { Text( "Visa Type", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -3088,7 +3134,9 @@ class TravellerDetailsState extends State { Text( "ValidFrom", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( @@ -3168,7 +3216,9 @@ class TravellerDetailsState extends State { Text( "Valid UpTo", style: GoogleFonts.poppins( - fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( diff --git a/lib/routes/custom_router.dart b/lib/routes/custom_router.dart index 487ff68..bdc8c29 100644 --- a/lib/routes/custom_router.dart +++ b/lib/routes/custom_router.dart @@ -28,6 +28,7 @@ import '../Screens/department/department_list.dart'; import '../Screens/costCenter/costCenter_list.dart'; import '../Screens/dashboard/status_dashboard.dart'; import '../Screens/hotels/hotels_list.dart'; +import '../Screens/traveller/travellerList.dart'; final GoRouter router = GoRouter( routes: [ @@ -153,6 +154,10 @@ final GoRouter router = GoRouter( path: '/statusdashboard', builder: (context, state) => StatusDashboard(), ), + GoRoute( + path: '/traveller', + builder: (context, state) => TravellerList(), + ), GoRoute( path: '/CreateGroup', pageBuilder: (context, state) => MaterialPage( diff --git a/lib/routes/organizationSetting.dart b/lib/routes/organizationSetting.dart index 432ac13..a4bf4d3 100644 --- a/lib/routes/organizationSetting.dart +++ b/lib/routes/organizationSetting.dart @@ -100,6 +100,13 @@ class OrganizationSettingState extends State { 'label': 'Hotels', 'description': 'Create and Edit Hotels' }, + { + 'value': '/traveller', + 'icon': Icons.travel_explore, + 'label': 'Traveller', + 'description': 'Create and Edit Traveller' + }, + ]; // List rows = []; diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index 5d60c78..99e61e8 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -1190,4 +1190,48 @@ class ApiService { throw Exception('Failed to load plans'); } } + + Future> getTravellerDetailsFind(int id) async { + final String apiUrldata = '$apiUrl/api/travellers/find?traveller_id=$id'; + + + //c + final token = await getToken(); + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + try { + final data = json.decode(response.body); + + if (!data.containsKey('data') || data['data'] is! List) { + throw Exception("Invalid response format: 'data' field is missing or not a List"); + } + + final List> listData = + List>.from(data['data']); + + if (listData.isEmpty) { + throw Exception("No Traveller data found with ID $id"); + } + + return listData[0]; + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + throw Exception('Failed to load Hotel details'); + } + } + } From ac303e666f72fe9188fe77e2278244d8daa6211a Mon Sep 17 00:00:00 2001 From: VE10-Sanjeev Date: Sat, 31 May 2025 17:08:06 +0530 Subject: [PATCH 08/29] issues fixes --- .../create_user/change_password.dart | 383 ++++++++++++++++++ .../create_user/create_user.dart | 4 +- .../create_user/office_details.dart | 22 +- .../create_user/personal_details.dart | 69 +++- .../create_user/traveller_details.dart | 107 ++++- 5 files changed, 570 insertions(+), 15 deletions(-) create mode 100644 lib/Screens/userManagement/create_user/change_password.dart diff --git a/lib/Screens/userManagement/create_user/change_password.dart b/lib/Screens/userManagement/create_user/change_password.dart new file mode 100644 index 0000000..3ecc927 --- /dev/null +++ b/lib/Screens/userManagement/create_user/change_password.dart @@ -0,0 +1,383 @@ +import 'dart:convert'; + +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:http/http.dart' as http; + +import '../../../config/apiUrl.dart'; +import '../../../services/apiService.dart'; +import '../../../utils/auth_utils.dart'; +import '../../../widgets/custom_user_form.dart'; + + +class ChangePasswordDialogData extends StatefulWidget { + + final dynamic isDesktop; + final dynamic layoutColor; + final dynamic updaterUserId; + final dynamic updaterEmail; + + const ChangePasswordDialogData({ + super.key, + this.isDesktop, + this.layoutColor, + this.updaterUserId, + this.updaterEmail + }); + + + @override + ChangePasswordDialogDataState createState() => ChangePasswordDialogDataState(); + } + +class ChangePasswordDialogDataState extends State { + + final ApiService apiService = ApiService(); + + final Map controllers = {}; + Map errorMessages = {}; + + + String? loggeduserId; + String? updaterUserIdForAPI; + + List dataHeader = [ + "email", + "changePassword", + "confirmPassword" + ]; + + // @override + // void initState() { + // super.initState(); + // + // for (var field in dataHeader) { + // controllers[field] = TextEditingController(); + // } + // + // setState(() { + // controllers['email']?.text = widget.updaterEmail ?? ''; + // controllers['changePassword']?.text = ''; + // controllers['confirmPassword']?.text = ''; + // }); + // } + + @override + void initState() { + super.initState(); + print("widget.updaterEmail: ${widget.updaterEmail}"); + + for (var field in dataHeader) { + controllers[field] = TextEditingController(); + } + + setState(() { + controllers['email']?.text = widget.updaterEmail ; + controllers['changePassword']?.text = ''; + controllers['confirmPassword']?.text = ''; + updaterUserIdForAPI = widget.updaterUserId; + }); + } + + + + void _clearError() { + setState(() { + errorMessages.clear(); + }); + } + + @override + void dispose() { + for (var controller in controllers.values) { + controller.dispose(); + } + super.dispose(); + } + + + + bool validateData() { + errorMessages.clear(); + + final String? email = controllers["email"]?.text; + final String? changePassword = controllers["changePassword"]?.text; + final String? confirmPassword = controllers["confirmPassword"]?.text; + + // Required fields check + if (email == null || email.trim().isEmpty) { + errorMessages["email"] = "Required"; + } + + if (changePassword == null || changePassword.trim().isEmpty) { + errorMessages["changePassword"] = "Required"; + } + + if (confirmPassword == null || confirmPassword.trim().isEmpty) { + errorMessages["confirmPassword"] = "Required"; + } + + // Password match check + if ((changePassword?.isNotEmpty ?? false) && + (confirmPassword?.isNotEmpty ?? false) && + changePassword != confirmPassword) { + errorMessages["changePassword"] = "Passwords do not match"; + errorMessages["confirmPassword"] = "Passwords do not match"; + } + + // setState(() {}); // Update UI with any error messages + return errorMessages.isEmpty; + } + + + Future handleSubmit() async { + loggeduserId = await getUserId(); + + setState(() { + // This triggers UI rebuild with error messages + if (validateData()) { + postData(); + } + }); + + } + + Future postData() async { + // final remarksData = getData(); + print('sss$updaterUserIdForAPI'); + final loggedInUserId = await getUserId(); + + final password = controllers["changePassword"]?.text ?? ''; + final confirmPassword = controllers["confirmPassword"]?.text ?? ''; + final String apiUrldata = '$apiUrl/api/user/user-password/$updaterUserIdForAPI'; + + final token = await getToken(); + final headers = { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }; + + try { + final uri = Uri.parse(apiUrldata); + final headers = { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }; + final body = jsonEncode({ + "password": password, + "updated_by": loggedInUserId, + }); + + final response = await http.put(uri, headers: headers, body: body); + + if (response.statusCode == 200 || response.statusCode == 201) { + print("Forex Details Created successfully!"); + print("Response: ${response.body}"); + _clearError(); + Navigator.of(context).pop(); + } else if (response.statusCode == 404) { + Navigator.of(context).pop(); + final message = jsonDecode(response.body)['message'] ?? 'Unknown error'; + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: Colors.redAccent, + behavior: SnackBarBehavior.floating, + ), + ); + } else { + print("Failed to submit plan. Status: ${response.statusCode}"); + print("Error: ${response.body}"); + } + } catch (e) { + print(" Error submitting plan: $e"); + } + } + + @override + Widget build(BuildContext context) { + + return AlertDialog( + backgroundColor: Colors.white, + contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30), + // contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Row 1: Title + Edit + Delete buttons + Row( + children: [ + Text( + 'Change Password', + style: GoogleFonts.poppins(fontSize: 15, color: Colors.black), + ), + const Spacer(), + ], + ), + const SizedBox(height: 2), + Divider( + thickness: 0.2, + color: Colors.blueGrey.shade100, + ), + const SizedBox(height: 5), + + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Email", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldUserWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + color: Colors.transparent, + // width: isDesktop + // ? MediaQuery.of(context).size.width * 0.330 + // : MediaQuery.of(context).size.width * 0.66, + child: SizedBox( + height: 40, + child: TextField( + controller: controllers["email"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Email", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + )), + ), + if (errorMessages["email"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["email"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + SizedBox( + height: 10, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Change Password", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldUserWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + color: Colors.transparent, + child: SizedBox( + height: 40, + child: TextField( + controller: controllers["changePassword"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Change Password", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + )), + ), + if (errorMessages["changePassword"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["changePassword"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + SizedBox( + height: 15, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Confirm Password", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldUserWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + color: Colors.transparent, + child: SizedBox( + height: 40, + child: TextField( + controller: controllers["confirmPassword"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Confirm Password", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + )), + ), + if (errorMessages["confirmPassword"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["confirmPassword"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + SizedBox( + height: 15, + ), + + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + child: ElevatedButton( + onPressed: () { + handleSubmit(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: widget.layoutColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Text('Save', + style: GoogleFonts.poppins( + fontSize: 11, color: Colors.white)), + ), + ), + ], + ) + // : SizedBox.shrink(), + ], + ), + ); + } +} + diff --git a/lib/Screens/userManagement/create_user/create_user.dart b/lib/Screens/userManagement/create_user/create_user.dart index 86bad75..9cdff8d 100644 --- a/lib/Screens/userManagement/create_user/create_user.dart +++ b/lib/Screens/userManagement/create_user/create_user.dart @@ -54,6 +54,7 @@ class _CreateUserFormDetialsState extends State { String? userId; String? orgId; + String? userIdApi; String? token; @@ -201,7 +202,7 @@ class _CreateUserFormDetialsState extends State { print("API Selected User Has Data - $apiselectedUser"); } - + userIdApi = apiselectedUser?["user_id"] ?? ""; controllers["Fname"]?.text = apiselectedUser?["first_name"] ?? ""; controllers["Lname"]?.text = apiselectedUser?["last_name"] ?? ""; controllers["email"]?.text = apiselectedUser?["email"] ?? ""; @@ -976,6 +977,7 @@ class _CreateUserFormDetialsState extends State { personalDetailsKey: personalDetailsKey, isDesktop: isDesktop, // pass isDesktop as a named argument isViewMode: isViewMode, + userIdApi:userIdApi, controllers: controllers, errorMessages: errorMessages, selectedGender: selectedGender, diff --git a/lib/Screens/userManagement/create_user/office_details.dart b/lib/Screens/userManagement/create_user/office_details.dart index 4a6c71f..10950f4 100644 --- a/lib/Screens/userManagement/create_user/office_details.dart +++ b/lib/Screens/userManagement/create_user/office_details.dart @@ -1108,6 +1108,12 @@ class _OfficeDetailsState extends State { _selectedCheckOutDate = pickedDate; widget.controllers["delegationStartDate"]?.text = DateFormat('dd-MM-yyyy').format(pickedDate); + + if (_selectedEndDate != null && + _selectedEndDate!.isBefore(_selectedCheckOutDate!)) { + _selectedEndDate = null; + widget.controllers["delegationEndDate"]?.text = ''; + } }); } } @@ -1173,6 +1179,10 @@ class _OfficeDetailsState extends State { DateTime now = DateTime.now(); DateTime today = DateTime(now.year, now.month, now.day); + DateTime minDate = _selectedCheckOutDate != null + ? _selectedCheckOutDate! + : today; + // Parse date from notifier if available, else use today DateTime initialDate; @@ -1188,11 +1198,11 @@ class _OfficeDetailsState extends State { DateTime? pickedDate = await showDatePicker( context: context, - initialDate: - _selectedEndDate != null && _selectedEndDate!.isAfter(today) - ? _selectedEndDate! - : today, - firstDate: today, + initialDate: _selectedEndDate != null && + _selectedEndDate!.isAfter(minDate) + ? _selectedEndDate! + : minDate, + firstDate: minDate, lastDate: DateTime(2100), ); @@ -1201,8 +1211,6 @@ class _OfficeDetailsState extends State { _selectedEndDate = pickedDate; widget.controllers["delegationEndDate"]?.text = DateFormat('dd-MM-yyyy').format(pickedDate); - // textControllers["_forexEndDate"]?.text = - // DateFormat('dd-MM-yyyy').format(initialDate); }); } } diff --git a/lib/Screens/userManagement/create_user/personal_details.dart b/lib/Screens/userManagement/create_user/personal_details.dart index c07af96..63b5e3b 100644 --- a/lib/Screens/userManagement/create_user/personal_details.dart +++ b/lib/Screens/userManagement/create_user/personal_details.dart @@ -6,11 +6,14 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; import '../../../services/apiService.dart'; import '../../../utils/auth_utils.dart'; import '../../../widgets/custom_user_form.dart'; +import '../../../config/apiUrl.dart'; +import 'change_password.dart'; class PersonalDetails extends StatefulWidget { final GlobalKey personalDetailsKey; @@ -31,6 +34,7 @@ class PersonalDetails extends StatefulWidget { final String? selectedGender; final String? selectedCountry; final String? selectedRole; + final String? userIdApi; // const PersonalDetails(this.isDesktop, this.isViewMode, {super.key},); const PersonalDetails( @@ -49,7 +53,7 @@ class PersonalDetails extends StatefulWidget { this.onGenderChanged, this.onCountryChanged, this.onRoleChanged, - this.onUserTypeChanged}) + this.onUserTypeChanged, this.userIdApi}) : super(key: key); @override @@ -124,8 +128,14 @@ class PersonalDetailsState extends State { Color? layoutColor; Color? bodyColor; + final Map controllers = {}; + Map errorMessages2 = {}; + @override void initState() { + + + super.initState(); apiCountryData = null; @@ -144,6 +154,7 @@ class PersonalDetailsState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { loadAllServices(); getOrganizationData(); + loadInitialData(); }); } @@ -153,6 +164,21 @@ class PersonalDetailsState extends State { }); } + 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; + }); + } + Future fetchRoles() async { try { final response = await apiService.fetchMasterDropdown(); @@ -526,6 +552,24 @@ class PersonalDetailsState extends State { } Widget _buildThirdRow(bool isDesktop) { + + void _openPopup() { + final emailValue = widget.controllers["email"]?.text ?? ""; + final updaterUserId = widget.userIdApi ?? ""; + + showDialog( + context: context, + builder: (context) { + return ChangePasswordDialogData( + updaterEmail: emailValue, + updaterUserId: updaterUserId, + layoutColor: layoutColor, + isDesktop: widget.isDesktop, + ); + }, + ); + } + return Container( color: Colors.white, child: isDesktop @@ -535,8 +579,16 @@ class PersonalDetailsState extends State { if (!widget.apiselectedUser) ...[ buildPassword(), SizedBox(width: 15), + buildRole() + ] + else ...[ + buildRole(), + SizedBox(height: 8, width: 15), + TextButton( + onPressed: () => _openPopup(), + child: Text("Change Password"), + ), ], - buildRole() ], ) : Column( @@ -544,9 +596,15 @@ class PersonalDetailsState extends State { children: [ if (!widget.apiselectedUser) ...[ buildPassword(), - SizedBox(height: 8)], // - buildRole() - + SizedBox(height: 8),buildRole() + ] else ...[ + buildRole(), + SizedBox(height: 8), + TextButton( + onPressed: () => _openPopup(), + child: Text("Change Password"), + ), + ], ], ), ); @@ -1282,3 +1340,4 @@ class PersonalDetailsState extends State { // apiselectedUser != null // ? SizedBox() } + diff --git a/lib/Screens/userManagement/create_user/traveller_details.dart b/lib/Screens/userManagement/create_user/traveller_details.dart index 6d737ce..8421a29 100644 --- a/lib/Screens/userManagement/create_user/traveller_details.dart +++ b/lib/Screens/userManagement/create_user/traveller_details.dart @@ -3093,6 +3093,109 @@ class TravellerDetailsState extends State { ); } + Widget buildVisaType2(entry) { + late Map visaTypeMap; // Mapping country_code -> country_name + late List visaTypeCodes; // List of country codes + + // List purposeList = apiData?['visa_type_of_visa']; + + List purposeList = apiData?['visa_type_of_visa']; + + print("purposeList - $purposeList"); + + + visaTypeMap = { + for (var item in purposeList) + item['visa_type_id'] as String: item['visa_type_of_visa'] as String + }; + + // Extract only country codes for processing + visaTypeCodes = visaTypeMap.keys.toList(); + + // selectedPurpose ??= null; + + String? selectedPurpose = entry['visa_type_of_visa']; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Visa Type", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.17 + : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: DropdownSearch( + selectedItem: visaTypeMap[selectedPurpose], + popupProps: PopupProps.menu( + showSearchBox: true, // Enables search functionality + menuProps: const MenuProps( + backgroundColor: Colors.white, + ), + // constraints: BoxConstraints(maxHeight: 250), + itemBuilder: (context, item, isSelected) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 6.0), + child: Text( + item, + style: GoogleFonts.poppins(fontSize: 11.5), + ), + ), + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search Visa Type...", + hintStyle: GoogleFonts.poppins(fontSize: 11.5), + contentPadding: EdgeInsets.symmetric(horizontal: 10), + ), + ), + ), + items: visaTypeMap.values.toList(), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 1, + ), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + // Center-align selected item + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select Visa Type", + style: TextStyle(fontSize: 12), + ), + ), + onChanged: (String? newValue) { + setState(() { + // Find the country_code based on selected country_name + // selectedCountry = countryMap.entries + // .firstWhere((entry) => entry.value == newValue) + // .key; + + final selectedPurpose = visaTypeMap.entries + .firstWhere((entry) => entry.value == newValue) + .key; + entry['visa_type_of_visa'] = selectedPurpose; + }); + }, + ), + ), + ), + ], + ); + } + Widget buildVisaValidFrom(Map entry) { DateTime? _selectedCheckOutDate; TimeOfDay? _selectedCheckOutTime; @@ -3132,7 +3235,7 @@ class TravellerDetailsState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "ValidFrom", + "Valid From", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, @@ -3214,7 +3317,7 @@ class TravellerDetailsState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Valid UpTo", + "Valid To", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, From 41010df10f4f53b381e88e72400b33fdb16b1324 Mon Sep 17 00:00:00 2001 From: VE10-Sanjeev Date: Sat, 31 May 2025 17:49:22 +0530 Subject: [PATCH 09/29] tolltip for trips --- lib/Screens/allTrips/list_all_plans.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Screens/allTrips/list_all_plans.dart b/lib/Screens/allTrips/list_all_plans.dart index 6e8c628..81ff48b 100644 --- a/lib/Screens/allTrips/list_all_plans.dart +++ b/lib/Screens/allTrips/list_all_plans.dart @@ -62,7 +62,6 @@ class _ListAllPlansState extends State { // }); // }); }); - // futurePlans = fetchPlans(); } @@ -764,6 +763,7 @@ class _ListAllPlansState extends State { Icons.remove_red_eye, color: Color(0xFF475569), size: 18), + tooltip: 'View Trips', onPressed: () { Navigator.pop( context); // Close popup manually @@ -788,6 +788,7 @@ class _ListAllPlansState extends State { icon: Icon( Icons.cancel_rounded, size: 18), + tooltip: 'Cancellation Trips', onPressed: () { Navigator.pop(context); deletePlan(plan.planId); @@ -797,6 +798,7 @@ class _ListAllPlansState extends State { icon: Icon(Icons.download, color: Color(0xFF114D8B), size: 18), + tooltip: 'Download Trips Detials', onPressed: () { Navigator.pop(context); apiService.getPdfDownload( @@ -809,6 +811,7 @@ class _ListAllPlansState extends State { color: Color(0xFF475569), size: 11, ), + tooltip: 'Trips Comments', onPressed: () { showDialog( context: context, From 5ddd028fa8684d42933c3e4d4458982bc7b6fa39 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Mon, 2 Jun 2025 15:13:28 +0530 Subject: [PATCH 10/29] Mail Template Basic Format --- android/app/src/main/AndroidManifest.xml | 19 +- android/app/src/profile/AndroidManifest.xml | 1 + lib/Screens/myTemplates/Tempale | 562 +++++ lib/Screens/myTemplates/assets.dart | 3 +- lib/Screens/myTemplates/custom_toolbar.dart | 56 +- .../myTemplates/dialog_placeholders.dart | 81 + .../src/common/default_image_insert.dart | 30 - .../src/common/default_video_insert.dart | 30 - .../src/common/extensions/attribute.dart | 12 - .../src/common/extensions/controller_ext.dart | 37 +- .../src/common/image_video_utils.dart | 122 -- .../common/utils/dart_ui/dart_ui_fake.dart | 43 - .../common/utils/dart_ui/dart_ui_real.dart | 1 - .../element_utils/element_shared_utils.dart | 84 - .../utils/element_utils/element_utils.dart | 106 - .../element_utils/element_web_utils.dart | 60 - .../src/common/utils/patterns.dart | 17 - .../myTemplates/src/common/utils/string.dart | 30 - .../myTemplates/src/common/utils/utils.dart | 30 - .../myTemplates/src/common/utils/web/web.dart | 1 - .../src/common/utils/web/web_real.dart | 46 - .../src/common/utils/web/web_stub.dart | 19 - .../src/editor/image/config/image_config.dart | 166 +- .../editor/image/config/image_web_config.dart | 12 +- .../src/editor/image/image_embed.dart | 78 +- .../src/editor/image/image_embed_types.dart | 68 +- .../src/editor/image/image_load_utils.dart | 36 - .../src/editor/image/image_menu.dart | 246 --- .../src/editor/image/image_save_utils.dart | 254 --- .../src/editor/image/image_web_embed.dart | 65 +- .../src/editor/image/widgets/image.dart | 186 -- .../editor/image/widgets/image_resizer.dart | 126 -- .../src/editor/video/config/video_config.dart | 47 +- .../editor/video/config/video_web_config.dart | 7 +- .../src/editor/video/video_embed.dart | 56 +- .../src/editor/video/video_web_embed.dart | 56 +- .../src/editor/video/widgets/video_app.dart | 122 -- .../src/editor/video/youtube_video_url.dart | 32 - .../myTemplates/src/flutter_quill_embeds.dart | 107 +- .../src/toolbar/camera/camera_button.dart | 133 +- .../src/toolbar/camera/camera_types.dart | 40 +- .../toolbar/camera/config/camera_config.dart | 29 +- .../toolbar/camera/select_camera_action.dart | 52 - .../toolbar/image/config/image_config.dart | 40 +- .../src/toolbar/image/image_button.dart | 136 +- .../toolbar/image/select_image_source.dart | 59 - .../src/toolbar/quill_simple_toolbar_api.dart | 12 - .../src/toolbar/video/config/video.dart | 51 +- .../toolbar/video/config/video_config.dart | 33 +- .../toolbar/video/select_video_source.dart | 57 - .../src/toolbar/video/video_button.dart | 135 +- lib/Screens/myTemplates/src/tst | 244 --- lib/Screens/myTemplates/template.dart | 607 +++++- lib/Screens/myTemplates/templateTest.dart | 27 +- lib/Screens/policy/policy_list.dart | 180 +- .../create_user/traveller_details.dart | 1914 +++++++++-------- lib/Screens/userManagement/user_List.dart | 1455 +++++++------ lib/app.dart | 243 ++- lib/config/apiUrl.dart | 4 +- lib/routes/custom_router.dart | 95 +- pubspec.lock | 76 +- pubspec.yaml | 23 +- web/index.html | 1 + 63 files changed, 3468 insertions(+), 5232 deletions(-) create mode 100644 lib/Screens/myTemplates/Tempale create mode 100644 lib/Screens/myTemplates/dialog_placeholders.dart delete mode 100644 lib/Screens/myTemplates/src/common/default_image_insert.dart delete mode 100644 lib/Screens/myTemplates/src/common/default_video_insert.dart delete mode 100644 lib/Screens/myTemplates/src/common/extensions/attribute.dart delete mode 100644 lib/Screens/myTemplates/src/common/image_video_utils.dart delete mode 100644 lib/Screens/myTemplates/src/common/utils/dart_ui/dart_ui_fake.dart delete mode 100644 lib/Screens/myTemplates/src/common/utils/dart_ui/dart_ui_real.dart delete mode 100644 lib/Screens/myTemplates/src/common/utils/element_utils/element_shared_utils.dart delete mode 100644 lib/Screens/myTemplates/src/common/utils/element_utils/element_utils.dart delete mode 100644 lib/Screens/myTemplates/src/common/utils/element_utils/element_web_utils.dart delete mode 100644 lib/Screens/myTemplates/src/common/utils/patterns.dart delete mode 100644 lib/Screens/myTemplates/src/common/utils/string.dart delete mode 100644 lib/Screens/myTemplates/src/common/utils/utils.dart delete mode 100644 lib/Screens/myTemplates/src/common/utils/web/web.dart delete mode 100644 lib/Screens/myTemplates/src/common/utils/web/web_real.dart delete mode 100644 lib/Screens/myTemplates/src/common/utils/web/web_stub.dart delete mode 100644 lib/Screens/myTemplates/src/editor/image/image_load_utils.dart delete mode 100644 lib/Screens/myTemplates/src/editor/image/image_menu.dart delete mode 100644 lib/Screens/myTemplates/src/editor/image/image_save_utils.dart delete mode 100644 lib/Screens/myTemplates/src/editor/image/widgets/image.dart delete mode 100644 lib/Screens/myTemplates/src/editor/image/widgets/image_resizer.dart delete mode 100644 lib/Screens/myTemplates/src/editor/video/widgets/video_app.dart delete mode 100644 lib/Screens/myTemplates/src/editor/video/youtube_video_url.dart delete mode 100644 lib/Screens/myTemplates/src/toolbar/camera/select_camera_action.dart delete mode 100644 lib/Screens/myTemplates/src/toolbar/image/select_image_source.dart delete mode 100644 lib/Screens/myTemplates/src/toolbar/quill_simple_toolbar_api.dart delete mode 100644 lib/Screens/myTemplates/src/toolbar/video/select_video_source.dart delete mode 100644 lib/Screens/myTemplates/src/tst diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a69436c..0625ccb 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,15 +5,7 @@ android:label="frontend" android:name="${applicationName}" android:icon="@mipmap/ic_launcher"> - - - + + + + + diff --git a/lib/Screens/myTemplates/Tempale b/lib/Screens/myTemplates/Tempale new file mode 100644 index 0000000..660b989 --- /dev/null +++ b/lib/Screens/myTemplates/Tempale @@ -0,0 +1,562 @@ +import 'dart:convert'; +import 'dart:io' as io show Directory, File; +import 'package:flutter/cupertino.dart' as dom; +import 'package:flutter_quill/flutter_quill.dart' hide Text; +import 'package:flutter/material.dart'; +import 'package:flutter_quill/flutter_quill.dart' hide Text; +import 'package:flutter_quill/quill_delta.dart'; +import 'package:flutter_quill_delta_from_html/flutter_quill_delta_from_html.dart'; +import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart'; + +import 'package:flutter_quill/flutter_quill.dart' as quill; +import 'package:html/parser.dart' show parse; +import 'package:html/dom.dart' as dom hide Element; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_quill/flutter_quill_internal.dart'; +import 'package:flutter_quill/quill_delta.dart'; + +import 'package:go_router/go_router.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:http/http.dart' as http; +import 'package:path/path.dart' as path; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../../config/apiUrl.dart'; +import '../../routes/custom_appBar.dart'; +import '../../routes/custom_drawer.dart'; +import '../../services/apiService.dart'; +import '../../utils/auth_utils.dart'; +import '../../widgets/custom_user_travel.dart'; + +class Template extends StatefulWidget { + final Map? templateData; + + const Template({super.key, required this.templateData}); + + static Template fromState(GoRouterState state) { + return Template(templateData: state.extra as Map?); + } + + @override + TemplateState createState() => TemplateState(); +} + +class TemplateState extends State